Migration of JSONJAXBContext jersey 1.x to 2.x - java

I want to upgrade my jersey version to 2.x from 1.x.
In my code I had:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private static final Class<?>[] classes = {
A.class, B.class, C.class, D.class, E.class,
F.class, G.class
};
private JAXBContext context;
public JAXBContextResolver() throws Exception {
context = new JSONJAXBContext(JSONConfiguration.natural()
.humanReadableFormatting(true).rootUnwrapping(true).build(),
classes);
}
public JAXBContext getContext(Class<?> objectType) {
return context;
}
}
But JSONJAXBContext and JSONConfiguration are not defined in jersey 2.x.
How can I make the change accordingly?
The question Where did JSONConfiguration go in Jersey 2.5.x? is not answering my question because it does not explain how do I add my class which I want to return as output

There is no need for this. You either are going to use MOXy or Jackson as your JSON provider in Jersey 2.x. For the latter, you configure with MoxyJsonConfig. For Jackson, you use ObjectMapper. Figure out which provider you are using, and configure the according object. Both can be configured in a ContextResolver like you're currently doing.
As far as your current configurations
You won't need to configure any classes with either of these.
Unwrapped objects are serialized by default.
And to pretty print you would do the following
Jackson
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
MOXy
MoxyJsonConfig config = new MoxyJsonConfig()
.setFormattedOutput(true);

Related

Dropwizard ObjectMapper not honouring enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)

I want to be able for jackson to parse case insensitive enums. For e.g
public enum OperType {
SUM
PRODUCT
}
i want to accept both "SUM" and "sum" in the POST request.
I am getting hold of objectMapper in Application::run and enabling the setting:
environment.getObjectMapper().enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
But this is having no effect!
Jersey doesn't use objectMapper from Dropwizard bootstrap despite what Dropwizard's official documentation might lead one to believe.
Needed to register custom ContextResolver in the Application::run to make it work:
environment.jersey().register(new ObjectMapperContextResolver(injector.getInstance(ObjectMapper.class)));
where:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
Man these documentations around the dropwizard ecosystem can be really confusing for someone who isn't as well versed yet!

Jersey 2.6, Spring 4: Context Resolver with #Component doesn't work

Based on other issues that were actually resolved in Jersey 2.6, I suspect this might be a Jersey bug, but I wanted to vet it here first.
The following works as expected:
#Provider
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper mapper;
#Value("${json.prettyPrint}")
private boolean prettyPrint = false;
public ObjectMapperResolver() {
mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.INDENT_OUTPUT, prettyPrint);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
/**
* Push Joda de/serializers into the actual mapper
*/
#PostConstruct
private void configureJodaSerializers() {
mapper.registerModule(new JodaModule()
// Our deserializers are more forgiving
.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer())
.addDeserializer(LocalTime.class, new CustomLocalTimeDeserializer())
// Custom serializer to avoid HH:mm:ss.SSS (we don't want millis)
.addSerializer(LocalTime.class, new LocalTimeSerializer()));
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
In all my resources, my Joda date types are properly serialized. However, I wanted to grab the same ObjectMapper to use in a non-Jersey managed context (outside of my resources), so I added #Component to the above class with the intention of auto-wiring it elsewhere. After adding #Component (org.springframework.stereotype.Component), Jersey no longer picks up the ObjectMapper from the resolver and my date serialization goes back to the defaults.
Unless I completely misunderstand the annotations, I don't think giving Spring control of the life-cycle should impede Jersey's ability to pick up my resolver. Additionally worth noting is the fact that when we were on Jersey 1.9, we HAD to have #Component on there or else it would not get picked up. In order to get our upgrade from 1.9 to 2.6 working, I actually had initially removed it, but was hoping to put it back.
From my pom:
Java 1.7
Jackson 2.3.1
Jersey 2.6
Joda 2.1
Spring 4.0.1-RELEASE
I had a similar issue with a similar setup as your one.
While probably there's something wrong in Jersey 2.x Spring integration beahviour, i think you can do the follow:
Declare the object mapper as a Spring bean, so you can inject it via spring where you need it:
#Component
public class ObjectMapperBean extends ObjectMapper {
public ObjectMapperBean() {
super();
// Configuration here...
}
}
Then you write a Jersey context resolver for it:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
#Autowired
private ObjectMapperBean objectMapper;
#Override
public ObjectMapper getContext(Class<?> type) {
return objectMapper;
}
}
Even if not declared as a component you will get the ObjectMapperBean injected in it.
Hope it helps!

Changing Default JSON Time Format with RESTEasy 3.x

I am using RESTEasy to implement a REST Service using JSON serialization. Currently, Dates are getting serialized to milliseconds since 1970. To improve compatibility, I would like to get my dates into one of two formats; milliseconds + timezone offset or ISO 8061.
It seems that RESTEasy used to use Jettison for JSON serialization, but from what I've been reading they've switch to Jackson ... all of this has made googling for help pretty hit or miss.
From what I can tell, I need to implement a ContextResolver along the lines of:
public class JacksonConfig impelments ContextResolver<ObjectMapper>
{
private final OBjectMapper objectMapper;
public JacksonConfig() throws Exception
{
objectMapper = new ObjectMapper.configure(
SerializationFeature.WRITE_DATE_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> arg0)
{
return objectMapper;
}
}
The thing I haven't been able to find, is what do I do with this? Where do I put it?
So the larger questions are, am I heading in the right direction and are my assumptions correct?
You need to register your ContextResolver implementation with Resteasy. You can do this by annotating your class with the #Provider annotation and allowing Resteasy to automatically scan it during startup, registering it in web.xml, or registering it in a class that extends javax.ws.rs.core.Application (if that is how you are bootstrapping Resteasy).
Registering via Annotations
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper>
{
private final ObjectMapper objectMapper;
public JacksonConfig() throws Exception
{
objectMapper = new ObjectMapper.configure(
SerializationFeature.WRITE_DATE_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> arg0)
{
return objectMapper;
}
}
Verify that classpath scanning is enabled in your web.xml file like so:
<context-param>
<param-name>resteasy.scan</param-name>
<param-value>true</param-value>
</context-param>
NOTE: If you are deploying this in JBoss 7 do not set the resteasy.scan context parameter as it is enabled by default.
Registering via web.xml
Add the following context parameter to your web.xml file. The value of the parameter should be the fully qualified class name of your ContextResolver.
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>foo.contextresolver.JacksonConfig</paramvalue>
</context-param>
Registering via Application
If you are using an Application class to configure Resteasy you can add your provider to the set of services and providers to register with Resteasy like so:
public class MyApp extends Application
{
#Override
public Set<Class<?>> getClasses()
{
HashSet<Class<?>> set = new HashSet<Class<?>>(2);
set.add(JacksonConfig.class);
set.add(MyService.class);
return set;
}
}
More on standalone configuration HERE
Using with the JSR310 (new api date) - LocalDate, LocalDateTime, LocalTime
Add dependence:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.4.0</version>
</dependency>
And create a provider to register the module:
#Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {
private final ObjectMapper objectMapper;
public JacksonConfig() throws Exception {
objectMapper = new ObjectMapper()
.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS )
.disable( SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS )
.setSerializationInclusion( JsonInclude.Include.NON_NULL )
.registerModule( new JSR310Module() );
}
#Override
public ObjectMapper getContext( Class<?> arg0 ) {
return objectMapper;
} }
Just annotate your fields with (note the string literal could be externalized/referred from a constant):
#javax.json.bind.annotation.JsonbDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
Date myDate;

Missing dependency for field when trying to inject a custom context with Jersey

I have a custom context:
public class MyContext {
public String doSomething() {...}
}
I have created a context resolver:
#Provider
public class MyContextResolver implements ContextResolver<MyContext> {
public MyContext getContext(Class<?> type) {
return new MyContext();
}
}
Now in the resource I try to inject it:
#Path("/")
public class MyResource {
#Context MyContext context;
}
And I get the following error:
SEVERE: Missing dependency for field: com.something.MyContext com.something.MyResource.context
The same code works fine with Apache Wink 1.1.3, but fails with Jersey 1.10.
Any ideas will be appreciated.
JAX-RS specification does not mandate the behavior provided by Apache
Wink. IOW, the feature you are trying to use that works on Apache Wink
makes your code non-portable.
To produce 100% JAX-RS portable code, you need to inject
javax.ws.rs.ext.Providers instance and then use:
ContextResolver<MyContext> r = Providers.getContextResolver(MyContext.class, null);
MyContext ctx = r.getContext(MyContext.class);
to retrieve your MyContext instance.
In Jersey, you can also directly inject ContextResolver,
which saves you one line of code from the above, but note that this
strategy is also not 100% portable.
Implement a InjectableProvider. Most likely by extending PerRequestTypeInjectableProvider or SingletonTypeInjectableProvider.
#Provider
public class MyContextResolver extends SingletonTypeInjectableProvider<Context, MyContext>{
public MyContextResolver() {
super(MyContext.class, new MyContext());
}
}
Would let you have:
#Context MyContext context;

How to serialize Java primitives using Jersey REST

In my application I use Jersey REST to serialize complex objects. This works quite fine. But there are a few method which simply return an int or boolean.
Jersey can't handle primitive types (to my knowledge), probably because they're no annotated and Jersey has no default annotation for them. I worked around that by creating complex types like a RestBoolean or RestInteger, which simply hold an int or boolean value and have the appropriate annotations.
Isn't there an easier way than writing these container objects?
Have a look at Genson.It helped me a lot with a similar problem.With Genson you could use generics like int,boolean, lists and so on...Here is a quick example.
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getMagicList() {
List<Object> objList = new ArrayList<>();
stringList.add("Random String");
stringList.add(121); //int
stringList.add(1.22); //double
stringList.add(false); //bolean
return Response.status(Status.OK).entity(objList).build();
}
This will produce a valid JSON witch can be retrieved very simple like this:
Client client = Client.create();
WebResource webResource = client.resource("...path to resource...");
List objList = webResource.accept(MediaType.APPLICATION_JSON).get(ArrayList.class);
for (Object obj : objList) {
System.out.println(obj.getClass());
}
You will see that Genson will help you decode the JSON on the client side also and output the correct class for each.
Are you writing a service or a client? In the service-end of things, you would simply write a MessageBodyWriter to serialize a stream of data to a Java object for your types. In my use cases, the services I'm writing output to JSON or XML, and in XML's case, I just throw one JAXB annotation on the top of my classes and I'm done.
Have you looked at the Jersey User guide regarding this?
3.6. Adding support for new representations
Actually your best bet is to write a custom ContextResolver Provider like the following that uses natural building of JSON.
#Provider
public class YourContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = { YourSpecialBean.class };
public YourContextResolver() throws Exception {
this.context = new JSONJAXBContext(
JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType) {
for (int i = 0; i < this.types.length; i++)
if (this.types[i].equals(objectType)) return context;
return null;
}
}
The only thing special here to notice is the YourSpecialBean.class in the Class[]. This defines an array of class types that this provider will resolve naturally.
Tell Jersey generate proper JSON documents (natural json). I use same class for rest app and JAXBContext resolver, found it the most clean encapsulation.
Better programmer could implement helper to iterate .class files and list appropriate classes automatically by identifying #Annotation tags. I don't know how to do it runtime in an own source code.
These two links were helpful studying this extra java jargon. I don't know why there is no Jersey parameter to make all just work out of the box.
http://jersey.java.net/nonav/documentation/latest/json.html
http://jersey.java.net/nonav/documentation/latest/chapter_deps.html
https://maven.java.net/content/repositories/releases/com/sun/jersey/jersey-archive/
http://search.maven.org/remotecontent?filepath=asm/asm/3.3.1/asm-3.3.1.jar
https://github.com/rmuller/infomas-asl/
WEB-INF/web.xml (snippet):
<servlet>
<servlet-name>RESTServlet</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.myapp.rest.RESTApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>RESTServlet</servlet-name>
<url-pattern>/servlet/rest/*</url-pattern>
</servlet-mapping>
com.myapp.rest.RESTApplication.java
package com.myapp.rest;
import java.util.*;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.ContextResolver;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.api.json.JSONJAXBContext;
public class RESTApplication extends Application implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types;
public RESTApplication() throws JAXBException {
// list JAXB bean types to be used for REST serialization
types = new Class[] {
com.myapp.rest.MyBean1.class,
com.myapp.rest.MyBean2.class,
};
context = new JSONJAXBContext(JSONConfiguration.natural().build(), types);
}
#Override
public Set<Class<?>> getClasses() {
// list JAXB resource/provider/resolver classes
Set<Class<?>> classes = new HashSet<Class<?>>();
//for(Class<?> type : types)
// classes.add(type);
classes.add(MyBeansResource.class);
classes.add(this.getClass()); // used as a ContextResolver class
return classes;
}
#Override
public JAXBContext getContext(Class<?> objectType) {
// this is called each time when rest path was called by remote client
for (Class<?> type : types) {
if (type==objectType)
return context;
}
return null;
}
}
Classes MyBean1,MyBean2 are plain java objects and MyBeansResource class is the one with #Path rest functions. There is nothing special in them expect standard jaxp #Annotations here and there. After this java jargon JSON documents have
zero or single-element List arrays are always written as json array ([] field)
primitive integers and boolean fields are written as json primitives (without quotations)
I use the following environment
Sun Java JDK1.6.x
Apache Tomcat 6.x
Jersey v1.14 libraries (jersey-archive-1.14.zip)
webapps/myapp/WEB-INF/lib folder has asm-3.3.1.jar, jackson-core-asl.jar, jersey-client.jar, jersey-core.jar, jersey-json.jar, jersey-server.jar, jersey-servlet.jar libraries
add optional annotation-detector.jar if you use infomas-asl discovery tool
jersey-archive.zip had older asm-3.1.jar file, probably works fine but chapter_deps.html links to a newer file. See link list at the top.
Edit
I found an excellent(fast, lightweight just 15KB) annotation discovery tool. See this post about how I autodiscover types at runtime and no longer need to edit RESTApplication each time new java(jaxb) bean is added.
https://github.com/rmuller/infomas-asl/issues/7
I had the same problem today and didn't give up until i found a really good suitable solution. I can not update the jersey library from 1.1.5 it is a Legacy System. My Rest Service returns a List and they should follow those rules.
Empty Lists are rendered as [] (almost impossible)
One Element Lists are rendered as [] (difficult but only mapping configuration)
Many Element Lists are rendered as [] (easy)
Start from easy to impossible.
3) nothing today normal JSON Mapping
2) Register JAXBContextResolver like the following
#Provider
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class<?>> types;
private Class<?>[] ctypes = { Pojo.class }; //your pojo class
public JAXBContextResolver() throws Exception {
this.types = new HashSet<Class<?>>(Arrays.asList(ctypes));
this.context = new JSONJAXBContext(JSONConfiguration.mapped()
.rootUnwrapping(true)
.arrays("propertyName") //that should rendered as JSONArray even if the List only contain one element but doesn't handle the empty Collection case
.build()
, ctypes);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
1) The following approach only works for Collections$EmptyList class. May you find a way to make it general for all Collections they are empty. May code deal with EmptyList so.
#Provider
#Produces(value={MediaType.APPLICATION_JSON})
public class EmptyListWriter implements MessageBodyWriter<AbstractList> {
private static final String EMPTY_JSON_ARRAY = "[]";
#Override
public long getSize(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return EMPTY_JSON_ARRAY.length();
}
#Override
public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return clazz.getName().equals("java.util.Collections$EmptyList");
}
#Override
public void writeTo(AbstractList list, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> headers, OutputStream outputStream) throws IOException, WebApplicationException {
if (list.isEmpty())
outputStream.write(EMPTY_JSON_ARRAY.getBytes());
}
}
I've just discovered that returning a primitive type with Jersey is problematic. I've decided to return String instead. Maybe this is not clean, but I don't think it's too dirty. The Java client, which is written by the same author of the server most of the times, can wrap such a string return value and convert it back to int. Clients written in other languages must be aware of return types any way.
Defining RestInteger, RestBoolean may be another option, however it's more cumbersome and I see too little advantage in it to be attractive.
Or maybe am I missing something important here?

Categories

Resources