Spring RestTemplate: How to serialize java.util.Collection - java

I need to call a RESTful webservice from a Java program and pass it to a DateTime Collection.
So, my code is like the following:
RestTemplate restTemplate = new RestTemplate();
try {
String scheme = request.getScheme();
String userInfo = request.getRemoteUser();
String host = request.getLocalAddr();
int port = request.getLocalPort();
String path = "/myapp/common/myapi";
MultiValueMap<String, Object> requestParams = new LinkedMultiValueMap<String, Object>();
requestParams.add("aId", objA.getId());
requestParams.add("bIb", objB.getId());
for (DateTime date : dates) {
requestParams.add("dates", date);
}
URI apiUri = new URI(scheme, userInfo, host, port, path, null, null);
result = restTemplate.postForObject(apiUri.toString(), request, BigDecimal.class,
requestParams);
} catch (URISyntaxException e) {
logger.error(e.getMessage());
} catch (DataAccessException e) {
logger.error(e.getMessage());
}
And the webservice's signature is like:
#RequestMapping(value = "myapi", method = RequestMethod.POST)
public #ResponseBody BigDecimal myApi(
#RequestParam("dates") final List<DateTime> dates,
#RequestParam("aId") final Integer aId, #RequestParam("bId") final Integer bId) {
[...]
return result;
}
But I get the error:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: No serializer found for class java.util.Collections$3 and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
I understand that the problem is the serialization of the collection, but I do not know how to solve it.
Updated
I also tried with a Command class, so something like:
public class CalcolaGiorniResiduiCommand implements Serializable {
private List<DateTime> dates;
//[...]
}
Changed the controller:
#RequestMapping(value = "myapi", method = RequestMethod.POST)
public #ResponseBody BigDecimal myApi(
#RequestParam("command") final MyCommand command) {
And finally:
MyCommand command = MyCommand.build(1, 1, dates); //dates is a DateTime Collection
restTemplate.postForObject(apiUri.toString(), request, BigDecimal.class, command);
But I get the same error.
Any help, please?

I think you have to pass a list of dateTime object rather than adding it multiple time. Can you please try with the below snippet.
List<DateTime> dates = new ArrayList<>();
for (DateTime date : dates) {
dates.add(date);
}
requestParams.add("dates", dates);

From the documentation, the request parameter is the Object to be POSTed, so the command object have to be passed as second parameter instead of the object request.
So the code should be changed from:
restTemplate.postForObject(apiUri.toString(), request, BigDecimal.class, command);
to:
restTemplate.postForObject(apiUri.toString(), command, BigDecimal.class);

Related

Unable to get mocked response from Feign Client in Spring Boot

I am unable to get the mocked response from Feign Client. I provide below the code.
In the service class, it has been written like this.
public String getInfo(HttpServletRequest request, String id, String type) {
.... other code .....
try {
statusAsJsonString = myFeignClient.getStatus(cookie, id, type);
System.out.println("statusAsJsonString--------->"+statusAsJsonString);
ObjectNode node = new ObjectMapper().readValue(statusAsJsonString, ObjectNode.class);
if (node.has(CommonConstants.STATUS)) {
statusValue = node.get(CommonConstants.STATUS).asText();
}
} catch (FeignException fe) {
byte[] contents = fe.content();
String jsonContents = null;
if(contents != null) {
jsonContents = new String(contents);
}
statusValue = getErrorParsedStatusValue(jsonContents);
} catch (Exception ex) {
ex.printStackTrace();
}
log.debug("status: " + statusValue);
return statusValue;
}
In the unit test, I am trying to write in the following manner.
String responseBody = "[]";
when(myFeignClient.getStatus("cookievalue", "id", "SOme-Value")).thenReturn(responseBody);
I have also used, WireMock to achieve it.
wireMockServer.stubFor(WireMock.get("/rest/v1/somna/{id}/phase").withRequestBody(WireMock.equalToJson("{ \"name\": \"Phone\", \"initialStock\": 3}"))
.willReturn(WireMock.okJson(responseBody)));
The following piece of code is never covered and executed.
statusAsJsonString = myFeignClient.getStatus(cookie, id, type);
System.out.println("statusAsJsonString--------->"+statusAsJsonString);
Also the invocation of Feign client is inside a service method, first want to get the mocked result of that Feign client.
PLease help me.
I provide below my Feign CLient
#FeignClient(name = CommonConstants.FEIGN_CLIENT_NAME, url = "${feign.service.url}", primary = false)
public interface MyFeignClient {
#GetMapping(value = "/rest/v1/project/{id}/phaseName")
String getStatus(#RequestHeader("Cookie") String cookie,
#PathVariable("id") Stringid, #RequestParam("type") String type);
}
In my test class, I have added the followings.
#Autowired
private MyServiceImpl readyService = new MyServiceImpl();
#Mock
private MyFeignClient myFeignClient;
#ClassRule
public static WireMockServer wireMockServer = new WireMockServer(new WireMockConfiguration().port(8088));
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
httpServletRequest = Mockito.mock(HttpServletRequest.class);
ReflectionTestUtils.setField(someService, "cookieName", "cookieName");
wireMockServer.start();
}

Spring Controller always produce json

Is there anyway to force spring to always produce json, even an empty json object if there's no data to return.
Our services go through another service that rejects any response that isn't valid json (regardless of status code). It's not nice but we have no control of this.
With spring controllers you can tell them to produce json, but this only works when there's content to return. Is there a quick and elegant way to make all responses be json?
#RequestMapping(value = "/test", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public
#ResponseBody
ResponseEntity<String> test(){
// if this returns null or an empty string the response body will be emtpy
// and the content-type header will not be set.
return service.getData();
}
The simply fix here is to simply add an if statement to check for null. But that's ugly as I'll have to manually set the header and the response body.
I'm hoping someone knows of a nicer way?
Thanks
If you want all responses to return application/json, then you can set this at a single place by overriding postHandle() from HandlerInterceptorAdapter:
#Component
public class ResponseInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler,
final ModelAndView modelAndView) throws IOException {
if (response.getContentType() == null || response.getContentType().equals("")) {
response.setContentType("application/json");
}
}
}
You can look here
You may wrap the response in a "Container" object
For example I use this BaseAjaxResponse:
public class BaseAjaxResponse implements Serializable
{
private static final long serialVersionUID = 9087132709920851138L;
private int codiceOperazione;
private String descrizioneEsitoOperazione;
private long numeroTotaleOggetti;
private long numeroOggettiRestituiti;
private List<? extends Object> payload;
//Constructors and getter/setter
}
Then in my controllers I use this strategy:
#RequestMapping(method = { RequestMethod.POST }, value = { "/find" })
public ResponseEntity<BaseAjaxResponse> createCandidato(#RequestBody CandidatoDto candidato){
BaseAjaxResponse bar = new BaseAjaxResponse();
HttpStatus statusCode = null;
List<Object> payload = null;
StopWatch sw = new StopWatch("Find");
try
{
sw.start();
payload = myService.find();
sw.stop();
if( payload == null || payload.isEmpty() )
{
statusCode = HttpStatus.NO_CONTENT;
bar.setCodiceOperazione(statusCode.value());
bar.setDescrizioneEsitoOperazione("No result");
}
else
{
statusCode = HttpStatus.OK;
bar.setCodiceOperazione(statusCode.value());
bar.setDescrizioneEsitoOperazione("Got result");
//Set the object count and the number of found objects
}
}
catch (Exception e)
{
String message = "Errore nell'inserimento di un candidato; "+e.getMessage();
statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
bar.setCodiceOperazione(statusCode.value());
bar.setDescrizioneEsitoOperazione(message);
logger.error(message, e);
}
finally
{
if( sw.isRunning() )
{
sw.stop();
if( logger.isDebugEnabled() )
{
logger.debug("CHIUSURA STOPWATCH FORZATA. "+sw.toString());
}
}
}
return new ResponseEntity<BaseAjaxResponse>(bar, statusCode);
}
I hope this can be useful
Angelo

How to use Spring`s RestTemplate to POST an array of strings

I am trying to post a simple array of strings using the spring`s restTemplate. Did anyone succeed with that ?
The client:
public void save(){
String company = "12345";
String productId = "10";
String[] colors = {"A","B","C","D","E"};
String convertUrl = "http://localhost:8080/cool-web/save";
MultiValueMap<String, Object> convertVars = new LinkedMultiValueMap<String, Object>();
convertVars.add("companyID", StringUtils.trimToEmpty(company));
convertVars.add("productId", StringUtils.trimToEmpty(productId));
convertVars.add("disclaimer", StringUtils.trimToEmpty("ffs"));
convertVars.add("colorsArray", colors);
restTemplate.postForObject(convertUrl, null, String.class, convertVars);
}
The Service is:
#RequestMapping(value = "/save", method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
public void save(#RequestParam("colorsArray[]") String[] colors,
#RequestParam("disclaimer") String disclaimer,
#RequestParam("companyID") String companyID,
#RequestParam("productId") String productId) {
resourceService.save(colors, disclaimer, companyID, productId);
}
I got 400 Bad Request.
What am I doing wrong ?
I am using the default messageConverters.
Do I need to implement custom messageConverter for a simple array of Strings ?
Here is the solution:
public void save(){
String company = "12345";
String productId = "10";
String[] colors = {"A","B","C","D","E"};
String convertUrl = "http://localhost:8080/cool-web/save";
MultiValueMap<String, Object> convertVars = new LinkedMultiValueMap<String, Object>();
convertVars.add("companyID", StringUtils.trimToEmpty(company));
convertVars.add("productId", StringUtils.trimToEmpty(productId));
convertVars.add("disclaimer", StringUtils.trimToEmpty("ffs"));
for(String color:colors){
convertVars.add("colorsArray[]", color);
}
restTemplate.postForObject(convertUrl, convertVars , String.class);
}
If you are trying POST MultiValueMap, use Map.class instead of String.class in the code:
restTemplate.postForObject(convertUrl, null, Map.class, convertVars);
And your service method is wrong I guess. Because you are posting MultiValueMap and in save method you are trying to get all the internal variables as method parameters i.e. RequestParam.
That's not going to happen. You will have to accept only MultiValueMap there and take things out of it for use.
public void save(#RequestParam("colorsArray[]") MultiValueMap<String, Object> convertVars ) {
resourceService.save(convertVars.getColors(), .... );
}
#RequestParam(value) value - "The name of the request parameter to bind to." So #RequestParam("colorsArray[]") String[] colors trying to find param with name "colorsArray[]" but u put parameter with name "colorsArray". May be that is the reason.

Set spring jackson beanpropertyfilters in controller

I'm trying to serialize a hierarchy of objects using jackson, but I must use different fields in each controller method. Currently, I do this:
#RequestMapping(value = "/chat/message/create",
headers = "Accept=application/json",
produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET
)
#Transactional
public #ResponseBody
ChatMessage createChatMessage(#RequestParam("chat_id") Integer chatId,
#RequestParam("chat_from_id") Integer chatFromId,
#RequestParam("chat_content") String content) {
User fromUser = userDAO.find(User.class, chatFromId);
ChatMessage message = new ChatMessage();
message.setContent(content);
message.setLastUpdateDate(new Date());
message.setFromUser(fromUser);
Chat chat = chatDAO.find(Chat.class, chatId);
message.setChat(chat);
chatMessageDAO.save(message);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
FilterProvider filters = new SimpleFilterProvider()
.addFilter("chatMessageFilter", SimpleBeanPropertyFilter.serializeAllExcept(Collections.EMPTY_SET));
ObjectWriter writer = mapper.writer(filters);
try {
return writer.writeValueAsString(message);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
The problem is I want to use MappingJackson2HttpMessageConverter, but I cannot find a way to set the filter provider of list of beanpropertyfilters on it. I WANT to do something like this:
#RequestMapping(value = "/chat/message/create",
headers = "Accept=application/json",
produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET
)
#Transactional
public #ResponseBody
ChatMessage createChatMessage(#RequestParam("chat_id") Integer chatId,
#RequestParam("chat_from_id") Integer chatFromId,
#RequestParam("chat_content") String content) {
User fromUser = userDAO.find(User.class, chatFromId);
ChatMessage message = new ChatMessage();
message.setContent(content);
message.setLastUpdateDate(new Date());
message.setFromUser(fromUser);
Chat chat = chatDAO.find(Chat.class, chatId);
message.setChat(chat);
chatMessageDAO.save(message);
getFilterProvider().addFilter("chatMessageFilter", SimpleBeanPropertyFilter.serializeAllExcept(Collections.EMPTY_SET));
return message;
}
The getFilterProvider() would get the FilterProvider that is used by MappingJackson2HttpMessageConverter. That way, I would set 5 or 6 filters in one method, and a completely different set of filters in another method.
Note: Json views will not help me here. They are way too static, and I can't combine to specify which views to use for nested objects.

How to make Content-Type header optional?

I've an heartbeat API implemeted using Spring REST service:
#RequestMapping(value = "heartbeat", method = RequestMethod.GET, consumes="application/json")
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}
And MyService.java has below method:
public String getCurrentTime() throws Exception {
String currentDateTime = null;
MyJson json = new MyJson();
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
try {
Date currDate = new Date(System.currentTimeMillis());
currentDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").format(currDate);
json.setTime(currentDateTime);
ObjectWriter writer = mapper.writerWithView(Views.HeartBeatApi.class);
return writer.writeValueAsString(json);
} catch (Exception e) {
throw new Exception("Excpetion", HttpStatus.BAD_REQUEST);
}
}
It works as expected but have 2 issues:
When I invoke this API, Content-Type header is mandatory & I want to know how to make this header optional.
How to add "Accept" header so that it can support other format such as Google Protobuf?
Thanks!
If you don't want to require Content-Type exist and be "application/json", you can just omit the consumes section entirely.
"Accept" is available via the "produces" value, as opposed to "consumes." So if you wanted to support Google Protobuf OR application/json, you could do this:
#Controller
#RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json")
#ResponseBody
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return Util.getResponse(curr_time, HttpStatus.OK);
}

Categories

Resources