Getting rid of default values in spring rest - java

I'm using Spring rest with below code base:
When I invoke /info by passing string value in request body, I'm expecting the below response if this value is not present in my backend database.
{"output":-10}
but instead it returns me below response:
{"id": 0, "output":-10}
Can any one tell me how to get rid of this id default value? If there is a boolean variable in JSON mapper, then that would also get returned as
{"id": 0, "booleanVar": false, "output":-10}
Can any one tell me how to get rid of this default value?
Controller.java
#RequestMapping(value = "heartbeat", method = RequestMethod.GET, consumes="application/json")
public ResponseEntity<String> getHeartBeat() throws Exception {
String curr_time = myService.getCurrentTime();
return MyServiceUtil.getResponse(curr_time, HttpStatus.OK);
}
#RequestMapping(value = "info", method = RequestMethod.POST, consumes="application/json")
public ResponseEntity<String> getData(#RequestBody String body) throws Exception {
....
myInfo = myService.getMyInfo(myServiceJson);
return MyServiceUtil.getResponse(myInfo, responseHeader, HttpStatus.OK);
}
MyService.java
#Override
public String getCurrentTime() throws Exception {
String currentDateTime = null;
MyServiceJson json = new MyServiceJson();
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.setCurrentDateTime(currentDateTime);
ObjectWriter writer = mapper.writerWithView(Views.HeartBeatAPI.class);
return writer.writeValueAsString(json);
} catch (Exception e) {
throw new Exception("Excpetion in getCurrentTime: ", HttpStatus.BAD_REQUEST);
}
}
#Override
public String getMyInfo(MyServiceJson myServiceJson) throws Exception {
MyServiceJson json = new MyServiceJson();
json.setFirstName("hhh");
json.setLastName("abc");
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(json);
}
Views.java
public class Views {
public static class HeartBeatAPI { }
}
MyServiceJson.java
#JsonSerialize(include = Inclusion.NON_NULL)
public class MyServiceJson {
private int id;
private String firstName;
private String lastName;
#JsonView(Views.HeartBeatAPI.class)
private String currentDateTime;
// Getter/Setter for the above variables here
.....
}

Use Integer class instead of int primitive type. Primitive types always hold default values, where class type defaults to null.

Related

HMAC Validation in SpringBoot failing due to rearrangement of JSON

I am trying to have HMAC in springBoot for REST API.
The request I send from Postman is
{
"name":"xyz",
"description":"hello world",
"phone":"123456",
"id":"1"
}
it reached my controller and then to the service where I have a function to validate HMAC.
In the controller I pass the signature as the header and payload in the requestBody
#RestController
public class UserController {
#Autowired
UserInterface userInterface;
#PostMapping(value = "/" ,consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public void createUser(#RequestBody User user, #RequestHeader Map<String, String> headers) {
userInterface.hmacValidation(user, headers);
}
}
#Service
public class UserService implements UserInterface {
public void hmacValidation(User requestBody, Map<String, String> header) {
var headerSignature = header.get("signature");
var payload = getRequestBodyAsString(requestBody);
String result = Hashing.hmacSha256("12345".getBytes(StandardCharsets.UTF_8)).hashString(payload,StandardCharsets.UTF_8).toString();
}
private String getRequestBodyAsString(User requestBody) {
var mapper = new ObjectMapper();
String payload = null;
try {
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
payload = mapper.writeValueAsString(requestBody);
} catch (JsonProcessingException e) {
}
return payload;
}
}
here from the getRequestBodyAsString(User requestbody) function the output I get is a shuffled/rearranged JSON request which generates different Signature which then mismatches the signature client is sending.
the payload that is converted back from UserObject:
{"name":"xyz","id":"1","description":"hello world","phone":"123456"}
public class User {
private String name;
private String id;
private String description;
private String phone;
}
The client can send the request in any order but I have to validate signature regardless of the order the request comes in
Is there any other way to validate HMAC?
You should not deserialize if you want to take hash value. Use string or byte for the request. And map it to your pojo later on once you have the hash
For ex :
public #ResponseBody String controllerMethod(HttpServletRequest httpReq,
HttpServletResponse httpResponse) {
BufferedReader bufferedReader;
StringBuilder sb;
sb = new StringBuilder();
bufferedReader = httpReq.getReader();
char[] charBuffer = new char[128];
int bytesRead;
while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {
sb.append(charBuffer, 0, bytesRead);
}
String reqBody = sb.toString();
}
Use reqBody to get your hashed value.

How to test getting parameters on the Rest service using the Post method

I'm trying to test getting parameters for processing a request using the Post method
#RestController
#RequestMapping("api")
public class InnerRestController {
…
#PostMapping("createList")
public ItemListId createList(#RequestParam String strListId,
#RequestParam String strDate) {
…
return null;
}
}
test method
variant 1
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class InnerRestControllerTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
void innerCreatePublishList() {
String url = "http://localhost:" + this.port;
String uri = "/api/createList";
String listStr = "kl";
String strDate = "10:21";
URI uriToEndpoint = UriComponentsBuilder
.fromHttpUrl(url)
.path(uri)
.queryParam("strListId", listStr)
.queryParam("strDate ", strDate)
.build()
.encode()
.toUri();
ResponseEntity< ItemListId > listIdResponseEntity =
restTemplate.postForEntity(uri, uriToEndpoint, ItemListId.class);
}
}
variant 2
#Test
void createList() {
String uri = "/api/createList";
String listStr = "kl";
String strDate = "10:21";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(uri)
.queryParam("strListId", listStr)
.queryParam("strDate ", strDate);
Map<String, String> map = new HashMap<>();
map.put("strListId", listStr);//request parameters
map.put("strDate", strDate);
ResponseEntity< ItemListId > listIdResponseEntity =
restTemplate.postForEntity(uri, map, ItemListId.class);
}
Update_1
In my project exceptions is handled thus:
dto
public final class ErrorResponseDto {
private String errorMsg;
private int status;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
LocalDateTime timestamp;
...
handler
#RestControllerAdvice
public class ExceptionAdviceHandler {
#ExceptionHandler(value = PublishListException.class)
public ResponseEntity<ErrorResponseDto> handleGenericPublishListDublicateException(PublishListException e) {
ErrorResponseDto error = new ErrorResponseDto(e.getMessage());
error.setTimestamp(LocalDateTime.now());
error.setStatus((HttpStatus.CONFLICT.value()));
return new ResponseEntity<>(error, HttpStatus.CONFLICT);
}
}
In methods, where necessary, I throw a specific exception...
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.web.bind.MissingServletRequestParameterException:
Required String parameter 'strListId' is not present]
Who knows what the error is. Please explain what you need to add here and why ?
Let's take a look on declarations of postEntity:
postForEntity(URI url, Object request, Class<T> responseType)
...
postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
As you can see, first argument is either URI or String with uriVariables, but second argument is always request entity.
In you first variant you put uri String as URI and then pass uriToEndpoint as request entity, pretending that it is request object. Correct solution will be:
ResponseEntity<ItemListId> listIdResponseEntity =
restTemplate.postForEntity(uriToEndpoint, null, ItemListId.class);
Addressing your comments.
If server responded with HTTP 409, RestTemplate will throw exception with content of your ErrorResponseDto. You can catch RestClientResponseException and deserialize server response stored in exception. Something like this:
try {
ResponseEntity<ItemListId> listIdResponseEntity =
restTemplate.postForEntity(uriToEndpoint, null,
ItemListId.class);
...
} catch(RestClientResponseException e) {
byte[] errorResponseDtoByteArray = e.getResponseBodyAsByteArray();
// Deserialize byte[] array using Jackson
}

How to mock a constructor object properties using Mockito?

I am trying to mock a constructor 'EmailParams' in my test class.
Mocking is failing since the constructor EmailParams mocks as null.
Below is my test method
#Test
public void getContactEmailsByFilterSuccessTest() throws Exception {
String contactId = "752";
String emailAddress = "test#gmail.com";
String emailType = "EW";
MockHttpServletRequest request = new MockHttpServletRequest();
when(helper.isNumeric(any(String.class))).thenReturn(true);
List<ContactXref> sourcedContacts = getContactXrefs();
when(contactXrefServiceMock.getContactsForId(contactId)).thenReturn(sourcedContacts);
EmailParams emailParams = new EmailParams("test#gmail.com", "EW", sourcedContacts.get(0).getContact().getContactId().toString());
List<Email> emailsList = getEmailsList();
when(emailServiceMock.getEmailByFilter(emailParams)).thenReturn(emailsList);
ResponseEntity<List<Email>> response = contactControllerMock.getContactEmailsByFilter(request, contactId, emailAddress, emailType);
Assert.assertEquals("getContactEmailsByFilterSuccessTest: Expected response code to be 200", "200",
response.getStatusCode().toString());
}
This is the method I am trying to mock. Test fails when its trying to mock the constructor
#GetMapping(value = "/{contactId}/" + UrlMapping.EMAILS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Email>> getContactEmailsByFilter(HttpServletRequest request,
#PathVariable(name = RequestParams.CONTACTID) String contacId,
#RequestParam(required = false, name = RequestParams.EMAILADDRESS) String emailAddress,
#RequestParam(required = false, name = RequestParams.EMAILTYPE) String emailType)
throws Exception {
ResponseEntity response = new ResponseEntity("Only numeric contactId is allowed", HttpStatus.BAD_REQUEST);
List<Email> emailList;
List<ContactXref> sourcedContacts;
if (helper.isNumeric(contactId)) {
sourcedContacts = contXrefService.getContactsForId(contactId);
EmailParams params = new EmailParams(emailAddress, emailType, sourcedContacts.get(0).getContact().getContactId().toString());
emailList = emailService.getEmailByFilter(params);
if (emailList != null) {
response = emailList.size() == 0 ? new ResponseEntity("No emails were found for the request", HttpStatus.NOT_FOUND) : new ResponseEntity(emailList, new HttpHeaders(), HttpStatus.OK);
} else {
response = new ResponseEntity("Encountered exception in retrieving emails", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
return response;
}
Here is my class which has the constructor.
public class EmailParams {
String email;
String emailType;
String ptyId;
public EmailParams() {
super();
}
public EmailParams(String pEmail, String pEmailType, String pPtyId) {
email = pEmail;
emailType = pEmailType;
ptyId = pPtyId;
}
}
How to mock it properly? thanks in advance.
If the equals method is not overridden in EmailParams class by default Mockito uses Object.equals to compare the EmailParams passed to getEmailByFilter method. In your case both object properties have same values but still they are different objects. So either override the equals method in EmailParams or
use ArgumentMatchers.argThat
when(emailServiceMock.getEmailByFilter(ArgumentMatchers.argThat(p -> p.getPEmail().equals("test#gmail.com") && condition2 && condition3 )))
.thenReturn(emailsList);
So emailService is expected to be invoked with emailParams. The emailParams is constructed using emailAddress, emailType and a contactId. If you look closely, you'll realize that sourcedContacts in your controller is the result of contXrefService.getContactsForId(contactId).
Why is this a problem? Well, look at this line in your test:
when(contactXrefServiceMock.getContactsForEcmId(contactId)).thenReturn(sourcedContacts)
You're mocking getContactsForEcmId to return the sourcedContacts. Instead, you should be mocking getContactsForId.

Json dynamic deserialization with jackson

I've already have a look at the question "Jackson dynamic property names" but it does not really answer to my question.
I want to deserialize something like this :
public class Response<T> {
private String status;
private Error error;
private T data;
}
but data can have different names since different services exist and return the same structure with some different data. For example 'user' and 'contract' :
{
response: {
status: "success",
user: {
...
}
}
}
or
{
response: {
status: "failure",
error : {
code : 212,
message : "Unable to retrieve contract"
}
contract: {
...
}
}
}
I'd like genericize my responses objects like this :
public class UserResponse extends Response<User> {}
I've tried the following but i'm not sure it is my use case or if don't use it in the good way :
#JsonTypeInfo(include = As.WRAPPER_OBJECT, use = Id.CLASS)
#JsonSubTypes({#Type(value = User.class, name = "user"),
#Type(value = Contract.class, name = "contract")})
Finally, i've created a custom Deserializer. It works but i'm not satisfied:
public class ResponseDeserializer extends JsonDeserializer<Response> {
#Override
public Response deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Response responseData = new Response();
Object data = null;
for (; jp.getCurrentToken() != JsonToken.END_OBJECT; jp.nextToken()) {
String propName = jp.getCurrentName();
// Skip field name:
jp.nextToken();
if ("contract".equals(propName)) {
data = mapper.readValue(jp, Contract.class);
} else if ("user".equals(propName)) {
data = mapper.readValue(jp, User.class);
} else if ("status".equals(propName)) {
responseData.setStatus(jp.getText());
} else if ("error".equals(propName)) {
responseData.setError(mapper.readValue(jp, com.ingdirect.dg.business.object.community.api.common.Error.class));
}
}
if (data instanceof Contract) {
Response<Contract> response = new Response<Ranking>(responseData);
return response;
}
if (data instanceof User) {
Response<User> response = new Response<User>(responseData);
return response;
}
// in all other cases, the type is not yet managed, add it when needed
throw new JsonParseException("Cannot parse this Response", jp.getCurrentLocation());
}
}
Any idea to do this clean with annotations ? Thanks in advance !
Jackson framework provides inbuilt support for dynamic types.
//Base type
#JsonTypeInfo(property = "type", use = Id.NAME)
#JsonSubTypes({ #Type(ValidResponse.class),
#Type(InvalidResponse.class)
})
public abstract class Response<T> {
}
//Concrete type 1
public class ValidResponse extends Response<T>{
}
//Concrete type 2
public class InvalidResponse extends Response<T>{
}
main {
ObjectMapper mapper = new ObjectMapper();
//Now serialize
ValidResponse response = (ValidResponse)(mapper.readValue(jsonString, Response.class));
//Deserialize
String jsonString = mapper.writeValueAsString(response);
}
Have you tried:
public class AnyResponse {
private String status;
private Error error;
private Contract contract;
private User user;
// And all other possibilities.
}
// ...
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
This should fill in whatever object appears in the JSON and leave the rest null.
You could then fill in a Response with the relevant object.

spring generic json response

I am using Spring MVC and returning JSON as response. I would like to create a generic JSON response where I can put in any TYPE and want the response to look like this
{
status : "success",
data : {
"accounts" : [
{ "id" : 1, "title" : "saving", "sortcode" : "121212" },
{ "id" : 2, "title" : "current", "sortcode" : "445566" },
]
}
}
So I created a Response<T> object
public class Response<T> {
private String status;
private String message;
T data;
...
...
}
Is this the correct way of doing this, or is there a better way?.
How do you use this Response object in Spring controller to return an empty response object and/or a populated response object.
Thanks in advance GM
UPDATE:
In order to get the similar JSON output as the one described, i.e. with "accounts" key in JSON, I had to use Response<Map<String, List<Account>>> the following in the controller:
#RequestMapping(value = {"/accounts"}, method = RequestMethod.POST, produces = "application/json", headers = "Accept=application/json")
#ResponseBody
public Response<Map<String, List<Account>>> findAccounts(#RequestBody AccountsSearchRequest request) {
//
// empty accounts list
//
List<Account> accountsList = new ArrayList<Account>();
//
// response will hold a MAP with key="accounts" value="List<Account>
//
Response<Map<String, List<Account>>> response = ResponseUtil.createResponseWithData("accounts", accountsList);
try {
accountsList = searchService.findAccounts(request);
response = ResponseUtil.createResponseWithData("accounts", accountsList);
response.setStatus("success");
response.setMessage("Number of accounts ("+accounts.size()+")");
} catch (Exception e) {
response.setStatus("error");
response.setMessage("System error " + e.getMessage());
response.setData(null);
}
return response;
}
Is this the right way of doing this? i.e. in order to get the "accounts" key in JSON output?
While your example JSON is not valid (status and data are not enclosed in quotations), this approach will work.
You will want to ensure that you have the Jackson jars on your classpath, and Spring will take care of the rest.
To get this to work, I would create a constructor for your response class that looks something like this:
public class Response<T> {
private String status;
private String message;
private T data;
public Response(String status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
//...getter methods here
}
And then in your Spring controller, you just return this object from your method that is mapped with #RequestMapping
#Controller
public class MyController {
#RequestMapping(value="/mypath", produces="application/json")
public Response<SomeObject> myPathMethod() {
return new Response<SomeObject>("200", "success!", new SomeObject());
}
}

Categories