I'm using CXF ClientBuilder to send POST data to a REST service. The response I get back looks like this right now:
errorCode=206&errorMessage=blah+blah
I want to unmarshal this into fields in a POJO.
The following code block illustrates what I have right now:
public void validateToken(String token) {
WebTarget target = client.target(getHostPort()).path(getPath());
Builder request = target.request(MediaType.APPLICATION_JSON_TYPE);
Form form = new Form();
form.param("TokenID", token);
Response postResponse = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
System.out.println("postResponse[" + postResponse + "]");
System.out.println("response.text[" + postResponse.readEntity(String.class) + "]");
// CodeAndMessage codeAndMessage = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), CodeAndMessage.class);
// System.out.println("codeAndMessage[" + codeAndMessage + "]");
}
public static class CodeAndMessage {
private String errorCode;
private String errorMessage;
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
#Override
public String toString() {
return new ToStringBuilder(this).
append("errorCode", getErrorCode()).
append("errorMessage", getErrorMessage()).
build();
}
}
As written right now, I get the response as I originally described. I'm trying to figure out some variation of those last commented-out lines to replace the first "request.post()" and the two following lines, to get the result I'm looking for.
Update:
I did find at least one way to do this, but I don't know if it's the best way.
Form responseForm = request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE), Form.class);
System.out.println("responseForm[" + responseForm + "] map[" + responseForm.asMap() + "]");
return new CodeAndMessage().
errorCode(responseForm.asMap().getFirst("errorCode")).
errorMessage(responseForm.asMap().getFirst("errorMessage"));
The key was using the Form object for the response type. With this solution, I still have to reference the field names. Is there a cleaner way to do this?
Update:
I would guess that a cleaner solution would require implementing a MessageBodyReader for this CodeAndMessage class, but I'm not sure yet how to do that.
My MessageBodyReader implementation looks like this:
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.apache.cxf.jaxrs.provider.FormEncodingProvider;
#Provider
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public class StuffResponseReader implements MessageBodyReader<StuffResponse> {
private FormEncodingProvider<Form> formProvider = new FormEncodingProvider<>();
private static final String PROP_ERROR_CODE = "errorCode";
private static final String PROP_ERROR_DESCRIPTION = "errorMessage";
...
#Override
public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type.isAssignableFrom(StuffResponse.class);
}
#Override
public StuffResponse readFrom(Class<StuffResponse> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
throws IOException, WebApplicationException {
Form form = formProvider.readFrom(Form.class, Form.class, annotations, mediaType, httpHeaders, entityStream);
MultivaluedMap<String, String> data = form.asMap();
return new StuffResponse().
errorCode(data.getFirst(PROP_ERROR_CODE)).
errorDescription(data.getFirst(PROP_ERROR_DESCRIPTION)).
...;
}
}
When creating the ClientBuilder, I register the MBR like this:
ClientBuilder builder = ClientBuilder.newBuilder().register(StuffResponseReader.class);
Related
Is it necessary to wrap in a backing object? I want to do this:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody String str1, #RequestBody String str2) {}
And use a JSON like this:
{
"str1": "test one",
"str2": "two test"
}
But instead I have to use:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Holder holder) {}
And then use this JSON:
{
"holder": {
"str1": "test one",
"str2": "two test"
}
}
Is that correct? My other option would be to change the RequestMethod to GET and use #RequestParam in query string or use #PathVariable with either RequestMethod.
While it's true that #RequestBody must map to a single object, that object can be a Map, so this gets you a good way to what you are attempting to achieve (no need to write a one off backing object):
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Map<String, String> json) {
//json.get("str1") == "test one"
}
You can also bind to Jackson's ObjectNode if you want a full JSON tree:
public boolean getTest(#RequestBody ObjectNode json) {
//json.get("str1").asText() == "test one"
You are correct, #RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object, so you essentially will have to go with your options.
If you absolutely want your approach, there is a custom implementation that you can do though:
Say this is your json:
{
"str1": "test one",
"str2": "two test"
}
and you want to bind it to the two params here:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
public boolean getTest(String str1, String str2)
First define a custom annotation, say #JsonArg, with the JSON path like path to the information that you want:
public boolean getTest(#JsonArg("/str1") String str1, #JsonArg("/str2") String str2)
Now write a Custom HandlerMethodArgumentResolver which uses the JsonPath defined above to resolve the actual argument:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.jayway.jsonpath.JsonPath;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String body = getRequestBody(webRequest);
String val = JsonPath.read(body, parameter.getMethodAnnotation(JsonArg.class).value());
return val;
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) servletRequest.getAttribute(JSONBODYATTRIBUTE);
if (jsonBody==null){
try {
String body = IOUtils.toString(servletRequest.getInputStream());
servletRequest.setAttribute(JSONBODYATTRIBUTE, body);
return body;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return "";
}
}
Now just register this with Spring MVC. A bit involved, but this should work cleanly.
For passing multiple object, params, variable and so on. You can do it dynamically using ObjectNode from jackson library as your param. You can do it like this way:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjectNode objectNode) {
// And then you can call parameters from objectNode
String strOne = objectNode.get("str1").asText();
String strTwo = objectNode.get("str2").asText();
// When you using ObjectNode, you can pas other data such as:
// instance object, array list, nested object, etc.
}
I hope this help.
You can mix up the post argument by using body and path variable for simpler data types:
#RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
public ResponseEntity<List<String>> newTrade(#RequestBody Trade trade, #PathVariable long portfolioId) {
...
}
The easy solution is to create a payload class that has the str1 and the str2 as attributes:
#Getter
#Setter
public class ObjHolder{
String str1;
String str2;
}
And after you can pass
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjHolder Str) {}
and the body of your request is:
{
"str1": "test one",
"str2": "two test"
}
#RequestParam is the HTTP GET or POST parameter sent by client, request mapping is a segment of URL which's variable:
http:/host/form_edit?param1=val1¶m2=val2
var1 & var2 are request params.
http:/host/form/{params}
{params} is a request mapping. you could call your service like : http:/host/form/user or http:/host/form/firm
where firm & user are used as Pathvariable.
Instead of using json, you can do simple thing.
$.post("${pageContext.servletContext.contextPath}/Test",
{
"str1": "test one",
"str2": "two test",
<other form data>
},
function(j)
{
<j is the string you will return from the controller function.>
});
Now in the controller you need to map the ajax request as below:
#RequestMapping(value="/Test", method=RequestMethod.POST)
#ResponseBody
public String calculateTestData(#RequestParam("str1") String str1, #RequestParam("str2") String str2, HttpServletRequest request, HttpServletResponse response){
<perform the task here and return the String result.>
return "xyz";
}
Hope this helps you.
I have adapted the solution of Biju:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
private ObjectMapper om = new ObjectMapper();
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String jsonBody = getRequestBody(webRequest);
JsonNode rootNode = om.readTree(jsonBody);
JsonNode node = rootNode.path(parameter.getParameterName());
return om.readValue(node.toString(), parameter.getParameterType());
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
if (jsonBody==null){
try {
jsonBody = IOUtils.toString(servletRequest.getInputStream());
webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonBody;
}
}
What's the different:
I'm using Jackson to convert json
I don't need a value in the annotation, you can read the name of the
parameter out of the MethodParameter
I also read the type of the parameter out of the Methodparameter => so the solution should be generic (i tested it with string and DTOs)
BR
Not sure where you add the json but if i do it like this with angular it works without the requestBody:
angluar:
const params: HttpParams = new HttpParams().set('str1','val1').set('str2', ;val2;);
return this.http.post<any>( this.urlMatch, params , { observe: 'response' } );
java:
#PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
log.debug("found: {} and {}", str1, str2);
}
You can also use a MultiValue Map to hold the requestBody in.
here is the example for it.
foosId -> pathVariable
user -> extracted from the Map of request Body
unlike the #RequestBody annotation when using a Map to hold the request body we need to annotate with #RequestParam
and send the user in the Json RequestBody
#RequestMapping(value = "v1/test/foos/{foosId}", method = RequestMethod.POST, headers = "Accept=application"
+ "/json",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE ,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public String postFoos(#PathVariable final Map<String, String> pathParam,
#RequestParam final MultiValueMap<String, String> requestBody) {
return "Post some Foos " + pathParam.get("foosId") + " " + requestBody.get("user");
}
Use an inner class
#RestController
public class MyController {
#PutMapping("/do-thing")
public void updateFindings(#RequestBody Bodies.DoThing body) {
...
}
private static class Bodies {
public static class DoThing {
public String name;
public List<String> listOfThings;
}
}
}
request parameter exist for both GET and POST ,For Get it will get appended as query string to URL but for POST it is within Request Body
Good.
I suggest creating a Value Object (Vo) that contains the fields you need. The code is simpler, we do not change the functioning of Jackson and it is even easier to understand.
Regards!
You can achieve what you want by using #RequestParam. For this you should do the following:
Declare the RequestParams parameters that represent your objects and set the required option to false if you want to be able to send a null value.
On the frontend, stringify the objects that you want to send and include them as request parameters.
On the backend turn the JSON strings back into the objects they represent using Jackson ObjectMapper or something like that, and voila!
I know, its a bit of a hack but it works! ;)
you can also user #RequestBody Map<String, String> params,then use params.get("key") to get the value of parameter
If somebody is interested in the webflux solution, below is a reactive version, based on Biju answer.
Please note that there is one very small but synchronized chunk, needed to protect the body from being consumed more than once. If you prefer a fully non-blocking version, I suggest publishing the flux that obtains json on the same scheduler, to make checking and reading sequential.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
#Slf4j
#RequiredArgsConstructor
public class JsonArgumentResolver implements HandlerMethodArgumentResolver {
private static final String ATTRIBUTE_KEY = "BODY_TOSTRING_RESOLVER";
private final ObjectMapper objectMapper;
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArgument.class);
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
String fieldName = parameter.getParameterName();
Class<?> clz = parameter.getParameterType();
return getRequestBody(exchange).map(body -> {
try {
JsonNode jsonNode = objectMapper.readTree(body).get(fieldName);
String s = jsonNode.toString();
return objectMapper.readValue(s, clz);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
});
}
private Mono<String> getRequestBody(ServerWebExchange exchange) {
Mono<String> bodyReceiver;
synchronized (exchange) {
bodyReceiver = exchange.getAttribute(ATTRIBUTE_KEY);
if (bodyReceiver == null) {
bodyReceiver = exchange.getRequest().getBody()
.map(this::convertToString)
.single()
.cache();
exchange.getAttributes().put(ATTRIBUTE_KEY, bodyReceiver);
}
}
return bodyReceiver;
}
private String convertToString(DataBuffer dataBuffer) {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
}
}
I want to create a json payload like
{
"email":"dautpure#gmail.com",
"validators": ["SyntaxValidator", "MXValidator", "ListDetectiveValidator"]
}
I wrote the following code :
package com.forcelocker.loaddata;
import com.google.gson.JsonObject;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import java.lang.reflect.Array;
import java.util.ArrayList;
public class validateEmail
{
public static String CheckingURL = "h_ttps://abc.com/address/v1/validateEmail";
private static final String Content_Type ="application/json";
public static Boolean CheckEmail(String Email,String Token) throws UnirestException
{
Boolean IsmailOk=false;
String Authorization = "Bearer "+Token;
JsonObject payload = new JsonObject();
payload.addProperty("email", Email);
HttpResponse<String> response = Unirest.post(CheckingURL)
.header("Content-Type", Content_Type)
.header("Authorization", Authorization)
.body(payload).asString();
return IsmailOk;
}
}
But I don't know how to put the validator in JSON which can hold a comma seperate values
any help would be great.
what about
payload.addProperty("validator", new String [] {"SyntaxValidator", "MXValidator", "ListDetectiveValidator"});
The problem was solved by using hashmap as suggested by noname. then converting it to json string and using it as payload for api
Map<String,Object> map = new HashMap<>();
String[] val = new String[] {"SyntaxValidator", "MXValidator", "ListDetectiveValidator"};
map.put("email", Email);
map.put("validators", val);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(map);
Thanks noname
I wrote a Spring RestController that returns a SseEmitter (for server-sent-event), and adds HATEOAS links to each event. Here is a simplified but working example of this controller:
package hello;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import hello.Greeting.Status;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
#RestController
public class GreetingController {
private static final Logger log = LoggerFactory.getLogger(GreetingController.class);
private static final String template = "Hello, %s!";
class GreetingRequestHandler implements Runnable {
private ResponseBodyEmitter emitter;
private Greeting greeting;
public GreetingRequestHandler(final ResponseBodyEmitter emitter, final Greeting greeting) {
this.emitter = emitter;
this.greeting = greeting;
}
#Override
public void run() {
try {
log.info(this.greeting.toString());
this.emitter.send(this.greeting);
Thread.sleep(5000);
if (Status.COMPLETE.equals(this.greeting.getStatus())) {
this.emitter.complete();
} else {
this.greeting.incrementStatus();
new Thread(new GreetingRequestHandler(this.emitter, this.greeting)).start();
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
#RequestMapping(path = "/greeting")
public SseEmitter greeting(#RequestParam(value = "name", defaultValue = "World") final String name) {
SseEmitter emitter = new SseEmitter();
Greeting greeting = new Greeting(String.format(template, name));
greeting.add(linkTo(methodOn(GreetingController.class).greeting(name)).withSelfRel());
new Thread(new GreetingRequestHandler(emitter, greeting)).start();
log.info("returning emitter");
return emitter;
}
}
The Greeting class is the following:
package hello;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.hateoas.ResourceSupport;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Greeting extends ResourceSupport {
private final String content;
private final static AtomicInteger idProvider = new AtomicInteger();
private int greetingId;
private Status status;
enum Status {
ENQUEUED,
PROCESSING,
COMPLETE;
}
#JsonCreator
public Greeting(#JsonProperty("content") final String content) {
this.greetingId = idProvider.addAndGet(1);
this.status = Status.ENQUEUED;
this.content = content;
}
public Status getStatus() {
return this.status;
}
protected void setStatus(final Status status) {
this.status = status;
}
public int getGreetingId() {
return this.greetingId;
}
public String getContent() {
return this.content;
}
#Override
public String toString() {
return "Greeting{id='" + this.greetingId + "', status='" + this.status + "' content='" + this.content + "', " + super.toString() + "}";
}
public void incrementStatus() {
switch (this.status) {
case ENQUEUED:
this.status = Status.PROCESSING;
break;
case PROCESSING:
this.status = Status.COMPLETE;
break;
default:
break;
}
}
}
This code works perfectly. If I try to reach the REST service using a web browser, I see events appearing with correct content and link.
The result looks like (each event appearing 5 seconds after the previous one):
data:{"content":"Hello, Kraal!","greetingId":8,"status":"ENQUEUED","_links":{"self":{"href":"http://localhost:8080/greeting?name=Kraal"}}}
data:{"content":"Hello, Kraal!","greetingId":8,"status":"PROCESSING","_links":{"self":{"href":"http://localhost:8080/greeting?name=Kraal"}}}
data:{"content":"Hello, Kraal!","greetingId":8,"status":"COMPLETE","_links":{"self":{"href":"http://localhost:8080/greeting?name=Kraal"}}}
Now I need to call this REST service and read these events from another Spring application... But I have no clue how to write the client code using Spring. This does not work as RestTemplate is designed for synchronous client side HTTP access...
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new Jackson2HalModule());
// required for HATEOAS
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
converter.setObjectMapper(mapper);
// required in order to be able to read serialized objects
MappingJackson2HttpMessageConverter converter2 = new MappingJackson2HttpMessageConverter();
converter2.setSupportedMediaTypes(MediaType.parseMediaTypes("application/octet-stream"));
converter2.setObjectMapper(mapper);
// required to understand SSE events
MappingJackson2HttpMessageConverter converter3 = new MappingJackson2HttpMessageConverter();
converter3.setSupportedMediaTypes(MediaType.parseMediaTypes("text/event-stream"));
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(converter);
converters.add(converter2);
converters.add(converter3);
// probably wrong template
RestTemplate restTemplate = new RestTemplate();
restTemplate = new RestTemplate(converters);
// this does not work as I receive events and no a single object
Greeting greeting = restTemplate.getForObject("http://localhost:8080/greeting/?name=Kraal", Greeting.class);
log.info(greeting.toString());
The error message I get is:
Caused by: com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'data': was expecting ('true', 'false' or 'null')
Indeed each event is a SSE event and starts with "data:"...
So the questions are:
what ObjectMapper module should I register in order to be able to map SSE with Jackson ?
how can I subscribe to incoming SSE events (observer pattern) using Spring ?
Thanks in advance.
Side note: As I'm struggling doing it using Spring I tried to do it using Jersey SSE support as follows. Using Jersey I receive events as expected, but then I can't cast them to a Greeting class (for the same reason as above I guess which is that I don't have the right converter module .):
Client client = ClientBuilder.newBuilder().register(converter).register(SseFeature.class).build();
WebTarget target = client.target("http://localhost:8080/greeting/?name=Kraal");
EventInput eventInput = target.request().get(EventInput.class);
while (!eventInput.isClosed()) {
final InboundEvent inboundEvent = eventInput.read();
if (inboundEvent == null) {
// connection has been closed
break;
}
// this works fine and prints out events as they are incoming
System.out.println(inboundEvent.readData(String.class));
// but this doesn't as no proper way to deserialize the
// class with HATEOAS links can be found
// Greeting greeting = inboundEvent.readData(Greeting.class);
// System.out.println(greeting.toString());
}
as per the documentation
you can use inboundEvent.readData(Class<T> type)
I'm using Jersey resourse in my project, like:
#Path("/api")
public class MyResource {
#Path("/create")
#POST
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
public Response handle(final String xml, #Context final HttpServletRequest request) {
.....
}
and I'm trying to test it:
public class MobipayResourceTest extends JerseyTest {
private MockHttpServletRequest servletRequest;
#Override
#Before
public void setUp() throws Exception {
servletRequest = new MockHttpServletRequest();
servletRequest.setMethod("POST");
}
public MobipayResourceTest() throws TestContainerException {
super("ua.privatbank.mobipay.api.resource");
}
#Test
public void testRes(){
WebResource webResource = resource();
webResource.path("/api/create").post(???); // I need to pass 2 parameters in the request - xml (in the body of post) and HttpServletRequest
}
How can I pass 2 my parameters (String xml and HttpServletRequest) to the resourse in test?
You don't need to pass the HttpServletRequest, I believe.
As to the xml parameter, I think you should have a parameter
of some other class there, not just of type String. For example
Item, Customer, Order, i.e. any business object (bean, POJO).
The way you've done it now, you'd better declare
#Consumes(MediaType.TEXT_PLAIN) because you declare that
you expect just a String in your method. Normally when
you expect XML, this XML value is unmarshalled into
an object of some type (usually a bean, POJO, etc). You
get this on the fly and you can just work with the object.
Here is some sample code of a sample Java client.
package com.company.api.test;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import com.google.gson.Gson;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.LoggingFilter;
import com.company.common.DateUtil;
import com.company.api.input.ItemOperation;
import com.company.api.input.ItemOperationData;
import com.company.api.result.ItemResult;
public class JavaClientREST {
public static void main(String[] args) throws Exception {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
client.addFilter(new LoggingFilter());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
WebResource service = client.resource(getBaseURI());
ItemOperation op1 = new ItemOperation();
op1.setItemID("447");
Date d1 = DateUtil.getDate(2013, Calendar.DECEMBER, 20);
System.out.println("DT1 = " + sdf.format(d1));
op1.setDate(d1);
op1.setOperation("pause");
String res = service.path("Item")
.entity(op1, MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_JSON)
.post(String.class);
Gson gson = new Gson();
ItemResult result = gson.fromJson(res, ItemResult.class);
System.out.println("ID = [" + result.getId() + "]");
System.out.println("Error = [" + result.getError() + "]");
System.out.println("DONE!");
}
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost:8080/api/service").build();
}
}
Is it necessary to wrap in a backing object? I want to do this:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody String str1, #RequestBody String str2) {}
And use a JSON like this:
{
"str1": "test one",
"str2": "two test"
}
But instead I have to use:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Holder holder) {}
And then use this JSON:
{
"holder": {
"str1": "test one",
"str2": "two test"
}
}
Is that correct? My other option would be to change the RequestMethod to GET and use #RequestParam in query string or use #PathVariable with either RequestMethod.
While it's true that #RequestBody must map to a single object, that object can be a Map, so this gets you a good way to what you are attempting to achieve (no need to write a one off backing object):
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody Map<String, String> json) {
//json.get("str1") == "test one"
}
You can also bind to Jackson's ObjectNode if you want a full JSON tree:
public boolean getTest(#RequestBody ObjectNode json) {
//json.get("str1").asText() == "test one"
You are correct, #RequestBody annotated parameter is expected to hold the entire body of the request and bind to one object, so you essentially will have to go with your options.
If you absolutely want your approach, there is a custom implementation that you can do though:
Say this is your json:
{
"str1": "test one",
"str2": "two test"
}
and you want to bind it to the two params here:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
public boolean getTest(String str1, String str2)
First define a custom annotation, say #JsonArg, with the JSON path like path to the information that you want:
public boolean getTest(#JsonArg("/str1") String str1, #JsonArg("/str2") String str2)
Now write a Custom HandlerMethodArgumentResolver which uses the JsonPath defined above to resolve the actual argument:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.jayway.jsonpath.JsonPath;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String body = getRequestBody(webRequest);
String val = JsonPath.read(body, parameter.getMethodAnnotation(JsonArg.class).value());
return val;
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) servletRequest.getAttribute(JSONBODYATTRIBUTE);
if (jsonBody==null){
try {
String body = IOUtils.toString(servletRequest.getInputStream());
servletRequest.setAttribute(JSONBODYATTRIBUTE, body);
return body;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return "";
}
}
Now just register this with Spring MVC. A bit involved, but this should work cleanly.
For passing multiple object, params, variable and so on. You can do it dynamically using ObjectNode from jackson library as your param. You can do it like this way:
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjectNode objectNode) {
// And then you can call parameters from objectNode
String strOne = objectNode.get("str1").asText();
String strTwo = objectNode.get("str2").asText();
// When you using ObjectNode, you can pas other data such as:
// instance object, array list, nested object, etc.
}
I hope this help.
You can mix up the post argument by using body and path variable for simpler data types:
#RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
public ResponseEntity<List<String>> newTrade(#RequestBody Trade trade, #PathVariable long portfolioId) {
...
}
The easy solution is to create a payload class that has the str1 and the str2 as attributes:
#Getter
#Setter
public class ObjHolder{
String str1;
String str2;
}
And after you can pass
#RequestMapping(value = "/Test", method = RequestMethod.POST)
#ResponseBody
public boolean getTest(#RequestBody ObjHolder Str) {}
and the body of your request is:
{
"str1": "test one",
"str2": "two test"
}
#RequestParam is the HTTP GET or POST parameter sent by client, request mapping is a segment of URL which's variable:
http:/host/form_edit?param1=val1¶m2=val2
var1 & var2 are request params.
http:/host/form/{params}
{params} is a request mapping. you could call your service like : http:/host/form/user or http:/host/form/firm
where firm & user are used as Pathvariable.
Instead of using json, you can do simple thing.
$.post("${pageContext.servletContext.contextPath}/Test",
{
"str1": "test one",
"str2": "two test",
<other form data>
},
function(j)
{
<j is the string you will return from the controller function.>
});
Now in the controller you need to map the ajax request as below:
#RequestMapping(value="/Test", method=RequestMethod.POST)
#ResponseBody
public String calculateTestData(#RequestParam("str1") String str1, #RequestParam("str2") String str2, HttpServletRequest request, HttpServletResponse response){
<perform the task here and return the String result.>
return "xyz";
}
Hope this helps you.
I have adapted the solution of Biju:
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{
private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
private ObjectMapper om = new ObjectMapper();
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArg.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String jsonBody = getRequestBody(webRequest);
JsonNode rootNode = om.readTree(jsonBody);
JsonNode node = rootNode.path(parameter.getParameterName());
return om.readValue(node.toString(), parameter.getParameterType());
}
private String getRequestBody(NativeWebRequest webRequest){
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
if (jsonBody==null){
try {
jsonBody = IOUtils.toString(servletRequest.getInputStream());
webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return jsonBody;
}
}
What's the different:
I'm using Jackson to convert json
I don't need a value in the annotation, you can read the name of the
parameter out of the MethodParameter
I also read the type of the parameter out of the Methodparameter => so the solution should be generic (i tested it with string and DTOs)
BR
Not sure where you add the json but if i do it like this with angular it works without the requestBody:
angluar:
const params: HttpParams = new HttpParams().set('str1','val1').set('str2', ;val2;);
return this.http.post<any>( this.urlMatch, params , { observe: 'response' } );
java:
#PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
log.debug("found: {} and {}", str1, str2);
}
You can also use a MultiValue Map to hold the requestBody in.
here is the example for it.
foosId -> pathVariable
user -> extracted from the Map of request Body
unlike the #RequestBody annotation when using a Map to hold the request body we need to annotate with #RequestParam
and send the user in the Json RequestBody
#RequestMapping(value = "v1/test/foos/{foosId}", method = RequestMethod.POST, headers = "Accept=application"
+ "/json",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE ,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#ResponseBody
public String postFoos(#PathVariable final Map<String, String> pathParam,
#RequestParam final MultiValueMap<String, String> requestBody) {
return "Post some Foos " + pathParam.get("foosId") + " " + requestBody.get("user");
}
Use an inner class
#RestController
public class MyController {
#PutMapping("/do-thing")
public void updateFindings(#RequestBody Bodies.DoThing body) {
...
}
private static class Bodies {
public static class DoThing {
public String name;
public List<String> listOfThings;
}
}
}
request parameter exist for both GET and POST ,For Get it will get appended as query string to URL but for POST it is within Request Body
Good.
I suggest creating a Value Object (Vo) that contains the fields you need. The code is simpler, we do not change the functioning of Jackson and it is even easier to understand.
Regards!
You can achieve what you want by using #RequestParam. For this you should do the following:
Declare the RequestParams parameters that represent your objects and set the required option to false if you want to be able to send a null value.
On the frontend, stringify the objects that you want to send and include them as request parameters.
On the backend turn the JSON strings back into the objects they represent using Jackson ObjectMapper or something like that, and voila!
I know, its a bit of a hack but it works! ;)
you can also user #RequestBody Map<String, String> params,then use params.get("key") to get the value of parameter
If somebody is interested in the webflux solution, below is a reactive version, based on Biju answer.
Please note that there is one very small but synchronized chunk, needed to protect the body from being consumed more than once. If you prefer a fully non-blocking version, I suggest publishing the flux that obtains json on the same scheduler, to make checking and reading sequential.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
#Slf4j
#RequiredArgsConstructor
public class JsonArgumentResolver implements HandlerMethodArgumentResolver {
private static final String ATTRIBUTE_KEY = "BODY_TOSTRING_RESOLVER";
private final ObjectMapper objectMapper;
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonArgument.class);
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
String fieldName = parameter.getParameterName();
Class<?> clz = parameter.getParameterType();
return getRequestBody(exchange).map(body -> {
try {
JsonNode jsonNode = objectMapper.readTree(body).get(fieldName);
String s = jsonNode.toString();
return objectMapper.readValue(s, clz);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
});
}
private Mono<String> getRequestBody(ServerWebExchange exchange) {
Mono<String> bodyReceiver;
synchronized (exchange) {
bodyReceiver = exchange.getAttribute(ATTRIBUTE_KEY);
if (bodyReceiver == null) {
bodyReceiver = exchange.getRequest().getBody()
.map(this::convertToString)
.single()
.cache();
exchange.getAttributes().put(ATTRIBUTE_KEY, bodyReceiver);
}
}
return bodyReceiver;
}
private String convertToString(DataBuffer dataBuffer) {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
}
}