Skip to content

Latest commit

 

History

History
1052 lines (817 loc) · 57.6 KB

ch08.asciidoc

File metadata and controls

1052 lines (817 loc) · 57.6 KB

REST and Addressable Services

Rest and be thankful.

— Inscription at a rest stop along Scotland's Highway A83

The concepts guiding the makeup of the modern Web could be considered a happy accident, or at least an implementation of ideas that had general applicability far beyond their initial design criteria. In the late 1980s we had the hardware and software necessary for networking; these were low-level tools for transmitting data from one computer to another. We even had some payload protocols and application layers available including IRC for chat, POP for email, and Usenet for general discussions. We were communicating, albeit over relatively constrained channels.

Out of necessity for his own research, Tim Berners-Lee of the European Organization for Nuclear Research (CERN) concocted a small recipe for publishing documents in a manner that would make his findings more accessible between departments and encourage updates over time. Called the "WorldWideWeb" (WWW), this project proposed a series of simple constructs:

Addressable resources

A unique key or address assigned to each document

Hypertext

A unidirectional pointer to an addressable resource

Browser

A client program capable of reading hypertext-enabled documents

We take these concepts lightly now, but it’s worthwhile considering the paradigm shift this evoked in the early 1990s; in only 10 years' time, most of the world’s university students and many homes were connected to a Web that contained a marketing presence for an overwhelming majority of the Fortune 500. These ideas ushered innovation and communication at a rate never before seen in the history of mankind. This was instant, global publishing, and it was free.

Central to the makeup of the WWW was the introduction of the Uniform Resource Identifier, or URI. The URI defined by RFC 3986 forms the basis of an addressable resource, and has the following makeup:

scheme ":" hierarchical-part ["?" query] ["#" fragment]

Examples from the RFC include:

foo://example.com:8042/over/there?name=ferret#nose

and:

urn:example:animal:ferret:nose

In a short time, Berners-Lee introduced the first version of the HyperText Markup Language (HTML), aimed at providing a more concise vernacular for incorporating links into a common markup that browsers could format for viewing. The WWW was built as a mechanism for document exchange, sharing of published material over a commonly understood protocol and payload format (commonly HTML).

In 2000, University of California at Irvine’s Roy Fielding published his dissertation "Architectural Styles and the Design of Network-based Software Architectures", which expanded the notion of addressing documents to include services among the data exchanged on the Web, and defined a system of REpresentational State Transfer (REST). With his background in coauthoring RFC-2616, which defined the HTTP/1.1 protocol, Fielding was in a position of expertise to rethink how the principles of the Web might be applied to services.

By addressing services and applying a set of conventions to these URIs, we’re able to compose a wide array of operations on services with the following key benefits:

  • Loose coupling

  • Interoperability

  • Encapsulation

  • Distributed programming

  • Modularization

Note

Clearly the study of REST is worthy of its own text, and we’ll recommend REST in Practice by Webber, et al. (O’Reilly, 2010) to those looking to explore in greater depth.

REST is certainly not the first distributed architecture: Remote Procedure Call (RPC) variants have been used in various forms (i.e., SOAP, XML-RPC) for a long while. In recent years, the trend toward REST has been largely attributed to its ease of use and slim profile when coupled with the HyperText Transfer Protocol (HTTP), an established communication protocol providing for methods, headers, and return status codes that map well to the objectives of the caller. In practice, the success of the WWW is inherently linked to HTTP, though this is only one protocol (scheme) that can be applied to the general guidelines of the Web. Due to its widespread usage and versatility, we’ll be employing HTTP throughout this chapter.

Because of its success, REST has become an abused buzzword in some circles. It’s helpful for us to clarify the stages of compliance with a truly RESTful system, and a maturity model developed by Leonard Richardson presents four rungs of evolution. Martin Fowler aptly sums these up in a blog post, and we’ll outline them here:

Stage 0

Using HTTP as a transport system for arbitrary payloads; typically used in plain RPC where a caller may wish to invoke upon a server over a network.

Stage 1

Addressable Resources; each domain object may be assigned to an address, and client requests contain all the necessary metadata needed to carry out the invocation.

Stage 2

HTTP Verbs; in addition to assigning each domain object or service an address, we use the conventions of the HTTP methods to differentiate between a "Create," "Update," "Delete," or other actions.

Stage 3

HATEOAS, or "Hypermedia As The Engine Of Application State"; a request upon a resource can return a list of links to the client in order to proceed to the next available actions. For instance, after "creating a user," the client may be given a success confirmation and shown links to "view the user," "edit the user," "view all users." Additionally, projects with Stage 3 maturity will utilize media types (content types) as part of content negotiation; an XML-based request should likely yield an XML-based response, while a JSON request might imply a JSON response. With media types set in the request, these can all take place using the same URI. Stage 3 is about workflow and transition; it guides the client through the stages of the application.

A RESTful system is always Stage 3, though this is an often-misunderstood and neglected understanding of the REST architecture, particularly for newcomers. In layman’s terms, a Stage 3 exchange may sound a little like this:

Server

You’ve just created an order. Do you want to pay? Do you want to add more items? Do you want to save your cart for later? Here are the links for each of these actions.

Client

I’m following the link to save my cart, here is the request.

Server

Your cart is saved. Do you want to continue shopping? Do you want to view your cart? Here are the links for these actions.

It’s important to consider that REST is an architectural style, agnostic of any particular programming model or language. At its core, REST is most simply explained as an API for accessing services and domain objects over the Web.

As the Java community has come to understand the REST principles, it has provided a mapping layer between requests and backend services: JAX-RS.

REST in Enterprise Java: The JAX-RS Specification

The Java API for RESTful Web Services, or JAX-RS, is a specification under the direction of the Java Community Process, defined by JSR-339 in its latest 2.0 version. Java EE6 incorprates the 1.1 revision, as defined by JSR-311; this is the version we’ll be covering here. From the specification document, its goals are to be/have:

POJO-based

API will provide a set of annotations and associated classes/interfaces that may be used with POJOs in order to expose them as web resources. The specification will define object lifecycle and scope.

HTTP-centric

The specification will assume HTTP is the underlying network protocol and will provide a clear mapping between HTTP and URI elements and the corresponding API classes and annotations. The API will provide high-level support for common HTTP usage patterns and will be sufficiently flexible to support a variety of HTTP applications, including WebDAV and the Atom Publishing Protocol.

Format independence

The API will be applicable to a wide variety of HTTP entity body content types. It will provide the necessary pluggability to allow additional types to be added by an application in a standard manner.

Container independence

Artifacts using the API will be deployable in a variety of web-tier containers. The specification will define how artifacts are deployed in a Servlet container and as a JAX-WS Provider.

Inclusion in Java EE

The specification will define the environment for a web resource class hosted in a Java EE container and will specify how to use Java EE features and components within a web resource class.

Note

Because it’s not our aim to provide a comprehensive overview of JAX-RS, we recommend RESTful Java with JAX-RS by Bill Burke (O’Reilly, 2009), a member of the JSR-339 Expert Group and lead of the JBoss Community’s RESTEasy implementation. The second revision of the book, covering the latest 2.0 version of the specification, is now on sale.

The JAX-RS Specification API provides a set of annotations helpful to developers seeking to map incoming HTTP-based requests to backend services. From the docs, these include:

ApplicationPath

Identifies the application path that serves as the base URI for all resource URIs provided by Path.

Consumes

Defines the media types that the methods of a resource class or MessageBodyReader can accept.

CookieParam

Binds the value of an HTTP cookie to a resource method parameter, resource class field, or resource class bean property.

DefaultValue

Defines the default value of request metadata that is bound using one of the following annotations: PathParam, QueryParam, MatrixParam, CookieParam, FormParam, or HeaderParam.

DELETE

Indicates that the annotated method responds to HTTP DELETE requests.

Encoded

Disables automatic decoding of parameter values bound using QueryParam, PathParam, FormParam, or MatrixParam.

FormParam

Binds the value(s) of a form parameter contained within a request entity body to a resource method parameter.

GET

Indicates that the annotated method responds to HTTP GET requests.

HEAD

Indicates that the annotated method responds to HTTP HEAD requests.

HeaderParam

Binds the value(s) of an HTTP header to a resource method parameter, resource class field, or resource class bean property.

HttpMethod

Associates the name of an HTTP method with an annotation.

MatrixParam

Binds the value(s) of a URI matrix parameter to a resource method parameter, resource class field, or resource class bean property.

OPTIONS

Indicates that the annotated method responds to HTTP OPTIONS requests.

Path

Identifies the URI path that a resource class or class method will serve requests for.

PathParam

Binds the value of a URI template parameter or a path segment containing the template parameter to a resource method parameter, resource class field, or resource class bean property.

POST

Indicates that the annotated method responds to HTTP POST requests.

Produces

Defines the media type(s) that the methods of a resource class or MessageBodyWriter can produce.

PUT

Indicates that the annotated method responds to HTTP PUT requests.

QueryParam

Binds the value(s) of an HTTP query parameter to a resource method parameter, resource class field, or resource class bean property.

These can be composed together to define the mapping between a business object’s methods and the requests it will service, as shown in the API documentation:

@Path("widgets/{widgetid}")
@Consumes("application/widgets+xml")
@Produces("application/widgets+xml")
public class WidgetResource {

    @GET
    public String getWidget(@PathParam("widgetid") String id) {
        return getWidgetAsXml(id);
    }

    @PUT
    public void updateWidget(@PathParam("widgetid") String id,Source update) {
        updateWidgetFromXml(id, update);
    }
    ...
 }

This defines an example of a business object that will receive requests to $applicationRoot/widgets/$widgetid, where $widgetid is the identifier of the domain object to be acted upon. HTTP GET requests will be serviced by the getWidget method, which will receive the $widgetid as a method parameter; HTTP PUT requests will be handled by the updateWidget method. The class-level @Consumes and @Produces annotations designate that all business methods of the class will expect and return a media type (content type) of application/widgets+xml.

Because the specification supplies only a contract by which JAX-RS implementations must behave, the runtime will vary between application server vendors. For instance, the Reference Implementation, Jersey, can be found in the GlassFish Application Server, while WildFly from the JBoss Community uses RESTEasy.

Use Cases and Requirements

Thus far, we’ve visited and described the internal mechanisms with which we interact with data. Now we’re able to work on building an API for clients to access the domain state in a self-describing fashion, and RESTful design coupled with JAX-RS affords us the tools to expose our application’s capabilities in a commonly understood way.

We’d like to encourage third-party integrators—​clients about whom we may not have any up-front knowledge—​to view, update, and create domain objects within the GeekSeek application. Therefore, our use case requirements will be simply summed up as the following:

  • As a third-party integrator, I should be able to perform CRUD operations upon:

    • A Conference

    • Sessions within Conferences

    • Attachments within Sessions

    • Attachments within Conferences

    • A Venue (and associate with a Conference and/or Session)

Additionally, we want to lay out a map of the application as the client navigates through state changes. For instance, at the root, a client should know what operations it’s capable of performing. Once that operation is complete, a series of possible next steps should be made available to the client such that it may continue execution. This guide is known as the Domain Application Protocol (DAP), and it acts as a slimming agent atop the wide array of possible HTTP operations in order to show the valid business processes that are available to a client as it progresses through the application’s various state changes. It’s this DAP layer that grants us the final HATEOAS step of the Richardson Maturity Model. Our DAP will define a series of addressable resources coupled with valid HTTP methods and media types to determine what actions are taken, and what links are to come next in the business process:

  • / application/vnd.ced+xml;type=root

    • GET → Links

    • Link → conference application/vnd.ced+xml;type=conference

    • Link → venue application/vnd.ced+xml;type=venue

  • /conference application/vnd.ced+xml;type=conference

    • GET → List

    • POST → Add

  • /conference/[c_id] application/vnd.ced+xml;type=conference

    • GET → Single

    • PUT → Update

    • DELETE → Remove

    • Link → session application/vnd.ced+xml;type=session

    • Link → venue application/vnd.ced+xml;type=venue

    • Link → attachments application/vnd.ced+xml;type=attachment

  • /conference/[c_id]/session application/vnd.ced+xml;type=session

    • GET → List

    • POST → Add

  • /conference/[c_id/session/[s_id] application/vnd.ced+xml;type=session

    • GET → Single

    • PUT → Update

    • DELETE → Remove

    • Link → venue application/vnd.ced+xml;type=room

    • Link → attachments application/vnd.ced+xml;type=attachment

    • Link → parent application/vnd.ced+xml;type=conference

  • /venue application/vnd.ced+xml;type=venue

    • GET → List

    • POST → Add

  • /venue/[v_id] application/vnd.ced+xml;type=venue

    • GET → Single

    • PUT → Update

    • DELETE → Remove

    • Link → room application/vnd.ced+xml;type=room

  • /venue/[v_id]/room application/vnd.ced+xml;type=room

    • GET → List

    • POST → Add

    • Link → attachments application/vnd.ced+xml;type=attachment

  • /venue/[v_id]/room/[r_id] application/vnd.ced+xml;type=room

    • GET → Single

    • PUT → Update

    • DELETE → Remove

    • Link → attachments application/vnd.ced+xml;type=attachment

  • /attachment application/vnd.ced+xml;type=attachment

    • GET → List

    • POST → Add

  • /attachment/[a_id] application/vnd.ced+xml;type=attachment

    • GET → List

    • POST → Add

The preceding DAP can be conceptually understood as a site map for services, and it defines the API for users of the system. By designing to the DAP, we provide clients with a robust mechanism by which the details of attaining each resource or invoking the application’s services can be read as the client navigates from state to state.

Implementation

With our requirements defined, we’re free to start implementation. Remember that our primary goal here is to create HTTP endpoints at the locations defined by our DAP, and we want to ensure that they perform the appropriate action and return the contracted response. By using JAX-RS we’ll be making business objects and defining the mapping between the path, query parameters, and media types of the request before taking action and supplying the correct response.

The first step is to let the container know that we have a JAX-RS component in our application; we do this by defining a javax.ws.rs.ApplicationPath annotation atop a subclass of javax.ws.rs.core.Application. Here we provide this in org.geekseek.rest.GeekSeekApplication:

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("api")
public class GeekSeekApplication extends Application {

}

This will be picked up by the container and signal that requests to paths under the $applicationRoot/api pattern will be serviced by JAX-RS.

Repository Resources

Looking over our requirements, we see that all paths in our DAP are capable of performing CRUD operations. Therefore, it makes sense for us to define a base upon which individual resources can build, while giving persistence capabilities to create, read, update, and delete. In GeekSeek, we’ll handle this by making a generic RepositoryResource base to give us a hook into the Repository abstractions detailed in [ch05]. Let’s walk through org.cedj.geekseek.web.rest.core.RepositoryResource:

public abstract class RepositoryResource<
  DOMAIN extends Identifiable&Timestampable,
  REP extends Representation<DOMAIN>>
    implements Resource {

Simple enough; an abstract class notes we’ll be extending this later for more specific resources that interact with a Respository. Let’s define the base media types our application will be using. Remember, media types are a key part of the maturity model in handling the types of responses to be returned, given the input from the request. For example, a JSON request should yield a JSON response in our known format:

protected static final String BASE_XML_MEDIA_TYPE = "application/vnd.ced+xml";
protected static final String BASE_JSON_MEDIA_TYPE = "application/vnd.ced+json";

Next up, some fields that will be set later by subclasses; this composes our abstraction point, which will need specialization later:

private Class<? extends Resource> resourceClass;
private Class<DOMAIN> domainClass;
private Class<REP> representationClass;

We’ll also use some instance members to be injected by either the CDI (@Inject) or JAX-RS (@Context) containers:

@Context
private UriInfo uriInfo;

@Context
private HttpHeaders headers;

@Inject
private Repository<DOMAIN> repository;

@Inject
private RepresentationConverter<REP, DOMAIN> converter;

The @Context annotation will help us gain access to the context of the request in flight: information about the URI or HTTP headers. The Repository is how we’ll access the persistence layer, and the RepresentationConverter will be responsible for mapping between the client payload and our own entity object model.

Now let’s make sure that subclasses set our extension fields properly:

public RepositoryResource(Class<? extends Resource> resourceClass,
  Class<DOMAIN> domainClass,
  Class<REP> representationClass) {
        this.resourceClass = resourceClass;
        this.domainClass = domainClass;
        this.representationClass = representationClass;
    }

That should do it for the fields needed by our RepositoryResource. Time to do something interesting; we want to map HTTP POST requests of our JSON and XML media types defined earlier to create a new entity. With a couple of annotations and a few lines of logic in a business method, JAX-RS can handle that for us:

@POST
@Consumes({ BASE_JSON_MEDIA_TYPE, BASE_XML_MEDIA_TYPE })
public Response create(REP representation) {
    DOMAIN entity = getConverter().to(
      uriInfo, representation);
    getRepository().store(entity);
    return Response.created(
      UriBuilder.fromResource(
        getResourceClass())
          .segment("{id}")
          .build(entity.getId())).build();
}

The @POST annotation defines that this method will service HTTP POST requests, and the @Consumes annotation designates the valid media types. The JAX-RS container will then map requests meeting those criteria to this create method, passing along the Representation of our Domain object. From there we can get a hook to the Repository, store the entity, and issue an HTTP Response to the client. Of importance is that we let the client know the ID of the entity that was created as part of the response; in this case, the ID is the URI to the newly created resource, which may take a form similar to Response: 201 Location: resource-uri.

We’ll handle the other CRUD operations in similar fashion:

@DELETE
@Path("/{id}")
public Response delete(@PathParam("id") String id) {
    DOMAIN entity = getRepository().get(id);
    if (entity == null) {
        return Response.status(Status.NOT_FOUND).build();
    }
    getRepository().remove(entity);
    return Response.noContent().build();
}

@GET
@Path("/{id}")
@Produces({ BASE_JSON_MEDIA_TYPE, BASE_XML_MEDIA_TYPE })
public Response get(@PathParam("id") String id) {
    DOMAIN entity = getRepository().get(id);
    if (entity == null) {
        return Response.status(Status.NOT_FOUND).type(
            getMediaType()).build();
    }

    return Response.ok(
      getConverter().from(uriInfo, entity))
          .type(getMediaType())
          .lastModified(entity.getLastModified())
          .build();
}

@PUT
@Path("/{id}")
@Consumes({ BASE_JSON_MEDIA_TYPE, BASE_XML_MEDIA_TYPE })
public Response update(@PathParam("id") String id,
    REP representation) {
    DOMAIN entity = getRepository().get(id);
    if (entity == null) {
        return Response.status(Status.BAD_REQUEST)
          .build();
    }

    getConverter().update(
        uriInfo, representation, entity);
    getRepository().store(entity);

    return Response.noContent().build();
}

Note that for GET, PUT, and DELETE operations we must know which entity to work with, so we use the @Path annotation to define a path parameter as part of the request, and pass this along as a PathParam to the method when it’s invoked. We also are sure to use the correct HTTP response codes when the situation warrants:

  • OK(200) on GET of an entity

  • NotFound(404) on GET of an entity with an ID that does not exist

  • Created(201) with Header: "Location $resourceUri" on successful POST and creation of a new entity

  • NoContent(204) on DELETE or successful update

  • BadRequest(400) on attemped PUT of a missing resource

With this base class in place, we have effectively made a nice mapping between the DAP API as part of our requirements and the backend Repository and JPA. Incoming client requests are mapped to business methods, which in turn delegate the appropriate action to the persistence layer and supply a response.

Let’s have a look at a concrete implementation of the RepositoryResource, one that handles interaction with User domain objects. We’ve aptly named this the org.cedj. geekseek.web.rest.user.UserResource:

@ResourceModel
@Path("/user")
public class UserResource
    extends RepositoryResource<User, UserRepresentation> {

    private static final String USER_XML_MEDIA_TYPE =
        BASE_XML_MEDIA_TYPE + "; type=user";
    private static final String USER_JSON_MEDIA_TYPE =
        BASE_JSON_MEDIA_TYPE + "; type=user";

    public UserResource() {
        super(UserResource.class, User.class, UserRepresentation.class);
    }

    @Override
    public String getResourceMediaType() {
        return USER_XML_MEDIA_TYPE;
    }

    @Override
    protected String[] getMediaTypes() {
        return new String[]{USER_XML_MEDIA_TYPE, USER_JSON_MEDIA_TYPE};
    }
}

Because we inherit all the support to interact with JPA from the parent RepositoryResource, this class needs to do little more than:

  • Note that we are an @ResourceModel, a custom type that is a CDI @Stereotype to add interceptors. We explain this in greater depth in The @ResourceModel.

  • Define a path for the resource, in this case, "/user" under the JAX-RS application root.

  • Supply the custom media types for user representations.

  • Set the resource type, the domain object type, and the representation type in the constructor.

Now we can handle CRUD operations for User domain objects; similar implementations to this are also in place for Conference, Session, etc.

The Representation Converter

We’ve seen that the underlying domain model implemented in JPA is not the same as the REST model we’re exposing to clients. Although EE allows us to annotate JPA models with JAX-B bindings etc., we likely would like to keep the two models separate because the REST model may:

  • Contain less data

  • Combine JPA models into one unified view

  • Link resources

  • Render itself in multiple different representations and formats

Additionally, some resources act as proxies and have no representation on their own. To allow these resources to operate in a modular fashion, we need a way to describe conversion—​for example, the relation resource links users to a conference (attendees, speakers). The relation itself knows nothing about the source or target types, but it knows how to get a converter that supports converting between these types. To handle this, we supply the org.cedj.geekseek.web.rest.core.RepresentationConverter:

public interface RepresentationConverter<REST, SOURCE> {

    Class<REST> getRepresentationClass();

    Class<SOURCE> getSourceClass();

    REST from(UriInfo uriInfo, SOURCE source);

    Collection<REST> from(UriInfo uriInfo, Collection<SOURCE> sources);

    SOURCE to(UriInfo uriInfo, REST representation);

    SOURCE update(UriInfo uriInfo, REST representation, SOURCE target);

    Collection<SOURCE> to(UriInfo uriInfo, Collection<REST> representations);

Inside the preceding interface is also a base implementation to handle the conversion, RepresentationConverter.Base:

public abstract static class Base<REST, SOURCE>
    implements RepresentationConverter<REST, SOURCE> {

    private Class<REST> representationClass;
    private Class<SOURCE> sourceClass;

    protected Base() {}

    public Base(Class<REST> representationClass,
        Class<SOURCE> sourceClass) {
        this.representationClass = representationClass;
        this.sourceClass = sourceClass;
    }

    @Override
    public Collection<REST> from(UriInfo uriInfo,
        Collection<SOURCE> ins) {
        Collection<REST> out = new ArrayList<REST>();
        for(SOURCE in : ins) {
            out.add(from(uriInfo, in));
        }
        return out;
    }

    @Override
    public Collection<SOURCE> to(UriInfo uriInfo,
        Collection<REST> ins) {
        Collection<SOURCE> out = new ArrayList<SOURCE>();
        for(REST in : ins) {
             out.add(to(uriInfo, in));
        }
            return out;
    }

    ...
}

CDI will dutifully inject the appropriate instance of this converter where required; for instance, in this field of the org.cedj.geekseek.web.rest.conference.Conference Resource:

@Inject
private RepresentationConverter<SessionRepresentation,
    Session> sessionConverter;

Through these converters we can easily delegate the messy business of parsing the media-type payload formats to and from our own internal domain objects.

The @ResourceModel

Because JAX-RS 1.x does not define an interceptor model, we need to apply these on our own to activate cross-cutting concerns such as security, validation, and resource linking to our JAX-RS endpoints. This is easily enough accomplished using the stereotype feature of CDI, where we can create our own annotation type (which itself has annotations): wherever our custom type is applied, the metadata we specify upon the stereotype will propagate. So we can create an annotation to apply all of the features we’d like upon a RepositoryResource, and we call it org.cedj.geekseek.web.rest. core.annotation.ResourceModel:

@REST
@RequestScoped
@Stereotype
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ResourceModel {

}

By placing this @ResourceModel annotation atop, for instance, UserResource as we’ve done here, this JAX-RS resource will now be marked as @REST via the CDI @Stereotype. This is a nice shortcut provided by CDI to compose behaviors together in one definition.

The @org.cedj.geekseek.web.rest.core.annotation.REST annotation is defined as a CDI @InterceptorBinding:

@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface REST {

}

To avoid having to define the entire interceptor chain for the REST layer in piecemeal fashion for each module that wants to use it, we create only one CDI Interceptor and define our own chain using pure CDI beans, which is handled in org.cedj.geekseek.web.rest.core.interceptor.RESTInterceptorEnabler:

@REST
@Interceptor
public class RESTInterceptorEnabler {

    @Inject
    private Instance<RESTInterceptor> instances;

    @AroundInvoke
    public Object intercept(final InvocationContext context) throws Exception {
        final List<RESTInterceptor> interceptors = sort(instances);
        InvocationContext wraped = new InvocationContext() {
            // Omitted for brevity
         }
        return wraped.proceed();

    }
...
}

Marking the RESTInterceptorEnabler with @REST and @Interceptor binds the RESTInterceptorEnabler to the use of the @REST annotation; then we can inject all valid RESTInterceptor instances and invoke them according to a sorted order in the intercept method annotated with @AroundInvoke. With our custom chain, we can rely on CDI to provide an Instance<X> of our desired custom interceptor type dynamically based on what is deployed rather then what is configured.

In practice, this means that our SecurityInterceptor, LinkedInterceptor, and ValidatedInterceptor (our implementations of type RESTInterceptor) will all be invoked for business methods on classes marked @ResourceModel.

LinkableRepresentation

As you may have noticed from our DAP, we have a series of paths that accept a source media type and return another media type representing the data in question. These are modeled by our org.cedj.geekseek.web.rest.core.Representation:

public interface Representation<X> {

    Class<X> getSourceType();

    String getRepresentationType();
}

Some paths are linkable; they contain pointers to resources that aren’t in the domain model itself. For example, a Session in a Conference is in the Conference domain, because a Conference contains N Session entities. A Conference may have a tracker (User), someone "following" the Conference for updates; this further links into the User domain via a Relation domain. Although each domain entity is separate, once we start to draw relationships between them, it’s helpful to consider a mechanism to link together these bonds.

So while domain model links are handled directly by JPA, the Representation, and a RepresentationConverter into the target formats, the relationships need to be addressed slightly differently.

For this we can introduce the notion of a org.cedj.geekseek.web.rest.core.LinkableRepresentation; a Representation type capable of coupling a source type with a series of links:

public abstract class LinkableRepresentation<X>
  implements Representation<X> {

    private List<ResourceLink> links;
    private Class<X> sourceType;
    private String representationType;
    private UriInfo uriInfo;

    protected LinkableRepresentation() {}

    public LinkableRepresentation(Class<X> sourceType,
      String representationType, UriInfo uriInfo) {
        this.sourceType = sourceType;
        this.representationType = representationType;
        this.uriInfo = uriInfo;
    }

    @XmlElement(name = "link", namespace = "urn:ced:link")
    public List<ResourceLink> getLinks() {
        if (this.links == null) {
            this.links = new ArrayList<ResourceLink>();
        }
        return links;
    }

    public void addLink(ResourceLink link) {
        getLinks().add(link);
    }

    public boolean doesNotContainRel(String rel) {
        return !containRel(rel);
    }

    public boolean containRel(String rel) {
        if(links == null || links.size() == 0) {
            return false;
        }
        for(ResourceLink link : links) {
            if(rel.equals(link.getRel())) {
                return true;
            }
        }
        return false;
    }

    @Override @XmlTransient
    public Class<X> getSourceType() {
        return sourceType;
    }

    @Override @XmlTransient
    public String getRepresentationType() {
        return representationType;
    }

    @XmlTransient
    public UriInfo getUriInfo() {
        return uriInfo;
    }
}

In The @ResourceModel, we see that our @ResourceModel stereotype is marked with @REST. This implies that we’ll apply an interceptor called org.cedj.geekseek.web.rest.core.interceptor.LinkedInterceptor to anything with this annotation. LinkedInterceptor has the responsibility of determining if the invocation has a linkable representation, and if so, link all of the LinkableRepresentation views together, as demonstrated in the preceding code sample. Anything with the @REST annotation will run this interceptor.

The reasoning behind this approach is: some Representation objects are linkable. Via the @ResourceModel (which contains @REST), a link provider can link a given resource to some other resource. This way, we can draw relationships between resources (entities) that are not described by JPA. The interceptor is implemented like so:

public class LinkedInterceptor implements RESTInterceptor {

    @Inject
    private Instance<LinkProvider> linkProviers;

    @Override
    public int getPriority() {
        return -10;
    }

    @Override
    public Object invoke(InvocationContext ic) throws Exception {
        Object obj = ic.proceed();
        if(hasLinkableRepresentations(obj)) {
            linkAllRepresentations(obj);
        }
        return obj;
    }

    private boolean hasLinkableRepresentations(Object obj) {
        return locateLinkableRepresentations(obj) != null;
    }

    private LinkableRepresentation<?> locateLinkableRepresentations(
       Object obj) {
        if(obj instanceof Response) {
            Object entity = ((Response)obj).getEntity();
            if(entity instanceof LinkableRepresentation) {
                return (LinkableRepresentation<?>)entity;
            }
        }
        return null;
    }

    private void linkAllRepresentations(Object obj) {
        LinkableRepresentation<?> linkable = locateLinkableRepresentations(obj);
        for(LinkProvider linker : linkProviers) {
            linker.appendLinks(linkable);
        }
    }
}

Recall from our DAP that many requests are to return a link to other resources as the client makes its way through state changes in the application. A link is really a value object to encapsulate a media type, href (link), and relation. We provide this in org.cedj.geekseek.web.rest.core.ResourceLink:

public class ResourceLink {

    private String rel;
    private URI href;
    private String type;

    public ResourceLink(String rel, URI href, String media) {
        this.rel = rel;
        this.href = href;
        this.type = media;
    }

    @XmlAttribute
    public String getHref() {
        if (href == null) {
            return null;
        }
        return href.toASCIIString();
    }

    @XmlAttribute
    public String getRel() {
        return rel;
    }

    @XmlAttribute
    public String getMediaType() {
        return type;
    }

    public void setHref(String href) {
        this.href = URI.create(href);
    }

    public void setRel(String rel) {
        this.rel = rel;
    }

    public void setType(String type) {
        this.type = type;
    }
}

LinkableRepresentation will use this value object in particular to handle its linking strategy between disparate entities that are not related in the JPA model.

Requirement Test Scenarios

With our implementation in place, leveraging JAX-RS to map our DAP to business methods, we’re set to test our endpoints. The core areas we want to assert are the expected responses from requests to:

  • PUT data

  • GET data

  • POST data

  • DELETE data

  • Obtain the appropriate links

A Black-Box Test

The general flow of our first test will be to model a user’s actions as she navigates through the site. To accomplish execution of the test methods in sequence, we’ll use Arquillian’s @InSequence annotation to signal the order of test execution. This will really position the test class as more of a "test scenario," with each test method acting as the separate tests that must maintain a proper order. In this fashion, we will follow the normal REST client flow from point A to B to C and so on. We’re going to execute requests to:

  • GET the Root resource

  • Locate the Conference link

  • POST to create a new Conference

  • GET to read the created Conference

  • Locate the Session link

  • POST to create a new Session

  • GET to read the created Session

  • PUT to update the Session

  • DELETE to delete the Session

  • PUT to update the Conference

  • DELETE to delete the Conference

This will be a pure client-side test; it requires something deployed that will talk to the REST APIs. We have provided this logic in org.cedj.geekseek.web.rest.conference.test.integration.story.CreateConferenceAndSessionStory:

@RunWith(Arquillian.class)
public class CreateConferenceAndSessionStory {

    private static String uri_conference = null;
    private static String uri_conferenceInstance = null;
    private static String uri_session = null;
    private static String uri_sessionInstance = null;

    @ArquillianResource
    private URL base;

    @BeforeClass
    public static void setup() {
        RestAssured.filters(
                ResponseLoggingFilter.responseLogger(),
                new RequestLoggingFilter());
    }

The @RunWith annotation should be familiar by now; Arquillian will be handling the test lifecycle for us. As noted previously, it’s good practice to allow Arquillian to inject the base URL of the application by using @ArquillianResource. And because we’re not bound to any frameworks in particular, we can also use the REST-assured project to provide a clean DSL to validate our REST services.

Notably missing from this declaration is the @Deployment method, which we supply in CreateConferenceAndSessionStoryTestCase so we can decouple the test scenario from the test deployment logic; this encourages re-use for running the same tests with different deployments, so we can further integrate other layers later. The deployment method for our purposes here looks like this:

@Deployment(testable = false)
public static WebArchive deploy() {
    return ConferenceRestDeployments.conference()
      .addAsWebInfResource(new File("src/main/resources/META-INF/beans.xml"));
}

Because this is a black-box test, we set testable to false to tell Arquillian not to equip the deployment with any additional test runners; we don’t want to test in-container here, but rather run requests from the outside of the server and analyze the response. The test should verify a behavior, not any internal details. We could likely write a test where we employ sharing of objects, and this might be easier to code and update, but it could also sneak in unexpected client changes that should have been caught by the tests. We’re interested only in testing the contract between the client and the server, which is specified by our DAP. Thus, black-box testing is an appropriate solution in this case.

In this deployment, we’ll also use "fake" implementations for the Repository/JPA layer; these are provided by the TestConferenceRepository and TestSessionRepository test classes, which simulate the JPA layer for testing purposes. We won’t be hitting the database for the tests at this level of integration. Later on, when we fully integrate the application, we’ll bring JPA back into the picture:

@ApplicationScoped
public abstract class TestRepository<T extends Identifiable>
    implements Repository<T> { .. }

public class TestConferenceRepository extends
  TestRepository<Conference> { .. }

On to the tests:

// Story: As a 3rd party Integrator I should be able to locate
// the Conference root Resource
@Test @InSequence(0)
public void shouldBeAbleToLocateConferenceRoot() throws Exception {
        //uri_conference = new URL(base, "api/conference").toExternalForm();
        uri_conference =
              given().
              then().
                  contentType(BASE_MEDIA_TYPE).
                  statusCode(Status.OK.getStatusCode()).
                  root("root").
                      body(
                         "link.find {it.@rel == 'conference'}.size()",
                         equalTo(1)).
              when().
                  get(new URL(base, "api/").toExternalForm()).
              body().
                  path("root.link.find {it.@rel == 'conference'}.@href");
    }

Our first test is charged with locating the conference root at the base URL + "api" (as we configured the path using the @ApplicationPath annotation in our application). We set the media type and expect to have our links for the conference returned to the client matching the @Path annotation we have sitting atop our ConferenceResource class (baseURL + "api" + "conference"). The @InSequence annotation set to a value of 0 will ensure that this test is run first.

Assuming that’s successful, we can move on to our next test, creating a conference:

// Story: As a 3rd party Integrator I should be able to create a Conference
@Test @InSequence(1)
public void shouldBeAbleToCreateConference() throws Exception { .. }
...

The rest of the test class contains test logic to fulfill our test requirements.

Validating the HTTP Contracts with Warp

We’ve ensured that the responses from the server are in expected form. We’d additionally like to certify that our service is obeying the general contracts of HTTP. Because by definition this will involve a lot of client-side requests and parsing of server responses, it’ll be helpful for us to avoid writing a lot of custom code to negotiate the mapping. For these tasks, we introduce an extension to Arquillian that is aimed at making this type of testing easier.

Arquillian Warp

Arquillian Warp fills the void between client- and server-side testing.

Using Warp, we can initiate an HTTP request using a client-side testing tool such as WebDriver and, in the same request cycle, execute in-container server-side tests. This powerful combination lets us cover integration across client and server.

Warp effectively removes the need for mocking and opens new possibilities for debugging. It also allows us to know as little or as much of the application under test as we want.

Gray-box testing

Initially, Warp can be used from any black-box testing tool (like HttpClient, REST client, Selenium WebDriver, etc.). But it allows us to hook into the server request lifecycle and verify what happens inside the box (referred to as white-box testing). Thus, we identify Warp as a hybrid "gray-box" testing framework.

Integration testing

No matter the granularity of our tests, Warp fits the best integration level of testing with an overlap to functional testing. We can either test components, application API, or functional behavior.

Technology independence

Whatever client-side tools we use for emitting an HTTP request, Warp allows us to assert and verify logic on the most appropriate place of client-server request lifecycle.

Use cases

Warp can:

  • Send a payload to a server

  • Verify an incoming request

  • Assert the state of a server context

  • Verify that a given event was fired during request processing

  • Verify a completed response

  • Send a payload to a client

Deploying Warp

Thanks to an ability to bring an arbitrary payload to a server and hook into server lifecycles, we can use Warp in partially implemented projects. We do not require the database layer to be implemented in order to test UI logic. This is especially useful for projects based on loosely coupled components (e.g., CDI).

Supported tools and frameworks
Cross-protocol Warp currently supports only the HTTP protocol, but conceptually it can be used with any protocol where we are able to intercept client-to-server communication on both the client and the server. Client-side testing tools Warp supports any client-side tools if you are using them in a way that requests can be intercepted (in the case of an HTTP protocol, you need to communicate through a proxy instead of direct communication with a server). Examples of such libraries/frameworks include: URL#openStream() Apache HTTP Client Selenium WebDriver To use Warp, you should inject an @ArquillianResource URL into the test case, which points to the proxy automatically.
Frameworks

Warp currently focuses on frameworks based on the Servlets API, but it provides special hooks and additional support for:

  • JSF

  • JAX-RS (REST)

  • Spring MVC

For more information about Warp, visit http://arquillian.org/.

Test Harness Setup

We’ll start by enabling the Arquillian Warp in the POM’s dependencyManagement section:

<dependency>
    <groupId>org.jboss.arquillian.extension</groupId>
    <artifactId>arquillian-warp-bom</artifactId>
    <version>${version.arquillian_warp}</version>
    <scope>import</scope>
    <type>pom</type>
</dependency>

This will lock down the versions correctly such that all Warp modules are of the expected version. A dependency declaration in the dependencies section will make Warp available for our use:

<dependency>
    <groupId>org.jboss.arquillian.extension</groupId>
    <artifactId>arquillian-warp-impl</artifactId>
    <scope>test</scope>
</dependency>

The HTTP Contracts Test

Now we’d like to test details of the REST service behavior; we’ll use Warp to allow easy control over permutations of data. Again, we’ll be swapping out alternate Repository implementations to bypass JPA and real peristence; we’re just interested in the HTTP request/response interactions at this stage.

What we’d like to do in this test is create Conference domain objects on the client side and transfer them to the server. Warp will allow us to control which data to fetch through the JAX-RS layer. We can look at the abstract base class of ConferenceResourceSpecificationTestCase as an example:

@Test
public void shouldReturnOKOnGETResource() throws Exception {
    final DOMAIN domain = createDomainObject();

    Warp.initiate(new Activity() {
        @Override
        public void perform() {
            responseValidation(
                given().
                then().
                    contentType(getTypedMediaType())
            , domain).when().
                get(createRootURL() + "/{id}",
                    domain.getId()).body();
        }
    }).inspect(
        new SetupRepository<DOMAIN>(
            getDomainClass(), domain));
}

Here we use Warp to produce the data we want the REST layer to receive, and validate that we obtain the correct HTTP response for a valid GET request.

Running this test locally, we’ll see that Warp constructs an HTTP GET request for us:

GET /9676980f-2fc9-4103-ae28-fd0261d1d7c3/api/conference/
ac5390ad-5483-4239-850c-62efaeee7bf1 HTTP/1.1[\r][\n]
Accept: application/vnd.ced+xml; type=conference[\r][\n]
Host: 127.0.1.1:18080[\r][\n]
Connection: Keep-Alive[\r][\n]
Accept-Encoding: gzip,deflate[\r][\n]

Because we’ve coded our JAX-RS endpoints and backing business objects correctly, we’ll receive the expected reply (an HTTP 200 OK status):

HTTP/1.1 200 OK
X-Arq-Enrichment-Response=3778738317992283532
Last-Modified=Wed, 21 Aug 2013 04:14:44 GMT
Content-Type=application/vnd.ced+xml; type=conference
Content-Length=564
Via=1.1.overdrive.home

<ns3:conference xmlns:ns3="urn:ced:conference">
  <ns2:link xmlns:ns2="urn:ced:link"
    href="http://127.0.1.1:18080/9676980f-2fc9-4103-ae28-fd0261d1d7c3/api/
    conference/ac5390ad-5483-4239-850c-62efaeee7bf1"
    rel="self"/>
  <ns2:link xmlns:ns2="urn:ced:link"
    href="http://127.0.1.1:18080/9676980f-2fc9-4103-ae28-fd0261d1d7c3/api/
    conference/ac5390ad-5483-4239-850c-62efaeee7bf1/session"
    rel="session"/>
  <end>
    2013-08-21T00:14:44.159-04:00
  </end>
  <name>
    Name
  </name>
  <start>
    2013-08-21T00:14:44.159-04:00
  </start>
  <tagLine>
    TagLine
  </tagLine>
</ns3:conference>

The response will contain our links to related resources, as well as information about the requested Conference object in the XML xmlns:ns3="urn:ced:conference" format. Using Warp, we can interact with and perform validations upon these types of payloads with ease.

There are plenty of other detailed Warp examples throughout the tests of the REST modules in the GeekSeek application code; we advise readers to peruse the source for additional ideas on using this very powerful tool for white-box testing of the request/response model.