Rest API with a context object as the parameter - java

I am designing a REST API using JAX-RS. The endpoint looks like the following:
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response get(
#QueryParam("param1") final String param1,
#QueryParam("param2") final String param2) {
// Code goes here
}
I have nearly 7-8 parameters. So I would like to do something like the following:
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response get(#Context MyContextObject context) {
// Code goes here
}
And I have the context object as follows:
final class MyContextObject {
// get methods
public MyContextObject(final Builder builder) {
// set final fields
}
private final String param1;
private final String param2;
public static final class Builder {
// builder code goes here
}
}
Can you please advise how I can do that?
Thanks in advance.

If you want to do by creating separate bean class as you said, you would need to get the query parameters in bean class like below.
final class MyContextObject {
// get methods
public MyContextObject(final Builder builder) {
// set final fields
}
private #QueryParam("param1") final String param1;
private #QueryParam("param2") final String param2;
//and so on...
public static final class Builder {
// builder code goes here
}
}
If you do so, the query parameters get bounded to these private variables in the bean class and you would get them in your rest service using getters.

Related

Downcasting a CompletableFuture's object type - Java/Spring Boot

I have the following classes:
public class AccountDetail {
private String accountNumber;
private Date effectiveDate;
private String status;
// a bunch of other properties
}
public class AccountDetailWithAlerts extends AccountDetail {
private LowMediumAlerts alerts;
}
public class AccountsAndAlerts {
private List<AccountDetailWithAlerts> accounts;
private HighCustomerAccountAlerts accountAlerts;
// getters and setters
}
public class CustomerAndAccountAlerts {
private List<AlertMessage> customerAlerts;
private List<AccountAlertMessages> accountAlerts;
}
public Class CompanyResponse<T> {
#JsonInclude(JsonInclude.Include.NON_NULL)
private T response;
// other things that aren't relevant
}
I have a controller, AccountsController, that does a #GetMapping and has a ResponseEntity method:
public ResponseEntity<CompanyResponse<AccountsAndAlerts> getAccountDetails {
#RequestParam MultiValueMap<String, String> queryParms,
// some #ApiParams for client-header, end-user-id & accountNumber
String accountId = queryParms.getFirst("accountId");
// setting RestHeaders, contentType
CompanyResponse<AccountsAndAlerts> response = accountDetailService.getAccountsWithAlerts(restHeaders, accountNumber, queryParms, accountId);
return new ResponseEntity<CompanyResponse<AccountsAndAlerts>>(response, headers, HttpStatus.valueOf(response.getStatus()));
}
Here is the method in accountDetailService:
public CompanyResponse<AccountsAndAlerts> getAccountsWithAlerts(RestHeaders restHeaders, String accountNumber, MultiValueMap<String, String> queryParms, String accountId) throws... {
CompanyResponse<AccountsAndAlerts> newResponse = new CompanyResponse<AccountsAndAlerts>();
try {
CompletableFuture<List<AccountDetailWithAlerts>> accountsFuture = accountDetails.getAccounts(newResponse, restHeaders, accountNumber, queryParms);
CompletableFuture<CustomerAndAccountAlerts> alertsFuture = accountDetails.getAlerts(newResponse, restHeaders, accountId);
accountsFuture.thenAcceptBoth(alertsFuture, (s1, s2) -> newResponse.setResponse(getResponse(s1, s2))).get();
} catch {
// catch code
}
return newResponse;
}
Finally, the getAccounts method in AccountDetails:
public CompletableFuture<List<AccountDetailWithAlerts>> getAccounts(CompanyResponse<AccountsAndAlerts> newResponse, RestHeaders restHeaders, String accountNumber, MultiValueMap<String, String> queryParms) throws ... {
// this has the restTemplate and the .supplyAsync()
}
What I need to do is create a new ResponseEntityMethod in the Controller:
public ResponseEntity<CompanyResponse<AccountDetail> getCertainAccountDetails
I have put in a return of that type, and I am attempting to create a new method in the accountDetailService, getCertainAccounts().
The problem is trying to set this all up without creating a whole other CompletableFuture method with an invoke and supplyAsync() and restTemplate and such.
It appears that I still need to call getAccounts(), but then I have to somewhere along this line downcast the AccountDetailWithMessages to AccountDetail. I don't know if I can somehow downcast CompletableFuture<List<AccountDetailWithAlerts>> to CompletableFuture<List<AccountDetail>> or how to do it, or if I really need to downcast CompanyResponse<AccountsAndAlerts> or how to do that.
Can anyone help?
PS. I changed the names of everything to protect my Company's code. If you see errors in methods or names or anything, please be assured that is not an issue and is just the result of my typing things out instead of copying and pasting. The only issue is how to do the downcasting.
Thanks!
PPS. In case it wasn't clear, with my new method and code I do not want to get the alerts. I am trying to get account details only without alerts.

How can I set an optional RequestBody field without it being deleted when I make the call?

I have a small program in spring-boot which through a get call having a #RequestBody returns me a message with all the specifications (in my case of cars)
public class CarsRequest implements Serializable {
private String name;
private String plate ;
private String price;
}
I would like to be able to make sure that if a field is set to null, it can still find the relative message with the other fields having a value, in my case, I wanted to put that the "name" field is optional in the RequestBody, is it possible to do this? I tried setting
public CarsResponse getCars(#RequestBody (required = false) CarsRequest request) throws IOException {
//some code
}
but then when I go to do the get it completely deletes the null field at the time of the get and therefore fails to do it
Just remove the #RequestBody annotation from the function and keep it as it is
public CarsResponse getCars(CarsRequest request) throws IOException {
//some code
}
Now all fields will be converted into query params and all will be optional, because query param by convention are optional
public class CarsRequest implements Serializable {
private String name;
private String plate ;
private String price;
}
And call like this
GET /someEndpoint?name=<value>&plate=null
But still if you want to make some params mandatory, then use javax.annotations or apply validation yourself.
EDIT: As asked in comment, if you are accepting JSON as parameter body then you can do one thing, you can accept it as String and then convert json to object inside function body
public CarsResponse getCars(#RequestParam(required = false) String request) throws IOException {
ObjectMapper mapper = new ObjectMapper();
CarRequest request = mapper.readValue(request,CarRequest.class);
// other code
}
and call it something like this
GET /someEndpoint?request="{ \"name\" : null, \"plate\": \"someValue\" }"
EDIT 2:
You can do one more thing if you want to keep sending json and have it transformed into object, you can declare a binder something like this
// Some controller class
class SomeController {
#Autowired
ObjectMapper mapper;
// Ommited methods here
#GetMapping("/carRequest")
public ResponseEntity<String> testBinder(#RequestParam CarRequest request) {
return ResponseEntity.ok("{\"success\": \"" + request.name+ "\"}");
}
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CarRequest.class, new CarRequestEditor(mapper));
}
static class CarRequestEditor extends PropertyEditorSupport {
private ObjectMapper objectMapper;
public CarRequestEditor(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
if (StringUtils.isEmpty(text)) {
setValue(new CarRequest());
} else {
try {
setValue(objectMapper.readValue(text, CarRequest.class));
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
}
}
}
Please note that the client need to send the json URL encoded like this
http://localhost:8180/carRequest?request=%7B%22name%22%3"test"%7D
Hi you are using #RequestBody (required = false) CarsRequest
that means your CarsRequest object itself is optional
rather than you can use
#NotEmpty
private String plate ;
#NotEmpty
private String price;
You can make a single field optional by making it an Optional, in your case Optional<String>. If the field does not appear in the request body, then the Optional will be empty.
public class CarsRequest implements Serializable {
private String name;
private String plate;
private Optional<String> price;
}

How to set default values of model class variables from yaml file?

In a service file I would simply use #Value and initialize the variable instially there. I have tried this approach in a model class but (I assume how things get autowired and that its a model class) this results in it always being null.
The need for this comes out that in different environments the default value is always different.
#Value("${type}")
private String type;
I would avoid trying to use Spring logic inside the models as they are not Spring beans themselves. Maybe use some form of a creational (pattern) bean in which the models are constructed, for example:
#Component
public class ModelFactory {
#Value("${some.value}")
private String someValue;
public SomeModel createNewInstance(Class<SomeModel> clazz) {
return new SomeModel(someValue);
}
}
public class SomeModel {
private String someValue;
public SomeModel(String someValue) {
this.someValue = someValue;
}
public String getSomeValue() {
return someValue;
}
}
#ExtendWith({SpringExtension.class})
#TestPropertySource(properties = "some.value=" + ModelFactoryTest.TEST_VALUE)
#Import(ModelFactory.class)
class ModelFactoryTest {
protected static final String TEST_VALUE = "testValue";
#Autowired
private ModelFactory modelFactory;
#Test
public void test() {
SomeModel someModel = modelFactory.createNewInstance(SomeModel.class);
Assertions.assertEquals(TEST_VALUE, someModel.getSomeValue());
}
}

How to make cxf jax-rs response.readEntity as generic

The below code is working fine. I need to reuse this methods for all requests. How to make it as generics?
public class ApiResponse {
}
public class QuoteRespWrapper extends ApiResponse{
private String responseType;
private QuoteRespValue responseValue;
}
public class PolicyRespWrapper extends ApiResponse{
private String responseType;
private PolicyRespValue responseValue;
}
public QuoteRespWrapper callService(String endPoint, String payload, Class<? extends ApiResponse> respClass) throws Exception {
private static List<JacksonJsonProvider> providerList = singletonList(JacksonConfig.jacksonJsonProvider());
String userName="user1";
String password="password1";
WebClient client = WebClient.create(endPoint, providerList, userName, password, null);
Response webClientresponse = client.post(payload);
QuoteRespWrapper strResponse = webClientresponse.readEntity(QuoteRespWrapper.class);
return strResponse;
}
I need to modify the below line based on class type. It can be any subclass of ApiResponse (QuoteRespWrapper,PolicyRespWrapper,....). I need to pass
argument .class dynamically to get the response.
QuoteRespWrapper strResponse = webClientresponse.readEntity(QuoteRespWrapper.class);
You can use toString to produce the class name output as expected (e.g. class java.lang.String):
webClientresponse.readEntity(respClass.toString());
public ApiResponse callService(String endPoint, String payload, Class<? extends ApiResponse> respClass) throws Exception {
private static List<JacksonJsonProvider> providerList = singletonList(JacksonConfig.jacksonJsonProvider());
String userName="user1";
String password="password1";
WebClient client = WebClient.create(endPoint, providerList, userName, password, null);
Response webClientresponse = client.post(payload);
ApiResponse strResponse = (ApiResponse) webClientresponse.readEntity(respClass);
return strResponse;
}
The above code fulfilled my need.

How to annotate jersey method if I want to pass json?

I have following jersey method declaration:
#POST
#Path("/fooPath")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
public Response isSellableOnline(#FormParam("productCodes") final List<String> productCodes,
#FormParam("storeName") final String storeName,
#Context HttpServletRequest request) {
In rest client I try to invoke following method like this:
When I debug method I see that received parameters are null:
How to rewrite method declaration?
It is because on the isSellableOnlie method you are expecting or trying to extract form parameters, but the incoming POST request is JSON.
Well if you want JSON you should make POJO Class to be able to serialize the JSON.
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Store {
private String storeName;
private List<String> productCodes;
public Store() {
}
public String getName() {
return name;
}
public List<String> getProductCodes() {
return productCodes;
}
}
And then in your method:
#POST
#Path("/fooPath")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
public Response isSellableOnline(Store store) {
store.getName();
...
}

Categories

Resources