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

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.

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 add logic to a GetMapping in Java REST API

I am trying to implement a very simple REST API with Spring Boot. In a GET Request I want to do a very basic transliteration. So the requestor will have to send an input string and transliteration string.
Those parameters should be passed to my method, that returns a transliteration. The Response should look like this:
"input": ...
"transliterationrule": ...
"transliteration": ...
To do so, I created a java spring boot project with the following classes:
Transliteration class
import com.ibm.icu.text.Transliterator;
public class Transliteration {
private final String input ;
private final String transliterationRule;
public Transliteration(String input, String transliterationRule) {
this.input = input;
this.transliterationRule = transliterationRule;
}
public String transliterateString(){
Transliterator transliterator = Transliterator.getInstance(this.transliterationRule);
return transliterator.transliterate(this.input);
}
public String getInput(){
return input;
}
public String getTransliterationRule(){
return transliterationRule;
}
}
And controller class:
#RestController
public class TransliterationController {
#GetMapping("/transliteration")
public Transliteration transliteration(#RequestParam(value="input", required=false, defaultValue="TestString") String input,
#RequestParam(value="rule", required=false, defaultValue="Any-Latin") String transliterationRule) {
return new Transliteration(input, transliterationRule);
}
}
Can somebody please explain me, how I can actually pass these parameters to my method transliterateString()? And how can I add the method result do the request?
Change your controller's method to return ResponseEntity and wrap a TransliterationResponse. TransliterationResponse is a dto holding
"input": ...
"transliterationrule": ...
"transliteration": ...
#GetMapping("/transliteration")
public ResponseEntity<TransliterationResponse> transliteration(#RequestParam(value="input", required=false, defaultValue="TestString") String input,
#RequestParam(value="rule", required=false, defaultValue="Any-Latin") String transliterationRule) {
// do your business logic
//build the response dto
TransliterationResponse dto = new TransliterationResponse(input, transliterationrule, transliteration);
ResponseEntity.status(HttpStatus.OK).body(dto);
}
Create a DTO class to contain all response values:
public class TransliterationDTO {
#JsonProperty("input")
private String input;
#JsonProperty("transliterationRule")
private String transliterationRule;
#JsonProperty("transliteration")
private String transliteration;
public TransliterationDTO() {
}
public TransliterationDTO(String input, String transliterationRule, String transliteration) {
this.input = input;
this.transliterationRule = transliterationRule;
this.transliteration = transliteration;
}
// Getters and Setters
}
Update your controller method to return a ResponseEntity :
#RestController
public class TransliterationController {
#GetMapping("/transliteration")
public ResponseEntity<TransliterationDTO> transliteration(#RequestParam(value="input", required=false, defaultValue="TestString") String input,
#RequestParam(value="rule", required=false, defaultValue="Any-Latin") String transliterationRule) {
Transliteration t = new Transliteration(input, transliterationRule);
return ResponseEntity.ok(new TransliterationDTO(t.getInput(), t.getTransliterationRule(), t.getTransliteration));
}
}

Rest API with a context object as the parameter

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.

Custom Spring Exceptions with bind variables

I want to create a custom business exception:
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BusinessException(String msg) {
super(msg);
}
public BusinessException(String msg, Object[] params) {
//Not sure how to pass params to #ExceptionHandler
super(msg);
}
}
and use it in my spring mvc rest controller:
#RequestMapping(value = "/{code}", method = RequestMethod.GET)
public #ResponseBody
String getState(#PathVariable String code) throws Exception {
String result;
if (code.equals("KL")) {
result = "Kerala";
} else {
throw new BusinessException("NotAValidStateCode",new Object[]{code});
}
return result;
}
I am handling all the businessException using common exception handler:
#ControllerAdvice
public class RestErrorHandler {
private static final Logger LOGGER = LoggerFactory
.getLogger(RestErrorHandler.class);
#Autowired
private MessageSource messageSource;
#ExceptionHandler(BusinessException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleException(
Exception ex) {
Object[] args=null; // Not sure how do I get the args from custom BusinessException
String message = messageSource.getMessage(ex.getLocalizedMessage(),
args, LocaleContextHolder.getLocale());
LOGGER.debug("Inside Handle Exception:" + message);
return message;
}
}
Now my problem is , I want to read the message text from messages property file where some of the keys are expecting run time bind variables e.g.
NotAValidStateCode= Not a valid state code ({0})
I am not sure how do I pass these arguments to handleException Method of RestErrorHandler.
This is simple as you have already done all the "heavy lifting":
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final Object[] params;
public BusinessException(String msg, Object[] params) {
super(msg);
this.params = params;
}
public Object[] getParams() {
return params;
}
}
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleException(BusinessException ex) {
String message = messageSource.getMessage(ex.getMessage(),
ex.getParams(), LocaleContextHolder.getLocale());
LOGGER.debug("Inside Handle Exception:" + message);
return message;
}
I would recommend encapsulating everything that you need to create the error message in BusinessException. You're already passing in code as part of the array of params. Either expose that whole array with a getParams() method, or (and this is the approach I would take) add a code field and getCode() method to BusinessException and add a code argument to BusinessException's constructor. You can then update handleException to take a BusinessException rather than an Exception and use getCode() when creating the arguments used to create the message.

Categories

Resources