I am trying to map a REST-call with my POJO. The POJO looks like this:
public class ResultWrapper implements Serializable{
private int total;
private List<Movies> movies; ... getters and setters
In the call I use:
WebResource webResource = client.resource(RequestURI + URLEncoder.encode(movie, "UTF-8"));
ResultWrapper result = webResource.accept("application/json").get(ResultWrapper.class);
The error:
com.sun.jersey.api.client.ClientHandlerException: A message body reader for Java class models.ResultWrapper, and Java type class models.ResultWrapper, and MIME media type text/javascript; charset=ISO-8859-1 was not found
Client is a Jersey client. I have tried making the call from Chrome (Postman) and it sais that the application type returned is "text/javascript", not "application/json" as one would expect? I think that would be my problem.
Is there any way I can get the ObjectMapper to resolve that it is actually "application/json" and not "text/javascript". I have tried using String.class and then I get the Json object just fine.
My purpose is to use automatic mapping from Jersey Client.
Thanks for any tips or advice.
try adding annotation #Produces (MediaType.APPLICATION_JSON )
You can try this:
#Provider
#Produces(application/json)
public class YourTestBodyWriter implements MessageBodyWriter<ResultWrapper> {
private static final Logger LOG = LoggerFactory.getLogger(YourTestBodyWriter.class);
#Override
public boolean isWriteable(
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType)
{
return ResultWrapper.class.isAssignableFrom(type);
}
#Override
public long getSize(
ResultWrapper t,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType)
{
return -1;
}
#Override
public void writeTo(
ResultWrapper t,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) throws IOException, WebApplicationException
{
String message = t.someMethod()
entityStream.write(message.getBytes(Charsets.UTF_8));
LOG.info(message);
}
}
Add in your app run():
// Serializer
environment.jersey().register(new YourTestBodyWriter ());
Thats the normal way in your application.
Related
jersey 2.21.
I have a resource file like below
……
#POST
#Path("/userReg")
#Produces("application/json;charset=UTF-8")
public JsonResp userReg(UserRegReq userRegReq) throws LoginNameExists {
HttpHeaderUtils.parseHeaders(userRegReq, headers);
//JsonResp is a custom java class.
JsonResp result = new JsonResp();
//will throw LoginNameExists
User user = userManager.register(userRegReq.getLoginName(), userRegReq.getPassword());
//success
result.setResult(0);
result.setData(user.getId);
return result;
}
……
To return the result to client, I implement a custom MessageBodyWriter like below
#Produces("application/json")
public class MyRespWriter implements MessageBodyWriter<JsonResp> {
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return type == JsonResp.class;
}
#Override
public long getSize(JsonResp jsonResp, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return 0;
}
#Override
public void writeTo(JsonResp jsonResp, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
//if these no exception in userReg(),
//the parameter annotations contains the annotations
//such as POST, Path, Produces;
//but if there is an exception in userReg(),
//the parameter annotations contains none of POST, Path, Produces;
//So, is there any way to retrieve the original annotations all along?
//JsonUtils is a custom java class.
String data = JsonUtils.toJsonString(jsonResp);
Writer osWriter = new OutputStreamWriter(outputStream, "UTF-8");
osWriter.write(data);
osWriter.flush();
}
}
And to handle the exceptions, I implement a ExceptionMapper like this:
public class MyExceptionMapper implements ExceptionMapper<Exception> {
public Response toResponse(Exception e) {
JsonResp result = new JsonResp();
//error
result.setResult(-1);
result.setErrMsg("System error.");
return Response.ok(result, MediaType.APPLICATION_JSON_TYPE).status(Response.Status.OK).build();
}
}
Now, if everything is ok and there’s no exception, the code execution router is userReg() -> MyRespWriter.writeTo(), the parameter "annotations" of MyRespWriter.writeTo() contains the correct annotations of method userReg(), such as POST, Path, Produces.
But if userReg() throws exception, the code execution router is userReg() -> MyExceptionMapper.toResponse() -> MyRespWriter.writeTo(), the parameter "annotations" of method MyRespWriter.writeTo() has none of the annotations of method userReg().
I want to know, is there any way that MyRespWriter.writeTo() can retrieve the original annotations all along?
You can inject ResourceInfo, then get the Method with ri.getResourceMethod(), then call method.getAnnotations() to get the annotations.
public class MyRespWriter implements MessageBodyWriter<JsonResp> {
#Context
ResourceInfo ri;
...
Annotations[] annos = ri.getResourceMethod().getAnnotations();
I'm trying to get Jersey to automatically deserialize into a subclass instance of the declared class for me.
Say A has one string field named "a". B extends A has one string field named "b". Similarly, C extends A has one string field named "c".
I'd like for an input which has fields "a" and "b" to be deserialized into an instance of B, and an input which has fields "a" and "c" to be deserialized into an instance of C with one endpoint definition, specifying the post body parameter type as A, as follow.
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public ServerResponse translateClientRequest(final A postBody) throws
ProxyException {
..
}
This example doesn't work. Is what I'm asking for possible? If so, how?
You probabily should have to use a custom response (or request) mapper.
1.- Create a class implementing MessageBodyReader in charge of writing / reading the request
#Provider
public class MyMessageBodyReader
implements MessageBodyReader<A> {
#Override
public boolean isReadable(final Class<?> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
// you can check if the media type is JSon also but it's enougth to check
// if the type is a subclass of A
return A.class.isAssignableFrom(type); // is a subclass of A?
}
#Override
public A readFrom(final Class<A> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String,String> httpHeaders,
final InputStream entityStream) throws IOException,
WebApplicationException {
// create an instance of B or C using Jackson to parse the entityStream (it's a POST method)
}
}
2.- Register the Mappers in your app
public class MyRESTApp
extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
s.add(MyMessageBodyReader.class);
return s;
}
}
Jersey will scan all registered Mappers calling their isReadable() method until one returns true... if so, this MessageBodyReader instance will be used to serialize the content
Sounds like you want a custom MessageBodyReader that will determine the class and convert the data to appropriate subclass.
See JAX-RS Entity Providers for examples.
I suspect this can't be done, but maybe there's a trick I'm missing. I want to use to different methods that take the same MediaType, but have different parameters to the mediatype. Perhaps this is abusing MediaType parameters...
#POST
#Consumes("application/json;internal=true")
public Response handleInternal(String request) {
}
#POST
#Consumes("application/json;internal=false")
public Response handleExternal(String request) {
}
Jersey complains I have two methods consuming the same MediaType, which is true. I was hoping it'd go on to pick the right one by the parameter. Is there some trick to making this work? In a nutshell, I have two use cases for how to treat the information coming in (specifically, domain level validation) and this seemed like a decent way to distinguish between those two.
You could use a MessageBodyReader along with two user types, one for internal json and the other for external json
1- Create two types than extends String (via delegation -using lombok is easier-):
#RequiredArgsConstructor
public class InternalJSON {
#Delegate
private final String _theJSONStr;
}
#RequiredArgsConstructor
public class ExternalJSON {
#Delegate
private final String _theJSONStr;
}
2- Create the MessageBodyReader type
#Provider
public class MyRequestTypeMapper
implements MessageBodyReader<Object> {
#Override
public boolean isReadable(final Class<?> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
// this matches both application/json;internal=true and application/json;internal=false
return mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}
#Override
public Object readFrom(final Class<Object> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String,String> httpHeaders,
final InputStream entityStream) throws IOException,
WebApplicationException {
if (mediaType.getSubType().equals("internal=true") {
// Build an InternalJSON instance parsing entityStream
// ... perhaps using JACKSON or JAXB by hand
} else if (mediaType.getSubType().equals("internal=false") {
// Build an ExternalJSON instance parsing entityStream
// ... perhaps using JACKSON or JAXB by hand
}
}
}
3- Register your MessageBodyReader at the Application (this is optional since jersey will scan the classpath for #Provider annotated types
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
...
s.add(MyRequestTypeMapper .class);
return s;
}
4- Reformat your rest methods usgin the two user types for internal and external json
I've got one resource (this is not a requirement) that has many different #GET methods in it. I've configured my web.xml to have this in it:
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
This turns on the POJO mapping feature which works great. It allows me to return a pojo and it gets automatically converted to JSON.
Problem is I've got some code I want to reuse that returns JSON as a String. I'm having trouble using these methods because Jersey does not interpret this as a JSON response, but as a String value to a JSON object. So if, for example, the method that returns a String returns an empty list the client sees
"[]"
instead of
[]
The problem is the JSON is wrapped in a double quotes. For these methods that return Strings, how can I tell Jersey to return the string as-is?
You probabily should have to use a custom response (or request) mapper.
1.- Create a class implementing MessageBodyWriter (or MessageBodyReader) in charge of writing / reading the response
#Provider
public class MyResponseTypeMapper
implements MessageBodyWriter<MyResponseObjectType> {
#Override
public boolean isWriteable(final Class<?> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
... use one of the arguments (either the type, an annotation or the MediaType)
to guess if the object shoud be written with this class
}
#Override
public long getSize(final MyResponseObjectType myObjectTypeInstance,
final Class<?> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
// return the exact response length if you know it... -1 otherwise
return -1;
}
#Override
public void writeTo(final MyResponseObjectType myObjectTypeInstance,
final Class<?> type,final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String,Object> httpHeaders,
final OutputStream entityStream) throws IOException, WebApplicationException {
... serialize / marshall the MyResponseObjectType instance using
whatever you like (jaxb, etC)
entityStream.write(serializedObj.getBytes());
}
}
2.- Register the Mappers in your app
public class MyRESTApp
extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
s.add(MyResponseTypeMapper.class);
return s;
}
}
Jersey will scan all registered Mappers calling their isWriteable() method until one returns true... if so, this MessageBodyWriter instance will be used to serialize the content to the client
I am trying to create a rest api server on top of my quartz scheduler. I want to be able to return the org.quartz.Trigger and org.quartz.JobDetail objects as JSON. The problem is that I cannot add the #XmlRootElement to these classes without having to recompile the jar and this causes problems with future upgrades etc. I have tested and am able to serialize a list of classes when adding the #XmlRootElement but when I try to return a List I get the error "A message body writer for Java class java.util.ArrayList, and Java type java.util.List, and MIME media type application/json was not found". I have tried adding a custom MessageBodyWriter but that does not seem to fix the problem either. Is there a way to marshal the quartz classes to JSON without having to modify the quartz code to add the #XmlRootElement. I am using this in an embedded web server with jetty btw.
#Path("/jobs")
public class JobsResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Trigger> listScheduledJobs() throws SchedulerException {
return TaskEngine.getInstance().listScheduledJobs();
}
}
Web server configuration
public class TaskEngineWebServer {
private static final Logger logger = Logger.getLogger(TaskEngineWebServer.class.getName());
private Server server;
public TaskEngineWebServer() {
this(8585);
}
public TaskEngineWebServer(Integer port) {
server = new Server(port);
logger.info("Configuring rest service to start at url /r");
ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.NO_SECURITY);
//handler.getInitParams().put("com.sun.jersey.api.json.POJOMappingFeature", "true");
PackagesResourceConfig packagesResourceConfig = new PackagesResourceConfig("com.hp.vf.scheduler.server.rest");
ServletContainer servletContainer = new ServletContainer(packagesResourceConfig);
handler.addServlet(new ServletHolder(servletContainer), "/r/*");
server.setHandler(handler);
logger.info("Done configuring rest service");
}
public void start() throws Exception {
server.start();
}
public void stop() throws Exception {
server.stop();
}
public boolean isStarted() {
return server.isStarted();
}
public boolean isStopped() {
return server.isStopped();
}
}
I dont think you can return a List as JSON directly. You need to have a wrapper class which contains this list. For eg try something like this
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class TriggerWrapper{
private List<Triggers> triggers;
public List<Triggers> getTriggers(){
if(triggers==null){
triggers = new ArrayList<Triggers>();
}
return triggers;
}
}
Then in your rest service class :
#Path("/jobs")
public class JobsResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public TriggerWrapper listScheduledJobs() throws SchedulerException {
TriggerWrapper response = new TriggerWrapper();
List<Triggers> triggers = TaskEngine.getInstance().listScheduledJobs();
response.getTriggers.addAll(triggers);
return response;
}
}
Your json would something like this :
{
"triggerwrapper": {
"triggers": [
{
"triggerid": 1
},
{
"triggerid": 2
}
]
}
}
And ofcourse if you want you can drop the root element tag from your json its configurable in jersey.
I finally figured out a clean solution, it involves creating my own MediaBodyWriter class and adding it as a provider. You have to make sure you are not using the jersey-bundle jar as the default jaxb to json provider will override yours.
jars required
jersey-core
jersey-servlet
jersey-server
jackson-annotations
jackson-databind
jackson-core
I found this MediaWriter example on the web somewhere. Sorry for not having the url but thanks to whoever write it.
#Provider
#Produces({ MediaType.APPLICATION_JSON })
public class JacksonWriter implements MessageBodyWriter<Object> {
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return true;
}
#Override
public long getSize(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
#Override
public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream) {
try {
MAPPER.writeValue(entityStream, value);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
When it loads you will see a log message that your provider was loaded.
This gave me the json output I was expecting as it does not rely on the JAXB annotations and simply uses the object mapper/ reflection. Probably less efficient but for my case it does not matter.