23.4. RESTful HTTP web services with RESTEasy

Seam integrates the RESTEasy implementation of the JAX-RS specification (JSR 311). You can decide which of the following features are integrated with your Seam application:
  • RESTEasy bootstrap and configuration, with automatic resource detection. and providers.
  • SeamResourceServlet-served HTTP/REST requests, without the need for an external servlet or configuration in web.xml.
  • Resources written as Seam components with full Seam life cycle management and bijection.

23.4.1. RESTEasy configuration and request serving

First, download the RESTEasy libraries and the jaxrs-api.jar, and deploy them alongside the integration library (jboss-seam-resteasy.jar) and any other libraries your application requires.
In seam-gen based projects, this can be done by appending jaxrs-api.jar, resteasy-jaxrs.jar and jboss-seam-resteasy.jar to the deployed-jars.list (war deployment) or deployed-jars-ear.list (ear deployment) file. For a JBDS based project, copy the libraries mentioned above to the EarContent/lib (ear deployment) or WebContent/WEB-INF/lib (war deployment) folder and reload the project in the IDE.
All classes annotated with @javax.ws.rs.Path will automatically be discovered and registered as HTTP resources at start up. Seam automatically accepts and serves HTTP requests with its built-in SeamResourceServlet. The URI of a resource is built like so:
  • The URI begins with the pattern mapped in web.xml for the SeamResourceServlet — in the examples provided, /seam/resource. Change this setting to expose your RESTful resources under a different base. Remember that this is a global change, and other Seam resources (s:graphicImage) will also be served under this base path.
  • Seam's RESTEasy integration then appends a configurable string to the base path (/rest by default). So, in the example, the full base path of your resources would be /seam/resource/rest. We recommend changing this string in your application to something more descriptive — add a version number to prepare for future REST API upgrades. This allows old clients to keep the old URI base.
  • Finally, the resource is made available under the defined @Path. For example, a resource mapped with @Path("/customer") would be available under /seam/resource/rest/customer.
The following resource definition would return a plain text representation for any GET request using the URI http://your.hostname/seam/resource/rest/customer/123:
@Path("/customer")
public class MyCustomerResource {

    @GET
    @Path("/{customerId}")
    @Produces("text/plain")
    public String getCustomer(@PathParam("customerId") int id) {
         return ...;
    }

}
If these defaults are acceptable, there is no need for additional configuration. However, if required, you can configure RESTEasy in your Seam application. First, import the resteasy namespace into your XML configuration file header:
<components
   xmlns="http://jboss.org/schema/seam/components"
   xmlns:resteasy="http://jboss.org/schema/seam/resteasy"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation=
   "http://jboss.org/schema/seam/resteasy
   http://jboss.org/schema/seam/resteasy-2.3.xsd
   http://jboss.org/schema/seam/components
   http://jboss.org/schema/seam/components-2.3.xsd">

<resteasy:application resource-path-prefix="/restv1"/>
The full base path to your resources is now /seam/resource/restv1/{resource}. Note that your @Path definitions and mappings do not change. This is an application-wide switch, usually used for versioning of the HTTP API.
If you want to map the full path in your resources, you can disable base path stripping:
<resteasy:application strip-seam-resource-path="false"/>
Here, the path of a resource is now mapped with @Path("/seam/resource/rest/customer"). Disabling this feature binds your resource class mappings to a particular deployment scenario. This is not recommended.
Seam scans your classpath for any deployed @javax.ws.rs.Path resources or @javax.ws.rs.ext.Provider classes. You can disable scanning and configure these classes manually like so:
<resteasy:application
     scan-providers="false"
     scan-resources="false"
     use-builtin-providers="true">

  <resteasy:resource-class-names>
    <value>org.foo.MyCustomerResource</value>
    <value>org.foo.MyOrderResource</value>
         <value>org.foo.MyStatelessEJBImplementation</value>
  </resteasy:resource-class-names>

  <resteasy:provider-class-names> 
    <value>org.foo.MyFancyProvider</value> 
  </resteasy:provider-class-names> 

 </resteasy:application>
The use-built-in-providers switch enables (default) or disables the RESTEasy built-in providers. Since these provide plain text, JSON and JAXB marshaling, we recommend that these are left enabled.
RESTEasy supports plain EJBs (EJBs that are not Seam components) as resources. Instead of configuring the JNDI names in a non-portable fashion in web.xml (see RESTEasy documentation), you can simply list the EJB implementation classes, not the business interfaces, in components.xml as shown above. Note that you have to annotate the @Local interface of the EJB with @Path, @GET, and so on - not the bean implementation class. This allows you to keep your application deployment-portable with the global Seam jndi-pattern switch on <core:init/>. Note that plain (non-Seam component) EJB resources will not be found even if scanning of resources is enabled, you always have to list them manually. Again, this whole paragraph is only relevant for EJB resources that are not also Seam components and that do not have an @Name annotation.
Finally, you can configure media type and language URI extensions:
<resteasy:application>

    <resteasy:media-type-mappings>
      <key>txt</key>
      <value>text/plain</value>
    </resteasy:media-type-mappings>

    <resteasy:language-mappings>
       <key>deutsch</key><value>de-DE</value>
    </resteasy:language-mappings>

</resteasy:application>
This definition would map the URI suffix of .txt.deutsch to the additional Accept and Accept-Language header values, text/plain and de-DE.

23.4.2. Resources and providers as Seam components

Resource and provider instances are, by default, managed by RESTEasy. A resource class will be instantiated by RESTEasy and serve a single request, after which it will be destroyed. This is the default JAX-RS life cycle. Providers are instantiated once for the entire application. These are stateless singletons.
Resources and providers can also be written as Seam components to take advantage of Seam's richer life cycle management, and bijection and security abilities. Make your resource class into a Seam component like so:
@Name("customerResource")
@Path("/customer")
public class MyCustomerResource {

    @In
    CustomerDAO customerDAO;

    @GET
    @Path("/{customerId}")
    @Produces("text/plain")
    public String getCustomer(@PathParam("customerId") int id) {
         return customerDAO.find(id).getName();
    }

}
A customerResource instance is now handled by Seam when a request hits the server. This component is event-scoped, so its life cycle is identical to that of the JAX-RS. However, the Seam JavaBean component gives you full injection support, and full access to all other components and contexts. Session, application, and stateless resource components are also supported. These three scopes allow you to create an effectively stateless Seam middle-tier HTTP request-processing application.
You can annotate an interface and keep the implementation free from JAX-RS annotations:
@Path("/customer")
public interface MyCustomerResource {

    @GET
    @Path("/{customerId}")
    @Produces("text/plain")
    public String getCustomer(@PathParam("customerId") int id);

}
@Name("customerResource")
@Scope(ScopeType.STATELESS)
public class MyCustomerResourceBean implements MyCustomerResource {

    @In
    CustomerDAO customerDAO;

    public String getCustomer(int id) {
         return customerDAO.find(id).getName();
    }

}
You can use SESSION-scoped Seam components. By default, the session will however be shortened to a single request. In other words, when an HTTP request is being processed by the RESTEasy integration code, an HTTP session will be created so that Seam components can utilize that context. When the request has been processed, Seam will look at the session and decide if the session was created only to serve that single request (no session identifier has been provided with the request, or no session existed for the request). If the session has been created only to serve this request, the session will be destroyed after the request!
Assuming that your Seam application only uses event, application, or stateless components, this procedure prevents exhaustion of available HTTP sessions on the server. The RESTEasy integration with Seam assumes by default that sessions are not used, hence anemic sessions would add up as every REST request would start a session that will only be removed when timed out.
If your RESTful Seam application has to preserve session state across REST HTTP requests, disable this behavior in your configuration file:
<resteasy:application destroy-session-after-request="false"/>
Every REST HTTP request will now create a new session that will only be removed by timeout or explicit invalidation in your code through Session.instance().invalidate(). It is your responsibility to pass a valid session identifier along with your HTTP requests, if you want to utilize the session context across requests.
Conversation-scoped resource components and conversation mapping are not currently supported.
Provider classes can also be Seam components. They must be either application-scoped or stateless.
Resources and providers can be EJBs or JavaBeans, like any other Seam component.
EJB Seam components are supported as REST resources. Always annotate the local business interface, not the EJB implementation class, with JAX-RS annotations. The EJB has to be STATELESS.
Sub-resources as defined in the JAX RS specification, section 3.4.1, can also be Seam component instances:
@Path("/garage")
@Name("garage")
public class GarageService
{
 ...
    
 @Path("/vehicles")
 public VehicleService getVehicles() {
  return (VehicleService) Component.getInstance(VehicleService.class);
 }
}

Note

RESTEasy components do not support hot redeployment. As a result, the components should never be placed in the src/hot folder. The src/main folder should be used instead.

Note

Sub-resources as defined in the JAX RS specification, section 3.4.1, can not be Seam component instances at this time. Only root resource classes can be registered as Seam components. In other words, do not return a Seam component instance from a root resource method.

23.4.3. Securing resources

You can enable the Seam authentication filter for HTTP Basic and Digest authentication in components.xml:
<web:authentication-filter url-pattern="/seam/resource/rest/*" auth-type="basic"/>
See the Seam security chapter on how to write an authentication routine.
After successful authentication, authorization rules with the common @Restrict and @PermissionCheck annotations are in effect. You can also access the client Identity, work with permission mapping, and so on. All regular Seam security features for authorization are available.

23.4.4. Mapping exceptions to HTTP responses

Section 3.3.4 of the JAX-RS specification defines how JAX RS handles checked and unchecked exceptions. Integrating RESTEasy with Seam allows you to map exceptions to HTTP response codes within Seam's pages.xml. If you use pages.xml already, this is easier to maintain than many JAX RS exception mapper classes.
For exceptions to be handled within Seam, the Seam filter must be executed for your HTTP request. You must filter all requests in your web.xml, not as a request URI pattern that does not cover your REST requests. The following example intercepts all HTTP requests and enables Seam exception handling:
<filter>
    <filter-name>Seam Filter</filter-name>
    <filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>Seam Filter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
To convert the unchecked UnsupportedOperationException thrown by your resource methods to a 501 Not Implemented HTTP status response, add the following to your pages.xml descriptor:
<exception class="java.lang.UnsupportedOperationException">
    <http-error error-code="501">
        <message>The requested operation is not supported</message>
    </http-error>
</exception>
Custom or checked exceptions are handled in the same way:
<exception class="my.CustomException" log="false">
    <http-error error-code="503">
        <message>Service not available: #{org.jboss.seam.handledException.message}</message>
    </http-error>
</exception>
You do not have to send a HTTP error to the client if an exception occurs. Seam lets you map the exception as a redirect to a view of your Seam application. Since this feature is typically used for human clients (web browsers) and not for REST API remote clients, you should pay attention to conflicting exception mappings in pages.xml.
The HTTP response does pass through the servlet container, so an additional mapping may apply if you have <error-page> mappings in your web.xml configuration. The HTTP status code would then be mapped to a rendered HTML error page with status 200 OK.

23.4.5. Exposing entities via RESTful API

Seam makes it really easy to use a RESTful approach for accessing application data. One of the improvements that Seam introduces is the ability to expose parts of your SQL database for remote access via plain HTTP calls. For this purpose, the Seam/RESTEasy integration module provides two components: ResourceHome and ResourceQuery, which benefit from the API provided by the Seam Application Framework (Chapter 13, The Seam Application Framework). These components allow you to bind domain model entity classes to an HTTP API.

23.4.5.1. ResourceQuery

ResourceQuery exposes entity querying capabilities as a RESTful web service. By default, a simple underlying Query component, which returns a list of instances of a given entity class, is created automatically. Alternatively, the ResourceQuery component can be attached to an existing Query component in more sophisticated cases. The following example demonstrates how easily ResourceQuery can be configured:
<resteasy:resource-query
   path="/user"
   name="userResourceQuery"
   entity-class="com.example.User"/>
With this single XML element, a ResourceQuery component is set up. The configuration is straightforward:
  • The component will return a list of com.example.User instances.
  • The component will handle HTTP requests on the URI path /user.
  • The component will by default transform the data into XML or JSON (based on client's preference). The set of supported mime types can be altered by using the media-types attribute, for example:
<resteasy:resource-query
   path="/user"
   name="userResourceQuery"
   entity-class="com.example.User"
   media-types="application/fastinfoset"/>
Alternatively, if you do not like configuring components using XML, you can set up the component by extension:
@Name("userResourceQuery")
@Path("user")
public class UserResourceQuery extends ResourceQuery<User>
{
}
Queries are read-only operations, the resource only responds to GET requests. Furthermore, ResourceQuery allows clients of a web service to manipulate the resultset of a query using the following path parameters:
Parameter name Example Description
start /user?start=20 Returns a subset of a database query result starting with the 20th entry.
show /user?show=10 Returns a subset of the database query result limited to 10 entries.
For example, you can send an HTTP GET request to /user?start=30&show=10 to get a list of entries representing 10 rows starting with row 30.

Note

RESTEasy uses JAXB to marshall entities. Thus, in order to be able to transfer them over the wire, you need to annotate entity classes with @XMLRootElement. Consult the JAXB and RESTEasy documentation for more information.

23.4.5.2. ResourceHome

Just as ResourceQuery makes Query's API available for remote access, so does ResourceHome for the Home component. The following table describes how the two APIs (HTTP and Home) are bound together.

Table 23.1. Bindings in ResourceHome

HTTP method Path Function ResourceHome method
GET {path}/{id} Read getResource()
POST {path} Create postResource()
PUT {path}/{id} Update putResource()
DELETE {path}/{id} Delete deleteResource()
  • You can GET, PUT, and DELETE a particular user instance by sending HTTP requests to /user/{userId}
  • Sending a POST request to /user creates a new user entity instance and persists it. Usually, you leave it up to the persistence layer to provide the entity instance with an identifier value and thus an URI. Therefore, the URI is sent back to the client in the Location header of the HTTP response.
The configuration of ResourceHome is very similar to ResourceQuery except that you need to explicitly specify the underlying Home component and the Java type of the entity identifier property.
<resteasy:resource-home
   path="/user"
   name="userResourceHome"
   entity-home="#{userHome}"
   entity-id-class="java.lang.Integer"/>
Again, you can write a subclass of ResourceHome instead of XML:
@Name("userResourceHome")
@Path("user")
public class UserResourceHome extends ResourceHome<User, Integer>
{

   @In
   private EntityHome<User> userHome;

   @Override
   public Home<?, User> getEntityHome()
   {
      return userHome;
   }
}
For more examples of ResourceHome and ResourceQuery components, take a look at the Seam Tasks example application, which demonstrates how Seam/RESTEasy integration can be used together with a jQuery web client. In addition, you can find more code example in the Restbay example, which is used mainly for testing purposes.