I am writing a web service which should accept type Object[]. Its universal and needs to accept different number and types of parameters in different scenarios.
Request object looks like this:
#XmlRootElement
public class SimilarityRequest {
private Object[] params;
private String similarity;
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
public String getSimilarity() {
return similarity;
}
public void setSimilarity(String similarity) {
this.similarity = similarity;
}
}
This is WebService:
#SessionScoped
#Path("/similarity/")
#Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
#Stateful
#StatefulTimeout(600000) // 10 minutes
public class SimilarityResource {
#POST
#Path("/")
public List<SimilarityResult> universalSimilarity(JAXBElement<SimilarityRequest> sr) {
Object[] params = sr.getValue().getParams();
String similarity = sr.getValue().getSimilarity();
}
}
I dont know what json it accepts for params in this case? I tried "params":{5,10} and "params":{"0":5,"1":10} and also "params":[5,10]. Something throws 500 and something 400 (bad request). Any ideas?
I've successfully implemented the service using Jersey, the code is the same, I've just removed the JAXBElement wrapper and the #XmlRootElement annotation.
The WEB-INF.xml file must include the folder containing the SimilarityRequest class in the
com.sun.jersey.config.property.packages parameter section and the com.sun.jersey.api.json.POJOMappingFeature parameter must be true.
The service receives correctly the following json:
{ "similarity": "test", "params":[5,10] }
The object array contains two Integer values.
Related
I have a POST endpoint which accepts a JSON as request body.
#Path("/drink")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class DrinkResource {
#POST
public Drink getDrink(Fruit fruit) {
return new Drink(fruit.getName());
}
}
The request body is supposed to be deserialized into this POJO :
public class Fruit {
private final String name;
#JsonCreator
public Fruit(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
I'm using Jackson for the deserialization.
Is it possible to make the deserialization fail when the JSON in the request body has duplicate keys ?
For example, if the request body looks like this : {"name" : "banana", "name" : "orange"}, I would like to get a 500 status code or another kind of error instead of having the json deserialized with the last property.
Basically, I'm looking for a solution with the same logic as the JsonParser.Feature.STRICT_DUPLICATE_DETECTION with the ObjectMapper but for a POST endpoint.
I'm also using quarkus so I don't know if there is a property for this. Something similar to quarkus.jackson.fail-on-unknown-properties=true but for the duplicate properties.
Add the following:
#Singleton
public class MyCustomizer implements ObjectMapperCustomizer {
#Override
public void customize(ObjectMapper objectMapper) {
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
}
}
If you do, the ObjectMapper that Quarkus uses will throw a JsonParseException thus leading to a HTTP 400 response.
I have this code using Angular 4 HttpClient, supposed to communicate with a simple RESTful JAVA web service and get a JSONstring back:
this.client.post('http://localhost:8080/ToDoService/rest/service/create', {id: 'foo', todo: 'bar'}).subscribe((data) => {
console.log("Got data: ", data);
}, (error) => {
console.log("Error", error);
})
It's not working. What I mean is that the id and todo parameters are not getting passed to the REST backend.
At the other hand, if I change above code to:
this.client.post('http://localhost:8080/ToDoService/rest/service/create?id=foo&todo=bar', '').subscribe((data) => {
console.log("Got data: ", data);
}, (error) => {
console.log("Error", error);
})
Everything works fine, but I'm sure the second snipped is wrong. It just looks wrong.
Could you give me a push and point my mistake?
P.s
The JAVA backend:
#Path("/service")
public class Rest extends Application{
#POST
#Path("/create")
public Response printMessage(#QueryParam("id") String userId, #QueryParam("todo") String toDo) {
JSONObject result = new JSONObject();
result.put("id", userId);
result.put("todo", toDo);
return Response.status(200).entity(result.toString()).build();
}
}
You're mapping QueryParams, you need to map that payload to either a Map or an Object:
class PayLoad {
private String id;
private String todo;
// Getter and Setters
}
#Path("/service")
public class Rest extends Application{
#POST
#Path("/create")
public Response printMessage(#RequestBody PayLoad payload) {
JSONObject result = new JSONObject();
result.put("id", payload.getId());
result.put("todo", payload.getTodo());
return Response.status(200).entity(result.toString()).build();
}
}
Hope this helps!
First of all, looking at the MyKong tutorial you can see that #QueryParam accepts parameters sent in the URL. That is a first problem here. Back to the main point.
I am not an expert in Angular 4, but I think the problem lies deeper in your architecture. You are sending to your backend:
{id: 'foo', todo: 'bar'}
and expect in your Java:
#QueryParam("id") String userId, #QueryParam("todo") String toDo
You pass an object and expect in your Java backend two strings. If you want to get your object, you might create this kind of class:
public class JsonWrapper {
private String id;
private String todo;
// Classic constructor and setters
}
Then, in your service:
#Path("/service")
public class Rest extends Application {
#POST
#Path("/create")
public Response printMessage(#RequestBody JsonWrapper wrapper) {
JSONObject result = new JSONObject();
result.put("id", wrapper.getId());
result.put("todo", wrapper.getTodo());
return Response.status(200).entity(result.toString()).build();
}
}
If the JsonWrapper does not work, I think a Map can do the trick, too.
I'm developing a rest web service using jax rs. In the current method i want to hava a #GET request where the user is passing a json parameter named "request" using the header of the http request.
Code:
#GET
#Path("load")
#Produces(MediaType.APPLICATION_JSON)
public LoadUserResponse load(#HeaderParam("request") LoadUserRequest sRequest) throws Exception{
User user= userBean.load(sRequest.getId());
LoadUserResponse response = new LoadUserResponse();
response.Transform(user);
return response;
}
this code gives me the exception :
java.lang.RuntimeException: Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.HeaderParam(\"request\") on public com.totempool.rest.responses.LoadUserResponse com.totempool.rest.services.UserService.load(com.totempool.rest.requests.LoadUserRequest) throws java.lang.Exception for basetype: com.totempool.rest.requests.LoadUserRequest"}}
POJO class:
package com.totempool.rest.requests;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class LoadUserRequest {
#XmlElement(name="id")
private long id;
public LoadUserRequest(){
}
public long getId() {
return id;
}
public LoadUserRequest fromString(String param){
return null;
}
}
My question is , is there a way to pass a #HeaderParam and autoparse it to the object?
By autoparse i mean something like this:
#POST
#Path("list")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public ListUserResponse list(ListUserRequest request) throws Exception{
List<User> users= userBean.list();
ListUserResponse response = new ListUserResponse();
response.Transform(users);
return response;
}
with this code the app will recognize the json send and parse it into an object.
A POSSIBLE OPTION BUT NOT SUITABLE FOR WHAT I NEED:
#GET
#Path("load")
#Produces(MediaType.APPLICATION_JSON)
public LoadUserResponse load(#HeaderParam("request") String sRequest) throws Exception{
ObjectMapper map= new ObjectMapper();
LoadUserRequest request=map.readValue(sRequest, LoadUserRequest.class);
User user= userBean.load(request.getId());
LoadUserResponse response = new LoadUserResponse();
response.Transform(user);
return response;
}
here i'm getting the string send and then parsing it manually, the problem is that the web service may have several similar methods and i don't want to do this manually in every one of them.
In my case it works with the following solution (GSON from Google). I don't know if this is the best one, but it works :-) I put this code in every REST-Transfer-Class.
/**
* Parse JSON to Object.
*
* #param p_json JSON String
* #return Object
*/
public static POJO_XY valueOf(String p_json) {
return gson.fromJson(p_json, POJO_XY.class);
}
I am trying to understand whether it is possible to serialize a java Map to a Json response from Jersey.
Here is my service :
#Singleton
#Path("/")
public class ApiServiceResource {
#Inject
ISeedingUpdateService seedingUpdateService;
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/map")
public List<String> getMap() {
return newArrayList(seedingUpdateService.toString());
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/pojo")
public TemplateMessage getTemplateMessage(#PathParam("param") String param) {
return new TemplateMessage(param, seedingUpdateService.toString());
}
#XmlRootElement
public static class TemplateMessage {
public Map<String,String > param;
public TemplateMessage() {
}
public TemplateMessage(String param1, String param2) {
this.param = newHashMap();
this.param.put(param1,param2);
}
}
}
The getMap method fails since it cannot serialize the Map -->
SEVERE: The registered message body writers compatible with the MIME media type are:
application/json ->
com.sun.jersey.json.impl.provider.entity.JSONJAXBElementProvider$App
com.sun.jersey.json.impl.provider.entity.JSONArrayProvider$App
com.sun.jersey.json.impl.provider.entity.JSONObjectProvider$App
com.sun.jersey.json.impl.provider.entity.JSONRootElementProvider$App
com.sun.jersey.json.impl.provider.entity.JSONListElementProvider$App
*/* ->
And the second method works just fine the POJO is serialized with the Map inside.
Is there something that i am missing ?
By the way the app is configured in Guice , so here is the guice configuration :
#Override
protected void configureServlets() {
bind(ApiServiceResource.class);
/* bind jackson converters for JAXB/JSON serialization */
bind(MessageBodyReader.class).to(JacksonJsonProvider.class);
bind(MessageBodyWriter.class).to(JacksonJsonProvider.class);
Map<String,String> parameters = newHashMap();
parameters.put("com.sun.jersey.config.property.packages", "com.delver.update.api");
serve("/rest/*").with(GuiceContainer.class, parameters);
}
Instead of binding MessageBodyWriter (since you already have more than one) you could try what we do in our app, we bind the Jackson writer and exception mappers in Guice through:
bind(forName("com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider")).in(Scopes.SINGLETON);
bind(forName("com.fasterxml.jackson.jaxrs.json.JsonParseExceptionMapper")).in(Scopes.SINGLETON);
bind(forName("com.fasterxml.jackson.jaxrs.json.JsonMappingExceptionMapper")).in(Scopes.SINGLETON);
I've got a function like:
#POST
#Path("/create")
#Produces({MediaType.APPLICATION_JSON})
public Playlist createPlaylist(#FormParam("name") String name)
{
Playlist p = playlistDao.create();
p.setName(name);
playlistDao.insert(p);
return p;
}
I want the "name" parameter to come from the form OR from the query parameter. If the user posts to /playlist/create/?name=bob then I want it to work. (This is mostly to aid with testing the API, but also for consuming it on different non-browser platforms.)
I'd be willing to subclass whatever makes the magic binding work... (#BothParam("name") String name) but would need some help to make that happen as I'm new to Jersey/Java Servlets.
Update: The next day...
I've solved this by implementing a ContainerRequestFilter that merges the form parameters into the query parameters. This isn't the best solution, but it does seem to work. I didn't have any luck merging anything into the form parameters.
Here's the code in case someone comes looking for it:
#Override
public ContainerRequest filter(ContainerRequest request)
{
MultivaluedMap<String, String> qParams = request.getQueryParameters();
Form fParams = request.getFormParameters();
for(String key : fParams.keySet())
{
String value = fParams.get(key).get(0);
qParams.add(key, value);
}
}
I would still appreciate knowing if there is a better way to do this so I'll leave this question open for now.
One way you could do this is with an InjectableProvider.
First, you'd define a BothParam annotation:
#Target({ ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface BothParam { String value(); }
Then, define an InjectableProvider for it:
#Provider
public class BothParamProvider implements Injectable<String>, InjectableProvider<BothParam, Type> {
#Context private HttpContext httpContext;
private String parameterName;
public BothParamProvider(#Context HttpContext httpContext) {
this.httpContext = httpContext;
}
public String getValue() {
if (httpContext.getRequest().getQueryParameters().containsKey(parameterName)) {
return httpContext.getRequest().getQueryParameters().getFirst(parameterName);
} else if(httpContext.getRequest().getFormParameters().containsKey(parameterName)) {
return httpContext.getRequest().getFormParameters().getFirst(parameterName);
}
return null;
}
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}
public Injectable getInjectable(ComponentContext cc, BothParam a, Type c) {
parameterName = a.value();
return this;
}
}
Note, this is not truly a coupling of QueryParam and FormParam. Targets annotated with either of those are injected in a much more sophisticated manner. However, if your needs are sufficiently limited, the method outlined above might work for you.