Help

Norman released Seam 2.1.2 yesterday and it comes with much improved support for REST processing, compared to previous 2.1.x versions. We started integrating RESTEasy - an implementation of JAX-RS (JSR 311) - with Seam almost a year ago in a first prototype. We then waited for the JAX-RS spec to be finalized and for RESTEasy to be GA, which happened a few months ago. So based on that stable foundation we were able to finish the integration with Seam.

I'm going to demonstrate some of the unique features of that integration here, how you can create a RESTful Seam application or simply add an HTTP web service interface to an existing one.

Deploying resources and providers

With JAX-RS you write a plain Java class and put @javax.ws.rs.Path("/customer") on it to make it available under the HTTP base URI path /customer. You then map methods of that class to particular sub-paths and HTTP methods with @javax.ws.rs.GET, @POST, @DELETE, and so on. These classes are called Resource classes. The default life cycle of an instance is per-HTTP-request, an instance is created for a request and destroyed when processing completes and the response has been sent.

Converting HTTP entities (the body of an HTTP request) is the job of Provider classes, annotated with @javax.ws.rs.ext.Provider and usually stateless or singleton. They transform content between HTTP and Java types, say my.Customer entity to and from XML with JAXB. Providers also are the extension point in JAX-RS for custom exception converters, etc.

RESTEasy has its own classpath scanning routine that detects all resources and providers by looking for annotations. That requires a servlet context listener configured in web.xml. You'd also have to configure a request dispatcher servlet. Finally, if you'd like to make your resource classes EJBs, for automatic transaction demarcation and persistence context handling, you'd have to list these EJBs in web.xml as well. This last feature is a RESTEasy enhancement and not part of the JAX-RS specification.

If you use Seam with RESTEasy, none of this extra work is necessary. Of course it still needs to be done but you most likely have already configured the basic Seam listener and resource servlet in web.xml - almost all Seam applications have.

You do not have to configure RESTEasy at all. Just drop in the right JAR files (see the reference docs) and your Seam application will automatically find all @Path resources and @Provider's. Your stateless EJBs still need to be listed to be found, but that can be done in Seam's components.xml or programmatically through the usual Seam APIs. All the other RESTEasy configuration options and some useful other configuration features are available as well.

So without changing any code, you get easier deployment and integrated configuration of JAX-RS artifacts in your Seam application.

Utilizing Seam components

Resources and providers can be made Seam components, with bijection, life cycle management, authorization, interception, etc. Just put an @Name on your resource class:

@Name("customerResource")
@Scope(ScopeType.EVENT) // Default
@Path("/customer")
public class MyCustomerResource {

    @In
    CustomerDAO customerDAO;

    @GET
    @Path("/{customerId}")
    @Produces("text/xml")
    @Restrict("#{s:hasRole('admin')}")
    public Customer getCustomer(@PathParam("customerId") int id) {
         return customerDAO.find(id);
    }
}

Naturally REST-oriented architecture assumes that clients are maintaining application state, so your resource components would be EVENT or APPLICATION scoped, or STATELESS. Although SESSION scope is available, by default a session only spans a single HTTP request and it's automatically destroyed after the HTTP request. This behavior and how to configure it if you really want to transmit a session identifier between the REST client and server and utilize server-side SESSION scope across requests is explained in more detail in the reference docs. We already have some ideas for CONVERSATION scope integration, follow this design document fore more info.

Of course your resource Seam component doesn't have to be a POJO, you can also use @Stateless and turn it into an EJB. Another advantage here is that you do not have to list that EJB in components.xml or web.xml anymore as all Seam components are automatically found and registered according to their type.

The @Restrict annotation is just a regular Seam authorization check, currently you can configure Basic or Digest authentication as you'd for any other Seam application.

CRUD framework integration

Seam has a framework for building basic CRUD database applications quickly, you probably already have seen EntityHome and EntityQuery in other Seam examples. Jozef Hartinger built an extension that allows you to create a basic CRUD application with full HTTP/REST support in minutes. You can declare it through components.xml:

<framework:entity-home name="customerHome"
                       entity-class="my.Customer"
                       auto-create="true"/>

<framework:entity-query name="customerQuery"
		        ejbql="select c from Customer c" order="lastname"/>

<resteasy:resource-home path="/customer" name="resourceCustomerHome"
                        entity-home="#{customerHome}" entity-id-class="java.lang.Long"
                        media-types="application/xml application/json"/>

<resteasy:resource-query path="/customer" name="resourceCustomerQuery"
                         entity-query="#{customerQuery}" entity-class="my.Customer"
                         media-types="application/xml application/json"/>

You only have to create the my.Customer entity and you are ready to read from and write to the database through HTTP.

  • A GET request to /customer?start=30&show=10 will execute the resourceCustomerQuery component and return a list of all customers with pagination, starting at row 30 with 10 rows in the result.
  • You can GET, PUT, and DELETE a particular customer instance by sending HTTP requests to /customer/<customerId>.
  • Sending a POST request to /customer creates a new customer entity instance and persists it.

Note that the <framework:...> mappings are part of the regular Seam CRUD framework with all the usual options such as query customization. The content will be transformed by the built-in RESTEasy providers for XML and JSON, for example. The XML transformation will use any JAXB bindings on your entity class.

You do not have to use XML configuration; as you'd with the Seam CRUD superclasses ResourceHome and ResourceQuery, you can write subclasses instead and configure the mapping with annotations.

There is a reason this CRUD framework feature is not documented in the current release: We are not sure the API will stay as it is. Consider this release as our proposal and we really need feedback on it, what works and what can be improved. Jozef also wrote a full-featured RESTful application with a jQuery based client for regular webbrowsers to demonstrate the CRUD framework. Have a look at the Tasks example in the Seam distribution. You can find more demo code and tests in the Restbay example which we use for general RESTEasy integration testing and demonstration.

Feature shortlist

I've only highlighted three of the main features of Seam and RESTEasy but there is more available and more to come:

Exceptions in JAX-RS applications are mapped to HTTP responses for clients with provider classes called ExceptionMapper. That can be much more work than it should, so you can also map exceptions in Seam's pages.xml declaratively, see docs.

You can write unit tests that pass mock HTTP request and response through Seam and RESTEasy, all with local calls not TCP sockets. We use them in the integration tests and so can you to test your application. See the reference docs.

There is already talk about MVC and REST. What this all comes down to, at least from my standpoint, is that hypertext should drive the application state through linked resources (HATEOAS, Hypertext as the engine of application state). From a technical perspective, it simply means that we need more control over how the view is rendered, not just marshaling dumb XML documents from Customer entities with JAXB defaults. We should render XHTML representations - which of course may include JAXB-rendered XML blobs in addition to links and forms - and be able to customize them with templates.

Facelets seems like a natural fit for this and we have a prototype for sending templated XHTML responses:

@GET
@Path("/customer/{id}")
@ProduceMime("application/xhtml+xml")
@FaceletsXhtmlResponse(
    template = "/some/path/to/template/#{thisCanEvenBeEL}/foo.xhtml"
)
@Out(value = "currentCustomer", scope = ScopeType.EVENT)
public Customer getCustomer(@PathParam("id") String id) { ... }

This is just pseudo-code, this feature is not available in the release. It wouldn't be very useful as it is, because we don't know how to transform incoming HTTP requests with XHTML payload back into a Facelet view. It's not trivial to implement either and we'll probably wait for JSF2 before we finalize this. But it shows that providing a JSF-based human client interface and a RESTful HTTP web service interface in the same application might be a natural fit with the given technologies.

Next version?

The currently available RESTEasy version is still not GA, although it is a release candidate. There are also a few open issues with the integration code that we'd like to close, and we have to finalize the CRUD framework interface. This is all expected to happen in the Seam 2.2 releases.

More elaborate additional features such as conversation integration, representation templating, or additional authentication schemes are probably reserved for Seam3 as we might want to build on the new JSF2/JCDI standards as much as possible. Follow this wiki page for updates.

P.S. This book is an excellent starting point if you are wondering what this stuff is all about.

Update: I forgot to mention one important feature that some of you might like. You can annotate your Seam component (POJO or EJB) interface and not the bean class. For EJB Seam components, you actually have to annotate the local business interface.

@Path("/customer")
public interface MyCustomerResource {

    @GET
    @Path("/{customerId}")
    @Produces("text/xml")
    public Customer getCustomer(@PathParam("customerId") int id);
}
@Name("customerResource")
public class MyCustomerResourceBean implements MyCustomerResource {

    @In
    CustomerDAO customerDAO;

    @Restrict("#{s:hasRole('admin')}")
    public Customer getCustomer(int id) {
         return customerDAO.find(id);
    }
}
10 comments:
 
09. Jun 2009, 23:20 CET | Link

For basic functionality are we locked into RESTeasy or could we use Jersey or other JAX-RS impls?

ReplyQuote
 
10. Jun 2009, 01:12 CET | Link

You can use any JAX-RS implementation with Seam. But the integration code has been written for RESTEasy and there is no SPI in the JAX-RS spec that would allow this to be portable. In other words: If you want the features, you have to use RESTEasy. Actually, it probably wouldn't be too hard to create an integration module for Jersey, provided that Jersey has a reasonable API for integration.

 
10. Jun 2009, 19:20 CET | Link

This looks great Christian, and timing is perfect as we are just about to start building an API for Javelin. Good to see it's stateless and sessions are cleaned up per request. Thanks!

25. Jun 2009, 22:54 CET | Link

Do you have plans to offer transactional support for REST? (Perhaps integrated with Conversations?)

 
02. Jul 2009, 07:08 CET | Link
I've started to do some tests with this tool and it looks very useful. One thing though, would it be possible to have some more flexibility on how the path is constructed? To take the example above, the path there is /seam/resource/rest/customer which I'd rather be able to map to /customer. Maybe one way to do that would be to add a servlet-mapping in web.xml for /customer to the seam resource servlet, then set strip-seam-resource-path=false in components.xml. Would that work?
 
02. Jul 2009, 18:27 CET | Link
Sverker wrote on Jul 02, 2009 01:08:
I've started to do some tests with this tool and it looks very useful. One thing though, would it be possible to have some more flexibility on how the path is constructed? To take the example above, the path there is /seam/resource/rest/customer which I'd rather be able to map to /customer. Maybe one way to do that would be to add a servlet-mapping in web.xml for /customer to the seam resource servlet, then set strip-seam-resource-path
  • false in components.xml. Would that work?

You should be able to use something like URLRewrite for this.

 
02. Jul 2009, 18:51 CET | Link

Like Pete said, look at URLRewrite. But keep in mind that there are very good reasons for versioning your HTTP API (e.g. /restv1/customer/...). Ultimately the prefix before the version shouldn't matter to your clients, you have to provision and configure the base URI (including prefix and version) anyway on each client. Read the O'Reilly REST book if you haven't already.

Remember that REST has nothing to do with pretty URLs.

 
20. Jan 2010, 09:13 CET | Link
Ben Groeneveld | bgroeneveld(AT)gmail.com

In testing this functionality we're finding that GET, POST, and DELETE work as advertised, but in performing a PUT on a resteasy:resource-home object we get an OptimisticLockException. One difference we're seeing is that our entities are @Version with a timestamp. We were wondering if a Hibernate merge operation was not working correctly?

This approach looks like a great way to get started. Once the application becomes more complex we need to perform data extractions with all kinds of constraints, traverse graphs, etc. That is, what we do today with HQL. It seems unlikely we would define what data we extract declaratively using JAXB - so our guess is we will end up writing services with RESTEasy that return some exact JSON / XML graphs needed - in our case for a GWT client. Are we missing a way to more dynamically pull data using the approach in this example - that is with these exposed home objects?

Thanks!

 
22. Jan 2010, 06:18 CET | Link
Ben Groeneveld | bgroeneveld(AT)gmail.com

We have figured out our trouble with optimistic locking - PUT works as advertised.

Using JAXB annotations to customize our entities is tedious. The cycles created cause us to sprinkle our domain model with @XmlTransient, @XmlIDREF, and so on. Submitting a constrained query to generate a 'JSON graph' of desired entities - any such capabilities in the works?

Thanks!

 
05. Nov 2014, 10:42 CET | Link
CEO of Fairfax

The big organizations, in particular the CEO of Fairfax Greg Hywood did not agree with Disney and suggested that the Press Authorities is successfully financed and should be limited to running a problems process. top cosmetic dentist NYC

Post Comment