SeamFramework.orgCommunity Documentation
Seam integrates with JBossWS to allow standard JEE web services to take full advantage of Seam's contextual framework, including support for conversational web services. This chapter walks through the steps required to allow web services to run within a Seam environment.
To allow Seam to intercept web service requests so that the necessary Seam contexts can be created for the request,
a special SOAP handler must be configured; org.jboss.seam.webservice.SOAPRequestHandler
is a SOAPHandler
implementation that does the work of managing Seam's lifecycle during the scope
of a web service request.
A special configuration file, standard-jaxws-endpoint-config.xml
should be placed
into the META-INF
directory of the jar
file that contains the
web service classes. This file contains the following SOAP handler configuration:
<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>
So how are conversations propagated between web service requests? Seam uses a SOAP header element present in both the SOAP request and response messages to carry the conversation ID from the consumer to the service, and back again. Here's an example of a web service request that contains a conversation ID:
<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.
As web services must be implemented as either a stateless session bean or POJO, it is recommended that for conversational web services, the web service acts as a facade to a conversational Seam component.
If the web service is written as a stateless session bean, then it is also possible to make it a Seam
component by giving it a @Name
. Doing this allows Seam's bijection (and other)
features to be used in the web service class itself.
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:
Seamless integration of RESTEasy bootstrap and configuration, automatic detection of resources and providers.
Serving HTTP/REST requests with the SeamResourceServlet, no external servlet or configuration in web.xml required.
Writing resources as Seam components, with full Seam lifecycle management and interception (bijection).
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:
The URI starts with the pattern mapped in web.xml
for the
SeamResourceServlet
, e.g /seam/resource
if you follow
the common examples. Change this setting to expose your RESTful resources under a different base.
Note that this is a global change and other Seam resources (e.g. s:graphicImage
)
are then also served under that base path.
The RESTEasy integration for Seam then appends a configurable string to the base path, by default
this is /rest
. Hence, the full base path of your resources would e.g. be
/seam/resource/rest
. We recommend that you change this string in your application,
you could for example add a version number to prepare for a future REST API upgrade of your services
(old clients would keep the old URI base): /seam/resource/restv1
.
Finally, the actual resource is available under the defined @Path
, e.g. a resource
mapped with @Path("/customer")
would be available under
/seam/resource/rest/customer
.
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}")
@ProduceMime("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-config 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-config 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-config
scan-providers="false"
scan-resources="false"
use-builtin-providers="true">
<resteasy:resource-class-names>
<value>org.foo.MyCustomerResource</value>
<value>org.foo.MyOrderResource</value>
</resteasy:resource-class-names>
<resteasy:provider-class-names>
<value>org.foo.MyFancyProvider</value>
</resteasy:provider-class-names>
</resteasy:application-config>
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-config>
<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-config>
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}")
@ProduceMime("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.