Configuring JSON for RESTful Web Services in Jersey 1.0.2

This is an update for a tech tip on configuring JSON in Jersey, which i wrote in October 2008. The way of JSON configuration, suggested in the tech tip, is now deprecated (but still functioning). Here i would like to describe the new API, which will hopefully last (and be supported) a way longer.

Notice: you will need to bundle jaxb-impl-2.1.10.jar with your application in order to take advantage of the recently added JSON NATURAL convention

Deprecated Configuration

Configuring JSON format, as described in the tech tip, meant to implement a JAXBContext resolver class returning an instance of JSONJAXBContext. This principle have not changed. What changed is a way, how the JSONJAXBContext itself is being configured. Lets look at the sample code below (using the deprecated API):

   @Provider
   public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {

       private JAXBContext context;
       private Class[] types = {StatusInfoBean.class, JobInfoBean.class};

       public MyJAXBContextResolver() throws Exception {
           Map props = new HashMap<String, Object>();
           props.put(JSONJAXBContext.JSON_NOTATION, JSONJAXBContext.JSONNotation.MAPPED);
           props.put(JSONJAXBContext.JSON_ROOT_UNWRAPPING, Boolean.TRUE);
           props.put(JSONJAXBContext.JSON_ARRAYS, new HashSet<String>(1){{add("jobs");}});
           props.put(JSONJAXBContext.JSON_NON_STRINGS, new HashSet<String>(1){{add("pages"); add("tonerRemaining");}});
           this.context = new JSONJAXBContext(types, props);
       }

       public JAXBContext getContext(Class<?> objectType) {
           return (types[0].equals(objectType)) ? context : null;
       }
   }

There you needed to create a property bag, put appropriate configuration options into it, and then pass it to the JSONJAXBContext constructor.

Jersey 1.0.2 JSON Configuration

In the currently available 1.0.2 Jersey version, a new JSONConfiguration class was introduced to became a central point for JSON configuration options. For creating a new JSONConfiguration instance, a builder pattern is employed. It is not only more user friendly, but also ensures only meaningful JSON options could be combined together. You can compare the following code, with the deprecated example above:

   @Provider
   public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {

       private JAXBContext context;
       private Class[] types = {StatusInfoBean.class, JobInfoBean.class};

       public MyJAXBContextResolver() throws Exception {
           this.context = new JSONJAXBContext(
                   JSONConfiguration.mapped()
                                      .rootUnwrapping(true)
                                      .arrays("jobs")
                                      .nonStrings("pages", "tonerRemaining")
                                      .build(),
                   types);
       }

       public JAXBContext getContext(Class<?> objectType) {
           return (types[0].equals(objectType)) ? context : null;
       }
   }

You can look at JSONConfiguration javadoc for detailed information on various configuration options.

Further Simplification

If you go a bit further, you can ask if the configuration could be simplified even more. Imagine you have much bigger number of JAXB beans in your model, and they are more complex. It could easily become unmanageable to maintain a reasonable JSON configuration as described so far. Then if you happen to have conflicting non-string/string values and/or arrays/non-arrays elements in your set, you could easily run out of options there.

A natural way to overcome above mentioned issues, is to simply use recently introduced Jersey NATURAL JSON notation. Then you need only to:

   @Provider
   public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {

       private JAXBContext context;
       private Class[] types = {StatusInfoBean.class, JobInfoBean.class};

       public MyJAXBContextResolver() throws Exception {
           this.context = new JSONJAXBContext(
                   JSONConfiguration.natural().build(),
                   types);
       }

       public JAXBContext getContext(Class<?> objectType) {
           return (types[0].equals(objectType)) ? context : null;
       }
   }

Such configuration is simple from user point of view, but yet very powerful. You do not need to keep various configuration options in sync with your actual JAXB beans, and be worried what options to actually use (what exact names, etc.). Jersey will automatically take care about serializing Java collections/arrays as JSON arrays, Java booleans as JSON booleans, Java ints as JSON integers,and so on.

Comments:

Thanks for the update. I just updated my JAXBContextResolver to use the new NATURAL JSON notation and got the following exception! Since I'm using JDK1.6.0_11 and glassfish-v2ur2, wondering on what environment you guys have tested this new feature?

javax.xml.bind.JAXBException: property "retainReferenceToInfo" is not supported
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:114)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:211)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:372)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
at com.sun.jersey.api.json.JSONJAXBContext.<init>(JSONJAXBContext.java:256)

Posted by mohammadwrk on February 15, 2009 at 08:33 PM CET #

@mohammadwrk: you need to explicitly use the latest jaxb-impl-2.1.10.jar with your project, as the new feature is not supported for the previous jaxb ri releases. And the latest one has not been integrated yet to the jdk1.6.

Posted by Jakub on February 16, 2009 at 01:00 AM CET #

Very nice,
but why do I acutally have to register my Classes. Couldn't it be possible to have natural as the default and register exceptions?

Posted by guest on February 16, 2009 at 03:25 AM CET #

I'm having a similar problem ---
retainReferenceToInfo is not supported. I downloaded the http://download.java.net/maven/1/com.sun.xml.bind/jars/jaxb-impl-2.1.10.jar and still get the exception when my app deploys. Using Weblogic 10.0, Java 1.5.0_17 and Jersey 1.0.2 distribution files from http://download.java.net/maven/2/com/sun/jersey/jersey-archive/1.0.2/jersey-archive-1.0.2.zip.

I went through the dependencies document and retrieved the remaining jars listed for JAXB. Please advise. The stacktrace is here:

Feb 17, 2009 3:07:11 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Provider classes found:
class mil.cnodb.rs.config.EJBProvider
class mil.cnodb.rs.config.JAXBContextResolver
Feb 17, 2009 3:07:12 PM com.sun.jersey.core.spi.component.ProviderFactory _getComponentProvider
SEVERE: The provider class, class mil.cnodb.rs.config.JAXBContextResolver, could not be instantiated
javax.xml.bind.JAXBException: property "retainReferenceToInfo" is not supported
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:210)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:368)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
at com.sun.jersey.api.json.JSONJAXBContext.<init>(JSONJAXBContext.java:256)
at mil.cnodb.rs.config.JAXBContextResolver.<init>(JAXBContextResolver.java:39)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:494)
at java.lang.Class.newInstance0(Class.java:350)
at java.lang.Class.newInstance(Class.java:303)
at com.sun.jersey.core.spi.component.ComponentConstructor._getInstance(ComponentConstructor.java:154)
at com.sun.jersey.core.spi.component.ComponentConstructor.getInstance(ComponentConstructor.java:143)
at com.sun.jersey.core.spi.component.ProviderFactory.getInstance(ProviderFactory.java:206)
at com.sun.jersey.core.spi.component.ProviderFactory._getComponentProvider(ProviderFactory.java:133)
at com.sun.jersey.core.spi.component.ProviderFactory.getComponentProvider(ProviderFactory.java:126)
at com.sun.jersey.core.spi.component.ProviderServices.getComponent(ProviderServices.java:168)
at com.sun.jersey.core.spi.component.ProviderServices.getProviders(ProviderServices.java:95)
at com.sun.jersey.core.spi.factory.ContextResolverFactory.<init>(ContextResolverFactory.java:90)
at com.sun.jersey.server.impl.application.WebApplicationImpl.initiate(WebApplicationImpl.java:410)
at com.sun.jersey.server.impl.application.WebApplicationImpl.initiate(WebApplicationImpl.java:317)
at com.sun.jersey.spi.container.servlet.WebComponent.initiate(WebComponent.java:422)
at com.sun.jersey.spi.container.servlet.WebComponent.load(WebComponent.java:433)
at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:167)
at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:197)
at weblogic.servlet.internal.StubSecurityHelper$ServletInitAction.run(StubSecurityHelper.java:282)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
at weblogic.security.service.SecurityManager.runAs(Unknown Source)
at weblogic.servlet.internal.StubSecurityHelper.createServlet(StubSecurityHelper.java:63)
at weblogic.servlet.internal.StubLifecycleHelper.createOneInstance(StubLifecycleHelper.java:58)
at weblogic.servlet.internal.StubLifecycleHelper.<init>(StubLifecycleHelper.java:48)
at weblogic.servlet.internal.ServletStubImpl.prepareServlet(ServletStubImpl.java:507)
at weblogic.servlet.internal.WebAppServletContext.preloadServlet(WebAppServletContext.java:1853)
at weblogic.servlet.internal.WebAppServletContext.loadServletsOnStartup(WebAppServletContext.java:1830)
at weblogic.servlet.internal.WebAppServletContext.preloadResources(WebAppServletContext.java:1750)
at weblogic.servlet.internal.WebAppServletContext.start(WebAppServletContext.java:2909)
at weblogic.servlet.internal.WebAppModule.startContexts(WebAppModule.java:973)
at weblogic.servlet.internal.WebAppModule.start(WebAppModule.java:361)
at weblogic.application.internal.flow.ModuleStateDriver$3.next(ModuleStateDriver.java:204)
at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:26)
at weblogic.application.internal.flow.ModuleStateDriver.start(ModuleStateDriver.java:60)
at weblogic.application.internal.flow.ScopedModuleDriver.start(ScopedModuleDriver.java:200)
at weblogic.application.internal.flow.ModuleListenerInvoker.start(ModuleListenerInvoker.java:117)
at weblogic.application.internal.flow.ModuleStateDriver$3.next(ModuleStateDriver.java:204)
at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:26)
at weblogic.application.internal.flow.ModuleStateDriver.start(ModuleStateDriver.java:60)
at weblogic.application.internal.flow.StartModulesFlow.activate(StartModulesFlow.java:26)
at weblogic.application.internal.BaseDeployment$2.next(BaseDeployment.java:635)
at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:26)
at weblogic.application.internal.BaseDeployment.activate(BaseDeployment.java:212)
at weblogic.application.internal.DeploymentStateChecker.activate(DeploymentStateChecker.java:154)
at weblogic.deploy.internal.targetserver.AppContainerInvoker.activate(AppContainerInvoker.java:80)
at weblogic.deploy.internal.targetserver.operations.AbstractOperation.activate(AbstractOperation.java:566)
at weblogic.deploy.internal.targetserver.operations.ActivateOperation.activateDeployment(ActivateOperation.java:136)
at weblogic.deploy.internal.targetserver.operations.ActivateOperation.doCommit(ActivateOperation.java:104)
at weblogic.deploy.internal.targetserver.operations.StartOperation.doCommit(StartOperation.java:139)
at weblogic.deploy.internal.targetserver.operations.AbstractOperation.commit(AbstractOperation.java:320)
at weblogic.deploy.internal.targetserver.DeploymentManager.handleDeploymentCommit(DeploymentManager.java:816)
at weblogic.deploy.internal.targetserver.DeploymentManager.activateDeploymentList(DeploymentManager.java:1223)
at weblogic.deploy.internal.targetserver.DeploymentManager.handleCommit(DeploymentManager.java:434)
at weblogic.deploy.internal.targetserver.DeploymentServiceDispatcher.commit(DeploymentServiceDispatcher.java:161)
at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer.doCommitCallback(DeploymentReceiverCallbackDeliverer.java
:181)
at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer.access$100(DeploymentReceiverCallbackDeliverer.java:12)
at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer$2.run(DeploymentReceiverCallbackDeliverer.java:67)
at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:464)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:200)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:172)

Posted by Mark Rabick on February 17, 2009 at 02:12 PM CET #

@Mark
Switching to jaxb-impl-2.1.10 worked for me. I think in your case Weblogic still loading ContextFactory from the old jar file. To trace where it loads from add "-verbose" to java command that launches Weblogic and then search for ContextFactory.

Posted by mohammadwrk on February 17, 2009 at 02:50 PM CET #

-mohammadwrk

it looks like it is loading it from the embedded Glassfish jars shipped with weblogic...
[Loaded com.sun.xml.bind.v2.ContextFactory from file:/C:/bea/modules/glassfish.jaxb_1.0.1.0_2-0-5.jar]

do you know how to get the app to 'prefer' the jars contained in the WEB-INF/lib directory of the war?

Posted by Mark Rabick on February 18, 2009 at 10:45 AM CET #

@mohammadwrd

Figured it out. Added element to weblogic.xml

<container-descriptor>
<!--
This setting will force the classloader to load the
com.sun.xml.bind.v2.ContextFactory classes
from the WEB-INF/lib jars (jaxb-impl-2.1.10.jar)
-->
<prefer-web-inf-classes>true</prefer-web-inf-classes>
</container-descriptor>

Posted by Mark Rabick on February 18, 2009 at 10:56 AM CET #

@193.171.188.3
Agree, we shouldn't be forced to register classes and I think Jersey should provide a provider out-of-the-box to so.
However, for now you can use the following modified version of Jakub's implementation to archive the same goal easily.

/\*\*
\*
\* @author Mohammad
\*/
@Provider @Produces(MediaType.APPLICATION_JSON)
public class JAXBContextResolver implements ContextResolver<JAXBContext> {

private JAXBContext context;

public JAXBContextResolver() throws Exception {

AnnotatedClassScanner classScanner = new AnnotatedClassScanner(
XmlRootElement.class, XmlType.class);
Set<Class<?>> classes = classScanner.scan(new String[]{"com.yourcompany.yourproject"});

this.context = new JSONJAXBContext(JSONConfiguration.natural().build(),
classes.toArray(new Class[classes.size()]));
}

public JAXBContext getContext(Class<?> objectType) {

return context;
}
}

Posted by Mohammadwrk on February 18, 2009 at 10:26 PM CET #

@193.171.188.3, Mohammad: The reason for not making the NATURAL notation the default one, was we simply did not want to break backward compatibility for existing users. The time for it's adoption was very short. I want to make NATURAL the default notation in one of the upcoming releases. Besides the question, what should be the default, i would also like to introduce a web application feature allowing to configure JSON notation globally for a whole application, whithout a need to specify a JAXB resolver.

Posted by Jakub on February 19, 2009 at 12:59 AM CET #

Apparently it even works in jersey 1.0.3 without this myjaxbcontentresolver class (jsconfiguration.natural.build())
Everything is out of the box ok.. ?

Wired stuff with spring, really cool, using json..

Posted by Tony Nys on April 22, 2009 at 11:07 AM CEST #

Hi,
I think that it would be great to have a simple way to set the default configuration to some value. Certain applications may want to change the default, but still have one and then apply the particular classes changes by specifying the classes like you do on the example if needed.

Cheers

Posted by xmariachi on July 06, 2009 at 11:56 AM CEST #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Jakub Podlesak

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today