SeamFramework.orgCommunity Documentation

Capitolo 24. Web Service

24.1. Configurazione ed impacchettamento
24.2. Web Service conversazionali
24.2.1. Una strategia raccomandata
24.3. Esempio di web service
24.4. Webservice RESTful HTTP con RESTEasy
24.4.1. RESTEasy configuration and request serving
24.4.2. Risorse e provider come componenti Seam
24.4.3. Mapping exceptions to HTTP responses

Seam si integra con JBossWS per consentire allo standard JEE web service di sfruttrare pienamente il framework contestuale di Seam, includendo il supporto ai web service conversazionali. Questo capitolo passa in rassegna tutti i passi richiesti per consentire ai web service di funzionare in ambiente Seam.

Per consentire a Seam di intercettare le richieste web service in modo tale da creare i contesti Seam necessari per la richiesta, deve essere configurato uno speciale handler SOAP; org.jboss.seam.webservice.SOAPRequestHandler è un'implementazione SOAPHandler che esegue il lavoro di gestione del ciclo di vita di Seam durante lo scope di una richiesta web service.

Uno speciale file di configurazione, standard-jaxws-endpoint-config.xml, deve essere collocato nella directory META-INF del file jar che contiene le classi web service. Questo file contiene la seguente configurazione handler SOAP:


<jaxws-config xmlns="urn:jboss:jaxws-config:2.0" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xmlns:javaee="http://java.sun.com/xml/ns/javaee"
              xsi:schemaLocation="urn:jboss:jaxws-config:2.0 jaxws-config_2_0.xsd">
   <endpoint-config>
      <config-name
>Seam WebService Endpoint</config-name>
      <pre-handler-chains>
         <javaee:handler-chain>
            <javaee:protocol-bindings
>##SOAP11_HTTP</javaee:protocol-bindings>
            <javaee:handler>
               <javaee:handler-name
>SOAP Request Handler</javaee:handler-name>
               <javaee:handler-class
>org.jboss.seam.webservice.SOAPRequestHandler</javaee:handler-class>
            </javaee:handler>
         </javaee:handler-chain>
      </pre-handler-chains>
   </endpoint-config>
</jaxws-config
>

Quindi come vengono propagate le conversazioni tra le richieste web service? Seam usa un elemento di intestazione SOAP presente in entrambi i messaggi di richiesta e di risposta SOAP per portare l'ID della conversazione dal consumatore al servizio, e viceversa. Ecco un esempio di richiesta web service che contiene un ID di conversazione:


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
    xmlns:seam="http://seambay.example.seam.jboss.org/">
  <soapenv:Header>
    <seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
  </soapenv:Header>
  <soapenv:Body>
    <seam:confirmAuction/>
  </soapenv:Body>
</soapenv:Envelope
>    
    

As you can see in the above SOAP message, there is a conversationId element within the SOAP header that contains the conversation ID for the request, in this case 2. Unfortunately, because web services may be consumed by a variety of web service clients written in a variety of languages, it is up to the developer to implement conversation ID propagation between individual web services that are intended to be used within the scope of a single conversation.

An important thing to note is that the conversationId header element must be qualified with a namespace of http://www.jboss.org/seam/webservice, otherwise Seam will not be able to read the conversation ID from the request. Here's an example of a response to the above request message:


<env:Envelope xmlns:env='http://schemas.xmlsoap.org/soap/envelope/'>
  <env:Header>
    <seam:conversationId xmlns:seam='http://www.jboss.org/seam/webservice'
>2</seam:conversationId>
  </env:Header>
  <env:Body>
    <confirmAuctionResponse xmlns="http://seambay.example.seam.jboss.org/"/>
  </env:Body>
</env:Envelope
>    
    

As you can see, the response message contains the same conversationId element as the request.

Let's walk through an example web service. The code in this section all comes from the seamBay example application in Seam's /examples directory, and follows the recommended strategy as described in the previous section. Let's first take a look at the web service class and one of its web service methods:

@Stateless

@WebService(name = "AuctionService", serviceName = "AuctionService")
public class AuctionService implements AuctionServiceRemote
{
   @WebMethod
   public boolean login(String username, String password)
   {
      Identity.instance().setUsername(username);
      Identity.instance().setPassword(password);
      Identity.instance().login();
      return Identity.instance().isLoggedIn();
   }
   // snip
}

As you can see, our web service is a stateless session bean, and is annotated using the JWS annotations from the javax.jws package, as defined by JSR-181. The @WebService annotation tells the container that this class implements a web service, and the @WebMethod annotation on the login() method identifies the method as a web service method. The name and serviceName attributes in the @WebService annotation are optional.

As is required by the specification, each method that is to be exposed as a web service method must also be declared in the remote interface of the web service class (when the web service is a stateless session bean). In the above example, the AuctionServiceRemote interface must declare the login() method as it is annotated as a @WebMethod.

As you can see in the above code, the web service implements a login() method that delegates to Seam's built-in Identity component. In keeping with our recommended strategy, the web service is written as a simple facade, passing off the real work to a Seam component. This allows for the greatest reuse of business logic between web services and other clients.

Let's look at another example. This web service method begins a new conversation by delegating to the AuctionAction.createAuction() method:

   @WebMethod

   public void createAuction(String title, String description, int categoryId)
   {
      AuctionAction action = (AuctionAction) Component.getInstance(AuctionAction.class, true);
      action.createAuction();
      action.setDetails(title, description, categoryId);
   }

And here's the code from AuctionAction:

   @Begin

   public void createAuction()
   {
      auction = new Auction();
      auction.setAccount(authenticatedAccount);
      auction.setStatus(Auction.STATUS_UNLISTED);        
      durationDays = DEFAULT_AUCTION_DURATION;
   }

From this we can see how web services can participate in long running conversations, by acting as a facade and delegating the real work to a conversational Seam component.

Seam integrates the RESTEasy implementation of the JAX-RS specification (JSR 311). You can decide how "deep" the integration into your Seam application is going to be:

First, get the RESTEasy libraries and the jaxrs-api.jar, deploy them with the other libraries of your application. Also deploy the integration library, jboss-seam-resteasy.jar

On startup, all classes annotated @javax.ws.rs.Path will be discovered automatically and registered as HTTP resources. Seam automatically accepts and serves HTTP requests with its built-in SeamResourceServlet. The URI of a resource is build as follows:

As an example, the following resource definition would return a plaintext representation for any GET requests 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 ...;
    }
}

No additional configuration is required, you do not have to edit web.xml or any other setting if these defauls are acceptable. However, you can configure RESTEasy in your Seam application. First import the resteasy namespace into your XML configuration file header:


<components
   xmlns="http://jboss.com/products/seam/components"
   xmlns:resteasy="http://jboss.com/products/seam/resteasy"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation=
     http://jboss.com/products/seam/resteasy
         http://jboss.com/products/seam/resteasy-2.1.xsd
     http://jboss.com/products/seam/components
         http://jboss.com/products/seam/components-2.1.xsd"
>

You can then change the /rest prefix as mentioned earlier:


<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.

You can disable stripping of the base path if you'd like to map the full path in your resources:


<resteasy:application strip-seam-resource-path="false"/>

The path of a resource is now mapped with e.g. @Path("/seam/resource/rest/customer"). We do not recommend disabling this feature, as your resource class mappings are then bound to a particular deployment scenario.

Seam will scan your classpath for any deployed @javax.ws.rs.Path resources and any @javax.ws.rs.ext.Provider classes. You can disable scanning and configure these classes manually:


<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
>

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 EJB resources will not be found even if scanning of resources is enabled, you always have to list them manually. Again, this is only relevant for EJB resources that are not also Seam components and that do not have a @Name annotation.

The use-built-in-providers switch enables (default) or disables the RESTEasy built-in providers. We recommend you leave them enabled, as they provide plaintext, JSON, and JAXB marshalling out of the box.

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 additional Accept and Accept-Language header values text/plain and de-DE.

Any resource and provider instances are managed by RESTEasy by default. That means 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 lifecycle. Providers are instantiated once for the whole application and are effectively singletons and supposed to be stateless.

You can write resources and providers as Seam components and benefit from the richer lifecycle management of Seam, and interception for bijection, security, and so on. Simply make your resource class a Seam component:

@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();
    }
}

An instance of customerResource is now handled by Seam when a request hits the server. This is a Seam JavaBean component that is EVENT-scoped, hence no different than the default JAX-RS lifecycle. However, you get full Seam injection support and all other Seam components and contexts are available to you. Currently also supported are SESSION, APPLICATION, and STATELESS resource components. Remember that any HTTP request has to transmit a valid session identifier (cookie, URI path parameter) for correct handling of the server-side session context.

Conversation-scoped resource components and mapping of conversations is currently not supported but will be available soon.

Provider classes can also be Seam components, they must be APPLICATION-scoped or STATELESS.

Resources and providers can be EJBs or JavaBeans, like any other Seam component.

Section 3.3.4 of the JAX-RS specification defines how checked or unchecked exceptions are handled by the JAX RS implementation. In addition to using an exception mapping provider as defined by JAX-RS, the integration of RESTEasy with Seam allows you to map exceptions to HTTP response codes within Seam's pages.xml facility. If you are already using pages.xml declarations, this is easier to maintain than potentially many JAX RS exception mapper classes.

Exception handling within Seam requires that the Seam filter is executed for your HTTP request. Ensure that you do filter all requests in your web.xml, not - as some Seam examples might show - a request URI pattern that doesn't 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 request operation is not supported</message>
    </http-error>
</exception
>

Custom or checked exceptions are handled the same:


<exception class="my.CustomException" log="false">
    <http-error error-code="503">
        <message
>The service is currently not available: #{org.jboss.seam.handledException.message}</message>
    </http-error>
</exception
>

You do not have to send an HTTP error to the client if an exception occurs. Seam allows you to map the exception as a redirect to a view of your Seam application. As this feature is typically used for human clients (web browsers) and not for REST API remote clients, you should pay extra attention to conflicting exception mappings in pages.xml.

Note that the HTTP response still passes through the servlet container, so an additional mapping might 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!