Jersey Notes

Here are random notes on Jersey as I feel the urge. Topics:

JSR-311, JAX-RS 1.1
JSR-339, JAX-RS 2.0
This Java Specification Request (JSR) will develop an API for providing support for RESTful(Representational State Transfer) Web Services in the Java Platform.


Some links...

To keep my services very tidy, I need to begin handling errors carefully now. Here are some examples and other excellent links that might be helpful.


Serializing/deserializing POJOs

In general the type in Java of the method parameter may:

  1. be a primitive type;
  2. Have a constructor that accepts a single String argument;
  3. Have a static method named
    • valueOf() or
    • fromString()
    that accepts a single String argument. See, for example,
    • Integer.valueOf( String ) and
    • java.util.UUID.fromString( String ) )
    or...
  4. Be List< T >, Set< T > or SortedSet< T >, where T satisfies 2 or 3 above. The resulting collection is read-only.

Using String in place of ObjectId in a DTO

The reason for using String is because Jersey leaves it null in the returned DTO. To get around this, consider the previous section?


Servlet filters

Jersey has filters it respects that can do some of what servlet filters in other frameworks. There are three I know about so far. I have used the second two, both in combination with the first in terms of what methods to override.

  1. ResourceFilter
  2. ContainerRequestFilter
  3. ContainerResponseFilter

These filters preconise a number of methods that you should override:

ResourceFilter

ContainerRequestFilter

ContainerResponseFilter

Usage

Assume you want to intercept requests and treat them somehow before they reach your ReST service handling code. Maybe you want to authenticate them. You'll create a filter that implements ContainerRequestFilter plus ResourceFilter.

If you wish to intercept a response, which will go back out as the result of a request, you'll create a filter that implements ContainerResponseFilter plus ResourceFilter.

From the filter(), return the request (or response) if you want Jersey to proceed. If you return nil, the request is considered a failure.

In both cases, you'll do your important work in the filter() method while providing access to the filter via the other two methods. For a request filter, this:

@Override
public ContainerRequestFilter getRequestFilter()
{
    return this;
}

@Override
public ContainerResponseFilter getResponseFilter()
{
    return null;
}

...and for a response filter:

@Override
public ContainerRequestFilter getRequestFilter()
{
    return null;
}

@Override
public ContainerResponseFilter getResponseFilter()
{
    return this;
}

Example: implementing per-thread local store in a servlet

(Or, "thread context.")

Let's imagine a way to keep state during a path through a ReST servlet. Let's imaging that we're going to record the IP address of our HTTP request and some other data which we won't go to the trouble of making an example of. This aspect of what we're showing here is part of a separate set of notes ( per-thread local store).

ThreadContextCreationFilter.java:
package com.acme.web.filter;

import org.apache.log4j.Logger;

import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;

import com.acme.web.user.thread.ThreadContext;

public class ThreadContextCreationFilter implements ResourceFilter, ContainerRequestFilter
{
    private static Logger log = Logger.getLogger( ThreadContextCreationFilter.class );

    @Context HttpServletRequest httpRequest;

    public ThreadContextCreationFilter()
    {
        log.info( "ThreadContextCreationFilter is installed" );
    }

    /**
     * Set up per-thread context for gathering state, for example, that will be used
     * to issue statements to the log.
     */
    @Override
    public ContainerRequest filter( ContainerRequest request )
    {
        ThreadContext ctx = ThreadContext.get();        // get our thread-local store...
        URI           uri = request.getRequestUri();

        ctx.setIpaddress( httpRequest.getRemoteAddr() );// get the caller's IP address...
        ...

        return request;
    }

    @Override
    public ContainerRequestFilter getRequestFilter()
    {
        return this;
    }

    @Override
    public ContainerResponseFilter getResponseFilter()
    {
        return null;
    }
}
ThreadContextTearDownFilter.java:
package com.acme.web.filter;

import org.apache.log4j.Logger;

public class ThreadContextTearDownFilter implements ResourceFilter, ContainerResponseFilter
{
    private static Logger log = Logger.getLogger( ThreadContextTearDownFilter.class );

    public ThreadContextTearDownFilter()
    {
        log.info( "ThreadContextTearDownFilter is installed" );
    }

    /**
     * Tear down per-thread context here (see ThreadContextCreationFilter) because
     * Tomcat likely will not clear it off our thread before putting it back into its
     * pool. This would otherwise result in a memory leak.
     */
    @Override
    public ContainerResponse filter( ContainerRequest request, ContainerResponse response )
    {
        ThreadContext.remove();
        return response;
    }

    @Override
    public ContainerRequestFilter getRequestFilter()
    {
        return null;
    }

    @Override
    public ContainerResponseFilter getResponseFilter()
    {
        return this;
    }
}

web.xml registration

These are registered in web.xml as init-params. If you have two of the same sort, i.e.: two ContainerRequestFilters, you list them with a comma as here where we're also showing the rest of the servlet definition of an imaginary servlet:

<servlet>
    <servlet-name>     Jersey REST Service                                   </servlet-name>
    <servlet-class>    com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class>
    <init-param>
      <param-name>    com.sun.jersey.config.property.packages                </param-name>
      <param-value>   com.acme.web.user.service                              </param-value>
    </init-param>
    <!-- Do not disturb the order of these two request filters! -->
    <init-param>
      <param-name>    com.sun.jersey.spi.container.ContainerRequestFilters   </param-name>
      <param-value>   com.acme.web.filter.ThreadContextCreationFilter,com.acme.web.filter.HeaderDetectionFilter </param-value>
    </init-param>
    <init-param>
      <param-name>    com.sun.jersey.spi.container.ContainerResponseFilters  </param-name>
      <param-value>   com.acme.web.filter.ThreadContextTearDownFilter        </param-value>
    </init-param>
    <load-on-startup> 1            </load-on-startup>
</servlet>

Above, HeaderDetectionFilter is registered only to show how two or more of these are specified (the mechanism might not have been obvious if you've never done this before and, if you have, you're probably not reading this).


Servlet listeners

A concrete example: implementing application start-up

In web.xml, you'll need to add:

 Acme Application 

     com.acme.web.filter.ApplicationInitialize 

This tells Tomcat there's code listening for its start-up. Here's the code. I'm just doing an imaginary MongoDB start-up so that MongoDB's up and running before Jersey lets the first service request through to me.

package com.acem.web.filter;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.log4j.Logger;

import com.acme.web.mongodb.MongoDB;
import com.acme.web.user.configuration.ApplicationProperties;

public class ApplicationInitialize implements ServletContextListener
{
    private static Logger log = Logger.getLogger( ApplicationInitialize.class );

    /**
     * This method is called when the servlet context is initialized (when the Web application is
     * launched). You can initialize servlet context-related data here. We use this listener to
     * ensure that we're all up and running before any of our services get registered.
     */
    @Override
    public void contextInitialized( ServletContextEvent event )
    {
        log.info( "################################################### Initializing acme application context here:" );

        log.info( "Forcing discovery of application properties from " + ApplicationProperties.APPLICATION_PROPERTIES );
        ApplicationProperties.reloadProperties();

        log.info( "Setting up MongoDB connections..." );
        MongoDB.startup();

        log.info( "################################################### Initializing acme application complete!" );
    }

    /**
     * This method is invoked when the Servlet Context (the Web application) is undeployed or server shuts down.
     */
    @Override
    public void contextDestroyed( ServletContextEvent event )
    {
        log.info( "Discard acme application context here!" );
        log.info( "Cleaning up MongoDB connections..." );
        MongoDB.cleanup();
    }
}

What do you see at Tomcat start-up? In green are the signs your servlet filters are registered. In bold black is the listener-registered application start-up noise. (Tomcat start-up noise in the Eclipse console is always red.)


Feb 6, 2013 6:06:45 PM org.apache.catalina.core.AprLifecycleListener init
Feb 6, 2013 6:06:46 PM org.apache.tomcat.util.digester.SetPropertiesRule begin
WARNING: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.jee.server:accountmgr' did not find a matching property.
Feb 6, 2013 6:06:46 PM org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8000
Feb 6, 2013 6:06:46 PM org.apache.catalina.startup.Catalina load
INFO: Initialization processed in 633 ms
Feb 6, 2013 6:06:46 PM org.apache.catalina.core.StandardService start
INFO: Starting service Catalina
Feb 6, 2013 6:06:46 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.35
18:06:46,747  INFO AppInitializer:48 - ################################################### Initializing acme application context here:
18:06:46,751  INFO AppInitializer:50 - Forcing discovery of application properties from /opt/acme/application/conf/application.properties
18:06:46,761 DEBUG ApplicationProperties:88 - Reloading properties on request
18:06:46,762  INFO SecurityProperties:54 - (Re)loading security properties...
18:06:46,762  INFO AccountDatabaseProperties:96 - (Re)loading AccountDatabase properties...
18:06:46,769  INFO PaymentDatabaseProperties:78 - (Re)loading PaymentDatabase properties...
18:06:46,769  INFO ApplicationInitialize:55 - Setting up MongoDB connections...
18:06:46,793 TRACE MongoDB:67 - MongoDB getInstance()
18:06:46,795  INFO MongoDB:38 - --------------- accountdb database replica connection details -------------------------
18:06:47,039 ERROR MongoDB:61 - [   ] acmedb01:37017, Staging
18:06:47,235 ERROR MongoDB:61 - [   ] acmedb02:37018, Americas
18:06:47,506 ERROR MongoDB:61 - [   ] acmedb03:37019, Europe-Middle East-Africa
18:06:47,711 ERROR MongoDB:61 - [   ] acmedb04:37020, Asia-Pacific
18:06:48,047 ERROR MongoDB:61 - [   ] acmedb05:37021, Timbuktu
18:06:48,048  INFO MongoDB:74 - [ x ] localhost:27017, trying this hostname:port because nothing reachable or specified
18:06:48,048  INFO MongoDB:82 - ---------------------------------------------------------------------------------------
18:06:48,158  INFO MongoDB:95 - Collections present: [accounts, addresses, payments, system.indexes]
18:06:48,241  INFO AppInitializer:63 - ################################################### Initializing acme application complete!
Feb 6, 2013 6:06:48 PM com.sun.jersey.api.core.PackagesResourceConfig init
INFO: Scanning for root resource and provider classes in the packages:
  com.acme.web.user.service
Feb 6, 2013 6:06:48 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Root resource classes found:
  class com.acme.web.user.service.AccountService
  class com.acme.web.user.service.PaymentService
  class com.acme.web.user.service.AuthenticationService
  class com.acme.web.user.service.PingService
  class com.acme.web.user.service.AddressService
Feb 6, 2013 6:06:48 PM com.sun.jersey.api.core.ScanningResourceConfig init
INFO: No provider classes found.
Feb 6, 2013 6:06:48 PM com.sun.jersey.server.impl.application.WebApplicationImpl _initiate
INFO: Initiating Jersey application, version 'Jersey: 1.16 11/28/2012 02:09 PM'
18:06:48,614  INFO ThreadContextCreationFilter:29 - ThreadContextCreationFilter is installed
18:06:48,626  INFO ThreadContextTearDownFilter:18 - ThreadContextTearDownFilter is installed
Feb 6, 2013 6:06:49 PM com.sun.jersey.spi.inject.Errors processErrorMessages
Feb 6, 2013 6:06:49 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8000
Feb 6, 2013 6:06:49 PM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Feb 6, 2013 6:06:49 PM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/48  config=null
Feb 6, 2013 6:06:49 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 3410 ms

Jackson resolvers
import java.text.SimpleDateFormat;

import javax.ws.rs.Produces;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;

@Provider
@Produces( "application/json" )
public class JacksonContextResolver implements ContextResolver< ObjectMapper >
{
    private static final String FORMAT_STR = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    private ObjectMapper        mapper     = new ObjectMapper();

    @SuppressWarnings( "deprecation" )
    public JacksonContextResolver()
    {
        SerializationConfig   serialConfig   = mapper.getSerializationConfig();
        DeserializationConfig deserialConfig = mapper.getDeserializationConfig();

        serialConfig.setDateFormat( new SimpleDateFormat( FORMAT_STR ) );
        deserialConfig.setDateFormat( new SimpleDateFormat( FORMAT_STR ) );

        mapper.configure( SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false );
    }

    @Override
    public ObjectMapper getContext( Class< ? > arg0 )
    {
        return mapper;
    }
}

Jersey steps into service code...

When the HTTP(S) request makes it into the service code, it's already passed through all filters registered in web.xml. There are 1) resource filters, 2) container request filters and 3) container response filters. The last type are filters that run after the thread has run completely through the service code. For example,

    .
    .
    .
    <init-param>
      <param-name>   com.sun.jersey.spi.container.ResourceFilters                </param-name>
      <param-value>  com.etretatlogiciels.cloudmgr.filters.ApplicationFilter     </param-value>
    </init-param>
    <init-param>
      <param-name>   com.sun.jersey.spi.container.ContainerRequestFilters        </param-name>
      <param-value>  com.etretatlogiciels.cloudmgr.filters.ContextCreationFilter </param-value>
    </init-param>
    <init-param>
      <param-name>   com.sun.jersey.spi.container.ContainerResponseFilters       </param-name>
      <param-value>  com.etretatlogiciels.cloudmgr.filters.ContextTearDownFilter </param-value>
    </init-param>
    .
    .
    .

Execution will step into ApplicationFilter which, in my case, will add other filters (one to check SSL, a caller key, and an OAuth token), plus erect (and tear down afterward) some thread context in ContextCreationFilter and ContextTearDownFilter.

Stepping into the called service

Depending on the @Path registered with the service code, Jersey then passes execution into the service to be called. If there is a no-argument constructor, it calls that. (So, if you want or need arguments passed to the service's constructor, you are out of luck as far as I know at this point.)


Phantom start-up errors in Jersey...

I found that if something like this occurred on start-up:


    WARNING: Marking servlet Jersey REST Service as unavailable
    SEVERE: Error loading WebappClassLoader
      delegate: false
      repositories:
        /WEB-INF/classes/
    ----------> Parent Classloader:
    org.apache.catalina.loader.StandardClassLoader@56f631
     com.sun.jersey.spi.container.servlet.ServletContainer
    java.lang.ClassNotFoundException: com.sun.jersey.spi.container.servlet.ServletContainer

The solution seemed to be to re-build and clean it before asking Tomcat to start it again.


A Jersey testing example...

This is one person's way to test a Jersey entry point if that's what you wish to do.

SnDJerseyTest.java:
package com.acme.snd;

import static org.junit.Assert.assertNotNull;
import org.junit.Test;

import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.test.framework.JerseyTest;
import com.sun.jersey.test.framework.spi.container.TestContainerFactory;
import com.sun.jersey.test.framework.spi.container.grizzly.web.GrizzlyWebTestContainerFactory;

public class SnDJerseyTest extends JerseyTest
{
   public SnDJerseyTest() throws Exception
   {
      super( "com.acme.snd.resource" );
   }

   @Override
   protected TestContainerFactory getTestContainerFactory()
   {
      return new GrizzlyWebTestContainerFactory();
   }
}
SnDResourceTest.java:
package com.acme.snd.resource;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import javax.ws.rs.core.MediaType;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import com.acme.snd.SnDJerseyTest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

public class SnDResourceTest extends SnDJerseyTest
{
   public SnDResourceTest() throws Exception { super(); }

   @Mock
   private CTenantResource mockTenantResource;

   @Before
   public void setup()
   {
      MockitoAnnotations.initMocks( this );
   }

   @Test
   public void test_pingSndResource()
   {
      WebResource webResource = resource();
      String responseMsg = webResource.path( "snd" ).get( String.class );
      assertNotNull( responseMsg );
   }


   @Test
   public void test_getPingResource()
   {
      WebResource webResource = resource();
      ClientResponse response = webResource.path( "snd/ping").accept( MediaType.APPLICATION_XML ).get( ClientResponse.class );
      assertNotNull( response );
      assertEquals( 200, response.getStatus() );
   }
}

More recent Jersey work...

Jersey libraries for a server, version 2.x (2.7). Do not be tempted by something called, Jersey bundle.

I found this at: https://jersey.java.net/download.html.

There is a link for the convenience of non-Maven users on that page; that link is: http://repo1.maven.org/maven2/org/glassfish/jersey/bundles/jaxrs-ri/2.7/jaxrs-ri-2.7.zip. Once you get that, unzip jaxrs-ri-2.7.zip (ri stands for "reference implementation"—Jersey is the reference implementation of JAX-RS) you'll find:

~/Downloads $ tree jaxrs-ri/
jaxrs-ri/
├── api
│   └── javax.ws.rs-api-2.0.jar
├── ext
│   ├── aopalliance-repackaged-2.2.0.jar
│   ├── asm-all-repackaged-2.2.0.jar
│   ├── hk2-api-2.2.0.jar
│   ├── hk2-locator-2.2.0.jar
│   ├── hk2-utils-2.2.0.jar
│   ├── javassist-3.18.1-GA.jar
│   ├── javax.annotation-api-1.2.jar
│   ├── javax.inject-2.2.0.jar
│   ├── javax.servlet-api-3.0.1.jar
│   ├── jaxb-api-2.2.7.jar
│   ├── jersey-guava-2.7.jar
│   ├── org.osgi.core-4.2.0.jar
│   ├── osgi-resource-locator-1.0.1.jar
│   ├── persistence-api-1.0.jar
│   └── validation-api-1.1.0.Final.jar
├── Jersey-LICENSE.txt
├── lib
│   ├── jersey-client.jar
│   ├── jersey-common.jar
│   ├── jersey-container-servlet-core.jar
│   ├── jersey-container-servlet.jar
│   └── jersey-server.jar
└── third-party-license-readme.txt

You will need all bold above as shown below. Depending on what you do in your server application (restlet), you might need some or all of the others. However, for the two, simple entry points illustrated in code below, you need only these:

* Yes, you do have to have the Jersey client JAR! Otherwise, you can expect the follow error out of Tomcat on start-up:

WARNING: HK2 service reification failed for [org.glassfish.jersey.server.internal.inject.WebTargetValueFactoryProvider] with an exception:
MultiException stack 1 of 4
java.lang.NoClassDefFoundError: org/glassfish/jersey/client/ClientConfig
JsonTrackService.java:
package com.mkyong;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path( "/json/metallica" )
public class JsonTrackService
{
  /* GET, http://localhost:8080/mkyong-rest/rest/json/metallica/get
   *
   * 200 OK
   * {"title":"Enter Sandman","singer":"Metallica"}
   */
  @GET
  @Path( "/get" )
  @Produces( MediaType.APPLICATION_JSON )
  public Response getTrackInJson()
  {
     TrackPojo track = new TrackPojo();
     track.setTitle( "Enter Sandman" );
     track.setSinger( "Metallica" );

     return Response.ok( track.toString() ).build();
  }

  @POST
  @Path( "/post" )
  @Consumes( MediaType.APPLICATION_JSON )
  public Response createTrackInJson( TrackPojo track )
  {
    String result = "TrackPojo saved : " + track;
    return Response.status( 201 ).entity( result ).build();
  }
}
TrackPojo.java:
package com.mkyong;

public class TrackPojo
{
  String title;
  String singer;

  public String getTitle()                 { return title; }
  public void   setTitle( String title )   { this.title = title; }
  public String getSinger()                { return singer; }
  public void   setSinger( String singer ) { this.singer = singer; }

  @Override
  public String toString()
  {
    return "TrackPojo [title=" + title + ", singer=" + singer + "]";
  }
}
web.xml: The preceding JARs support this web.xml. We launch using Tomcat 6. (What's commented out are older definitions before everything was rolled into GlassFish.)
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
          http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
          version="2.5">

  <display-name>mkyong</display-name>

  <servlet>
    <servlet-name>jersey-servlet</servlet-name>
    <!-- <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> -->
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <!-- <param-name>com.sun.jersey.config.property.packages</param-name> -->
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.mkyong</param-value>
    </init-param>
    <init-param>
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>jersey-servlet</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
</web-app>

URL and output from this restlet are:

http://localhost:8080/mkyong/rest/json/metallica/get
TrackPojo [title=Enter Sandman, singer=Metallica]

POJO to JSON, JSON to POJO

See http://stackoverflow.com/questions/17568469/jersey-2-0-equivalent-to-pojomappingfeature. Tried...

.
.
.
    <init-param>
        <param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
.
.
.

    jackson-jaxrs-1.9.11.jar with web.xml paragraph above
    jackson-core-2.4.0-20140329.040435-28.jar without

Also, ...


Random 1...

Please examine what's in bold below. It's the service entry point in response to:

http://ftinit-acme-02.a.acme.net:8080/acme-write/persons?createUpdateLogEntries=true

The Acmecom is passed in as the payload.

@Path("persons")
public class PersonsWriteWebService
{
  @PUT
  @Consumes( { AcmecomxConstants.ACMECOMX_XML_MEDIA_TYPE, MediaType.APPLICATION_XML } )
  @Produces( { AcmecomxConstants.ACMECOMX_XML_MEDIA_TYPE, MediaType.APPLICATION_XML } )
  public Response setPerson( Acmecomx acmecomx,
                       @QueryParam( value = "createUpdateLogEntries" )
                       @DefaultValue( value = "true" ) boolean createUpdateLogEntries
  )
    throws AcmeWSException
  {
    if( acmecomx == null )
      throw new AcmeWSException( "Invalid data when calling this endpoint.  Acmecomx must be defined." );

    try
    {
      return Response.ok( getPersonsWorker().processPersons( acmecomx, createUpdateLogEntries ) ).build();
    }
    catch( AcmeException e )
    {
      StringBuilder builder = new StringBuilder( "599 AcmexWrite " );
      builder.append( e.getMessage() );
      String message = builder.toString();
      LOG.warn( message, e );
      return Response.ok( acmecomx ).header( "Warning", message ).build();
    }
  }

Random 2...

http://java.dzone.com/articles/jax-rs-20-custom-content

Random 3...

Jersey ReST Streaming
@GET
@Produces( MediaType.TEXT_PLAIN )
public Response streamExample()
{
  StreamingOutput stream = new StreamingOutput()
  {
    @Override
    public void write( OutputStream os ) throws IOException,
    WebApplicationException
    {
      Writer writer = new BufferedWriter( new OutputStreamWriter( os ) );
      writer.write( "test" );
      writer.flush();
    }
  };
  return Response.ok( stream ).build();
}

From http://stackoverflow.com/questions/12012724/jersey-example-of-using-streamingoutput-as-response-entity/12012867#12012867 and see also http://java.dzone.com/articles/jerseyjax-rs-streaming-json


JAX-RS, JAXB (XML) and JSON

JAXB defines standardized metadata and runtime API for converting Java domain objects to/from XML.

JAX-RS defines standardized metadata and runtime API for the creation of RESTful services. By default for the application/xml media type JAX-RS will use JAXB to convert the objects to/from XML. (To get application/json is covered separately.)

Example

In the following example when a GET operation is performed the JAX-RS implementation will return a Customer. A JAXB implementation will be used to convert that instance of Customer to the XML that the client will actually receive.

package org.example.service;

import javax.ejb.*;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import org.example.model.*;

@Stateless
@LocalBean
@Path( "/customers" )
public class CustomerResource
{
    @GET
    @Produces( MediaType.APPLICATION_XML )
    @Path( "{id}" )
    public Customer read( @PathParam( "id" ) int id )
    {
        Customer customer = new Customer();
        customer.setId( id );
        customer.setFirstName( "Jane" );
        customer.setLastName( null );

        PhoneNumber pn = new PhoneNumber();
        pn.setType( "work" );
        pn.setValue( "5551111" );
        customer.getPhoneNumbers().add( pn );

        return customer;
    }
}

JSON

You can override the default handling of application/xml by providing a custom MessageBodyReader/MessageBodyWriter. For other media types it depends. The application/json media type is popular but JAX-RS does not define what the default binding should be, implementations have come up with their own defaults. Here is an example: blog.bdoughan.com/2013/07/oracle-weblogic-1212-now-with.html.


MessageBodyReader/Writer

https://jersey.java.net/documentation/latest/message-body-workers.html#d0e4072

Jersey, via JAXB, contains internally entity providers to convert in and out of XML behind the scenes:

  POST/PUT XML request payload -> POJO
  GET POJO -> response payload

  (Make a table of this:)

  Other types supported:
  byte[] (*/*)
  String (ibid)
  InputStream (ibid)
  Reader (ibid)
  File (ibid)
  DataSource (ibid)
  Source (text/xml, application/xml, application/*+xml)
  JAXBElement (ibid)
  MultivaluedMap< K, V > (application/x-www-form-urlencoded)
  Form (application/x-www-form-urlencoded)
  StreamingOutput (*/*)
  Boolean, Character and Number (text/plain)
  CodeHaus writes a JSON provider.

  org.codehaus.jackson.jaxrs.JacksonProvider.{ readFrom(), writeTo() }

Predictably good example:
http://www.mkyong.com/webservices/jax-rs/json-example-with-jersey-jackson/