Getting values from external API using Spring Boot - Rest Template - java

I have an external API (like this - "https://www.example.com/en/products.json") and once hit the url it gives a json response like this:
{
"product1": {
"title": "Digital Adopter",
"description": "product description",
"image": ""
},
"product2": {
"title": "Router",
"description": "product2 description",
"image": ""
}
}
I want to use RestTemplate to consume above url(https://www.example.com/en/products.json) and tried following code. But didn't get any response or any error. I have less experience in development using Spring Boot RestTemplate and Can someone help me to fix this?
JsontestApplication .java
#SpringBootApplication
public class JsontestApplication {
public static void main(String[] args) {
SpringApplication.run(JsontestApplication.class, args);
}
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
Controller Class - Controller1.java
#RestController
public class Controller1 {
#Autowired
RestTemplate restTemplate;
#RequestMapping(value = "/jkl", method = RequestMethod.GET)
public String getProductList() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity <String> entity = new HttpEntity<String>(headers);
return restTemplate.exchange("https://www.example.com/en/products.json", HttpMethod.GET, entity, String.class).getBody();
}
}
In the application.properties file, I have set the port as server.port=8765 and application is running without any error. This is the url I hit using the Postman - http://localhost:8765/jkl

Related

Null Pointer Exception runiing AWS Lambda With Spring boot

I'm creating a sample of web service using Spring boot. When I run it in the localhost and test with Postman, it works fine. But when I deploy the application on AWS Lambda, it throws a nullpointerException.
When I was testing locally and on AWS Lambda this was working fine, until I started using #autowired
This is the project structure
The endpont or handle request is in the class LoginController
#Controller
#RequestMapping(value="api/auth/v1")
public class LoginController {
#Autowired
IClientCredentialService _clientCredentialService;
#RequestMapping(value = "/login", method = RequestMethod.POST, headers = "Accept=application/json")
public ResponseEntity<?> login(#RequestBody ClientCredential clientCredential){
//For testing from Postman and AWS, clientCredential is not null.
if(clientCredential == null) {
return new ResponseEntity<VOGenericResponse>(new VOGenericResponse(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.name()), HttpStatus.BAD_REQUEST);
}
if(clientCredential.getClientId() == null || clientCredential.getClientId().equals("")) {
return new ResponseEntity<VOGenericResponse>(new VOGenericResponse(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.name()), HttpStatus.BAD_REQUEST);
}
if(clientCredential.getClientSecret() == null || clientCredential.getClientSecret().equals("")) {
return new ResponseEntity<VOGenericResponse>(new VOGenericResponse(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.name()), HttpStatus.BAD_REQUEST);
}
else {
ClientCredential client = _clientCredentialService.findClientCredential(clientCredential.getClientId(), clientCredential.getClientSecret());
//TODO Obtener token aquí
Token token = new Token();
token.setCode(HttpStatus.OK.value());
token.setDescription(HttpStatus.OK.name());
//Temp
token.setAccessToken("123");
token.setCreatedDate("Hoy");
token.setUpdatedDate("Hoy");
token.setExpiresIn("Mañana");
token.setStartIn("Hoy");
token.setTokenId("321");
token.setTokenType("bearer");
token.setUpdatedDate("hoy");
return new ResponseEntity<Token>(token, HttpStatus.OK);
}
The interface IClientCredentialService, escencially has a method, and the class ClientCredentialServiceImpl, implements that interface:
#Service("clientCredentialService")
#Transactional
public class ClientCredentialServiceImpl implements IClientCredentialService{
#Override
public ClientCredential findClientCredential(String clientId, String clientSecret) {
return new ClientCredential();
}
}
This is the main class:
#SpringBootApplication
public class AwsLambdaSurveyAuthServerLoginV1Application {
public static void main(String[] args) {
SpringApplication.run(AwsLambdaSurveyAuthServerLoginV1Application.class, args);
}
}
And the classes ClientCredential and Token, has a constructor and some getters and setters.
When I run it from postman, It works fine, I get a 200 http response:
{
"code": 200,
"description": "OK",
"tokenId": "321",
"accessToken": "123",
"tokenType": "bearer",
"expiresIn": "Mañana",
"startIn": "Hoy",
"createdDate": "Hoy",
"updatedDate": "hoy"
}
but, when I run It from AWS Lambda y get this error:
{
"errorMessage": "java.lang.NullPointerException",
"errorType": "java.lang.NullPointerException",
"stackTrace": [
"com.gns.survey.authserver.login.controller.LoginController.login(LoginController.java:47)",
"sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
"sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)",
"sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)",
"java.lang.reflect.Method.invoke(Method.java:498)"
]
}
Anyone know what the error is? I really appreciate your help.
/Edit: Method with null validation

Spring Boot (2.3.6.RELEASE) Deserialization Fails when using RestTemplate and Unwrap Root

I'm trying to consume an API using RestTemplate but it will simply not deserialize the json response into my pojo
Here is the json payload I'm trying to deserialize:
"Response": {
"Count": 77,
"Data": [
{
"AllowDelete": "1",
"ContactCount": 1482,
"CreatedDate": "Dec 01, 2020",
"ID": "17991951",
"IsImporting": "0",
"IsMasterUnsubscribe": "0",
"ListAudited": "1",
"ListDescription": "City of Markham Staff - December 2020 (LATEST)",
"ListImportV3": "1",
"ListType": "0",
"ModifiedDate": "Dec 03, 2020",
"Name": "City of Markham Staff - December 2020 (LATEST)",
"NameShort": "City of Markham Staff - December 2020 (LATEST)",
"PermissionPassList": "0",
"Segments": [],
"Status": ""
},{
"AllowDelete": "0",
"ContactCount": 884,
"CreatedDate": "Nov 04, 2011",
"ID": "582203",
"IsImporting": "0",
"IsMasterUnsubscribe": "1",
"ListAudited": "1",
"ListDescription": "Master Unsubscribe List",
"ListImportV3": "0",
"ListType": "0",
"ModifiedDate": "Dec 04, 2020",
"Name": "Master Unsubscribe List",
"NameShort": "Master Unsubscribe List",
"PermissionPassList": "0",
"Segments": [],
"Status": ""
}
],
"Status": "1"
}
}
Here is my main pojo:
package com.markham.enews.model;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName(value = "Response")
public class Contact {
//Total number
private int count;
//1 if successful, -1 if error
private String status;
// Further details of the Contact List
private List<ContactFullRecord> data;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<ContactFullRecord> getData() {
return data;
}
public void setData(List<ContactFullRecord> data) {
this.data = data;
}
#Override
public String toString() {
return "Contact [count=" + count + ", status=" + status + ", data=" + data + "]";
}
}
As per this stack overflow link Spring Boot Jackson with Root name
I added the following to my application.properties:
spring.jackson.mapper.accept-case-insensitive-properties=true
spring.jackson.deserialization.unwrap-root-value=true
My rest controller get method is as follows:
#GetMapping(value = "/ContactTest")
private Contact getContactTest() {
String uri = "https://clientapi.benchmarkemail.com/Contact/";
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> request = new HttpEntity<String>(createHeaders());
ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);
Contact contact = response.getBody();
return contact;
}
But the resulting object has all empty/null values:
"count": 0,
"status": null,
"data": null
I think the unwrap root and/or case insensitive properties are not being picked up..
If I write the following unit test and use objectMapper directly, it works:
#Test
public void wrapRootValue() throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
String str = "{ \"Response\": {\"Count\": 77,\"Data\": [{\"AllowDelete\": \"0\",\"ContactCount\": 884,\"CreatedDate\": \"Nov 04, 2011\",\"ID\": \"582203\",\"IsImporting\": \"0\",\"IsMasterUnsubscribe\": \"1\",\"ListAudited\": \"1\",\"ListDescription\": \"Master Unsubscribe List\",\"ListImportV3\": \"0\",\"ListType\": \"0\",\"ModifiedDate\": \"Dec 03, 2020\",\"Name\": \"Master Unsubscribe List\",\"NameShort\": \"Master Unsubscribe List\",\"PermissionPassList\": \"0\",\"Segments\": [],\"Status\": \"\"}],\"Status\": \"1\"}}";
Contact root = mapper.readValue(str, Contact.class);
System.out.println(root);
}
Output:
Contact [count=77, status=1, data=[ContactFullRecord [id=582203, name=Master Unsubscribe List, nameShort=Master Unsubscribe List, status=, contactCount=884.0, createdDate=Nov 04, 2011, modifiedDate=Dec 03, 2020, permissionPassList=0, listAudited=1, listDescription=Master Unsubscribe List, isImporting=0, isMasterUnsubscribe=1, allowDelete=0, listImportV3=0]]]
Any help would be greatly appreciated!
Use spring boot pre configured RestTemplateBuilder ( has all the jackson message converter configuration applied ) and use build to request new RestTemplate instance.
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
}
Autowire the instance into controller class.
#Autowired
private RestTemplate restTemplate;
#GetMapping(value = "/ContactTest")
private Contact getContactTest() {
String uri = "https://clientapi.benchmarkemail.com/Contact/";
HttpEntity<String> request = new HttpEntity<String>(createHeaders());
ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);
Contact contact = response.getBody();
return contact;
}
You can also look at https://www.baeldung.com/spring-rest-template-builder for other set ups.
The problem is that you are configuring the Jackson deserialization behavior at the Spring Boot level, you are not configuring the deserialization behavior for your RestTemplate.
One possible approach you can follow is the one suggested by #s7vr in his/her answer, and reuse the Spring Boot provided configuration.
If you only want to customize the Jackson configuration for your RestTemplate you can do it with something like:
final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
// Base converters
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter(false));
messageConverters.add(new SourceHttpMessageConverter<>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
// Custom Jackson Converter
final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
final ObjectMapper mapper = mappingJackson2HttpMessageConverter.getObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
mapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);
messageConverters.add(mappingJackson2HttpMessageConverter);
final RestTemplate restTemplate = new RestTemplate(messageConverters);
// Use it as you consider appropriate
String uri = "https://clientapi.benchmarkemail.com/Contact/";
HttpEntity<String> request = new HttpEntity<String>(createHeaders());
ResponseEntity<Contact> response = restTemplate.exchange(uri, HttpMethod.GET, request, Contact.class);
Contact contact = response.getBody();
//...
Of course, you can reuse this configuration if needed by configuring a FactoryBean for RestTemplate and inject later in your controller, for instance.

Spring RestTemplate with rootUri return URI is not absolute error

I have a RestTemplate that I build it with RestTemplateBuilder. I set the rootUri for builder. In below method (updateState1) sometimes I got the "URI is not absolute" error. For example when I called this method concurrently for 2 times I often got 1 error.
EDIT and Solution:
I use this RestTemplate in service task of camunda process. I launch this project in kubernetes container that has different timezone with the oracle database. When I add timezone variable every things work fine.
Spring boot version: 2.1.1.RELEASE
Here is my code:
#Component
#Slf4j
public class CoreServiceClient {
private RestTemplate restTemplate;
private static final String root = "http://localhost:8080/test/api/";
public CoreServiceClient(RestTemplateBuilder restTemplateBuilder) {
restTemplate = restTemplateBuilder.rootUri(root).build();
}
public void updateState1(UpdateParam updateParam) {
HttpHeaders headers = generateHeader();
UpdateRequest updateRequest = new UpdateRequest(updateParam.getState());
HttpEntity<UpdateRequest> httpEntity = new HttpEntity<>(updateRequest, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/food/{id}/state",
HttpMethod.PUT, httpEntity, String.class, updateParam.getId());
}
public void updateState2(String id) {
HttpHeaders headers = generateHeader();
UpdateRequest updateRequest = new UpdateRequest("done");
HttpEntity<UpdateRequest> httpEntity = new HttpEntity<>(updateRequest, headers);
ResponseEntity<String> response = restTemplate.exchange(
"/food/{id}/state",
HttpMethod.PUT, httpEntity, String.class, id);
}
}
cuase (stacktrace):
Caused by: java.lang.IllegalArgumentException: URI is not absolute
at java.net.URI.toURL(URI.java:1088)
at org.springframework.http.client.SimpleClientHttpRequestFactory.createRequest(SimpleClientHttpRequestFactory.java:145)
at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:87)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:730)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:669)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:578)
at com.test.client.CoreServiceClient.updateState(CoreServiceClient.java:39)
at sun.reflect.GeneratedMethodAccessor263.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.camunda.bpm.engine.impl.javax.el.BeanELResolver.invoke(BeanELResolver.java:479)
... 85 more
Remove / in root:
private static final String root = "http://localhost:8080/test/api";
RestTemplate accepts uriTemplate as long as they start with / so your root should be without it. if it doesn't start with / it will consider it as a full URL
I try with the same code. I do not get this error.
Spring boot version: 1.5.0.RELEASE
Instead of POST, I tried with a GET API with same URL pattern.
The / at the end of the path does not matter.
#Component
public class CoreServiceClient {
private RestTemplate restTemplate;
private static final Logger LOGGER = LoggerFactory.getLogger(CoreServiceClient.class);
private static final String root = "http://localhost:8080/test/api/";
public CoreServiceClient(RestTemplateBuilder restTemplateBuilder) {
restTemplate = restTemplateBuilder.rootUri(root).build();
}
public void updateState(String id) {
try {
ResponseEntity<String> response =
restTemplate.exchange("/food/{id}/state", HttpMethod.GET, null, String.class, id);
LOGGER.info("Resp: {}", response.getStatusCode());
LOGGER.info("Resp: {}", response.getBody());
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
}
}
I added a dummy controller with the same path:
#RestController
#RequestMapping("/test/api")
public class FooController {
#GetMapping("/food/{id}/state")
public ResponseEntity<String> fooState(#PathVariable String id) {
return new ResponseEntity<String>("EATING", HttpStatus.OK);
}
}
To test, I added another controller:
#RestController
#RequestMapping("/client")
public class CoreServiceClientController {
#Autowired
private CoreServiceClient client;
#GetMapping
public ResponseEntity<String> goGet() {
client.updateState("1001");
return new ResponseEntity<>("HELLO", HttpStatus.OK);
}
}
Everything works fine for me.
Log:
2019-01-15 23:23:19.870 INFO 22570 --- [nio-8080-exec-1] com.example.demo001.CoreServiceClient : Resp: 200
2019-01-15 23:23:19.871 INFO 22570 --- [nio-8080-exec-1] com.example.demo001.CoreServiceClient : Resp: EATING

Spring MVC : POST and PUT not working

I am developing Spring MVC 4 Dynamic web module application.
In my application I have simple CRUD operations.
Get requests are working fine but POST and PUT are not working at all.
I am getting this error:
HTTP Status 400 - The request sent by the client was syntactically incorrect.
This is my controller code for GET:
#RequestMapping(value = "/getCustomreById/{id}", method = RequestMethod.GET)
public ResponseEntity<CustomerDetails> getCustomer(
#PathVariable("id") String id) {
System.out.println(id);
if (id != null)
return new ResponseEntity<CustomerDetails>(
serv.getCustomerById(id), HttpStatus.OK);
else
return new ResponseEntity<CustomerDetails>(
serv.getCustomerById("1"), HttpStatus.OK);
}
and for POST :
#RequestMapping(value = "/addCustomer", method = RequestMethod.POST)
public int AddCustomer(#RequestBody CustomerDetails customer) {
return serv.addCustomer(customer);
}
POST Request :
{
"customerName": "Sid",
"customerEmail": "sid#gmail.com",
"customerAddress": [{
"address1": "123 Street",
"address2": " ",
"zipCode": "400065",
"city": "Mumbai",
"state": "Maharashtra",
"country": "India",
"region": "Gateway of India"
}]
}
I read on stackoverflow on this question that I need to add multipart reosolver but even aafter adding that I am getting same error.
Assuming you just need to send int id as response, add #ResponseBody to the method
#RequestMapping(value = "/addCustomer", method = RequestMethod.POST)
#ResponseBody
public int AddCustomer(#RequestBody CustomerDetails customer) {
return serv.addCustomer(customer);
}
Otherwise return ResponseEntity as you are doing for GET
return new ResponseEntity<Integer>(serv.addCustomer(customer), HttpStatus.OK);
Adding #ResponseBody will work for this question

postForEntity does not produce correct JSON representation...At my wits end

I am new to Spring MVC REST. I spent hours googling and trying different ways to solve this problem.
Why does not this code produce JSON with double quotes???
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper);
mappingJacksonHttpMessageConverter.setPrettyPrint(true);
converters.add(mappingJacksonHttpMessageConverter);
template.setMessageConverters(converters);
ResponseEntity<EAApplication> response = template.postForEntity(
LOCAL_URI,
requestEntity, EAApplication.class);
produces following JSON without double quotes and so server is sending 400 Bad Request.
{ applications:
[ { submissionDate: '2014-09-05T08:28:17',
firstName: 'Mickey',
lastName: 'Mouse',
email: 'mm#disney.com',
dietaryRestrictions: 'Cheese only',
restSkillLevel: 'Novice',
jsonSchemaSkillLevel: 'Expert',
restStandardFamiliarity: true,
slimFamiliarity: true,
odataLibFamiliarity: true } ] }
You client side code works perfectly fine. The 400 Bad request may be due to an error on your controller side which might not be able to handle the content.
The JSON that you pasted above does not have double quotes around it because some editors or programs render the JSON that way just to increase readability. The raw string after serializing through the postForEntity is actually generating JSON with double quotes.
The below mentioned code is working, I tested it by writing a test.
package com.test.rest.controller;
import com.fasterxml.jackson.annotation.JsonInclude;
import ........
public class TestAppController {
#Test
public void testController() throws Exception {
Server mockServer = new Server(9190);
startMockServer("query-rs-main.xml", "query-rs", mockServer);
EAApplication requestEntity = new EAApplication();
Application application = new Application();
application.setDietaryRestrictions("Cheese only");
application.setFirstName("Mickey");
application.setLastName("Mouse");
application.setEmail("mm#disney.com");
application.setJsonSchemaSkillLevel("Expert");
application.setRestSkillLevel("Novice");
application.setSubmissionDate(new Date());
application.setOdataLibFamiliarity(true);
application.setRestStandardFamiliarity(true);
List<Application> list = new ArrayList<>();
list.add(application);
requestEntity.setApplications(list);
RestTemplate template = new RestTemplate();
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper);
mappingJacksonHttpMessageConverter.setPrettyPrint(true);
converters.add(mappingJacksonHttpMessageConverter);
template.setMessageConverters(converters);
ResponseEntity<EAApplication> response = template.postForEntity(
"http://localhost:9190/query-rs/eaapps",
requestEntity, EAApplication.class);
mockServer.stop();
}
public static void startMockServer(final String mainAppContextConfig, final String contextRoot, Server server) throws Exception {
final DispatcherServlet servlet = new DispatcherServlet();
servlet.setContextConfigLocation("classpath:" + mainAppContextConfig);
final ServletHolder servletHolder = new ServletHolder(servlet);
final ServletContextHandler context = new ServletContextHandler();
context.setErrorHandler(null);
context.setContextPath("/" + contextRoot);
context.addServlet(servletHolder, "/*");
server.setHandler(context);
server.start();
}
}
Controller on the server side -
package com.yt.nss.rest.query.mock.controller;
import org.springframework.http.HttpStatus;
import ....
#Controller
#RequestMapping("/eaapps")
public class EAApplicationsController {
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Object> submitMessage(final #RequestBody EAApplication eaApplication) {
System.out.println(eaApplication);
return new ResponseEntity<Object>(null, HttpStatus.OK);
}
}
EAApplication class or POJO for transferring the JSON
#XmlRootElement
public class EAApplication {
private List<Application> applications;
public List<Application> getApplications() {
return applications;
}
public void setApplications(List<Application> applications) {
this.applications = applications;
}
#Override
public String toString() {
return "EAApplication{" +
"applications=" + applications +
'}';
}
}
When I ran the client and the server, the call was successfully made to the controller and EAApplication object on the controller method was fully populated.
I suggest you look at your controller and spring mvc configuration on the server side or post it here.

Categories

Resources