JAX-RS

JAX-RS is a specification, a set of interfaces and annotations offered by Java EE. Well-known Implementations are RESTEasy and Jersey.

Applications

JAX-RS application has [1..] resources and [0..] providers.

Configuration: via application-supplied subclass of Application.

Publications

Applications are published in diff ways:

  1. if app runs in Java SE environment

  2. within container (is packed as .war file)

    1. app classes are packaged in WEB-INF/classes or WEB-INF/lib and required libs are packaged in WEB-INF/lib

Resources

Is a POJO that has at least one method annotated with @Path or a request method designator.

By default a new resource class instance is created for each request to that resource.

Resource methods

JAX-RS resource method designators: @GET, @POST, @PUT, @DELETE, @PATCH, @HEAD, @OPTIONS

Methods must be public.

Method parameters are mapped from the request according to the semantics of annotation:

  • @MatrixParam - extracts the value of URI matrix parameter

    • Matrix parameters are name-value pairs embedded within the path of a URI string

    • e.g. http://example.cars.com/mercedes/e55;color=black/2006

    • color=black is matrix parameter

  • @QueryParam - extracts the value of URI query parameter

  • @PathParam - extracts the value of URI template parameter

  • @CookieParam - extracts the value of a cookie

  • @HeaderParam - extracts the value of header

  • @FormParam

  • @DefaultValue is used to supply a default value for parameter

  • @Encode is used to disable auto URI decoding of param value

Return type

  • void - results in an empty entity body with 204 status code

    • 204 status code means that the request was received and understood, but that there is no need to send any data back

  • Response

    • entity property of Response -mapped-> entity body

    • status property of Response -mapped-> status code

    • if (Response == null) {
          status_code = 204;
      } 
      
      if (Response.status is NOT set) {
          if (Response.entity != null) {
              status_code = 200;
          }                
          if (Response.entity == null) {
              status_code = 204;
          }                
      }
    • use Response to provide metadata; use ResponseBuilder;

  • GenericEntity

    • Entity property of GenericEntity -mapped-> entity body

    • if (GenericEntity == null) {
          status_code = 204;
      }
      if (GenericEntity != null) {
          status_code = 200;
      }
      }}}
  • Other

    • returned instance -mapped-> entity body

    • if (class is anonymous inner class) {
          use super class;
      }
    • if (return_value == null) {
          status_code = 204;
      } else {
          status_code = 200;
      }

To illustrate consider a method that always returns an instance of ArrayList<String> (directly or wrapped).

Exceptions

// map WebApplicationException to response
exceptionProvider = exceptionMappingProvider<WebApplicationException>

if (!exception.response.contains(entity) && exceptionProvider.isAvailable()) {
    Response instance = exceptionProvider.create();        
} else {
    use exception.response directly
}
// finished
exceptionProvider = exceptionMappingProvider<? super CurrentException>

if (exceptionProvider.isAvailable()) {
    
    try {
        Response instance = exceptionProvider.create();        
    } catch () {
        return server_error_response(status code 500) to client
    }
}
if (unchecked_exception not_mapped) {
    re-throw and propagate to underlying container
} 

if (checked_exception not_mapped 
    && checked_exception cannot_be_thrown_directly) {
     
    wrappedException = container_specific_exception.wrap(checked_exception)
    throw to underlying container           
}    
  • Servlet-based implementation wrapper ServletException

  • JAX-WS Provider-based implementation wrapper WebServiceException

HEAD and OPTIONS

// how HEAD requests are handled
if (method.annotatedWith(HEAD) is present) {
    call method
} else {
    call method.annotatedWith(GET)
    discard returned entity
    
    // Performance risk if entity creation is expensive
}
// how OPTIONS requests are handled
if (method.annotatedWith(OPTIONS) is present) {
    call method
} else {
    generate response using metadata provided by JAX-RS annotations 
        on matching class and its methods
}

URI Templates

@Path("widgets/{id}")
public class Widget {
... }

@Path("widget list/{id}") equivalent to @Path("widget%20list/{id}") (encoded automatically).

@Path("widgets/{path:.+}")
public class Widget {
... }

any request starting with widgets and containing at least one more path segment will match. E.g. widgets/small/a => the value of path will be small/a

Sub Resources

Methods of resource class annotated by @Path are either:

  • sub-resource methods => handle HTTP request directly

  • sub-resource locators => return an object or class that will handle HTTP request

    • may have all the same parameters as a normal resource method, except they must not have entity parameter

@Path("widgets")
public class WidgetsResource {
 
    @GET
    @Path("offers")
    public WidgetList getDiscounted() {...} // sub-resource method
    
    @Path("{id}")
    public WidgetResource findWidget(@PathParam("id") String id) {
      return new WidgetResource(id);
    } 
}

public class WidgetResource {
    public WidgetResource(String id) {...}
    
    @GET
    public Widget getDetails() {...}
}

Media type capabilities

  • @Consumes

  • @Produces

Can be applied to resource method(overrides specified on class and on provider), resource class, entity provider.

@Path("widgets")
@Produces("application/widgets+xml")
public class WidgetsResource {

    @GET
    public Widgets getAsXML() {...}
    // response media type is "application/widgets+xml", taken from WidgetsProvider
    
    @GET
    @Produces("text/html")
    public String getAsHtml() {...}
    // String containg "text/html" will be returned, written by default impl of
    // MessageBodyWriter<String>

    @POST
    @Consumes("application/widgets+xml")
    public void addWidget(Widget widget) {...}
    // Value of widget will be mapped from request entity using WidgetProvider
}

@Provider
@Produces("application/widgets+xml")
public class WidgetsProvider implements MessageBodyWriter<Widgets> {...}

@Provider
@Consumes("application/widgets+xml")
public class WidgetProvider implements MessageBodyReader<Widget> {...}

Multiple media types

When accepting multiple media types client may indicate preference by using relative quality factor q parameter (q-value ). Q-value is [0..1]

  • 0 - undesired

  • 1 - highly desired (default value)

Example for above code: GET with header Accept: "text/html; q=1, application/widgets+xml; q=0.8" will match getAsHtml instead of getAsXML because of q.

A server can also indicate media type preference using the qs parameter.

@Path("widgets2")
public class WidgetsResource2 {

    @GET
    @Produces("application/xml; qs=1", "application/json; qs=0.75")
    public Widgets getWidget() {...}
}

With GET request with header Accept: "application/*; q=0.5" => media type application/xml is selected due to higher qs value.

if (@Produces.value != request Accept header) {
    sub-resource method is not invoked
}

if (@Consumes.value != request Content-Type header) {
    sub-resource method is not invoked    
}

Annotation inheritance

public interface ReadOnlyAtomFeed {
     @GET @Produces("application/atom+xml")
     Feed getFeed();
}
     
@Path("feed")
public class ActivityLog implements ReadOnlyAtomFeed {
     public Feed getFeed() {...}
}

ActivityLog.getFeed() inherits @GET and @Produces from the interface.

@Path("feed")
public class ActivityLog implements ReadOnlyAtomFeed {
    @Produces("application/atom+xml")
    public Feed getFeed() {...}
}

In the above case @GET annotation from ReadOnlyAtomFeed.getFeed() is not inherited. Because

If a subclass or implementation method has any JAX-RS annotations then all of the annotations on the superclass or interface method are ignored

Providers

Annotation @Provider is used by JAX-RS runtime to automatically discover provider classes via mechanisms such as class scanning.

  • Provider must have public constructor

  • Constructor may include parameter annotated with @Context

  • Providers may be annotated with @Priority. @Priority(1) > @Priority(10)

  • Default priority for application-supplied providers is javax.ws.rs.Priorities.USER

/**
* User-level filter/interceptor priority.
*/
public static final int USER = 5000;

MessageBodyReader

Message entity body -> Java type

  • implement MessageBodyReader

  • annotate with @Provider (for automatic discovery)

MessageBodyReader always operate on decoded HTTP entity body (decoded by container or JAX-RS runtime).

How entity body is mapped to Java method parameter:

var media_type = request.Content_Type
if (media_type == null) {
    media_type = application/octet_stream
}

-----------------

Identify Java type of sub-resource method parameter

-----------------

Select set of MessageBodyReader that support media_type

-----------------

for (MessageBodyReader reader : set) {
    if (reader.isReadable()) {
        if (more readers suitable) {
            choose reader with highest priority
            reader.readFrom(entity_body) 
            done
        }
    }
}
server throw NotSupportedException (415 status) + no entity
client generate ProcessingException

MessageBodyWriter

javax.ws.rs.ext.MessageBodyWriter

Contract for a provider that supports the conversion of a Java type to a stream.

A MessageBodyWriter implementation may be annotated with @javax.ws.rs.Produces to restrict the media types for which it will be considered suitable.

Providers implementing MessageBodyWriter contract must be either programmatically registered in a JAX-RS runtime or must be annotated with @javax.ws.rs.ext.Provider annotation to be automatically discovered by the JAX-RS runtime during a provider scanning phase.

Steps for mapping return value to message entity body:

var object = what_will_be_mapped_to_message_entity_body()

if (object instanceof <? extends Response>) {
    object = Response.entity
} else {
    object
}

-----------------

var media_type = determine()

-----------------

Select set of MessageBodyWriter that support object and media type of message body

-----------------

sort set (TODO criteria)

-----------------

for (MessageBodyWriter writer : set) {
    if (writer.isWriteable()) {
        if (more writers suitable) {
            choose writer with highest priority
            writer.writeTo()
            done
        }
    }
}
server generates InternalServerErrorException (status 500) + no entity
client generate ProcessingException

Media type capabilities

Readers and writers may restrict media types they support using @Consumes and @Produces. Absence of annotation means "*/*".

ExceptionMapper

must implement javax.ws.rs.ext.ExceptionMapper

Contract for a provider that maps Java exceptions (checker or runtime) to javax.ws.rs.core.Response. If >1 exception providers are applicable => use with highest priority.

Providers implementing ExceptionMapper contract must be either programmatically registered in a JAX-RS runtime or must be annotated with @javax.ws.rs.ext.Provider annotation to be automatically discovered by the JAX-RS runtime during a provider scanning phase.

Client API

javax.ws.rs.client

Filters

Filters execute code at an extension point but without wrapping a method invocation.

@Provider

public interface ClientRequestFilter {    // for client 
    void filter(ClientRequestContext requestContext) throws IOException;
}
// before HTTP request is delivered to the network

public interface ClientResponseFilter {    // for client 
    void filter(ClientRequestContext requestContext,
        ClientResponseContext responseContext) throws IOException;
}
// is executed upon receiving a server response
                   
public interface ContainerRequestFilter {    // for server
    void filter(ContainerRequestContext requestContext) throws IOException;
}
// is executed upon receiving a request from a client

public interface ContainerResponseFilter {    // for server
    void filter(ContainerRequestContext requestContext,
        ContainerResponseContext responseContext) throws IOException;
}
// before HTTP response is delivered to the network

Filters are grouped in Filter chain . There is a separate filter chain for each extension point (e.g. ClientRequest, ClientResponse, ContainerRequest, ContainerResponse, PreMatchContainerRequest).

ContainerRequestFilter annotated by @PreMatching executed upon receiving client request but before a resource method is matched.

Interceptors

Entity interceptors wrap around a method invocation at a specific extension point.

@Provider

public interface ReaderInterceptor {
    Object aroundReadFrom(ReaderInterceptorContext context)
        throws java.io.IOException, javax.ws.rs.WebApplicationException;
}       

Reader interceptor wraps around calls to MessageBodyReader readFrom().

public interface WriterInterceptor {
    void aroundWriteTo(WriterInterceptorContext context)
        throws java.io.IOException, javax.ws.rs.WebApplicationException;
} 

Writer interceptor wraps around calls to MessageBodyWriter writeTo().

Binding

Global

A filter/interceptor that has no annotations is assumed to be bound globally => applies to all resource methods in application (well either Provider or register manually in Application)

Name binding

@Provider
@MyLogged
class LoggingFilter implements ContainerRequestFilter{...}

@Provider
@MyAuthenticated
class AuthenticationFilter implements ContainerRequestFilter{...}


@Path("*/*")
public class MyResource {
    @MyLogged @MyAuthenticated
    @GET
    public String hello() {...}
}

@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyLogged { }

@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAuthenticated { }

JAX-RS request processing cycle

Open questions:

  • How JAX-RS configuration is done?

Last updated