Spring #RequestMapping method without #RequestBody - spring

In a Spring Boot Controller method, how do I get the body of a POST? All of the examples I have seen use #RequestBody. How do I get the body without using #RequestBody?
I am writing a method to handle Slack Events. When Slack POSTs an event, the body is in JSON and often contains a "user" key. Depending on the type of event, the value of "user" can either be a string or an object. Because of this, I cannot create a single Class and write
#RequestMapping(path = "/slackRequest", method = RequestMethod.POST)
public String handleSlackRequest(#RequestBody final SlackRequest slackRequest)
Answer: Implementing the approach suggested by #ChiDov, the solution is to keep the #RequestBody, import
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
define the user field (and a new field to store the 'user' if it is a simple String value) as
#OneToOne
private SlackEventUser user;
private String slackUserId;
and define its Setter method as
#JsonSetter("user")
public void setUser(JsonNode userNode) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
if (userNode.isObject()) {
SlackEventUser slackEventUser = mapper.convertValue(userNode, SlackEventUser.class);
this.user = slackEventUser;
} else {
String userString = mapper.convertValue(userNode, String.class);
this.slackUserId = userString;
this.user = null;
}
}

Updated: I would make your DTO like :
Class SlackRequest{
...
private String eventType;
private JsonNode user;
...
public String getUser(){
return user.asText();
}
}
and in controller:
#RequestMapping(path = "/slackRequest", method = RequestMethod.POST)
public String handleSlackRequest(#RequestBody final SlackRequest slackRequest){
if(slackRequest.getEventType == "objectEvent"){
SomeObject user = mapper.convertValue(slackRequest.getUser(), SomeObject.class);
// do something with the object
}else{
// do something with the user string
}
}
Get Inspiration from : How to deserialize dynamic field using Jackson?

Related

call external api in spring boot

I am using get api for getting data from url but it give response in string format i want exact response or json response
`
#RestController
public class HelloController {
#GetMapping("/discover")
private String getApi(){
String url ="https://discover.search.hereapi.com/v1/discover?at=18.49093,73.8332&limit=2&q=restaurant&in=countryCode:IND&apiKey=hhV0nHGeHgrg5LACzw7dDJNe49bYCNvkCWxV94LlHno";
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject(url,String.class);
//Object[] result = restTemplate.getForObject(url, Object[].class);
//List<String> arr = new ArrayList<String>();
//arr.add(restTemplate.getForObject(url,String.class));
return result;
}
}
`
In this case, you get what you ask for, which is a String. If the string being returned is JSON, you can either use an ObjectMapper to deserialize it into an object that represents the response or have RestTemplate do it for you.
Assuming the response payload looks like this:
{
"name": "some name"
}
Then you'd create a class to represent the response (using lombok for getters and setters):
#Getter
#Setter
public class ApiResponse {
private String name;
}
Then you can either let RestTemplate do the work for you:
ApiResponse result = restTemplate.getForObject(url, ApiResponse.class);
Or, handle it yourself
String result = restTemplate.getForObject(url,String.class);
ApiResponse apiResponse = new ObjectMapper().readerFor(ApiResponse.class).readValue(result);

Values in the response of api are empty

ResponseInJsonFormat is a class,
#Builder
#Getter
public class ResponseInJsonFormat {
// Id field in Eloqua
private String id;
// List of field values
private List<FieldValues> fieldValues;
}
And FieldValues is also a class defined as,
#Builder
#Setter
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class FieldValues {
// id of the parameter
private String ID;
// parameters
private String type;
// Value :- value of the parameter
private String value;
}
I'm writing an api ,
#RequestMapping(value = "/service/XYZService", method = RequestMethod.POST)
public ResponseInJsonFormat sendData(#RequestParam String a) {
FieldValues parameter = FieldValues.builder()
.ID(FIELD_ID)
.type(FILED_TYPE)
.value(a)
.build();
List<FieldValues> values = Arrays.asList(parameter);
ResponseInJsonFormat response = ResponseInJsonFormat .builder()
.fieldValues(values)
.id(ID)
.build();
// need to return ResponseInJsonFormat
return response
}
But when I call to a service it returns something like,
{
"id": "110",
"fieldValues": [
{}
]
}
Could somebody assist about this empty json object of fieldValues.
I havnt understand why to put #JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
if I dont use this annotation I get an error as,
no properties discovered to create BeanSerializer
So I used the annotation, the error has gone but getting empty value for the same, even though the value is explicitly assigned.
I do not understand why to do all this it just being a list.
Is there any document I should read about,please advice.
If I'm not wrong, spring return the response in JSON format. Then why to do all this.

Validate input before Jackson in Spring Boot

I've built a REST endpoint using Spring Boot. JSON is posted to the endpoint. Jackson converts the JSON giving me an object.
The JSON look like this:
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-02"
}
Jackson converts the JSON to an object based on this class:
public class ParameterDate {
#NotNull(message = "Parameter Date Unadjusted can not be blank or null")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateUnadjusted;
#NotNull(message = "Parameter Date Adjusted can not be blank or null")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date parameterDateAdjusted;
private Date parameterDateAdded;
private Date parameterDateChanged;
}
This all works fine. The issue I'm having is that I would like to validate the data before Jackson converts the data. For instance if I post
{
"parameterDateUnadjusted": "2017-01-01",
"parameterDateAdjusted": "2017-01-40"
}
Where parameterDateAdjusted is not a valid date (there is no month with 40 days in it). Jackson converts this to 2017-02-09. One way of getting around this is to have a class that is only strings let's call it ParameterDateInput. Validate each filed with Hibernate Validator in the parameterDateInput object and then copy the parameterDateInput object to parameterDate where each field has the correct type (dates are of type Date and not of type String). This to me doesn't look like a very elegant solution. Is there some other way I can solve this? How is data generally validated in Spring Boot when posted as JSON? I like to be able to send back a message to the user/client what is wrong with the data that is being posted.
How about a custom JSON deserializer where you can write down the logic you want:
#RestController
public class JacksonCustomDesRestEndpoint {
#RequestMapping(value = "/yourEndPoint", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Object createRole(#RequestBody ParameterDate paramDate) {
return paramDate;
}
}
#JsonDeserialize(using = RoleDeserializer.class)
public class ParameterDate {
// ......
}
public class RoleDeserializer extends JsonDeserializer<ParameterDate> {
#Override
public ParameterDate deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
String parameterDateUnadjusted = node.get("parameterDateUnadjusted").getTextValue();
//Do what you want with the date and set it to object from type ParameterDate and return the object at the end.
//Don't forget to fill all the properties to this object because you do not want to lose data that came from the request.
return something;
}
}
There is a way to check the dates. setLenient() method
public static boolean isValidDate(String inDate, String format) {
SimpleDateFormat dateFormat = new SimpleDateFormat(format);
dateFormat.setLenient(false);
try {
dateFormat.parse(inDate.trim());
} catch (ParseException pe) {
return false;
}
return true;
}
Just define own annotation to validate the value
#Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = MyDateFormatCheckValidator.class)
#Documented
public #interface MyDateFormatCheck {
String pattern();
...
and the validator class
public class MyDateFormatCheckValidator implements ConstraintValidator<MyDateFormatCheck, String> {
private MyDateFormatCheck check;
#Override
public void initialize(MyDateFormatCheck constraintAnnotation) {
this.check= constraintAnnotation;
}
#Override
public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
if ( object == null ) {
return true;
}
return isValidDate(object, check.pattern());
}
}

How to send Java collections containing subclasses to spring controller

I'm trying to send collections to my spring MVC controller:
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#ModelAttribute("mapperList") MapperList mapperList) {
//prints {"users":null}
System.out.println(new ObjectMapper().writeValueAsString(mapperList));
return new ResponseDTO();
}
this is the code posting my users :
public ResponseDTO postUsers(ArrayList<User> users) {
ResponseDTO serverResponse = null;
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
connection.setRequestMethod("POST");
// prints {"users":[{"property1":"x","property1":y}]}
System.out.println(objectMapper.writeValueAsString(new MapperList(users)));
objectMapper.writeValue(connection.getOutputStream(), objectMapper.writeValueAsString(new MapperList(users)));
//blabla ...
}
and this is the object containing my list :
public class MapperList implements Serializable {
private static final long serialVersionUID = 8561295813487706798L;
private ArrayList<User> users;
public MapperList() {}
public MapperList(ArrayList<User> users) {
this.setUsers(users);
}
public ArrayList<User> getUsers() {
return users;
}
public void setUsers(ArrayList<User> users) {
this.users = users;
}
}
and this is the users type to post:
public abstract class User implements Serializable {
private static final long serialVersionUID = -1811485256250922102L;
private String property1;
private String property2;
public User() {}
public User(String prop1, String prop2) {
// set properties
}
// getters and setters
}
the problem is, when I output the value of the users's array before to post it to the controller, I got the following json value :
{"users":[{"property1":"x","property1":y}]}
but in the controller, when I print what I get from the request body, I only get :
{"users":null}
I also tryed with the annotation #RequestBody instead of #ModelAttribute("mapperList") and a JSONException is displayed :
*A JSONObject text must begin with '{' at 1 [character 2 line 1]\r\n*
My array list of users contains only one user that should be displayed. I don't understand why this doesn't work...
Thanks for any help !
You can chnage your MapperList class definition as public class MapperList extends ArrayList<User>{ ..} you dont need to define any instance variable like private ArrayList users inside MapperList class. Use #Requestbody annotation. You will be able to use MapperList as a ArrayList
Try to use:
public class MapperList{
private List<User> users;
//setter and getter
//toString
}
public class User{
private String property1;
private String property2;
//getter + setter
}
json:
{"users":[{"property1":"x", "property2":"y"}]}
in controller use #RequestBody. In that case Jackson will map your json to ArrayList of users.
#ResponseStatus(HttpStatus.OK)
#RequestMapping("/postUsers.do")
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList users) {
System.out.println(users);
return null;
}
no need to get objectMapper in that case. Don't forget to set content-type in request header to application/json. It required by Spring to handle #RequestBody processing.
If not working try to change MapperList:
List<User> users = new ArrayList<User>();
On the server side keep the #RequestBody annotation:
public #ResponseBody ResponseDTO postUsers(#RequestBody MapperList mapperList)
...
But this line causes problems:
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
First it converts the object to JSON and then again uses objectMapper to JSON-encode the string into output stream. Try the following instead:
connection.getOutputStream().write(
objectMapper.writeValueAsString(new MapperList(users))
.getBytes("UTF-8")
);
or directly output to stream:
objectMapper.writeValue(
connection.getOutputStream(),
new MapperList(users))
);
Zbynek gave me part of the answer. Indeed
objectMapper.writeValue(
connection.getOutputStream(),
objectMapper.writeValueAsString(new MapperList(users))
);
doesn't work properly in my case
But moreover, my User class was an abstract class, with many type of User as subclasses. so the #RequestBody annotation couldn't work without specified the object type in the Json.
I used the following annotations on User class to make it working :
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SubClassA.class, name = "a"),
#JsonSubTypes.Type(value = SubClassB.class, name = "b")
})
Thanks a lot for all your answers.

How to parse SOAP headers inside an Endpoint method using Springs

I am using Springs 3. I have a method in an Endpoint class which handles the web service request. The method is annotated with #Action to make it asynchronous. The SOAP request header contains some information (like UUID, Reply Address, etc). I need to be able to access these header information from inside this method.
The Spring WS MessageContext as well as the Apache axis MessageContext seems to be empty, so I am not able to use it inside the method to derive the SOAP header.
With #Action and Reply Address - i am assuming that it is WS-Addressing parameters.
Not sure how you got the MessageContext to be null. I had once was in a similar situation earlier (but was not able to access the MessageContext from the Endpoint method).
I followed the steps mentioned in this website Access Spring WS MessageContext from anywhere.
Once MessageContext is retrieved (hopefully not null), the said parameters can be easily accessed by something like this
SoapMessage sm = (SoapMessage) mc.getRequest();
Iterator<SoapHeaderElement> iter = sm.getEnvelope().getHeader().examineAllHeaderElements();
while (iter.hasNext()) {
SoapHeaderElement she = iter.next();
log.info("\n"+she.getName().getLocalPart()+ " - "+she.getText());
}
For this you can use unmarshalling to get the header variables.
Suppose you have a header like this
<soapenv:Header>
<v2:XHeader>
<v2:name>xxx</v2:srvcName>
<v2:version>1.0</v2:srvcVersion>
<v2:sender>yyy</v2:senderApp>
</v2:XHeader>
</soapenv:Header>
and your aim is to get the values xxx,1.0,yyy inside your endpoint method of the webservice.For this what you have to do is , Create a class say it's name be MyHeader.java so that we will unmarshall the incoming header values to this class.This class will look like
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "XHeader", propOrder = {
"name",
"version",
"sender"
})
#XmlRootElement(name = "XHeader")
public class MyHeader {
#XmlElement(required = true)
protected String name;
#XmlElement(required = true)
protected String version;
#XmlElement(required = true)
protected String sender;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getVersion() {
return version;
}
public void setVersion(String value) {
this.version = value;
}
public String getSender() {
return sender;
}
public void setSnder(String value) {
this.sender = value;
}
}
here what i did is, we have created a java class and annoted it with
#XmlRootElement(name = "XHeader")
so that we are telling that the root element of the xml should be XHeader.
Similarly created three variables and annoted with
#XmlElement(required = true)
.Now what to do is inside the endpoint method, you create a jaxbcontext object like
JAXBContext jaxbContext = JAXBContext.newInstance(MyHeaderType.class);
SoapMessage requestMessage = (SoapMessage) messageContext.getRequest();
SoapHeader reqheader = requestMessage.getSoapHeader();
Iterator<SoapHeaderElement> itr = reqheader.examineAllHeaderElements();
while (itr.hasNext()) {
SoapHeaderElement ele = itr.next();
MyHeaderType headerType=(MyHeaderType)jaxbContext.createUnmarshaller().unmarshal(ele.getSource());
System.out.println(esbHeaderType.getName());
System.out.println(esbHeaderType.getVersion());
System.out.println(esbHeaderType.getSender());
}
.and now it will print the values xxx,1.0,yyy

Resources