Commercial development support, production support and training for RESTEasy JAX-RS is available through JBoss, a division of Red Hat Inc. (see http://www.jboss.com/).
In some of the example listings, what is meant to be displayed on one line does not fit inside the available page width. These lines have been broken up. A '\' at the end of a line means that a break has been introduced to fit in the page, with the following lines indented. So:
Let's pretend to have an extremely \ long line that \ does not fit This one is short
Is really:
Let's pretend to have an extremely long line that does not fit This one is short
JAX-RS 2.0, JSR-339, is a new JCP specification that provides a Java API for RESTful Web Services over the HTTP protocol. Resteasy is an portable implementation of this specification which can run in any Servlet container. Tighter integration with WildFly application server is also available to make the user experience nicer in that environment. Resteasy also comes with additional features on top of plain JAX-RS functionalities.
RESTEasy is distributed under the ASL 2.0 license. It does not distribute any thirdparty libraries that are GPL. It does ship thirdparty libraries licensed under Apache ASL 2.0 and LGPL.
Resteasy is installed and configured in different ways depending on which environment you are running in. If you are running in WildFly, Resteasy is already bundled and integrated completely so there is very little you have to do. If you are running in a different environment, there is some manual installation and configuration you will have to do.
In WildFly, Resteasy and the JAX-RS 2.0 API are automatically loaded into your deployment's classpath if and only if you are deploying a JAX-RS application (as determined by the presence of JAX-RS annotations). However, only some Resteasy features are automatically loaded. See Table 3.1. If you need any of those libraries which are not loaded automatically, you'll have bring them in with a jboss-deployment-structure.xml file in the WEB-INF directory of your WAR file. Here's an example:
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/> </dependencies> </deployment> </jboss-deployment-structure>
The services
attribute must be set to "import" for modules that have default providers
in a META-INF/services/javax.ws.rs.ext.Providers file.
The following table specifies which of the Resteasy modules currently shipped with WildFly are loaded by default when JAX-RS services are deployed and which aren't.
Table 3.1.
Module Name | Loaded by Default | Description |
---|---|---|
org.jboss.resteasy.resteasy-atom-provider | yes | Resteasy's atom library |
org.jboss.resteasy.resteasy-cdi | yes | Resteasy CDI integration |
org.jboss.resteasy.resteasy-crypto | yes | S/MIME, DKIM, and support for other security formats. |
org.jboss.resteasy.resteasy-jackson-provider | no | Integration with the JSON parser and object mapper Jackson (deprecated) |
org.jboss.resteasy.resteasy-jackson2-provider | yes | Integration with the JSON parser and object mapper Jackson 2 |
org.jboss.resteasy.resteasy-jaxb-provider | yes | XML JAXB integration. |
org.jboss.resteasy.resteasy-jaxrs | yes | Core Resteasy libraries for server and client. You will need to include this in your deployment if you are only using JAX-RS client. |
org.jboss.resteasy.resteasy-jettison-provider | no | Alternative JAXB-like parser for JSON (deprecated) |
org.jboss.resteasy.jose-jwt | no | JSON Web Token support. |
org.jboss.resteasy.resteasy-jsapi | yes | Resteasy's Javascript API |
org.jboss.resteasy.resteasy-json-p-provider | yes | JSON parsing API |
org.jboss.resteasy.resteasy-multipart-provider | yes | Support for multipart formats |
org.jboss.resteasy.skeleton-key | no | OAuth2 support. |
org.jboss.resteasy.resteasy-spring | no | Spring provider |
org.jboss.resteasy.resteasy-validator-provider-11 | yes | Resteasy's interface to Hibernate Bean Validation 1.1 |
org.jboss.resteasy.resteasy-yaml-provider | yes | YAML marshalling |
Not all Resteasy modules are bundled with WildFly. For example, resteasy-fastinfoset-provider and resteasy-wadl are not included among the modules listed in Section 3.1, “Resteasy modules in WildFly”. If you want to use them in your application, you can include them in your WAR as you would if you were deploying outside of WildFly. See Section 3.3, “Deploying to other servlet containers” for more information.
Resteasy is bundled with WildFly, but you may want to upgrade Resteasy in WildFly to the latest version. The Resteasy distribution comes with a zip file called resteasy-jboss-modules-<version>.zip. Unzip this file within the modules/system/layers/base/ directory of the WildFly distribution. This will configure WildFly to use new versions of the modules listed in Section 3.1, “Resteasy modules in WildFly”.
Resteasy is bundled with WildFly and completely integrated as per the requirements of Java EE 7. You can use it with EJB and CDI and you can rely completely on WildFly to scan for and deploy your JAX-RS services and providers. All you have to provide is your JAX-RS service and provider classes packaged within a WAR either as POJOs, CDI beans, or EJBs. The simplest way to configure an application is by simply providing an empty web.xml file. You can of course deploy any custom servlet, filter or security constraint you want to within your web.xml, but the least amount of work is to create an empty web.xml file:
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> </web-app>
Also, Resteasy context-params (see Section 3.4, “Configuration switches”) are available if you want to tweak or turn on/off any specific Resteasy feature.
Since we're not using a <servlet-mapping> element, we must define an
Application
class (see Section 3.5, “javax.ws.rs.core.Application”) that is annotated with
the @ApplicationPath
annotation. If you return any empty set for classes and singletons,
which is the behavior inherited from Application
, your WAR will
be scanned for resource and provider classes as indicated by the presence of JAX-RS annotations.
import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("/root-path") public class MyApplication extends Application { }
Note. As mentioned in Section 3.1.1, “Other Resteasy modules”, not all Resteasy modules are bundled with WildFly. For example, resteasy-fastinfoset-provider and resteasy-wadl are not included among the modules listed in Section 3.1, “Resteasy modules in WildFly”. If you want to use them in your application, you can include them in your WAR as you would if you were deploying outside of WildFly. See Section 3.3, “Deploying to other servlet containers” for more information.
If you are using Resteasy outside of WildFly, in a standalone servlet container like Tomcat or Jetty, for example, you will need to include the appropriate Resteasy jars in your WAR file. You will need the core classes in the resteasy-jaxrs module, and you may need additional facilities like the resteasy-jaxb-provider module. We strongly suggest that you use Maven to build your WAR files as Resteasy is split into a bunch of different modules:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>${resteasy.version}</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> <version>${resteasy.version}</version> </dependency>
You can see sample Maven projects in https://github.com/resteasy/resteasy-examples.
If you are not using Maven, you can include the necessary jars by hand. If you download Resteasy (from http://resteasy.jboss.org/downloads.html, for example) you will get a file like resteasy-jaxrs-<version>-all.zip. If you unzip it you will see a lib/ directory that contains the libraries needed by Resteasy. Copy these, as needed, into your /WEB-INF/lib directory. Place your JAX-RS annotated class resources and providers within one or more jars within /WEB-INF/lib or your raw class files within /WEB-INF/classes.
Resteasy uses the ServletContainerInitializer
integration interface in Servlet 3.0 containers
to initialize an application, automatically scanning for resources and providers. To enable automatic
scanning, you must also include the resteasy-servlet-initializer
artifact in your WAR
file as well:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-servlet-initializer</artifactId> <version>${resteasy.version}</version> </dependency>
The resteasy-servlet-initializer
artifact will not work in Servlet versions older than
3.0. You'll then have to manually declare the Resteasy servlet in your WEB-INF/web.xml file of your WAR project,
and you'll have to use an Application
class (see Section 3.5, “javax.ws.rs.core.Application”)
which explicitly lists resources and providers. For example:
<web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class> org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher </servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>com.restfully.shop.services.ShoppingApplication</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
The Resteasy servlet is responsible for initializing some basic components of Resteasy.
Note. It is likely that support for pre-3.0 Servlet specifications will be deprecated and eliminated eventually.
Resteasy receives configuration options from <context-param> elements.
Table 3.2.
Option Name | Default Value | Description |
---|---|---|
resteasy.servlet.mapping.prefix | no default | If the url-pattern for the Resteasy servlet-mapping is not /* |
resteasy.scan | false | Automatically scan WEB-INF/lib jars and WEB-INF/classes directory for both @Provider and JAX-RS resource classes (@Path, @GET, @POST etc..) and register them. This property is deprecated; please use a Servlet 3.0 container or higher and the ResteasyServletInitializer instead. |
resteasy.scan.providers | false | Scan for @Provider classes and register them. This property is deprecated; please use a Servlet 3.0 container or higher and the ResteasyServletInitializer instead. |
resteasy.scan.resources | false | Scan for JAX-RS resource classes. This property is deprecated; please use a Servlet 3.0 container or higher and the ResteasyServletInitializer instead. |
resteasy.providers | no default | A comma delimited list of fully qualified @Provider class names you want to register |
resteasy.use.builtin.providers | true | Whether or not to register default, built-in @Provider classes |
resteasy.resources | no default | A comma delimited list of fully qualified JAX-RS resource class names you want to register |
resteasy.jndi.resources | no default | A comma delimited list of JNDI names which reference objects you want to register as JAX-RS resources |
javax.ws.rs.Application | no default | Fully qualified name of Application class to bootstrap in a spec portable way |
resteasy.media.type.mappings | no default | Replaces the need for an Accept header by mapping file name extensions (like .xml or .txt) to a media type. Used when the client is unable to use an Accept header to choose a representation (i.e. a browser). See Chapter 17, JAX-RS Content Negotiation for more details. |
resteasy.language.mappings | no default | Replaces the need for an Accept-Language header by mapping file name extensions (like .en or .fr) to a language. Used when the client is unable to use an Accept-Language header to choose a language (i.e. a browser). See Chapter 17, JAX-RS Content Negotiation for more details. |
resteasy.media.type.param.mapping | no default | Names a query parameter that can be set to an acceptable media type, enabling content negotiation without an Accept header. See Chapter 17, JAX-RS Content Negotiation for more details. |
resteasy.role.based.security | false | Enables role based security. See Chapter 38, Securing JAX-RS and RESTEasy for more details. |
resteasy.document.expand.entity.references | false | Expand external entities in org.w3c.dom.Document documents and JAXB object representations |
resteasy.document.secure.processing.feature | true | Impose security constraints in processing org.w3c.dom.Document documents and JAXB object representations |
resteasy.document.secure.disableDTDs | true | Prohibit DTDs in org.w3c.dom.Document documents and JAXB object representations |
resteasy.wider.request.matching | false | Turns off the JAX-RS spec defined class-level expression filtering and instead tries to match version every method's full path. |
resteasy.use.container.form.params | false | Obtain form parameters by using HttpServletRequest.getParameterMap(). Use this switch if you are calling this method within a servlet filter or eating the input stream within the filter. |
resteasy.rfc7232preconditions | false | Enables RFC7232 compliant HTTP preconditions handling. |
resteasy.gzip.max.input | 10000000 | Imposes maximum size on decompressed gzipped . |
resteasy.secure.random.max.use | 100 | The number of times a SecureRandom can be used before reseeding. |
resteasy.buffer.exception.entity | true | Upon receiving an exception, the client side buffers any response entity before closing the connection. |
resteasy.add.charset | true | If a resource method returns a text/* or application/xml* media type without an explicit charset, Resteasy will add "charset=UTF-8" to the returned Content-Type header. Note that the charset defaults to UTF-8 in this case, independent of the setting of this parameter. |
Note. The resteasy.servlet.mapping.prefix <context param> variable must be set if your servlet-mapping for the Resteasy servlet has a url-pattern other than /*. For example, if the url-pattern is
<servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/restful-services/*</url-pattern> </servlet-mapping>
Then the value of resteasy.servlet.mapping.prefix must be:
<context-param> <param-name>resteasy.servlet.mapping.prefix</param-name> <param-value>/restful-services</param-value> </context-param>
The javax.ws.rs.core.Application
class is a standard JAX-RS class that you may implement to provide
information on your deployment. It is simply a class the lists all JAX-RS root resources and providers.
/** * Defines the components of a JAX-RS application and supplies additional * metadata. A JAX-RS application or implementation supplies a concrete * subclass of this abstract class. */ public abstract class Application { private static final Set<Object> emptySet = Collections.emptySet(); /** * Get a set of root resource and provider classes. The default lifecycle * for resource class instances is per-request. The default lifecycle for * providers is singleton. * <p/> * <p>Implementations should warn about and ignore classes that do not * conform to the requirements of root resource or provider classes. * Implementations should warn about and ignore classes for which * {@link #getSingletons()} returns an instance. Implementations MUST * NOT modify the returned set.</p> * * @return a set of root resource and provider classes. Returning null * is equivalent to returning an empty set. */ public abstract Set<Class<?>> getClasses(); /** * Get a set of root resource and provider instances. Fields and properties * of returned instances are injected with their declared dependencies * (see {@link Context}) by the runtime prior to use. * <p/> * <p>Implementations should warn about and ignore classes that do not * conform to the requirements of root resource or provider classes. * Implementations should flag an error if the returned set includes * more than one instance of the same class. Implementations MUST * NOT modify the returned set.</p> * <p/> * <p>The default implementation returns an empty set.</p> * * @return a set of root resource and provider instances. Returning null * is equivalent to returning an empty set. */ public Set<Object> getSingletons() { return emptySet; } }
Note. If your web.xml file does not have a
<servlet-mapping> element, you must use an Application
class
annotated with @ApplicationPath
.
This section is pretty much deprecated if you are using a Servlet 3.0 container or higher. Skip it if you are and read the configuration section above on installing in Servlet 3.0. The initialization of Resteasy can be performed within a ServletContextListener instead of within the Servlet. You may need this if you are writing custom Listeners that need to interact with Resteasy at boot time. An example of this is the Resteasy Spring integration that requires a Spring ServletContextListener. The org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap class is a ServletContextListener that configures an instance of an ResteasyProviderFactory and Registry. You can obtain instances of a ResteasyProviderFactory and Registry from the ServletContext attributes org.jboss.resteasy.spi.ResteasyProviderFactory and org.jboss.resteasy.spi.Registry. From these instances you can programmatically interact with Resteasy registration interfaces.
<web-app> <listener> <listener-class> org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap </listener-class> </listener> <!-- ** INSERT YOUR LISTENERS HERE!!!! --> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class> org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher </servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/Resteasy/*</url-pattern> </servlet-mapping> </web-app>
This section is pretty much deprecated if you are using a Servlet 3.0 container or higher. Skip it if you are and read the configuration section above on installing in Servlet 3.0. The downside of running Resteasy as a Servlet is that you cannot have static resources like .html and .jpeg files in the same path as your JAX-RS services. Resteasy allows you to run as a Filter instead. If a JAX-RS resource is not found under the URL requested, Resteasy will delegate back to the base servlet container to resolve URLs.
<web-app> <filter> <filter-name>Resteasy</filter-name> <filter-class> org.jboss.resteasy.plugins.server.servlet.FilterDispatcher </filter-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>com.restfully.shop.services.ShoppingApplication</param-value> </init-param> </filter> <filter-mapping> <filter-name>Resteasy</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
JAX-RS 2.0 conforming implementations such as Resteasy support a client side framework which simplifies communicating with restful applications. In Resteasy, the minimal set of modules needed for the client framework consists of resteasy-jaxrs and resteasy-client. You can access them by way of maven:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>${resteasy.version}</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>${resteasy.version}</version> </dependency>
Other modules, such as resteasy-jaxb-provider, may be brought in as needed.
@Path("/library") public class Library { @GET @Path("/books") public String getBooks() {...} @GET @Path("/book/{isbn}") public String getBook(@PathParam("isbn") String id) { // search my database and get a string representation and return it } @PUT @Path("/book/{isbn}") public void addBook(@PathParam("isbn") String id, @QueryParam("name") String name) {...} @DELETE @Path("/book/{id}") public void removeBook(@PathParam("id") String id {...} }
Let's say you have the Resteasy servlet configured and reachable at a root path of http://myhost.com/services. The requests would be handled by the Library class:
The @javax.ws.rs.Path annotation must exist on either the class and/or a resource method. If it exists on both the class and method, the relative path to the resource method is a concatenation of the class and method.
In the @javax.ws.rs package there are annotations for each HTTP method. @GET, @POST, @PUT, @DELETE, and @HEAD. You place these on public methods that you want to map to that certain kind of HTTP method. As long as there is a @Path annotation on the class, you do not have to have a @Path annotation on the method you are mapping. You can have more than one HTTP method as long as they can be distinguished from other methods.
When you have a @Path annotation on a method without an HTTP method, these are called JAXRSResourceLocators.
The @Path annotation is not limited to simple path expressions. You also have the ability to insert regular expressions into @Path's value. For example:
@Path("/resources) public class MyResource { @GET @Path("{var:.*}/stuff") public String get() {...} }
The following GETs will route to the getResource() method:
GET /resources/stuff GET /resources/foo/stuff GET /resources/on/and/on/stuff
The format of the expression is:
"{" variable-name [ ":" regular-expression ] "}"
The regular-expression part is optional. When the expression is not provided, it defaults to a wildcard matching of one particular segment. In regular-expression terms, the expression defaults to
"([]*)"
For example:
@Path("/resources/{var}/stuff")
will match these:
GET /resources/foo/stuff GET /resources/bar/stuff
but will not match:
GET /resources/a/bunch/of/stuff
@PathParam is a parameter annotation which allows you to map variable URI path fragments into your method call.
@Path("/library") public class Library { @GET @Path("/book/{isbn}") public String getBook(@PathParam("isbn") String id) { // search my database and get a string representation and return it } }
What this allows you to do is embed variable identification within the URIs of your resources. In the above example, an isbn URI parameter is used to pass information about the book we want to access. The parameter type you inject into can be any primitive type, a String, or any Java object that has a constructor that takes a String parameter, or a static valueOf method that takes a String as a parameter. For example, lets say we wanted isbn to be a real object. We could do:
@GET @Path("/book/{isbn}") public String getBook(@PathParam("isbn") ISBN id) {...} public class ISBN { public ISBN(String str) {...} }
Or instead of a public String constructors, have a valueOf method:
public class ISBN { public static ISBN valueOf(String isbn) {...} }
There are a few more complicated uses of @PathParams not discussed in the previous section.
You are allowed to specify one or more path params embedded in one URI segment. Here are some examples:
1. @Path("/aaa{param}bbb") 2. @Path("/{name}-{zip}") 3. @Path("/foo{name}-{zip}bar")
So, a URI of "/aaa111bbb" would match #1. "/bill-02115" would match #2. "foobill-02115bar" would match #3.
We discussed before how you can use regular expression patterns within @Path values.
@GET @Path("/aaa{param:b+}/{many:.*}/stuff") public String getIt(@PathParam("param") String bs, @PathParam("many") String many) {...}
For the following requests, lets see what the values of the "param" and "many" @PathParams would be:
The specification has a very simple abstraction for examining a fragment of the URI path being invoked on javax.ws.rs.core.PathSegment:
public interface PathSegment { /** * Get the path segment. * <p> * @return the path segment */ String getPath(); /** * Get a map of the matrix parameters associated with the path segment * @return the map of matrix parameters */ MultivaluedMap<String, String> getMatrixParameters(); }
You can have Resteasy inject a PathSegment instead of a value with your @PathParam.
@GET @Path("/book/{id}") public String getBook(@PathParam("id") PathSegment id) {...}
This is very useful if you have a bunch of @PathParams that use matrix parameters. The idea of matrix parameters is that they are an arbitrary set of name-value pairs embedded in a uri path segment. The PathSegment object gives you access to these parameters. See also MatrixParam.
A matrix parameter example is:
GET http://host.com/library/book;name=EJB 3.0;author=Bill Burke
The basic idea of matrix parameters is that it represents resources that are addressable by their attributes as well as their raw id.
The @QueryParam annotation allows you to map a URI query string parameter or url form encoded parameter to your method invocation.
GET /books?num=5
@GET public String getBooks(@QueryParam("num") int num) { ... }
Currently since Resteasy is built on top of a Servlet, it does not distinguish between URI query strings or url form encoded parameters. Like PathParam, your parameter type can be an String, primitive, or class that has a String constructor or static valueOf() method.
The @HeaderParam annotation allows you to map a request HTTP header to your method invocation.
GET /books?num=5
@GET public String getBooks(@HeaderParam("From") String from) { ... }
Like PathParam, your parameter type can be an String, primitive, or class that has a String constructor or static valueOf() method. For example, MediaType has a valueOf() method and you could do:
@PUT public void put(@HeaderParam("Content-Type") MediaType contentType, ...)
There are two mechanisms available in RESTEasy to link a resource to another, and to link resources to operations: the Link HTTP header, and Atom links inside the resource representations.
RESTEasy has both client and server side support for the Link header specification. See the javadocs for org.jboss.resteasy.spi.LinkHeader, org.jboss.resteasy.spi.Link, and org.jboss.resteasy.client.ClientResponse.
The main advantage of Link headers over Atom links in the resource is that those links are available without parsing the entity body.
RESTEasy allows you to inject Atom links directly inside the entity objects you are sending to the client, via auto-discovery.
This is only available when using the Jettison or JAXB providers (for JSON and XML).
The main advantage over Link headers is that you can have any number of Atom links directly over the concerned resources, for any number of resources in the response. For example, you can have Atom links for the root response entity, and also for each of its children entities.
There is no configuration required to be able to inject Atom links in your resource representation, you just have to have this maven artifact in your path:
Group | Artifact | Version |
---|---|---|
org.jboss.resteasy | resteasy-links | 3.0.22.Final |
You need three things in order to tell RESTEasy to inject Atom links in your entities:
Annotate the JAX-RS method with @AddLinks
to indicate that you want
Atom links injected in your response entity.
Add RESTServiceDiscovery
fields to the resource classes where you
want Atom links injected.
Annotate the JAX-RS methods you want Atom links for with @LinkResource
,
so that RESTEasy knows which links to create for which resources.
The following example illustrates how you would declare everything in order to get the Atom links injected in your book store:
@Path("/")
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
public interface BookStore {
@AddLinks
@LinkResource(value = Book.class)
@GET
@Path("books")
public Collection<Book> getBooks();
@LinkResource
@POST
@Path("books")
public void addBook(Book book);
@AddLinks
@LinkResource
@GET
@Path("book/{id}")
public Book getBook(@PathParam("id") String id);
@LinkResource
@PUT
@Path("book/{id}")
public void updateBook(@PathParam("id") String id, Book book);
@LinkResource(value = Book.class)
@DELETE
@Path("book/{id}")
public void deleteBook(@PathParam("id") String id);
}
And this is the definition of the Book resource:
@Mapped(namespaceMap = @XmlNsMap(jsonName = "atom", namespace = "http://www.w3.org/2005/Atom"))
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Book {
@XmlAttribute
private String author;
@XmlID
@XmlAttribute
private String title;
@XmlElementRef
private RESTServiceDiscovery rest;
}
If you do a GET /order/foo you will then get this XML representation:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book xmlns:atom="http://www.w3.org/2005/Atom" title="foo" author="bar">
<atom:link href="http://localhost:8081/books" rel="list"/>
<atom:link href="http://localhost:8081/books" rel="add"/>
<atom:link href="http://localhost:8081/book/foo" rel="self"/>
<atom:link href="http://localhost:8081/book/foo" rel="update"/>
<atom:link href="http://localhost:8081/book/foo" rel="remove"/>
</book>
And in JSON format:
{ "book": { "@title":"foo", "@author":"bar", "atom.link": [ {"@href":"http://localhost:8081/books","@rel":"list"}, {"@href":"http://localhost:8081/books","@rel":"add"}, {"@href":"http://localhost:8081/book/foo","@rel":"self"}, {"@href":"http://localhost:8081/book/foo","@rel":"update"}, {"@href":"http://localhost:8081/book/foo","@rel":"remove"} ] } }
Because the RESTServiceDiscovery
is in fact a JAXB type which inherits from
List
you are free to annotate it as you want to customise the JAXB serialisation,
or just rely on the default with @XmlElementRef
.
This is all done by annotating the methods with the @LinkResource
annotation.
It supports the following optional parameters:
Parameter | Type | Function | Default |
---|---|---|---|
value | Class |
Declares an Atom link for the given type of resources. | Defaults to the entity body type (non-annotated parameter), or the method's return type.
This default does not work with Response or Collection
types, they need to be explicitly specified. |
rel | String |
The Atom link relation |
|
You can add several @LinkResource
annotations on a single method by enclosing
them in a @LinkResources
annotation. This way you can add links to the same
method on several resource types. For example the /order/foo/comments
operation
can belongs on the Order
resource with the comments
relation, and on the Comment
resource with the list
relation.
When RESTEasy adds links to your resources it needs to insert the right values in the URI template.
This is done either automatically by guessing the list of values from the entity, or by specifying
the values in the @LinkResource
pathParameters
parameter.
URI template values are extracted from the entity from fields or Java Bean properties
annotated with @ResourceID
, JAXB's @XmlID
or
JPA's @Id
. If there are more than one URI template
value to find in a given entity, you can annotate your entity with @ResourceIDs
to list the names of fields or properties that make up this entity's Id. If there are other
URI template values required from a parent entity, we try to find that parent in a field or
Java Bean property
annotated with @ParentResource
. The list of URI template
values extracted up every @ParentResource
is then reversed and used
as the list of values for the URI template.
For example, let's consider the previous Book example, and a list of comments:
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Comment {
@ParentResource
private Book book;
@XmlElement
private String author;
@XmlID
@XmlAttribute
private String id;
@XmlElementRef
private RESTServiceDiscovery rest;
}
Given the previous book store service augmented with comments:
@Path("/")
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
public interface BookStore {
@AddLinks
@LinkResources({
@LinkResource(value = Book.class, rel = "comments"),
@LinkResource(value = Comment.class)
})
@GET
@Path("book/{id}/comments")
public Collection<Comment> getComments(@PathParam("id") String bookId);
@AddLinks
@LinkResource
@GET
@Path("book/{id}/comment/{cid}")
public Comment getComment(@PathParam("id") String bookId, @PathParam("cid") String commentId);
@LinkResource
@POST
@Path("book/{id}/comments")
public void addComment(@PathParam("id") String bookId, Comment comment);
@LinkResource
@PUT
@Path("book/{id}/comment/{cid}")
public void updateComment(@PathParam("id") String bookId, @PathParam("cid") String commentId, Comment comment);
@LinkResource(Comment.class)
@DELETE
@Path("book/{id}/comment/{cid}")
public void deleteComment(@PathParam("id") String bookId, @PathParam("cid") String commentId);
}
Whenever we need to make links for a Book
entity, we look up the ID
in the Book
's @XmlID
property. Whenever we make links
for Comment
entities, we have a list of values taken from the Comment
's
@XmlID
and its @ParentResource
: the Book
and
its @XmlID
.
For a Comment
with id
"1"
on a Book
with title
"foo"
we will therefore get a list of URI
template values of {"foo", "1"}
, to be replaced in the URI template, thus
obtaining either "/book/foo/comments"
or "/book/foo/comment/1"
.
If you do not want to annotate your entities with resource ID annotations (@ResourceID
,
@ResourceIDs
, @XmlID
or @Id
) and
@ParentResource
, you can also specify the URI template values inside the
@LinkResource
annotation, using Unified Expression Language expressions:
Parameter | Type | Function | Default |
---|---|---|---|
pathParameters | String[] |
Declares a list of UEL expressions to obtain the URI template values. | Defaults to using @ResourceID , @ResourceIDs ,
@XmlID or @Id and @ParentResource
annotations to extract the values from the model. |
The UEL expressions are evaluated in the context of the entity, which means that any unqualified
variable will be taken as a property for the entity itself, with the special variable
this
bound to the entity we're generating links for.
The previous example of Comment
service could be declared as such:
@Path("/")
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
public interface BookStore {
@AddLinks
@LinkResources({
@LinkResource(value = Book.class, rel = "comments", pathParameters = "${title}"),
@LinkResource(value = Comment.class, pathParameters = {"${book.title}", "${id}"})
})
@GET
@Path("book/{id}/comments")
public Collection<Comment> getComments(@PathParam("id") String bookId);
@AddLinks
@LinkResource(pathParameters = {"${book.title}", "${id}"})
@GET
@Path("book/{id}/comment/{cid}")
public Comment getComment(@PathParam("id") String bookId, @PathParam("cid") String commentId);
@LinkResource(pathParameters = {"${book.title}", "${id}"})
@POST
@Path("book/{id}/comments")
public void addComment(@PathParam("id") String bookId, Comment comment);
@LinkResource(pathParameters = {"${book.title}", "${id}"})
@PUT
@Path("book/{id}/comment/{cid}")
public void updateComment(@PathParam("id") String bookId, @PathParam("cid") String commentId, Comment comment);
@LinkResource(Comment.class, pathParameters = {"${book.title}", "${id}"})
@DELETE
@Path("book/{id}/comment/{cid}")
public void deleteComment(@PathParam("id") String bookId, @PathParam("cid") String commentId);
}
You can restrict which links are injected in the resource based on security restrictions for the client,
so that if the current client doesn't have permission to delete a resource he will not be presented
with the "delete"
link relation.
Security restrictions can either be specified on the @LinkResource
annotation,
or using RESTEasy and EJB's security annotation @RolesAllowed
on the JAX-RS
method.
Parameter | Type | Function | Default |
---|---|---|---|
constraint | String |
A UEL expression which must evaluate to true to inject this method's link in the response entity. | Defaults to using @RolesAllowed from the JAX-RS method. |
We've seen that both the URI template values and the security constraints of @LinkResource
use UEL to evaluate expressions, and we provide a basic UEL context with access only to the entity
we're injecting links in, and nothing more.
If you want to add more variables or functions in this
context, you can by adding a @LinkELProvider
annotation on the JAX-RS method,
its class, or its package. This annotation's value should point to a class that implements the
ELProvider
interface, which wraps the default ELContext
in order to add any missing functions.
For example, if you want to support the Seam annotation s:hasPermission(target, permission)
in your security constraints, you can add a package-info.java
file like this:
@LinkELProvider(SeamELProvider.class)
package org.jboss.resteasy.links.test;
import org.jboss.resteasy.links.*;
With the following provider implementation:
package org.jboss.resteasy.links.test;
import javax.el.ELContext;
import javax.el.ELResolver;
import javax.el.FunctionMapper;
import javax.el.VariableMapper;
import org.jboss.seam.el.SeamFunctionMapper;
import org.jboss.resteasy.links.ELProvider;
public class SeamELProvider implements ELProvider {
public ELContext getContext(final ELContext ctx) {
return new ELContext() {
private SeamFunctionMapper functionMapper;
@Override
public ELResolver getELResolver() {
return ctx.getELResolver();
}
@Override
public FunctionMapper getFunctionMapper() {
if (functionMapper == null)
functionMapper = new SeamFunctionMapper(ctx
.getFunctionMapper());
return functionMapper;
}
@Override
public VariableMapper getVariableMapper() {
return ctx.getVariableMapper();
}
};
}
}
And then use it as such:
@Path("/")
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
public interface BookStore {
@AddLinks
@LinkResources({
@LinkResource(value = Book.class, rel = "comments", constraint = "${s:hasPermission(this, 'add-comment')}"),
@LinkResource(value = Comment.class, constraint = "${s:hasPermission(this, 'insert')}")
})
@GET
@Path("book/{id}/comments")
public Collection<Comment> getComments(@PathParam("id") String bookId);
@AddLinks
@LinkResource(constraint = "${s:hasPermission(this, 'read')}")
@GET
@Path("book/{id}/comment/{cid}")
public Comment getComment(@PathParam("id") String bookId, @PathParam("cid") String commentId);
@LinkResource(constraint = "${s:hasPermission(this, 'insert')}")
@POST
@Path("book/{id}/comments")
public void addComment(@PathParam("id") String bookId, Comment comment);
@LinkResource(constraint = "${s:hasPermission(this, 'update')}")
@PUT
@Path("book/{id}/comment/{cid}")
public void updateComment(@PathParam("id") String bookId, @PathParam("cid") String commentId, Comment comment);
@LinkResource(Comment.class, constraint = "${s:hasPermission(this, 'delete')}")
@DELETE
@Path("book/{id}/comment/{cid}")
public void deleteComment(@PathParam("id") String bookId, @PathParam("cid") String commentId);
}
Sometimes it is useful to add resources which are just containers or layers on other resources. For
example if you want to represent a collection of Comment
with a start index
and a certain number of entries, in order to implement paging. Such a collection is not really an
entity in your model, but it should obtain the "add"
and "list"
link relations for the Comment
entity.
This is possible using resource facades. A resource facade is a resource which implements the
ResourceFacade<T>
interface for the type T
, and as
such, should receive all links for that type.
Since in most cases the instance of the T
type is not directly available in the
resource facade, we need another way to extract its URI template values, and this is done by calling
the resource facade's pathParameters()
method to obtain a map of URI template
values by name. This map will be used to fill in the URI template values for any link generated for
T
, if there are enough values in the map.
Here is an example of such a resource facade for a collection of Comment
s:
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class ScrollableCollection implements ResourceFacade<Comment> {
private String bookId;
@XmlAttribute
private int start;
@XmlAttribute
private int totalRecords;
@XmlElement
private List<Comment> comments = new ArrayList<Comment>();
@XmlElementRef
private RESTServiceDiscovery rest;
public Class<Comment> facadeFor() {
return Comment.class;
}
public Map<String, ? extends Object> pathParameters() {
HashMap<String, String> map = new HashMap<String, String>();
map.put("id", bookId);
return map;
}
}
This will produce such an XML collection:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<collection xmlns:atom="http://www.w3.org/2005/Atom" totalRecords="2" start="0">
<atom.link href="http://localhost:8081/book/foo/comments" rel="add"/>
<atom.link href="http://localhost:8081/book/foo/comments" rel="list"/>
<comment xmlid="0">
<text>great book</text>
<atom.link href="http://localhost:8081/book/foo/comment/0" rel="self"/>
<atom.link href="http://localhost:8081/book/foo/comment/0" rel="update"/>
<atom.link href="http://localhost:8081/book/foo/comment/0" rel="remove"/>
<atom.link href="http://localhost:8081/book/foo/comments" rel="add"/>
<atom.link href="http://localhost:8081/book/foo/comments" rel="list"/>
</comment>
<comment xmlid="1">
<text>terrible book</text>
<atom.link href="http://localhost:8081/book/foo/comment/1" rel="self"/>
<atom.link href="http://localhost:8081/book/foo/comment/1" rel="update"/>
<atom.link href="http://localhost:8081/book/foo/comment/1" rel="remove"/>
<atom.link href="http://localhost:8081/book/foo/comments" rel="add"/>
<atom.link href="http://localhost:8081/book/foo/comments" rel="list"/>
</comment>
</collection>
The idea of matrix parameters is that they are an arbitrary set of name-value pairs embedded in a uri path segment. A matrix parameter example is:
GET http://host.com/library/book;name=EJB 3.0;author=Bill Burke
The basic idea of matrix parameters is that it represents resources that are addressable by their attributes as well as their raw id. The @MatrixParam annotation allows you to inject URI matrix parameters into your method invocation
@GET public String getBook(@MatrixParam("name") String name, @MatrixParam("author") String author) {...}
There is one big problem with @MatrixParam that the current version of the specification does not resolve. What if the same MatrixParam exists twice in different path segments? In this case, right now, its probably better to use PathParam combined with PathSegment.
The @CookieParam annotation allows you to inject the value of a cookie or an object representation of an HTTP request cookie into your method invocation
GET /books?num=5
@GET public String getBooks(@CookieParam("sessionid") int id) { ... } @GET public String getBooks(@CookieParam("sessionid") javax.ws.rs.core.Cookie id) {...}
Like PathParam, your parameter type can be an String, primitive, or class that has a String constructor or static valueOf() method. You can also get an object representation of the cookie via the javax.ws.rs.core.Cookie class.
When the input request body is of the type "application/x-www-form-urlencoded", a.k.a. an HTML Form, you can inject individual form parameters from the request body into method parameter values.
<form method="POST" action="/resources/service"> First name: <input type="text" name="firstname"> <br> Last name: <input type="text" name="lastname"> </form>
If you post through that form, this is what the service might look like:
@Path("/") public class NameRegistry { @Path("/resources/service") @POST public void addName(@FormParam("firstname") String first, @FormParam("lastname") String last) {...}
You cannot combine @FormParam with the default "application/x-www-form-urlencoded" that unmarshalls to a MultivaluedMap<String, String>. i.e. This is illegal:
@Path("/") public class NameRegistry { @Path("/resources/service") @POST @Consumes("application/x-www-form-urlencoded") public void addName(@FormParam("firstname") String first, MultivaluedMap<String, String> form) {...}
This is a RESTEasy specific annotation that allows you to re-use any @*Param annotation within an injected class. RESTEasy will instantiate the class and inject values into any annotated @*Param or @Context property. This is useful if you have a lot of parameters on your method and you want to condense them into a value object.
public class MyForm { @FormParam("stuff") private int stuff; @HeaderParam("myHeader") private String header; @PathParam("foo") public void setFoo(String foo) {...} } @POST @Path("/myservice") public void post(@Form MyForm form) {...}
When somebody posts to /myservice, RESTEasy will instantiate an instance of MyForm and inject the form parameter "stuff" into the "stuff" field, the header "myheader" into the header field, and call the setFoo method with the path param variable of "foo".
Also, @Form has some expanded @FormParam features. If you specify a prefix within the Form param, this will prepend a prefix to any form parameter lookup. For example, let's say you have one Address class, but want to reference invoice and shipping addresses from the same set of form parameters:
public static class Person { @FormParam("name") private String name; @Form(prefix = "invoice") private Address invoice; @Form(prefix = "shipping") private Address shipping; } public static class Address { @FormParam("street") private String street; } @Path("person") public static class MyResource { @POST @Produces(MediaType.TEXT_PLAIN) @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public String post(@Form Person p) { return p.toString(); } }
In this example, the client could send the following form parameters:
name=bill invoice.street=xxx shipping.street=yyy
The Person.invoice and Person.shipping fields would be populated appropriately. Also, prefix mappings also support lists and maps:
public static class Person { @Form(prefix="telephoneNumbers") List<TelephoneNumber> telephoneNumbers; @Form(prefix="address") Map<String, Address> addresses; } public static class TelephoneNumber { @FormParam("countryCode") private String countryCode; @FormParam("number") private String number; } public static class Address { @FormParam("street") private String street; @FormParam("houseNumber") private String houseNumber; } @Path("person") public static class MyResource { @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public void post (@Form Person p) {}
The following form params could be submitted and the Person.telephoneNumbers and Person.addresses fields would be populated appropriately
request.addFormHeader("telephoneNumbers[0].countryCode", "31"); request.addFormHeader("telephoneNumbers[0].number", "0612345678"); request.addFormHeader("telephoneNumbers[1].countryCode", "91"); request.addFormHeader("telephoneNumbers[1].number", "9717738723"); request.addFormHeader("address[INVOICE].street", "Main Street"); request.addFormHeader("address[INVOICE].houseNumber", "2"); request.addFormHeader("address[SHIPPING].street", "Square One"); request.addFormHeader("address[SHIPPING].houseNumber", "13");
@DefaultValue is a parameter annotation that can be combined with any of the other @*Param annotations to define a default value when the HTTP request item does not exist.
@GET public String getBooks(@QueryParam("num") @DefaultValue("10") int num) {...}
JAX-RS allows you to get encoded or decoded @*Params and specify path definitions and parameter names using encoded or decoded strings.
The @javax.ws.rs.Encoded annotation can be used on a class, method, or param. By default, inject @PathParam and @QueryParams are decoded. By additionally adding the @Encoded annotation, the value of these params will be provided in encoded form.
@Path("/") public class MyResource { @Path("/{param}") @GET public String get(@PathParam("param") @Encoded String param) {...} }
In the above example, the value of the @PathParam injected into the param of the get() method will be URL encoded. Adding the @Encoded annotation as a paramater annotation triggers this affect.
You may also use the @Encoded annotation on the entire method and any combination of @QueryParam or @PathParam's values will be encoded.
@Path("/") public class MyResource { @Path("/{param}") @GET @Encoded public String get(@QueryParam("foo") String foo, @PathParam("param") String param) {} }
In the above example, the values of the "foo" query param and "param" path param will be injected as encoded values.
You can also set the default to be encoded for the entire class.
@Path("/") @Encoded public class ClassEncoded { @GET public String get(@QueryParam("foo") String foo) {} }
The @Path annotation has an attribute called encode. Controls whether the literal part of the supplied value (those characters that are not part of a template variable) are URL encoded. If true, any characters in the URI template that are not valid URI character will be automatically encoded. If false then all characters must be valid URI characters. By default this is set to true. If you want to encoded the characters yourself, you may.
@Path(value="hello%20world", encode=false)
Much like @Path.encode(), this controls whether the specified query param name should be encoded by the container before it tries to find the query param in the request.
@QueryParam(value="hello%20world", encode=false)
The @Context annotation allows you to inject instances of
objects.
Resource classes are able to partially process a request and provide another "sub" resource object that can process the remainder of the request. For example:
@Path("/") public class ShoppingStore { @Path("/customers/{id}") public Customer getCustomer(@PathParam("id") int id) { Customer cust = ...; // Find a customer object return cust; } } public class Customer { @GET public String get() {...} @Path("/address") public String getAddress() {...} }
Resource methods that have a @Path annotation, but no HTTP method are considered sub-resource locators. Their job is to provide an object that can process the request. In the above example ShoppingStore is a root resource because its class is annotated with @Path. The getCustomer() method is a sub-resource locator method.
If the client invoked:
GET /customer/123
The ShoppingStore.getCustomer() method would be invoked first. This method provides a Customer object that can service the request. The http request will be dispatched to the Customer.get() method. Another example is:
GET /customer/123/address
In this request, again, first the ShoppingStore.getCustomer() method is invoked. A customer object is returned, and the rest of the request is dispatched to the Customer.getAddress() method.
Another interesting feature of Sub-resource locators is that the locator method result is dynamically processed at runtime to figure out how to dispatch the request. So, the ShoppingStore.getCustomer() method does not have to declare any specific type.
@Path("/") public class ShoppingStore { @Path("/customers/{id}") public java.lang.Object getCustomer(@PathParam("id") int id) { Customer cust = ...; // Find a customer object return cust; } } public class Customer { @GET public String get() {...} @Path("/address") public String getAddress() {...} }
In the above example, getCustomer() returns a java.lang.Object. Per request, at runtime, the JAX-RS server will figure out how to dispatch the request based on the object returned by getCustomer(). What are the uses of this? Well, maybe you have a class hierarchy for your customers. Customer is the abstract base, CorporateCustomer and IndividualCustomer are subclasses. Your getCustomer() method might be doing a Hibernate polymorphic query and doesn't know, or care, what concrete class is it querying for, or what it returns.
@Path("/") public class ShoppingStore { @Path("/customers/{id}") public java.lang.Object getCustomer(@PathParam("id") int id) { Customer cust = entityManager.find(Customer.class, id); return cust; } } public class Customer { @GET public String get() {...} @Path("/address") public String getAddress() {...} } public class CorporateCustomer extends Customer { @Path("/businessAddress") public String getAddress() {...} }
The HTTP protocol has built in content negotiation headers that allow the client and server to specify what content they are transferring and what content they would prefer to get. The server declares content preferences via the @Produces and @Consumes headers.
@Consumes is an array of media types that a particular resource or resource method consumes. For example:
@Consumes("text/*") @Path("/library") public class Library { @POST public String stringBook(String book) {...} @Consumes("text/xml") @POST public String jaxbBook(Book book) {...} }
When a client makes a request, JAX-RS first finds all methods that match the path, then, it sorts things based on the content-type header sent by the client. So, if a client sent:
POST /library Content-Type: text/plain This is a nice book
The stringBook() method would be invoked because it matches to the default "text/*" media type. Now, if the client instead sends XML:
POST /library Content-Type: text/xml <book name="EJB 3.0" author="Bill Burke"/>
The jaxbBook() method would be invoked.
The @Produces is used to map a client request and match it up to the client's Accept header. The Accept HTTP header is sent by the client and defines the media types the client prefers to receive from the server.
@Produces("text/*") @Path("/library") public class Library { @GET @Produces("application/json") public String getJSON() {...} @GET public String get() {...}
So, if the client sends:
GET /library Accept: application/json
The getJSON() method would be invoked.
@Consumes and @Produces can list multiple media types that they support. The client's Accept header can also send multiple types it might like to receive. More specific media types are chosen first. The client Accept header or @Produces @Consumes can also specify weighted preferences that are used to match up requests with resource methods. This is best explained by RFC 2616 section 14.1 . Resteasy supports this complex way of doing content negotiation.
A variant in JAX-RS is a combination of media type, content-language, and content encoding as well as etags, last modified headers, and other preconditions. This is a more complex form of content negotiation that is done programmatically by the application developer using the javax.ws.rs.Variant, VarianListBuilder, and Request objects. Request is injected via @Context. Read the javadoc for more info on these.
Some clients, like browsers, cannot use the Accept and Accept-Language headers to negotiation the representation's media type or language. RESTEasy allows you to map file name suffixes like (.xml, .txt, .en, .fr) to media types and languages. These file name suffixes take the place and override any Accept header sent by the client. You configure this using the resteasy.media.type.mappings and resteasy.language.mappings context-param variables within your web.xml.
<web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>resteasy.media.type.mappings</param-name> <param-value>html : text/html, json : application/json, xml : application/xml</param-value> </context-param> <context-param> <param-name>resteasy.language.mappings</param-name> <param-value>en : en-US, es : es, fr : fr</param-value> </context-param> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Mappings are a comma delimited list of suffix/mediatype or suffix/language mappings. Each mapping is delimited by a ':'. So, if you invoked GET /foo/bar.xml.en, this would be equivalent to invoking the following request:
GET /foo/bar Accept: application/xml Accept-Language: en-US
The mapped file suffixes are stripped from the target URL path before the request is dispatched to a corresponding JAX-RS resource.
RESTEasy can do content negotiation based in a parameter in query string. To enable this, the web.xml can be configured like follow:
<web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>resteasy.media.type.param.mapping</param-name> <param-value>someName</param-value> </context-param> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
The param-value is the name of the query string parameter that RESTEasy will use in the place of the Accept header.
Invoking http://service.foo.com/resouce?someName=application/xml, will give the application/xml media type the highest priority in the content negotiation.
In cases where the request contains both the parameter and the Accept header, the parameter will be more relevant.
It is possible to left the param-value empty, what will cause the processor to look for a parameter named 'accept'.
Resteasy can automatically marshal and unmarshal a few different message bodies.
Table 18.1.
Media Types | Java Type |
---|---|
application/*+xml, text/*+xml, application/*+json, application/*+fastinfoset, application/atom+* | JaxB annotated classes |
application/*+xml, text/*+xml | org.w3c.dom.Document |
*/* | java.lang.String |
*/* | java.io.InputStream |
text/plain | primitives, java.lang.String, or any type that has a String constructor, or static valueOf(String) method for input, toString() for output |
*/* | javax.activation.DataSource |
*/* | java.io.File |
*/* | byte[] |
application/x-www-form-urlencoded | javax.ws.rs.core.MultivaluedMap |
The JAX-RS specification allows you to plug in your own request/response body reader and writers. To do this, you annotate a class with @Provider and specify the @Produces types for a writer and @Consumes types for a reader. You must also implement a MessageBodyReader/Writer interface respectively. Here is an example:
@Provider @Produces("text/plain") @Consumes("text/plain") public class DefaultTextPlain implements MessageBodyReader, MessageBodyWriter { public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { // StringTextStar should pick up strings return !String.class.equals(type) && TypeConverter.isConvertable(type); } public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { InputStream delegate = NoContent.noContentCheck(httpHeaders, entityStream); String value = ProviderHelper.readString(delegate, mediaType); return TypeConverter.getType(type, value); } public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { // StringTextStar should pick up strings return !String.class.equals(type) && !type.isArray(); } public long getSize(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { String charset = mediaType.getParameters().get("charset"); if (charset != null) try { return o.toString().getBytes(charset).length; } catch (UnsupportedEncodingException e) { // Use default encoding. } return o.toString().getBytes(StandardCharsets.UTF_8).length; } public void writeTo(Object o, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { String charset = mediaType.getParameters().get("charset"); if (charset == null) entityStream.write(o.toString().getBytes(StandardCharsets.UTF_8)); else entityStream.write(o.toString().getBytes(charset)); } }
The Resteasy ServletContextLoader will automatically scan your WEB-INF/lib and classes directories for classes annotated with @Provider or you can manually configure them in web.xml. See Installation/Configuration.
javax.ws.rs.ext.Providers is a simple injectable interface that allows you to look up MessageBodyReaders, Writers, ContextResolvers, and ExceptionMappers. It is very useful, for instance, for implementing multipart providers. Content types that embed other random content types.
public interface Providers { /** * Get a message body reader that matches a set of criteria. The set of * readers is first filtered by comparing the supplied value of * {@code mediaType} with the value of each reader's * {@link javax.ws.rs.Consumes}, ensuring the supplied value of * {@code type} is assignable to the generic type of the reader, and * eliminating those that do not match. * The list of matching readers is then ordered with those with the best * matching values of {@link javax.ws.rs.Consumes} (x/y > x/* > */*) * sorted first. Finally, the * {@link MessageBodyReader#isReadable} * method is called on each reader in order using the supplied criteria and * the first reader that returns {@code true} is selected and returned. * * @param type the class of object that is to be written. * @param mediaType the media type of the data that will be read. * @param genericType the type of object to be produced. E.g. if the * message body is to be converted into a method parameter, this will be * the formal type of the method parameter as returned by * <code>Class.getGenericParameterTypes</code>. * @param annotations an array of the annotations on the declaration of the * artifact that will be initialized with the produced instance. E.g. if the * message body is to be converted into a method parameter, this will be * the annotations on that parameter returned by * <code>Class.getParameterAnnotations</code>. * @return a MessageBodyReader that matches the supplied criteria or null * if none is found. */ <T> MessageBodyReader<T> getMessageBodyReader(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); /** * Get a message body writer that matches a set of criteria. The set of * writers is first filtered by comparing the supplied value of * {@code mediaType} with the value of each writer's * {@link javax.ws.rs.Produces}, ensuring the supplied value of * {@code type} is assignable to the generic type of the reader, and * eliminating those that do not match. * The list of matching writers is then ordered with those with the best * matching values of {@link javax.ws.rs.Produces} (x/y > x/* > */*) * sorted first. Finally, the * {@link MessageBodyWriter#isWriteable} * method is called on each writer in order using the supplied criteria and * the first writer that returns {@code true} is selected and returned. * * @param mediaType the media type of the data that will be written. * @param type the class of object that is to be written. * @param genericType the type of object to be written. E.g. if the * message body is to be produced from a field, this will be * the declared type of the field as returned by * <code>Field.getGenericType</code>. * @param annotations an array of the annotations on the declaration of the * artifact that will be written. E.g. if the * message body is to be produced from a field, this will be * the annotations on that field returned by * <code>Field.getDeclaredAnnotations</code>. * @return a MessageBodyReader that matches the supplied criteria or null * if none is found. */ <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); /** * Get an exception mapping provider for a particular class of exception. * Returns the provider whose generic type is the nearest superclass of * {@code type}. * * @param type the class of exception * @return an {@link ExceptionMapper} for the supplied type or null if none * is found. */ <T extends Throwable> ExceptionMapper<T> getExceptionMapper(Class<T> type); /** * Get a context resolver for a particular type of context and media type. * The set of resolvers is first filtered by comparing the supplied value of * {@code mediaType} with the value of each resolver's * {@link javax.ws.rs.Produces}, ensuring the generic type of the context * resolver is assignable to the supplied value of {@code contextType}, and * eliminating those that do not match. If only one resolver matches the * criteria then it is returned. If more than one resolver matches then the * list of matching resolvers is ordered with those with the best * matching values of {@link javax.ws.rs.Produces} (x/y > x/* > */*) * sorted first. A proxy is returned that delegates calls to * {@link ContextResolver#getContext(java.lang.Class)} to each matching context * resolver in order and returns the first non-null value it obtains or null * if all matching context resolvers return null. * * @param contextType the class of context desired * @param mediaType the media type of data for which a context is required. * @return a matching context resolver instance or null if no matching * context providers are found. */ <T> ContextResolver<T> getContextResolver(Class<T> contextType, MediaType mediaType); }
A Providers instance is injectable into MessageBodyReader or Writers:
@Provider @Consumes("multipart/fixed") public class MultipartProvider implements MessageBodyReader { private @Context Providers providers; ... }
XML document parsers are subject to a form of attack known as the XXE (Xml eXternal Entity) Attack (http://www.securiteam.com/securitynews/6D0100A5PU.html), in which expanding an external entity causes an unsafe file to be loaded. For example, the document
<?xml version="1.0"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <search> <user>bill</user> <file>&xxe;<file> </search>
could cause the passwd file to be loaded.
By default, Resteasy's built-in unmarshaller for org.w3c.dom.Document documents will not expand external entities, replacing them by the empty string instead. It can be configured to replace external entities by values defined in the DTD by setting the context parameter
resteasy.document.expand.entity.references
to "true" in the web.xml file:
<context-param> <param-name>resteasy.document.expand.entity.references</param-name> <param-value>true</param-value> </context-param>
Another way of dealing with the problem is by prohibiting DTDs, which Resteasy does by default. This behavior can be changed by setting the context parameter
resteasy.document.secure.disableDTDs
to "false".
Documents are also subject to Denial of Service Attacks when buffers are overrun by large entities or too many attributes. For example, if a DTD defined the following entities
<!ENTITY foo 'foo'> <!ENTITY foo1 '&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;&foo;'> <!ENTITY foo2 '&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;&foo1;'> <!ENTITY foo3 '&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;&foo2;'> <!ENTITY foo4 '&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;&foo3;'> <!ENTITY foo5 '&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;&foo4;'> <!ENTITY foo6 '&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;&foo5;'>
then the expansion of &foo6; would result in 1,000,000 foos. By default, Resteasy will limit the number of expansions and the number of attributes per entity. The exact behavior depends on the underlying parser. The limits can be turned off by setting the context parameter
resteasy.document.secure.processing.feature
to "false".
The JAX-RS specification says
When writing responses, implementations SHOULD respect application-supplied character set metadata and SHOULD use UTF-8 if a character set is not specified by the application or if the application specifies a character set that is unsupported.
On the other hand, the HTTP specification says
When no explicit charset parameter is provided by the sender, media subtypes of the "text" type are defined to have a default charset value of "ISO-8859-1" when received via HTTP. Data in character sets other than "ISO-8859-1" or its subsets MUST be labeled with an appropriate charset value.
It follows that, in the absence of a character set specified by a resource or resource method, Resteasy SHOULD use UTF-8 as the character set for text media types, and, if it does, it MUST add an explicit charset parameter to the Content-Type response header. Resteasy started adding the explicit charset parameter in releases 3.1.2.Final and 3.0.22.Final, and that new behavior could cause some compatibility problems. To specify the previous behavior, in which UTF-8 was used for text media types, but the explicit charset was not appended, the context parameter "resteasy.add.charset" may be set to "false". It defaults to "true".
Note. By "text" media types, we mean
The latter set includes "application/xml-external-parsed-entity" and "application/xml-dtd".
As required by the specification, RESTEasy JAX-RS includes support for (un)marshalling JAXB annotated classes. RESTEasy provides multiple JAXB Providers to address some subtle differences between classes generated by XJC and classes which are simply annotated with @XmlRootElement, or working with JAXBElement classes directly.
For the most part, developers using the JAX-RS API, the selection of which provider is invoked will be completely transparent. For developers wishing to access the providers directly (which most folks won't need to do), this document describes which provider is best suited for different configurations.
A JAXB Provider is selected by RESTEasy when a parameter or return type is an object that is annotated with JAXB annotations (such as @XmlRootEntity or @XmlType) or if the type is a JAXBElement. Additionally, the resource class or resource method will be annotated with either a @Consumes or @Produces annotation and contain one or more of the following values:
RESTEasy will select a different provider based on the return type or parameter type used in the resource. This section describes how the selection process works.
@XmlRootEntity When a class is annotated with a @XmlRootElement annotation, RESTEasy will select the JAXBXmlRootElementProvider. This provider handles basic marshaling and unmarshalling of custom JAXB entities.
@XmlType Classes which have been generated by XJC will most likely not contain an @XmlRootEntity annotation. In order for these classes to marshalled, they must be wrapped within a JAXBElement instance. This is typically accomplished by invoking a method on the class which serves as the XmlRegistry and is named ObjectFactory.
The JAXBXmlTypeProvider provider is selected when the class is annotated with an XmlType annotation and not an XmlRootElement annotation.
This provider simplifies this task by attempting to locate the XmlRegistry for the target class. By default, a JAXB implementation will create a class called ObjectFactory and is located in the same package as the target class. When this class is located, it will contain a "create" method that takes the object instance as a parameter. For example, if the target type is called "Contact", then the ObjectFactory class will have a method:
public JAXBElement createContact(Contact value) {..
JAXBElement<?> If your resource works with the JAXBElement class directly, the RESTEasy runtime will select the JAXBElementProvider. This provider examines the ParameterizedType value of the JAXBElement in order to select the appropriate JAXBContext.
Resteasy's JAXB providers have a pluggable way to decorate Marshaller and Unmarshaller instances. The way it works is that you can write an annotation that can trigger the decoration of a Marshaller or Unmarshaller. Your decorators can do things like set Marshaller or Unmarshaller properties, set up validation, stuff like that. Here's an example. Let's say we want to have an annotation that will trigger pretty-printing, nice formatting, of an XML document. If we were doing raw JAXB, we would set a property on the Marshaller of Marshaller.JAXB_FORMATTED_OUTPUT. Let's write a Marshaller decorator.
First we define a annotation:
import org.jboss.resteasy.annotations.Decorator; @Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Decorator(processor = PrettyProcessor.class, target = Marshaller.class) public @interface Pretty {}
To get this to work, we must annotate our @Pretty annotation with a meta-annotation called @Decorator. The target() attribute must be the JAXB Marshaller class. The processor() attribute is a class we will write next.
import org.jboss.resteasy.core.interception.DecoratorProcessor; import org.jboss.resteasy.annotations.DecorateTypes; import javax.xml.bind.Marshaller; import javax.xml.bind.PropertyException; import javax.ws.rs.core.MediaType; import javax.ws.rs.Produces; import java.lang.annotation.Annotation; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ @DecorateTypes({"text/*+xml", "application/*+xml"}) public class PrettyProcessor implements DecoratorProcessor<Marshaller, Pretty> { public Marshaller decorate(Marshaller target, Pretty annotation, Class type, Annotation[] annotations, MediaType mediaType) { target.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); } }
The processor implementation must implement the DecoratorProcessor interface and should also be annotated with @DecorateTypes. This annotation specifies what media types the processor can be used with. Now that we've defined our annotation and our Processor, we can use it on our JAX-RS resource methods or JAXB types as follows:
@GET @Pretty @Produces("application/xml") public SomeJAXBObject get() {...}
If you are confused, check the Resteasy source code for the implementation of @XmlHeader
You should not use this feature unless you know what you're doing.
Based on the class you are marshalling/unmarshalling, RESTEasy will, by default create and cache JAXBContext instances per class type. If you do not want RESTEasy to create JAXBContexts, you can plug-in your own by implementing an instance of javax.ws.rs.ext.ContextResolver
public interface ContextResolver<T> { T getContext(Class<?> type); } @Provider @Produces("application/xml") public class MyJAXBContextResolver implements ContextResolver<JAXBContext> { JAXBContext getContext(Class<?> type) { if (type.equals(WhateverClassIsOverridedFor.class)) return JAXBContext.newInstance()...; } }
You must provide a @Produces annotation to specify the media type the context is meant for. You must also make sure to implement ContextResolver<JAXBContext>. This helps the runtime match to the correct context resolver. You must also annotate the ContextResolver class with @Provider.
There are multiple ways to make this ContextResolver available.
Resteasy is required to provide JAXB provider support for XML. It has a few extra annotations that can help code your app.
Sometimes when outputting XML documents you may want to set an XML header. Resteasy provides the @org.jboss.resteasy.annotations.providers.jaxb.XmlHeader annotation for this. For example:
@XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/header") @Produces("application/xml") @XmlHeader("<?xml-stylesheet type='text/xsl' href='${baseuri}foo.xsl' ?>") public Thing get() { Thing thing = new Thing(); thing.setName("bill"); return thing; } }
The @XmlHeader here forces the XML output to have an xml-stylesheet header. This header could also have been put on the Thing class to get the same result. See the javadocs for more details on how you can use substitution values provided by resteasy.
Resteasy also has a convenience annotation for stylesheet headers. For example:
@XmlRootElement public static class Thing { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } @Path("/test") public static class TestService { @GET @Path("/stylesheet") @Produces("application/xml") @Stylesheet(type="text/css", href="${basepath}foo.xsl") @Junk public Thing getStyle() { Thing thing = new Thing(); thing.setName("bill"); return thing; } }
RESTEasy allows you to marshall JAXB annotated POJOs to and from JSON. This provider wraps the Jettison JSON library to accomplish this. You can obtain more information about Jettison and how it works from:
http://jettison.codehaus.org/
To use this integration with Jettision you need to import the resteasy-jettison-provider Maven module. Older versions of RESTEasy used to include this within the resteasy-jaxb-provider but we decided to modularize it more.
Jettison has two mapping formats. One is BadgerFish the other is a Jettison Mapped Convention format. The Mapped Convention is the default mapping.
For example, consider this JAXB class:
@XmlRootElement(name = "book") public class Book { private String author; private String ISBN; private String title; public Book() { } public Book(String author, String ISBN, String title) { this.author = author; this.ISBN = ISBN; this.title = title; } @XmlElement public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @XmlElement public String getISBN() { return ISBN; } public void setISBN(String ISBN) { this.ISBN = ISBN; } @XmlAttribute public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
This is how the JAXB Book class would be marshalled to JSON using the BadgerFish Convention:
{"book": { "@title":"EJB 3.0", "author":{"$":"Bill Burke"}, "ISBN":{"$":"596529260"} } }
Notice that element values have a map associated with them and to get to the value of the element, you must access the "$" variable. Here's an example of accessing the book in Javascript:
var data = eval("(" + xhr.responseText + ")"); document.getElementById("zone").innerHTML = data.book.@title; document.getElementById("zone").innerHTML += data.book.author.$;
To use the BadgerFish Convention you must use the @org.jboss.resteasy.annotations.providers.jaxb.json.BadgerFish annotation on the JAXB class you are marshalling/unmarshalling, or, on the JAX-RS resource method or parameter:
@BadgerFish @XmlRootElement(name = "book") public class Book {...}
If you are returning a book on the JAX-RS method and you don't want to (or can't) pollute your JAXB classes with RESTEasy annotations, add the annotation to the JAX-RS method:
@BadgerFish @GET public Book getBook(...) {...}
If a Book is your input then you put it on the parameter:
@POST public void newBook(@BadgerFish Book book) {...}
The default Jettison Mapped Convention would return JSON that looked like this:
{ "book" : { "@title":"EJB 3.0", "author":"Bill Burke", "ISBN":596529260 } }
Notice that the @XmlAttribute "title" is prefixed with the '@' character. Unlike BadgerFish, the '$' does not represent the value of element text. This format is a bit simpler than the BadgerFish convention which is why it was chose as a default. Here's an example of accessing this in Javascript:
var data = eval("(" + xhr.responseText + ")"); document.getElementById("zone").innerHTML = data.book.@title; document.getElementById("zone").innerHTML += data.book.author;
The Mapped Convention allows you to fine tune the JAXB mapping using the @org.jboss.resteasy.annotations.providers.jaxb.json.Mapped annotation. You can provide an XML Namespace to JSON namespace mapping. For example, if you defined your JAXB namespace within your package-info.java class like this:
@javax.xml.bind.annotation.XmlSchema(namespace="http://jboss.org/books") package org.jboss.resteasy.test.books;
You would have to define a JSON to XML namespace mapping or you would receive an exception of something like this:
java.lang.IllegalStateException: Invalid JSON namespace: http://jboss.org/books at org.codehaus.jettison.mapped.MappedNamespaceConvention.getJSONNamespace(MappedNamespaceConvention.java:151) at org.codehaus.jettison.mapped.MappedNamespaceConvention.createKey(MappedNamespaceConvention.java:158) at org.codehaus.jettison.mapped.MappedXMLStreamWriter.writeStartElement(MappedXMLStreamWriter.java:241)
To fix this problem you need another annotation, @Mapped. You use the @Mapped annotation on your JAXB classes, on your JAX-RS resource method, or on the parameter you are unmarshalling
import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped; import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap; ... @GET @Produces("application/json") @Mapped(namespaceMap = { @XmlNsMap(namespace = "http://jboss.org/books", jsonName = "books") }) public Book get() {...}
Besides mapping XML to JSON namespaces, you can also force @XmlAttribute's to be marshaled as XMLElements.
@Mapped(attributeAsElements={"title"}) @XmlRootElement(name = "book") public class Book {...}
If you are returning a book on the JAX-RS method and you don't want to (or can't) pollute your JAXB classes with RESTEasy annotations, add the annotation to the JAX-RS method:
@Mapped(attributeAsElements={"title"}) @GET public Book getBook(...) {...}
If a Book is your input then you put it on the parameter:
@POST public void newBook(@Mapped(attributeAsElements={"title"}) Book book) {...}
RESTEasy supports the FastinfoSet mime type with JAXB annotated classes. Fast infoset documents are faster to serialize and parse, and smaller in size, than logically equivalent XML documents. Thus, fast infoset documents may be used whenever the size and processing time of XML documents is an issue. It is configured the same way the XML JAXB provider is so really no other documentation is needed here.
To use this integration with Fastinfoset you need to import the resteasy-fastinfoset-provider Maven module. Older versions of RESTEasy used to include this within the resteasy-jaxb-provider but we decided to modularize it more.
RESTEasy will automatically marshal arrays, java.util.Set's, and java.util.List's of JAXB objects to and from XML, JSON, Fastinfoset (or any other new JAXB mapper Restasy comes up with).
@XmlRootElement(name = "customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement private String name; public Customer() { } public Customer(String name) { this.name = name; } public String getName() { return name; } } @Path("/") public class MyResource { @PUT @Path("array") @Consumes("application/xml") public void putCustomers(Customer[] customers) { Assert.assertEquals("bill", customers[0].getName()); Assert.assertEquals("monica", customers[1].getName()); } @GET @Path("set") @Produces("application/xml") public Set<Customer> getCustomerSet() { HashSet<Customer> set = new HashSet<Customer>(); set.add(new Customer("bill")); set.add(new Customer("monica")); return set; } @PUT @Path("list") @Consumes("application/xml") public void putCustomers(List<Customer> customers) { Assert.assertEquals("bill", customers.get(0).getName()); Assert.assertEquals("monica", customers.get(1).getName()); } }
The above resource can publish and receive JAXB objects. It is assumed that are wrapped in a collection element
<collection> <customer><name>bill</name></customer> <customer><name>monica</name></customer> <collection>
You can change the namespace URI, namespace tag, and collection element name by using the @org.jboss.resteasy.annotations.providers.jaxb.Wrapped annotation on a parameter or method
@Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Wrapped { String element() default "collection"; String namespace() default "http://jboss.org/resteasy"; String prefix() default "resteasy"; }
So, if we wanted to output this XML
<foo:list xmlns:foo="http://foo.org"> <customer><name>bill</name></customer> <customer><name>monica</name></customer> </foo:list>
We would use the @Wrapped annotation as follows:
@GET @Path("list") @Produces("application/xml") @Wrapped(element="list", namespace="http://foo.org", prefix="foo") public List<Customer> getCustomerSet() { List<Customer> list = new ArrayList<Customer>(); list.add(new Customer("bill")); list.add(new Customer("monica")); return list; }
Resteasy supports using collections with JSON. It encloses lists, sets, or arrays of returned JAXB objects within a simple JSON array. For example:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public static class Foo { @XmlAttribute private String test; public Foo() { } public Foo(String test) { this.test = test; } public String getTest() { return test; } public void setTest(String test) { this.test = test; } }
This a List or array of this Foo class would be represented in JSON like this:
[{"foo":{"@test":"bill"}},{"foo":{"@test":"monica}"}}]
It also expects this format for input
RESTEasy will automatically marshal maps of JAXB objects to and from XML, JSON, Fastinfoset (or any other new JAXB mapper Restasy comes up with). Your parameter or method return type must be a generic with a String as the key and the JAXB object's type.
@XmlRootElement(namespace = "http://foo.com") public static class Foo { @XmlAttribute private String name; public Foo() { } public Foo(String name) { this.name = name; } public String getName() { return name; } } @Path("/map") public static class MyResource { @POST @Produces("application/xml") @Consumes("application/xml") public Map<String, Foo> post(Map<String, Foo> map) { Assert.assertEquals(2, map.size()); Assert.assertNotNull(map.get("bill")); Assert.assertNotNull(map.get("monica")); Assert.assertEquals(map.get("bill").getName(), "bill"); Assert.assertEquals(map.get("monica").getName(), "monica"); return map; } }
The above resource can publish and receive JAXB objects within a map. By default, they are wrapped in a "map" element in the default namespace. Also, each "map" element has zero or more "entry" elements with a "key" attribute.
<map> <entry key="bill" xmlns="http://foo.com"> <foo name="bill"/> </entry> <entry key="monica" xmlns="http://foo.com"> <foo name="monica"/> </entry> </map>
You can change the namespace URI, namespace prefix and map, entry, and key element and attribute names by using the @org.jboss.resteasy.annotations.providers.jaxb.WrappedMap annotation on a parameter or method
@Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface WrappedMap { /** * map element name */ String map() default "map"; /** * entry element name * */ String entry() default "entry"; /** * entry's key attribute name */ String key() default "key"; String namespace() default ""; String prefix() default ""; }
So, if we wanted to output this XML
<hashmap> <hashentry hashkey="bill" xmlns:foo="http://foo.com"> <foo:foo name="bill"/> </hashentry> </map>
We would use the @WrappedMap annotation as follows:
@Path("/map") public static class MyResource { @GET @Produces("application/xml") @WrappedMap(map="hashmap", entry="hashentry", key="hashkey") public Map<String, Foo> get() { ... return map; } }
Resteasy supports using maps with JSON. It encloses maps returned JAXB objects within a simple JSON map. For example:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public static class Foo { @XmlAttribute private String test; public Foo() { } public Foo(String test) { this.test = test; } public String getTest() { return test; } public void setTest(String test) { this.test = test; } }
This a List or array of this Foo class would be represented in JSON like this:
{ "entry1" : {"foo":{"@test":"bill"}}, "entry2" : {"foo":{"@test":"monica}"}}}
It also expects this format for input
If you have the resteasy-jackson-provider-xxx.jar in your classpath, the Jackson JSON provider will be triggered. This will screw up code that is dependent on the Jettison JAXB/JSon provider. If you had been using the Jettison JAXB/Json providers, you must either remove Jackson from your WEB-INF/lib or classpath, or use the @NoJackson annotation on your JAXB classes.
Some objects models use abstract classes and interfaces heavily. Unfortunately, JAXB doesn't work with interfaces that are root elements and RESTEasy can't unmarshal parameters that are interfaces or raw abstract classes because it doesn't have enough information to create a JAXBContext. For example:
public interface IFoo {} @XmlRootElement public class RealFoo implements IFoo {} @Path("/jaxb") public class MyResource { @PUT @Consumes("application/xml") public void put(IFoo foo) {...} }
In this example, you would get an error from RESTEasy of something like "Cannot find a MessageBodyReader for...". This is because RESTEasy does not know that implementations of IFoo are JAXB classes and doesn't know how to create a JAXBContext for it. As a workaround, RESTEasy allows you to use the JAXB annotation @XmlSeeAlso on the interface to correct the problem. (NOTE, this will not work with manual, hand-coded JAXB).
@XmlSeeAlso(RealFoo.class) public interface IFoo {}
The extra @XmlSeeAlso on IFoo allows RESTEasy to create a JAXBContext that knows how to unmarshal RealFoo instances.
As a consumer of XML datasets, JAXB is subject to a form of attack known as the XXE (Xml eXternal Entity) Attack (http://www.securiteam.com/securitynews/6D0100A5PU.html), in which expanding an external entity causes an unsafe file to be loaded. Preventing the expansion of external entities is discussed in Section 18.4, “Configuring Document Marshalling”. The same context parameter,
resteasy.document.expand.entity.references
applies to JAXB unmarshallers as well.
Section 18.4, “Configuring Document Marshalling” also discusses the prohibition of DTDs and the imposition of limits on entity expansion and the number of attributes per element. The context parameters
resteasy.document.secure.disableDTDs
and
resteasy.document.secure.processing.feature
discussed there, and their default values, also apply to the representation of JAXB objects.
From W3.org (http://tools.ietf.org/html/rfc4287):
"Atom is an XML-based document format that describes lists of related information known as "feeds". Feeds are composed of a number of items, known as "entries", each with an extensible set of attached metadata. For example, each entry has a title. The primary use case that Atom addresses is the syndication of Web content such as weblogs and news headlines to Web sites as well as directly to user agents."
Atom is the next-gen RSS feed. Although it is used primarily for the syndication of blogs and news, many are starting to use this format as the envelope for Web Services, for example, distributed notifications, job queues, or simply a nice format for sending or receiving data in bulk from a service.
RESTEasy has defined a simple object model in Java to represent Atom and uses JAXB to marshal and unmarshal it. The main classes are in the org.jboss.resteasy.plugins.providers.atom package and are Feed, Entry, Content, and Link. If you look at the source, you'd see that these are annotated with JAXB annotations. The distribution contains the javadocs for this project and are a must to learn the model. Here is a simple example of sending an atom feed using the Resteasy API.
import org.jboss.resteasy.plugins.providers.atom.Content; import org.jboss.resteasy.plugins.providers.atom.Entry; import org.jboss.resteasy.plugins.providers.atom.Feed; import org.jboss.resteasy.plugins.providers.atom.Link; import org.jboss.resteasy.plugins.providers.atom.Person; @Path("atom") public class MyAtomService { @GET @Path("feed") @Produces("application/atom+xml") public Feed getFeed() throws URISyntaxException { Feed feed = new Feed(); feed.setId(new URI("http://example.com/42")); feed.setTitle("My Feed"); feed.setUpdated(new Date()); Link link = new Link(); link.setHref(new URI("http://localhost")); link.setRel("edit"); feed.getLinks().add(link); feed.getAuthors().add(new Person("Bill Burke")); Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setType(MediaType.TEXT_HTML_TYPE); content.setText("Nothing much"); entry.setContent(content); feed.getEntries().add(entry); return feed; } }
Because Resteasy's atom provider is JAXB based, you are not limited to sending atom objects using XML. You can automatically re-use all the other JAXB providers that Resteasy has like JSON and fastinfoset. All you have to do is have "atom+" in front of the main subtype. i.e. @Produces("application/atom+json") or @Consumes("application/atom+fastinfoset")
The org.jboss.resteasy.plugins.providers.atom.Content class allows you to unmarshal and marshal JAXB annotated objects that are the body of the content. Here's an example of sending an Entry with a Customer object attached as the body of the entry's content.
@XmlRootElement(namespace = "http://jboss.org/Customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement private String name; public Customer() { } public Customer(String name) { this.name = name; } public String getName() { return name; } } @Path("atom") public static class AtomServer { @GET @Path("entry") @Produces("application/atom+xml") public Entry getEntry() { Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setJAXBObject(new Customer("bill")); entry.setContent(content); return entry; } }
The Content.setJAXBObject() method is used to tell the content object you are sending back a Java JAXB object and want it marshalled appropriately. If you are using a different base format other than XML, i.e. "application/atom+json", this attached JAXB object will be marshalled into that same format.
If you have an atom document as your input, you can also extract JAXB objects from Content using the Content.getJAXBObject(Class clazz) method. Here is an example of an input atom document and extracting a Customer object from the content.
@Path("atom") public static class AtomServer { @PUT @Path("entry") @Produces("application/atom+xml") public void putCustomer(Entry entry) { Content content = entry.getContent(); Customer cust = content.getJAXBObject(Customer.class); } }
Besides the Jettision JAXB adapter for JSON, Resteasy also supports integration with the Jackson project. Many users find the output from Jackson much nicer than the Badger format or Mapped format provided by Jettison. For more on Jackson 2, see http://wiki.fasterxml.com/JacksonHome. Besides JAXB like APIs, it has a JavaBean based model, described at http://wiki.fasterxml.com/JacksonDataBinding, which allows you to easily marshal Java objects to and from JSON. Resteasy integrates with the JavaBean model. While Jackson does come with its own JAX-RS integration, Resteasy expanded it a little, as decribed below.
NOTE. The resteasy-jackson-provider module, which is based on the outdated Jackson 1.9.x, is currently deprecated, and will be removed in a release subsequent to 3.1.0.Final. The resteasy-jackson2-provider module is based on Jackson 2.
If you're deploying Resteasy outside of WildFly, add the Resteasy Jackson provder to your WAR pom.xml build:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson-provider</artifactId> <version>${version.resteasy}</version> </dependency>
If you're deploying Resteasy with WildFly 8, there's nothing you need to do except to make sure you've updated your installation with the latest and greatest Resteasy. See the Installation/Configuration section of this documentation for more details.
If you're deploying Resteasy outside of WildFly, add the Resteasy Jackson provder to your WAR pom.xml build:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jackson2-provider</artifactId> <version>${version.resteasy}</version> </dependency>
If you're deploying Resteasy with WildFly 9 or above, there's nothing you need to do except to make sure you've updated your installation with the latest and greatest Resteasy. See the Installation/Configuration section of this documentation for more details.
The first extra piece that Resteasy added to the integration was to support "application/*+json". Jackson would only accept "application/json" and "text/json" as valid media types. This allows you to create json-based media types and still let Jackson marshal things for you. For example:
@Path("/customers") public class MyService { @GET @Produces("application/vnd.customer+json") public Customer[] getCustomers() {} }
Another problem that occurs is when you are using the Resteasy JAXB providers alongside Jackson. You may want to use Jettison and JAXB to output your JSON instead of Jackson. In this case, you must either not install the Jackson provider, or use the annotation @org.jboss.resteasy.annotations.providers.NoJackson on your JAXB annotated classes. For example:
@XmlRootElement @NoJackson public class Customer {...} @Path("/customers") public class MyService { @GET @Produces("application/vnd.customer+json") public Customer[] getCustomers() {} }
If you can't annotate the JAXB class with @NoJackson, then you can use the annotation on a method parameter. For example:
@XmlRootElement public class Customer {...} @Path("/customers") public class MyService { @GET @Produces("application/vnd.customer+json") @NoJackson public Customer[] getCustomers() {} @POST @Consumes("application/vnd.customer+json") public void createCustomer(@NoJackson Customer[] customers) {...} }
If your Jackson classes are annotated with JAXB annotations and you have the resteasy-jaxb-provider in your classpath, you may trigger the Jettision JAXB marshalling code. To turn off the JAXB json marshaller use the @org.jboss.resteasy.annotations.providers.jaxb.IgnoreMediaTypes("application/*+json") on your classes.
If you're using Jackson, Resteasy has JSONP that you can turn on by adding
the provider org.jboss.resteasy.plugins.providers.jackson.JacksonJsonpInterceptor
(Jackson2JsonpInterceptor if you're using the Jackson2 provider)
to your deployments.
If the media type of the response is json and a callback query parameter is given, the response will be a javascript snippet with
a method call of the method defined by the callback parameter.
For example:
GET /resources/stuff?callback=processStuffResponse
will produce this response:
processStuffResponse(<nomal JSON body>)
This supports the default behavior of jQuery.
To enable JacksonJsonpInterceptor in WildFly, you need to import annotations from org.jboss.resteasy.resteasy-jackson-provider
module using jboss-deployment-structure.xml:
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-jackson-provider" annotations="true"/> </dependencies> </deployment> </jboss-deployment-structure>
You can change the name of the callback parameter by setting the callbackQueryParameter property.
JacksonJsonpInterceptor can wrap the response into a try-catch block:
try{processStuffResponse(<normal JSON body>)}catch(e){}
You can enable this feature by setting the resteasy.jsonp.silent property to true
Note. Because JSONP can be used in
Cross Site Scripting Inclusion (XSSI) attacks,
Jackson2JsonpInterceptor
is disabled by default. Two steps are necessary to enable it:
Jackson2JsonpInterceptor
must be
included in the deployment. For example,
a service file META-INF/services/javax.ws.rs.ext.Providers with the line
org.jboss.resteasy.plugins.providers.jackson.Jackson2JsonpInterceptormay be included on the classpath
If you are using the Jackson 2 provider, Resteasy has provided a pretty-printing annotation simliar with the one in JAXB provider:
org.jboss.resteasy.annotations.providers.jackson.Formatted
Here is an example:
@GET @Produces("application/json") @Path("/formatted/{id}") @Formatted public Product getFormattedProduct() { return new Product(333, "robot"); }
As the example shown above, the @Formatted annotation will enable the underlying Jackson option "SerializationFeature.INDENT_OUTPUT".
In Jackson2 , there is new feature JsonFilter to allow annotate class with @JsonFilter and doing dynamic filtering. Here is an example which defines mapping from "nameFilter" to filter instances and filter bean properties when serilize to json format:
@JsonFilter(value="nameFilter") public class Jackson2Product { protected String name; protected int id; public Jackson2Product() { } public Jackson2Product(final int id, final String name) { this.id = id; this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } }
@JsonFilter annotates resource class to filter out some property not to serialize in the json response. To map the filter id and instance we need to create another jackson class to add the id and filter instance map:
public class ObjectFilterModifier extends ObjectWriterModifier { public ObjectFilterModifier() { } @Override public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> httpHeaders, Object valueToWrite, ObjectWriter w, JsonGenerator jg) throws IOException { FilterProvider filterProvider = new SimpleFilterProvider().addFilter( "nameFilter", SimpleBeanPropertyFilter.filterOutAllExcept("name")); return w.with(filterProvider); } }
Here the method modify()
will take care of filtering all properties except "name" property before write. To make this work, we need let Resteasy know this mapping info. This can be easily set either in a WriterInterceptor:
@Provider public class JsonFilterWriteInterceptor implements WriterInterceptor{ private ObjectFilterModifier modifier = new ObjectFilterModifier(); @Override public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { //set a threadlocal modifier ObjectWriterInjector.set(modifier); context.proceed(); } }
or in a servlet filter:
public class ObjectWriterModifierFilter implements Filter { private static ObjectFilterModifier modifier = new ObjectFilterModifier(); @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { ObjectWriterInjector.set(modifier); chain.doFilter(request, response); } @Override public void destroy() { } }
Resteasy get this modfiler from ThreadLocal variable and configure to modify ObjectWriter before write to response.
No, this is not the JSONP you are thinking of! JSON-P is a new Java EE 7 JSON parsing API. Horrible name for a new JSON parsing API! What were they thinking? Anyways, Resteasy has a provider for it. If you are using WildFly, it is required by Java EE 7 so you will have it automatically bundled. Otherwise, use this maven dependency.
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-json-p-provider</artifactId> <version>3.0.22.Final</version> </dependency>
It has built in support for JsonObject, JsonArray, and JsonStructure as request or response entities. It should not conflict with Jackson or Jettison if you have that in your path too.
Resteasy has rich support for the "multipart/*" and "multipart/form-data" mime types. The multipart mime format is used to pass lists of content bodies. Multiple content bodies are embedded in one message. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it.
RESTEasy provides a custom API for reading and writing multipart types as well as marshalling arbitrary List (for any multipart type) and Map (multipart/form-data only) objects
When writing a JAX-RS service, RESTEasy provides an interface that allows you to read in any multipart mime type. org.jboss.resteasy.plugins.providers.multipart.MultipartInput
package org.jboss.resteasy.plugins.providers.multipart; public interface MultipartInput { List<InputPart> getParts(); String getPreamble(); // You must call close to delete any temporary files created // Otherwise they will be deleted on garbage collection or on JVM exit void close(); } public interface InputPart { MultivaluedMap<String, String> getHeaders(); String getBodyAsString(); <T> T getBody(Class<T> type, Type genericType) throws IOException; <T> T getBody(org.jboss.resteasy.util.GenericType<T> type) throws IOException; MediaType getMediaType(); boolean isContentTypeFromMessage(); }
MultipartInput is a simple interface that allows you to get access to each part of the multipart message. Each part is represented by an InputPart interface. Each part has a set of headers associated with it You can unmarshall the part by calling one of the getBody() methods. The Type genericType parameter can be null, but the Class type parameter must be set. Resteasy will find a MessageBodyReader based on the media type of the part as well as the type information you pass in. The following piece of code is unmarshalling parts which are XML into a JAXB annotated class called Customer.
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(MultipartInput input) { List<Customer> customers = new ArrayList...; for (InputPart part : input.getParts()) { Customer cust = part.getBody(Customer.class, null); customers.add(cust); } input.close(); } }
Sometimes you may want to unmarshall a body part that is sensitive to generic type metadata. In this case you can use the org.jboss.resteasy.util.GenericType class. Here's an example of unmarshalling a type that is sensitive to generic type metadata.
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(MultipartInput input) { for (InputPart part : input.getParts()) { List<Customer> cust = part.getBody(new GenericType>List>Customer<<() {}); } input.close(); } }
Use of GenericType is required because it is really the only way to obtain generic type information at runtime.
If your body parts are uniform, you do not have to manually unmarshall each and every part. You can just provide a java.util.List as your input parameter. It must have the type it is unmarshalling with the generic parameter of the List type declaration. Here's an example again of unmarshalling a list of customers.
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(List<Customer> customers) { ... } }
When writing a JAX-RS service, RESTEasy provides an interface that allows you to read in multipart/form-data mime type. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it. The interface used for form-data input is org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput
public interface MultipartFormDataInput extends MultipartInput { @Deprecated Map<String, InputPart> getFormData(); Map<String, List<InputPart>> getFormDataMap(); <T> T getFormDataPart(String key, Class<T> rawType, Type genericType) throws IOException; <T> T getFormDataPart(String key, GenericType<T> type) throws IOException; }
It works in much the same way as MultipartInput described earlier in this chapter.
With form-data, if your body parts are uniform, you do not have to manually unmarshall each and every part. You can just provide a java.util.Map as your input parameter. It must have the type it is unmarshalling with the generic parameter of the List type declaration. Here's an example of of unmarshalling a Map of Customer objects which are JAXB annotated classes.
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/form-data") public void put(Map<String, Customer> customers) { ... } }
When writing a JAX-RS service, RESTEasy provides an interface that allows you to read in multipart/related mime type. A multipart/related is used to indicate that message parts should not be considered individually but rather as parts of an aggregate whole. One example usage for multipart/related is to send a web page complete with images in a single message. Every multipart/related message has a root/start part that references the other parts of the message. The parts are identified by their "Content-ID" headers. multipart/related is defined by RFC 2387. The interface used for related input is org.jboss.resteasy.plugins.providers.multipart.MultipartRelatedInput
public interface MultipartRelatedInput extends MultipartInput { String getType(); String getStart(); String getStartInfo(); InputPart getRootPart(); Map<String, InputPart> getRelatedMap(); }
It works in much the same way as MultipartInput described earlier in this chapter.
RESTEasy provides a simple API to output multipart data.
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartOutput { public OutputPart addPart(Object entity, MediaType mediaType) public OutputPart addPart(Object entity, GenericType type, MediaType mediaType) public OutputPart addPart(Object entity, Class type, Type genericType, MediaType mediaType) public List<OutputPart> getParts() public String getBoundary() public void setBoundary(String boundary) } public class OutputPart { public MultivaluedMap<String, Object> getHeaders() public Object getEntity() public Class getType() public Type getGenericType() public MediaType getMediaType() }
When you want to output multipart data it is as simple as creating a MultipartOutput object and calling addPart() methods. Resteasy will automatically find a MessageBodyWriter to marshall your entity objects. Like MultipartInput, sometimes you may have marshalling which is sensitive to generic type metadata. In that case, use GenericType. Most of the time though passing in an Object and its MediaType is enough. In the example below, we are sending back a "multipart/mixed" format back to the calling client. The parts are Customer objects which are JAXB annotated and will be marshalling into "application/xml".
@Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") public MultipartOutput get() { MultipartOutput output = new MultipartOutput(); output.addPart(new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart(new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; } }
If your body parts are uniform, you do not have to manually marshall each and every part or even use a MultipartOutput object.. You can just provide a java.util.List. It must have the generic type it is marshalling with the generic parameter of the List type declaration. You must also annotate the method with the @PartType annotation to specify what media type each part is. Here's an example of sending back a list of customers back to a client. The customers are JAXB objects
@Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") @PartType("application/xml") public List<Customer> get() { ... } }
RESTEasy provides a simple API to output multipart/form-data.
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartFormDataOutput extends MultipartOutput { public OutputPart addFormData(String key, Object entity, MediaType mediaType) public OutputPart addFormData(String key, Object entity, GenericType type, MediaType mediaType) public OutputPart addFormData(String key, Object entity, Class type, Type genericType, MediaType mediaType) public Map<String, OutputPart> getFormData() }
When you want to output multipart/form-data it is as simple as creating a MultipartFormDataOutput object and calling addFormData() methods. Resteasy will automatically find a MessageBodyWriter to marshall your entity objects. Like MultipartInput, sometimes you may have marshalling which is sensitive to generic type metadata. In that case, use GenericType. Most of the time though passing in an Object and its MediaType is enough. In the example below, we are sending back a "multipart/form-data" format back to the calling client. The parts are Customer objects which are JAXB annotated and will be marshalling into "application/xml".
@Path("/form") public class MyService { @GET @Produces("multipart/form-data") public MultipartFormDataOutput get() { MultipartFormDataOutput output = new MultipartFormDataOutput(); output.addPart("bill", new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart("monica", new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; } }
If your body parts are uniform, you do not have to manually marshall each and every part or even use a MultipartFormDataOutput object.. You can just provide a java.util.Map. It must have the generic type it is marshalling with the generic parameter of the Map type declaration. You must also annotate the method with the @PartType annotation to specify what media type each part is. Here's an example of sending back a list of customers back to a client. The customers are JAXB objects
@Path("/multipart") public class MyService { @GET @Produces("multipart/form-data") @PartType("application/xml") public Map<String, Customer> get() { ... } }
RESTEasy provides a simple API to output multipart/related.
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartRelatedOutput extends MultipartOutput { public OutputPart getRootPart() public OutputPart addPart(Object entity, MediaType mediaType, String contentId, String contentTransferEncoding) public String getStartInfo() public void setStartInfo(String startInfo) }
When you want to output multipart/related it is as simple as creating a MultipartRelatedOutput object and calling addPart() methods. The first added part will be used as the root part of the multipart/related message. Resteasy will automatically find a MessageBodyWriter to marshall your entity objects. Like MultipartInput, sometimes you may have marshalling which is sensitive to generic type metadata. In that case, use GenericType. Most of the time though passing in an Object and its MediaType is enough. In the example below, we are sending back a "multipart/related" format back to the calling client. We are sending a html with 2 images.
@Path("/related") public class MyService { @GET @Produces("multipart/related") public MultipartRelatedOutput get() { MultipartRelatedOutput output = new MultipartRelatedOutput(); output.setStartInfo("text/html"); Map<String, String> mediaTypeParameters = new LinkedHashMap<String, String>(); mediaTypeParameters.put("charset", "UTF-8"); mediaTypeParameters.put("type", "text/html"); output.addPart( "<html><body>\n" + "This is me: <img src='cid:http://example.org/me.png' />\n" + "<br />This is you: <img src='cid:http://example.org/you.png' />\n" + "</body></html>", new MediaType("text", "html", mediaTypeParameters), "<mymessage.xml@example.org>", "8bit"); output.addPart("// binary octets for me png", new MediaType("image", "png"), "<http://example.org/me.png>", "binary"); output.addPart("// binary octets for you png", new MediaType( "image", "png"), "<http://example.org/you.png>", "binary"); client.putRelated(output); return output; } }
If you have a exact knowledge of your multipart/form-data packets, you can map them to and from a POJO class to and from multipart/form-data using the @org.jboss.resteasy.annotations.providers.multipart.MultipartForm annotation and the JAX-RS @FormParam annotation. You simple define a POJO with at least a default constructor and annotate its fields and/or properties with @FormParams. These @FormParams must also be annotated with @org.jboss.resteasy.annotations.providers.multipart.PartType if you are doing output. For example:
public class CustomerProblemForm { @FormParam("customer") @PartType("application/xml") private Customer customer; @FormParam("problem") @PartType("text/plain") private String problem; public Customer getCustomer() { return customer; } public void setCustomer(Customer cust) { this.customer = cust; } public String getProblem() { return problem; } public void setProblem(String problem) { this.problem = problem; } }
After defining your POJO class you can then use it to represent multipart/form-data. Here's an example of sending a CustomerProblemForm using the RESTEasy client framework:
@Path("portal") public interface CustomerPortal { @Path("issues/{id}") @Consumes("multipart/form-data") @PUT public void putProblem(@MultipartForm CustomerProblemForm, @PathParam("id") int id); } { CustomerPortal portal = ProxyFactory.create(CustomerPortal.class, "http://example.com"); CustomerProblemForm form = new CustomerProblemForm(); form.setCustomer(...); form.setProblem(...); portal.putProblem(form, 333); }
You see that the @MultipartForm annotation was used to tell RESTEasy that the object has @FormParam and that it should be marshalled from that. You can also use the same object to receive multipart data. Here is an example of the server side counterpart of our customer portal.
@Path("portal") public class CustomerPortalServer { @Path("issues/{id}) @Consumes("multipart/form-data") @PUT public void putIssue(@MultipartForm CustoemrProblemForm, @PathParam("id") int id) { ... write to database... } }
RESTEasy supports Xop messages packaged as multipart/related. What does this mean? If you have a JAXB annotated POJO that also holds some binary content you may choose to send it in such a way where the binary does not need to be encoded in any way (neither base64 neither hex). This results in faster transport while still using the convenient POJO. More about Xop can be read here: http://www.w3.org/TR/xop10/. Now lets see an example:
First we have a JAXB annotated POJO to work with. @XmlMimeType tells JAXB the mime type of the binary content (its not required to do XOP packaging but it is recommended to be set if you know the exact type):
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public static class Xop { private Customer bill; private Customer monica; @XmlMimeType(MediaType.APPLICATION_OCTET_STREAM) private byte[] myBinary; @XmlMimeType(MediaType.APPLICATION_OCTET_STREAM) private DataHandler myDataHandler; // methods, other fields ... }
In the above POJO myBinary and myDataHandler will be processed as binary attachments while the whole Xop object will be sent as xml (in the places of the binaries only their references will be generated). javax.activation.DataHandler is the most general supported type so if you need an java.io.InputStream or a javax.activation.DataSource you need to go with the DataHandler. Some other special types are supported too: java.awt.Image and javax.xml.transform.Source. Let's assume that Customer is also JAXB friendly POJO in the above example (of course it can also have binary parts). Now lets see a an example Java client that sends this:
// our client interface: @Path("mime") public static interface MultipartClient { @Path("xop") @PUT @Consumes(MultipartConstants.MULTIPART_RELATED) public void putXop(@XopWithMultipartRelated Xop bean); } // Somewhere using it: { MultipartClient client = ProxyFactory.create(MultipartClient.class, "http://www.example.org"); Xop xop = new Xop(new Customer("bill"), new Customer("monica"), "Hello Xop World!".getBytes("UTF-8"), new DataHandler(new ByteArrayDataSource("Hello Xop World!".getBytes("UTF-8"), MediaType.APPLICATION_OCTET_STREAM))); client.putXop(xop); }
We used @Consumes(MultipartConstants.MULTIPART_RELATED) to tell RESTEasy that we want to send multipart/related packages (that's the container format that will hold our Xop message). We used @XopWithMultipartRelated to tell RESTEasy that we want to make Xop messages. So we have a POJO and a client service that is willing to send it. All we need now a server that can read it:
@Path("/mime") public class XopService { @PUT @Path("xop") @Consumes(MultipartConstants.MULTIPART_RELATED) public void putXopWithMultipartRelated(@XopWithMultipartRelated Xop xop) { // do very important things here } }
We used @Consumes(MultipartConstants.MULTIPART_RELATED) to tell RESTEasy that we want to read multipart/related packages. We used @XopWithMultipartRelated to tell RESTEasy that we want to read Xop messages. Of course we could also produce Xop return values but we would than also need to annotate that and use a Produce annotation, too.
There are a lot of frameworks doing multipart parsing automatically with the help of filters and interceptors. Like org.jboss.seam.web.MultipartFilter in Seam or org.springframework.web.multipart.MultipartResolver in Spring. However the incoming multipart request stream can be parsed only once. Resteasy users working with multipart should make sure that nothing parses the stream before Resteasy gets it.
By default if no Content-Type header is present in a part, "text/plain; charset=us-ascii" is used as fallback. This is the value defined by the MIME RFC. However for example some web clients (like most, if not all, web browsers) do not send Content-Type headers for all fields in a multipart/form-data request (only for the file parts). This can cause character encoding and unmarshalling errors on the server side. To correct this there is an option to define an other, non-rfc compliant fallback value. This can be done dynamically per request with the PreProcessInterceptor infrastructure of RESTEasy. In the following example we will set "*/*; charset=UTF-8" as the new default fallback:
import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY, "*/*; charset=UTF-8"); return null; } }
Using an interceptor and the InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
attribute allows setting a default Content-Type, but it is also possible to override
the Content-Type, if any, in any input part by calling
org.jboss.resteasy.plugins.providers.multipart.InputPart.setMediaType()
.
For example:
@POST @Path("query") @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_PLAIN) public Response setMediaType(MultipartInput input) throws IOException { List<InputPart> parts = input.getParts(); InputPart part = parts.get(0); part.setMediaType(MediaType.valueOf("application/foo+xml")); String s = part.getBody(String.class, null); ... }
Sometimes, a part may have a Content-Type header with no charset parameter. If the
InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
property is set and the value has a charset parameter,
that value will be appended to an existing Content-Type header that has no charset parameter.
It is also possible to specify a default charset using the constant
InputPart.DEFAULT_CHARSET_PROPERTY
(actual value "resteasy.provider.multipart.inputpart.defaultCharset"):
import org.jboss.resteasy.plugins.providers.multipart.InputPart; @Provider @ServerInterceptor public class ContentTypeSetterPreProcessorInterceptor implements PreProcessInterceptor { public ServerResponse preProcess(HttpRequest request, ResourceMethod method) throws Failure, WebApplicationException { request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8"); return null; } }
If both InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
and
InputPart.DEFAULT_CHARSET_PROPERTY
are set, then the value of
InputPart.DEFAULT_CHARSET_PROPERTY
will override any charset in the value of
InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
.
RESTEasy comes with built in support for YAML using the SnakeYAML library. To enable YAML support, you need to drop in the SnakeYaml 1.8 jar and the resteasy-yaml-provider.jar (whatever the current version is) in RestEASY's classpath.
SnakeYaml jar file can either be downloaded from Google code at http://code.google.com/p/snakeyaml/downloads/list
Or if you use maven, the SnakeYaml jar is available through SonaType public repositories and included using this dependency:
<dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.8</version> </dependency>
When starting resteasy look out in the logs for a line stating that the YamlProvider has been added - this indicates that resteasy has found the Jyaml jar:
2877 Main INFO org.jboss.resteasy.plugins.providers.RegisterBuiltin - Adding YamlProvider
The Yaml provider recognises three mime types:
This is an example of how to use Yaml in a resource method.
import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @Path("/yaml") public class YamlResource { @GET @Produces("text/x-yaml") public MyObject getMyObject() { return createMyObject(); } ... }
@PathParam, @QueryParam, @MatrixParam, @FormParam, and @HeaderParam are represented as strings in a raw HTTP request. The specification says that these types of injected parameters can be converted to objects if these objects have a valueOf(String) static method or a constructor that takes one String parameter. What if you have a class where valueOf() or this string constructor doesn't exist or is inappropriate for an HTTP request? JAX-RS 2.0 has the javax.ws.rs.ext.ParamConverterProvider to help in this situation. See javadoc for more details.
You can build custom responses using the javax.ws.rs.core.Response and ResponseBuilder classes. If you want to do your own streaming, your entity response must be an implementation of javax.ws.rs.core.StreamingOutput. See the java doc for more information.
ExceptionMappers are custom, application provided, components that can catch thrown application exceptions and write specific HTTP responses. The are classes annotated with @Provider and that implement this interface
package javax.ws.rs.ext; import javax.ws.rs.core.Response; /** * Contract for a provider that maps Java exceptions to * {@link javax.ws.rs.core.Response}. An implementation of this interface must * be annotated with {@link Provider}. * * @see Provider * @see javax.ws.rs.core.Response */ public interface ExceptionMapper<E> { /** * Map an exception to a {@link javax.ws.rs.core.Response}. * * @param exception the exception to map to a response * @return a response mapped from the supplied exception */ Response toResponse(E exception); }
When an application exception is thrown it will be caught by the JAX-RS runtime. JAX-RS will then scan registered ExceptionMappers to see which one support marshalling the exception type thrown. Here is an example of ExceptionMapper
@Provider public class EJBExceptionMapper implements ExceptionMapper<javax.ejb.EJBException> { public Response toResponse(EJBException exception) { return Response.status(500).build(); } }
You register ExceptionMappers the same way you do MessageBodyReader/Writers. By scanning, through the resteasy provider context-param (if you're deploying via a WAR file), or programmatically through the ResteasyProviderFactory class.
Resteasy has a set of built-in exceptions that are thrown by it when it encounters errors during dispatching or marshalling. They all revolve around specific HTTP error codes. You can find them in RESTEasy's javadoc under the package org.jboss.resteasy.spi. Here's a list of them:
Table 27.1.
Exception | HTTP Code | Description |
---|---|---|
ReaderException | 400 | All exceptions thrown from MessageBodyReaders are wrapped within this exception. If there is no ExceptionMapper for the wrapped exception or if the exception isn't a WebApplicationException, then resteasy will return a 400 code by default. |
WriterException | 500 | All exceptions thrown from MessageBodyWriters are wrapped within this exception. If there is no ExceptionMapper for the wrapped exception or if the exception isn't a WebApplicationException, then resteasy will return a 400 code by default. |
o.j.r.plugins.providers.jaxb.JAXBUnmarshalException | 400 | The JAXB providers (XML and Jettison) throw this exception on reads. They may be wrapping JAXBExceptions. This class extends ReaderException |
o.j.r.plugins.providers.jaxb.JAXBMarshalException | 500 | The JAXB providers (XML and Jettison) throw this exception on writes. They may be wrapping JAXBExceptions. This class extends WriterException |
ApplicationException | N/A | This exception wraps all exceptions thrown from application code. It functions much in the same way as InvocationTargetException. If there is an ExceptionMapper for wrapped exception, then that is used to handle the request. |
Failure | N/A | Internal Resteasy. Not logged |
LoggableFailure | N/A | Internal Resteasy error. Logged |
DefaultOptionsMethodException | N/A | If the user invokes HTTP OPTIONS and no JAX-RS method for it, Resteasy provides a default behavior by throwing this exception |
If you are scanning your path for JAX-RS annotated resource beans, your beans will be registered in per-request mode. This means an instance will be created per HTTP request served. Generally, you will need information from your environment. If you are running within a servlet container using the WAR-file distribution, in Beta-2 and lower, you can only use the JNDI lookups to obtain references to Java EE resources and configuration information. In this case, define your EE configuration (i.e. ejb-ref, env-entry, persistence-context-ref, etc...) within web.xml of the resteasy WAR file. Then within your code do jndi lookups in the java:comp namespace. For example:
web.xml
<ejb-ref> <ejb-ref-name>ejb/foo</ejb-ref-name> ... </ejb-ref>
resource code:
@Path("/") public class MyBean { public Object getSomethingFromJndi() { new InitialContext.lookup("java:comp/ejb/foo"); } ... }
You can also manually configure and register your beans through the Registry. To do this in a WAR-based deployment, you need to write a specific ServletContextListener to do this. Within the listener, you can obtain a reference to the registry as follows:
public class MyManualConfig implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { Registry registry = (Registry) event.getServletContext().getAttribute(Registry.class.getName()); } ... }
Please also take a look at our Spring Integration as well as the Embedded Container's Spring Integration
Resteasy supports (though not by default - see below) GZIP decompression. If properly configured, the client framework or a JAX-RS service, upon receiving a message body with a Content-Encoding of "gzip", will automatically decompress it. The client framework can (though not by default - see below) automatically set the Accept-Encoding header to be "gzip, deflate" so you do not have to set this header yourself.
Resteasy also supports (though not by default - see below) automatic compression. If the client framework is sending a request or the server is sending a response with the Content-Encoding header set to "gzip", Resteasy will (if properly configured) do the compression. So that you do not have to set the Content-Encoding header directly, you can use the @org.jboss.resteasy.annotation.GZIP annotation.
@Path("/") public interface MyProxy { @Consumes("application/xml") @PUT public void put(@GZIP Order order); }
In the above example, we tag the outgoing message body, order, to be gzip compressed. You can use the same annotation to tag server responses
@Path("/") public class MyService { @GET @Produces("application/xml") @GZIP public String getData() {...} }
Note. Decompression carries a risk of attack from a bad actor that can package an entity that will expand greatly. Consequently, Resteasy disables GZIP compression / decompression by default.
There are three interceptors that are relevant to GZIP compression / decompression:
GZIPDecodingInterceptor
will install an InputStream
that decompresses the message body.
GZIPEncodingInterceptor
will install an OutputStream
that compresses the message body.
AcceptEncodingGZIPFilter
will add Accept-Encoding with the value "gzip, deflate". If the Accept-Encoding header exists but
does not contain "gzip", AcceptEncodingGZIPFilter
will append ", gzip".
Note that enabling GZIP compression / decompression does not depend on the presence of this
interceptor.
If GZIP decompression is enabled, an upper limit is imposed on the number of bytes
GZIPDecodingInterceptor
will extract from a compressed message body.
The default limit is 10,000,000, but a different value can be configured. See below.
The interceptors may be enabled by including their classnames in a
javax.ws.rs.ext.Providers file on the classpath. The upper limit on deflated files may
be configured by setting the web application context parameter "resteasy.gzip.max.input".
If the limit is exceeded on the server side, GZIPDecodingInterceptor will return a
Response
with status 413 ("Request Entity Too Large") and
a message specifying the upper limit.
Note.
As of release 3.1.0.Final, the GZIP interceptors have moved from package
org.jboss.resteasy.plugins.interceptors.encoding
to
org.jboss.resteasy.plugins.interceptors
. and they should be
named accordingly in javax.ws.rs.ext.Providers. However, they continue to exist in
org.jboss.resteasy.plugins.interceptors.encoding
in module
resteasy-legacy, so, if resteasy-legacy is available, the original names can be used.
See Chapter Migration to Resteasy 3.1 for more
information.
The interceptors may be enabled by registering them with, for example, a
Client
or WebTarget
. For example,
Client client = new ResteasyClientBuilder() // Activate gzip compression on client: .register(AcceptEncodingGZIPFilter.class) .register(GZIPDecodingInterceptor.class) .register(GZIPEncodingInterceptor.class) .build();
The upper limit on deflated files may configured by creating an instance of
GZIPDecodingInterceptor
with a specific value:
Client client = new ResteasyClientBuilder() // Activate gzip compression on client: .register(AcceptEncodingGZIPFilter.class) .register(new GZIPDecodingInterceptor(256)) .register(GZIPEncodingInterceptor.class) .build();
If the limit is exceeded on the client side, GZIPDecodingInterceptor will throw a
ProcessingException
with a message specifying the upper limit.
Resteasy has a ContainerRequestFilter
that can be used to handle CORS preflight and actual requests.
org.jboss.resteasy.plugins.interceptors.CorsFilter
. You must allocate this and register it
as a singleton provider from your Application class. See the javadoc or its various settings.
CorsFilter filter = new CorsFilter(); filter.getAllowedOrigins().add("http://localhost");
Resteasy supports Range
requests for java.io.File
response entities.
@Path("/") public class Resource { @GET @Path("file") @Produces("text/plain") public File getFile() { return file; } } Response response = client.target(generateURL("/file")).request() .header("Range", "1-4").get(); Assert.assertEquals(response.getStatus(), 206); Assert.assertEquals(4, response.getLength()); System.out.println("Content-Range: " + response.getHeaderString("Content-Range"));
Resteasy provides numerous annotations and facilities to support HTTP caching semantics. Annotations to make setting Cache-Control headers easier and both server-side and client-side in-memory caches are available.
Resteasy provides an extension to JAX-RS that allows you to automatically set Cache-Control headers on a successful GET request. It can only be used on @GET annotated methods. A successful @GET request is any request that returns 200 OK response.
package org.jboss.resteasy.annotations.cache; public @interface Cache { int maxAge() default -1; int sMaxAge() default -1; boolean noStore() default false; boolean noTransform() default false; boolean mustRevalidate() default false; boolean proxyRevalidate() default false; boolean isPrivate() default false; } public @interface NoCache { String[] fields() default {}; }
While @Cache builds a complex Cache-Control header, @NoCache is a simplified notation to say that you don't want anything cached i.e. Cache-Control: nocache.
These annotations can be put on the resource class or interface and specifies a default cache value for each @GET resource method. Or they can be put individually on each @GET resource method.
Resteasy has the ability to set up a client-side, browser-like, cache. You can use it with the Client Proxy Framework, or with raw ClientRequests. This cache looks for Cache-Control headers sent back with a server response. If the Cache-Control headers specify that the client is allowed to cache the response, Resteasy caches it within local memory. The cache obeys max-age requirements and will also automatically do HTTP 1.1 cache revalidation if either or both the Last-Modified and/or ETag headers are sent back with the original response. See the HTTP 1.1 specification for details on how Cache-Control or cache revalidation works.
It is very simple to enable caching. Here's an example of using the client cache with the Client Proxy Framework
@Path("/orders") public interface OrderServiceClient { @Path("{id}") @GET @Produces("application/xml") public Order getOrder(@PathParam("id") String id); }
To create a proxy for this interface and enable caching for that proxy requires only a few simple steps:
import org.jboss.resteasy.client.ProxyFactory; import org.jboss.resteasy.client.cache.CacheFactory; import org.jboss.resteasy.client.cache.LightweightBrowserCache; public static void main(String[] args) throws Exception { RegisterBuiltin.register(ResteasyProviderFactory.getInstance()); OrderServiceClient proxy = ProxyFactory.create(OrderServiceClient.class, generateBaseUrl()); // This line enables caching LightweightBrowserCache cache = CacheFactory.makeCacheable(proxy); }
If you are using the ClientRequest class to make invocations rather than the proxy framework, it is just as easy
import org.jboss.resteasy.client.ProxyFactory; import org.jboss.resteasy.client.cache.CacheFactory; import org.jboss.resteasy.client.cache.LightweightBrowserCache; public static void main(String[] args) throws Exception { RegisterBuiltin.register(ResteasyProviderFactory.getInstance()); // This line enables caching LightweightBrowserCache cache = new LightweightBrowserCache(); ClientRequest request = new ClientRequest("http://example.com/orders/333"); CacheFactory.makeCacheable(request, cache); }
The LightweightBrowserCache, by default, has a maximum 2 megabytes of caching space. You can change this programmatically by callings its setMaxBytes() method. If the cache gets full, the cache completely wipes itself of all cached data. This may seem a bit draconian, but the cache was written to avoid unnecessary synchronizations in a concurrent environment where the cache is shared between multiple threads. If you desire a more complex caching solution or if you want to plug in a thirdparty cache please contact our resteasy-developers list and discuss it with the community.
Resteasy has a server-side cache that can sit in front of your JAX-RS services. It automatically caches marshalled responses from HTTP GET JAX-RS invocations if, and only if your JAX-RS resource method sets a Cache-Control header. When a GET comes in, the Resteasy Server Cache checks to see if the URI is stored in the cache. If it does, it returns the already marshalled response without invoking your JAX-RS method. Each cache entry has a max age to whatever is specified in the Cache-Control header of the initial request. The cache also will automatically generate an ETag using an MD5 hash on the response body. This allows the client to do HTTP 1.1 cache revalidation with the IF-NONE-MATCH header. The cache is also smart enough to perform revalidation if there is no initial cache hit, but the jax-rs method still returns a body that has the same ETag.
The cache is also automatically invalidated for a particular URI that has PUT, POST, or DELETE invoked on it. You can also obtain a reference to the cache by injecting a org.jboss.resteasy.plugins.cache.ServerCache via the @Context annotation
@Context ServerCache cache; @GET public String get(@Context ServerCache cache) {...}
To set up the server-side cache you must register an instance of org.jboss.resteasy.plugins.cache.server.ServerCacheFeature via your Application getSingletons() or getClasses() methods. The underlying cache is Infinispan. By default, Resteasy will create an Infinispan cache for you. Alternatively, you can create and pass in an instance of your cache to the ServerCacheFeature constructor. You can also configure Infinispan by specifying various context-param variables in your web.xml. First, if you are using Maven you must depend on the resteasy-cache-core artifact:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-cache-core</artifactId> <version>3.0.22.Final</version> </dependency>
The next thing you should probably do is set up the Infinispan configuration in your web.xml.
<web-app> <context-param> <param-name>server.request.cache.infinispan.config.file</param-name> <param-value>infinispan.xml</param-value> </context-param> <context-param> <param-name>server.request.cache.infinispan.cache.name</param-name> <param-value>MyCache</param-value> </context-param> </web-app>
server.request.cache.infinispan.config.file can either be a classpath or a file path. server.request.cache.infinispan.cache.name is the name of the cache you want to reference that is declared in the config file.
JAX-RS provides an API for evaluating HTTP preconditions based on "If-Match"
, "If-None-Match"
, "If-Modified-Since"
and "If-Unmodified-Since"
headers.
Response.ResponseBuilder rb = request.evaluatePreconditions(lastModified, etag);
By default Resteasy will return status code 304 (Not modified) or 412 (Precondition failed) if any of conditions fails.
However it is not compliant with RFC 7232 which states that headers "If-Match"
, "If-None-Match"
MUST have higher precedence.
You can enable RFC 7232 compatible mode by setting resteasy.rfc7232preconditions
context parameter to true
JAX-RS 2.0 has two different concepts for interceptions: Filters and Interceptors. Filters are mainly used to modify or process incoming and outgoing request headers or response headers. They execute before and after request and response processing.
On the server-side you have two different types of filters. ContainerRequestFilters run before your JAX-RS resource method is invoked. ContainerResponseFilters run after your JAX-RS resource method is invoked. As an added caveat, ContainerRequestFilters come in two flavors: pre-match and post-matching. Pre-matching ContainerRequestFilters are designated with the @PreMatching annotation and will execute before the JAX-RS resource method is matched with the incoming HTTP request. Pre-matching filters often are used to modify request attributes to change how it matches to a specific resource method (i.e. strip .xml and add an Accept header). ContainerRequestFilters can abort the request by calling ContainerRequestContext.abortWith(Response). A filter might want to abort if it implements a custom authentication protocol.
After the resource class method is executed, JAX-RS will run all ContainerResponseFilters. These filters allow you to modify the outgoing response before it is marshalling and sent to the client. So given all that, here's some pseudo code to give some understanding of how things work.
// execute pre match filters for (ContainerRequestFilter filter : preMatchFilters) { filter.filter(requestContext); if (isAborted(requestContext)) { sendAbortionToClient(requestContext); return; } } // match the HTTP request to a resource class and method JaxrsMethod method = matchMethod(requestContext); // Execute post match filters for (ContainerRequestFilter filter : postMatchFilters) { filter.filter(requestContext); if (isAborted(requestContext)) { sendAbortionToClient(requestContext); return; } } // execute resource class method method.execute(request); // execute response filters for (ContainerResponseFilter filter : responseFilters) { filter.filter(requestContext, responseContext); }
On the client side you also have two types of filters: ClientRequestFilter and ClientResponseFilter. ClientRequestFilters run before your HTTP request is sent over the wire to the server. ClientResponseFilters run after a response is received from the server, but before the response body is unmarshalled. ClientRequestFilters are also allowed to abort the execute of the request and provide a canned response without going over the wire to the server. ClientResponseFilters can modfiy the Response object before it is handed back to application code. Here's some pseudo code to illustrate things.
// execute request filters for (ClientRequestFilter filter : requestFilters) { filter.filter(requestContext); if (isAborted(requestContext)) { return requestContext.getAbortedResponseObject(); } } // send request over the wire response = sendRequest(request); // execute response filters for (ClientResponseFilter filter : responseFilters) { filter.filter(requestContext, responseContext); }
While filters modify request or response headers, interceptors deal with message bodies. Interceptors are executed in the same call stack as their corresponding reader or writer. ReaderInterceptors wrap around the execution of MessageBodyReaders. WriterInterceptors wrap around the execution of MessageBodyWriters. They can be used to implement a specific content-encoding. They can be used to generate digital signatures or to post or pre-process a Java object model before or after it is marshalled.
Sometimes you want a filter or interceptor to only run for a specific resource method. You can do this in two different ways: register an implementation of DynamicFeature or use the @NameBinding annotation. The DynamicFeature interface is executed at deployment time for each resource method. You just use the Configurable interface to register the filters and interceptors you want for the specific resource method. @NameBinding works a lot like CDI interceptors. You annotate a custom annotation with @NameBinding and then apply that custom annotation to your filter and resource method
@NameBinding public @interface DoIt {} @DoIt public class MyFilter implements ContainerRequestFilter {...} @Path("/root") public class MyResource { @GET @DoIt public String get() {...} }
Asynchronous HTTP Request Processing is a relatively new technique that allows you to process a single HTTP request using non-blocking I/O and, if desired in separate threads. Some refer to it as COMET capabilities. The primary use case for Asynchronous HTTP is in the case where the client is polling the server for a delayed response. The usual example is an AJAX chat client where you want to push/pull from both the client and the server. These scenarios have the client blocking a long time on the server’s socket waiting for a new message. What happens in synchronous HTTP where the server is blocking on incoming and outgoing I/O is that you end up having a thread consumed per client connection. This eats up memory and valuable thread resources. Not such a big deal in 90% of applications (in fact using asynchronous processing may actually hurt your performance in most common scenarios), but when you start getting a lot of concurrent clients that are blocking like this, there’s a lot of wasted resources and your server does not scale that well.
The JAX-RS 2.0 specification has added asynchronous HTTP support via two classes. The @Suspended annotation, and AsyncResponse interface.
Injecting an AsynchronousResponse as a parameter to your jax-rs methods tells Resteasy that the HTTP request/response should be detached from the currently executing thread and that the current thread should not try to automatically process the response.
The AsyncResponse is the callback object. The act of calling one of the resume() methods will cause a response to be sent back to the client and will also terminate the HTTP request. Here is an example of asynchronous processing:
import javax.ws.rs.Suspend; import javax.ws.rs.core.AsynchronousResponse; @Path("/") public class SimpleResource { @GET @Path("basic") @Produces("text/plain") public void getBasic(@Suspended final AsyncResponse response) throws Exception { Thread t = new Thread() { @Override public void run() { try { Response jaxrs = Response.ok("basic").type(MediaType.TEXT_PLAIN).build(); response.resume(jaxrs); } catch (Exception e) { e.printStackTrace(); } } }; t.start(); } }
AsyncResponse also has other methods to cancel the execution. See javadoc for more details.
NOTE: The old Resteasy proprietary API for async http has been deprecated and may be removed as soon as Resteasy 3.1.
In particular, the Resteasy @Suspend annotation is replaced by javax.ws.rs.container.Suspended
, and
org.jboss.resteasy.spi.AsynchronousResponse
is replaced by
javax.ws.rs.container.AsyncResponse
. Note that @Suspended does not have a value field,
which represented a timeout limit. Instead, AsyncResponse.setTimeout()
may be called.
The Resteasy Asynchronous Job Service is an implementation of the Asynchronous Job pattern defined in O'Reilly's "Restful Web Services" book. The idea of it is to bring asynchronicity to a synchronous protocol.
While HTTP is a synchronous protocol it does have a faint idea of asynchronous invocations. The HTTP 1.1 response code 202, "Accepted" means that the server has received and accepted the response for processing, but the processing has not yet been completed. The Resteasy Asynchronous Job Service builds around this idea.
POST http://example.com/myservice?asynch=true
For example, if you make the above post with the asynch query parameter set to true, Resteasy will return a 202, "Accepted" response code and run the invocation in the background. It also sends back a Location header with a URL pointing to where the response of the background method is located.
HTTP/1.1 202 Accepted Location: http://example.com/asynch/jobs/3332334
The URI will have the form of:
/asynch/jobs/{job-id}?wait={millisconds}|nowait=true
You can perform the GET, POST, and DELETE operations on this job URL. GET returns whatever the JAX-RS resource method you invoked returned as a response if the job was completed. If the job has not completed, this GET will return a response code of 202, Accepted. Invoking GET does not remove the job, so you can call it multiple times. When Resteasy's job queue gets full, it will evict the least recently used job from memory. You can manually clean up after yourself by calling DELETE on the URI. POST does a read of the JOB response and will remove the JOB it has been completed.
Both GET and POST allow you to specify a maximum wait time in milliseconds, a "wait" query parameter. Here's an example:
POST http://example.com/asynch/jobs/122?wait=3000
If you do not specify a "wait" parameter, the GET or POST will not wait at all if the job is not complete.
NOTE!! While you can invoke GET, DELETE, and PUT methods asynchronously, this breaks the HTTP 1.1 contract of these methods. While these invocations may not change the state of the resource if invoked more than once, they do change the state of the server as new Job entries with each invocation. If you want to be a purist, stick with only invoking POST methods asynchronously.
Security NOTE! Resteasy role-based security (annotations) does not work with the Asynchronous Job Service. You must use XML declarative security within your web.xml file. Why? It is impossible to implement role-based security portably. In the future, we may have specific JBoss integration, but will not support other environments.
NOTE. A SecureRandom
object is used to generate unique job ids. For security purposes, the
SecureRandom
is periodically reseeded. By default, it is reseeded after 100 uses. This value
may be configured with the servlet init parameter "resteasy.secure.random.max.use".
Resteasy also supports the notion of fire and forget. This will also return a 202, Accepted response, but no Job will be created. This is as simple as using the oneway query parameter instead of asynch. For example:
POST http://example.com/myservice?oneway=true
Security NOTE! Resteasy role-based security (annotations) does not work with the Asynchronous Job Service. You must use XML declaritive security within your web.xml file. Why? It is impossible to implement role-based security portably. In the future, we may have specific JBoss integration, but will not support other environments.
You must enable the Asynchronous Job Service in your web.xml file as it is not turned on by default.
<web-app> <!-- enable the Asynchronous Job Service --> <context-param> <param-name>resteasy.async.job.service.enabled</param-name> <param-value>true</param-value> </context-param> <!-- The next context parameters are all optional. Their default values are shown as example param-values --> <!-- How many jobs results can be held in memory at once? --> <context-param> <param-name>resteasy.async.job.service.max.job.results</param-name> <param-value>100</param-value> </context-param> <!-- Maximum wait time on a job when a client is querying for it --> <context-param> <param-name>resteasy.async.job.service.max.wait</param-name> <param-value>300000</param-value> </context-param> <!-- Thread pool size of background threads that run the job --> <context-param> <param-name>resteasy.async.job.service.thread.pool.size</param-name> <param-value>100</param-value> </context-param> <!-- Set the base path for the Job uris --> <context-param> <param-name>resteasy.async.job.service.base.path</param-name> <param-value>/asynch/jobs</param-value> </context-param> <listener> <listener-class> org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap </listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class> org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher </servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Resteasy has a few different plugins for different embedabble HTTP and/or Servlet containers if use Resteasy in a test environment, or within an environment where you do not want a Servlet engine dependency.
Undertow is a new Servlet Container that is used by WildFly (JBoss Community Server). You can embed Undertow as you wish. Here's a a test that shows it in action.
import io.undertow.servlet.api.DeploymentInfo; import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; import org.jboss.resteasy.test.TestPortProvider; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import javax.ws.rs.ApplicationPath; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class UndertowTest { private static UndertowJaxrsServer server; @Path("/test") public static class Resource { @GET @Produces("text/plain") public String get() { return "hello world"; } } @ApplicationPath("/base") public static class MyApp extends Application { @Override public Set<Class<?>> getClasses() { HashSet<Class<?>> classes = new HashSet<Class<?>>(); classes.add(Resource.class); return classes; } } @BeforeClass public static void init() throws Exception { server = new UndertowJaxrsServer().start(); } @AfterClass public static void stop() throws Exception { server.stop(); } @Test public void testApplicationPath() throws Exception { server.deploy(MyApp.class); Client client = ClientBuilder.newClient(); String val = client.target(TestPortProvider.generateURL("/base/test")) .request().get(String.class); Assert.assertEquals("hello world", val); client.close(); } @Test public void testApplicationContext() throws Exception { server.deploy(MyApp.class, "/root"); Client client = ClientBuilder.newClient(); String val = client.target(TestPortProvider.generateURL("/root/test")) .request().get(String.class); Assert.assertEquals("hello world", val); client.close(); } @Test public void testDeploymentInfo() throws Exception { DeploymentInfo di = server.undertowDeployment(MyApp.class); di.setContextPath("/di"); di.setDeploymentName("DI"); server.deploy(di); Client client = ClientBuilder.newClient(); String val = client.target(TestPortProvider.generateURL("/di/base/test")) .request().get(String.class); Assert.assertEquals("hello world", val); client.close(); } }
The Sun JDK comes with a simple HTTP server implementation (com.sun.net.httpserver.HttpServer) which you can run Resteasy on top of.
HttpServer httpServer = HttpServer.create(new InetSocketAddress(port), 10); contextBuilder = new HttpContextBuilder(); contextBuilder.getDeployment().getActualResourceClasses().add(SimpleResource.class); HttpContext context = contextBuilder.bind(httpServer); context.getAttributes().put("some.config.info", "42"); httpServer.start(); contextBuilder.cleanup(); httpServer.stop(0);
Create your HttpServer the way you want then use the org.jboss.resteasy.plugins.server.sun.http.HttpContextBuilder to initialize Resteasy and bind it to an HttpContext. The HttpContext attributes are available by injecting in a org.jboss.resteasy.spi.ResteasyConfiguration interface using @Context within your provider and resource classes.
Maven project you must include is:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jdk-http</artifactId> <version>3.0.22.Final</version> </dependency>
RESTEasy integrates with the TJWS Embeddable Servlet container. It comes with this distribution, or you can reference the Maven artifact. You must also provide a servlet API dependency as well.
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>tjws</artifactId> <version>3.0.22.Final</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency>
From the distribution, move the jars in resteasy-jaxrs.war/WEB-INF/lib into your classpath. You must both programmatically register your JAX-RS beans using the embedded server's Registry. Here's an example:
@Path("/") public class MyResource { @GET public String get() { return "hello world"; } public static void main(String[] args) throws Exception { TJWSEmbeddedJaxrsServer tjws = new TJWSEmbeddedJaxrsServer(); tjws.setPort(8080); tjws.start(); tjws.getRegistry().addPerRequestResource(RestEasy485Resource.class); } }
The server can either host non-encrypted or SSL based resources, but not both. See the Javadoc for TJWSEmbeddedJaxrsServer as well as its superclass TJWSServletServer. The TJWS website is also a good place for information.
If you want to use Spring, see the SpringBeanProcessor. Here's a pseudo-code example
public static void main(String[] args) throws Exception { final TJWSEmbeddedJaxrsServer tjws = new TJWSEmbeddedJaxrsServer(); tjws.setPort(8081); tjws.start(); org.jboss.resteasy.plugins.server.servlet.SpringBeanProcessor processor = new SpringBeanProcessor(tjws.getDeployment().getRegistry(), tjws.getDeployment().getFactory(); ConfigurableBeanFactory factory = new XmlBeanFactory(...); factory.addBeanPostProcessor(processor); }
NOTE: TJWS is now deprecated. Consider using the more modern Undertow.
Resteasy has integration with the popular Netty project as well..
public static void start(ResteasyDeployment deployment) throws Exception { netty = new NettyJaxrsServer(); netty.setDeployment(deployment); netty.setPort(TestPortProvider.getPort()); netty.setRootResourcePath(""); netty.setSecurityDomain(null); netty.start(); }
Maven project you must include is:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-netty</artifactId> <version>3.0.22.Final</version> </dependency>
Resteasy has integration with the popular Vert.x project as well..
public static void start(VertxResteasyDeployment deployment) throws Exception { VertxJaxrsServer server = new VertxJaxrsServer(); server.setDeployment(deployment); server.setPort(TestPortProvider.getPort()); server.setRootResourcePath(""); server.setSecurityDomain(null); server.start(); }
Maven project you must include is:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-vertx</artifactId> <version>3.0.22.Final</version> </dependency>
The server will bootstrap its own Vert.x instance and Http server.
When a resource is called, it is done with the Vert.x Event Loop thread, keep in mind to not block this thread and respect the Vert.x programming model, see the related Vert.x manual page.
Vert.x extends the Resteasy registry to provide a new binding scope that creates resources per Event Loop:
VertxResteasyDeployment deployment = new VertxResteasyDeployment(); // Create an instance of resource per Event Loop deployment.getRegistry().addPerInstanceResource(Resource.class);
The per instance binding scope caches the same resource instance for each event loop providing the same concurrency model than a verticle deployed multiple times.
Vert.x can also embed a Resteasy deployment, making easy to use Jax-RS annotated controller in Vert.x applications:
Vertx vertx = Vertx.vertx(); HttpServer server = vertx.createHttpServer(); // Set an handler calling Resteasy server.requestHandler(new VertxRequestHandler(vertx, deployment)); // Start the server server.listen(8080, "localhost");
Vert.x objects can be injected in annotated resources:
@GET @Path("/somepath") @Produces("text/plain") public String context( @Context io.vertx.core.Context context, @Context io.vertx.core.Vertx vertx, @Context io.vertx.core.http.HttpServerRequest req, @Context io.vertx.core.http.HttpServerResponse resp) { return "the-response"; }
Although RESTEasy has an Embeddable Container, you may not be comfortable with the idea of starting and stopping a web server within unit tests (in reality, the embedded container starts in milli seconds), or you might not like the idea of using Apache HTTP Client or java.net.URL to test your code. RESTEasy provides a mock framework so that you can invoke on your resource directly.
import org.jboss.resteasy.mock.*; ... Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); POJOResourceFactory noDefaults = new POJOResourceFactory(LocatingResource.class); dispatcher.getRegistry().addResourceFactory(noDefaults); { MockHttpRequest request = MockHttpRequest.get("/locating/basic"); MockHttpResponse response = new MockHttpResponse(); dispatcher.invoke(request, response); Assert.assertEquals(HttpServletResponse.SC_OK, response.getStatus()); Assert.assertEquals("basic", response.getContentAsString()); }
See the RESTEasy Javadoc for all the ease-of-use methods associated with MockHttpRequest, and MockHttpResponse.
Because Resteasy is deployed as a servlet, you must use standard web.xml constraints to enable authentication and authorization.
Unfortunately, web.xml constraints do not mesh very well with JAX-RS in some situations. The problem is that web.xml URL pattern matching is very very limited. URL patterns in web.xml only support simple wildcards, so JAX-RS resources like:
/{pathparam1}/foo/bar/{pathparam2}
Cannot be mapped as a web.xml URL pattern like:
/*/foo/bar/*
To get around this problem you will need to use the security annotations defined below on your JAX-RS methods. You will still need to set up some general security constraint elements in web.xml to turn on authentication.
Resteasy JAX-RS supports the @RolesAllowed, @PermitAll and @DenyAll annotations on JAX-RS methods. By default though, Resteasy does not recognize these annotations. You have to configure Resteasy to turn on role-based security by setting a context parameter. NOTE!!! Do not turn on this switch if you are using EJBs. The EJB container will provide this functionality instead of Resteasy.
<web-app> ... <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> </web-app>
There is a bit of quirkiness with this approach. You will have to declare all roles used within the Resteasy JAX-RS war file that you are using in your JAX-RS classes and set up a security constraint that permits all of these roles access to every URL handled by the JAX-RS runtime. You'll just have to trust that Resteasy JAX-RS authorizes properly.
How does Resteasy do authorization? Well, its really simple. It just sees if a method is annotated with @RolesAllowed and then just does HttpServletRequest.isUserInRole. If one of the @RolesAllowed passes, then allow the request, otherwise, a response is sent back with a 401 (Unauthorized) response code.
So, here's an example of a modified RESTEasy WAR file. You'll notice that every role declared is allowed access to every URL controlled by the Resteasy servlet.
<web-app> <context-param> <param-name>resteasy.role.based.security</param-name> <param-value>true</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web-resource-name>Resteasy</web-resource-name> <url-pattern>/security</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <role-name>user</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>Test</realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>user</role-name> </security-role> </web-app>
The overall goal of Resteasy Skeleton Key is to provide a unified way for both Browser and JAX-RS clients to be secured in an integrated and seemless fashion. We want to support a network of applications and services so that if one server needs to execute or forward requests to another, there is a secure and scalable way to do this without hitting a central authentication server each and every request.
The OAuth 2.0 Authorization Framework enables a third-party to obtain access to an HTTP resource on behalf of a resource owner without the third-party having to know the credentials of the resource owner. It does this by issuing access tokens via a browser redirect protocol, or by a direct grant. The access tokens can then be transmitted by the OAuth2 Bearer Token protocol to access the protected resource.
Resteasy Skeleton Key is an OAuth 2.0 implementation that allows you to use existing Wildfly security infrastructure to secure your web applications and restful services. You can turn an existing web app into an OAuth 2.0 Access Token Provider or you can turn a Wildfly Security Domain into a central authentication and authorization server that a whole host of applications and services can use. Here are the features in a nutshell:
Turn an existing servlet-form-auth-based web application into an OAuth 2.0 provider.
Provide Distributed Single-Sign-On (SSO) from a central authentication server. Log in once, and you can securely access any browser-based app configured to work in the domain.
Provide Distributed Logout. Following one link from any application can log you out of all your distributed applications configured to use SSO.
Web apps can interact securely with any remote restful service by forwarding access tokens through the standard Authorization header.
Access tokens are digitally signed by the oauth2 framework and can be used to access any service configured to work in the domain. The tokens contain both identity and role mapping information. Because they are digitally signed, there's no need to overload the central authentication server with each request to verify identity and to determine permissions.
The Resteasy distribution comes with an OAuth2 Skeleton key example. This is a great way to see OAuth2 in action and how it is configured. You may also want to use this as a template for your applications.
Wildfly
HTTPS is required. See the Wildfly documentation on how to enable SSL for web applications
A username/password based Wildfly security domain
Browser-based apps must be configured to use servlet FORM authentication and web.xml security constraints
Access tokens are digitally signed and verified by an RSA keypair. You must generate this keypair using the JDK's keytool command or by something like openssl.
$ keytool -genkey -alias mydomain -keyalg rsa -keystore realm.jks
This will ask you a series of questions that will be used to create the X509 public certificate. Basic PKI stuff that you hopefully are already familiar with. Move this keystore file into a directory that you can reference from a configuration file. I suggest the standalone/configuration directory of your Wildfly distribution.
The next thing you're gonna want to do is set up a web application to be your OAuth2 provider. This can be an existing web app or you can create a new WAR to be your central authentication server. An existing web app must be configured to use servlet FORM authentication. Enabling OAuth2 within this app will not change how normal users interact with it.
You can use any set of Wildfly login modules you want to store your username, passwords and role mappings.
Each security domain will be comprised of regular users, oauth clients, and admins. Oauth clients
represent either a web application that wants to use the auth-server to do SSO, or they are traditional
oauth clients that want access permision to act on behalf of another user (the traditional OAuth use case).
Every oauth client must have a username, password, and a specific role mapping that gives them various permissions to
participate in OAuth 2 protocols. There is a role that grants an oauth client permission
to login as a specific user (default is login
. This is the SSO case. There is a role that grants a client permission
to request permission to act on behalf of a user (default is oauth
). Additional
role mappings assigned to the oauth client define what additional permissions they are allowed to have. These
additional permissions are the role mappings of the application and are the intersection of the permissions
given to the user the client is acting on behalf of. This is better explained by an example role mapping file:
wburke=user,admin loginclient=login oauthclient1=oauth,* oauthclient2=oauth,user
In the above role mapping file with have a simple user wburke
. He has application
role permissions of user
and admin
. One oauth client user
is loginclient
. It has been given a role mapping of login
.
This client is allowed to login as the user and is given all roles of the user. The oauthclient1
user is not allowed to login as the user, but is allowed to obtain an OAuth grant to act on behalf of the
user. The *
role means that oauthclient1
is granted the same
roles as the user it is acting on behalf of. If oauthclient1
acts on behalf of
wburke
then it will have both user
and admin
permissions.
The oauthclient2
is also allowed to use the oauth grant protocol, but it will only
ever be granted user
permissions.
You are not confined to login, oauth, and * as role mapping names. You can configure them to be whatever you want.
Why have different login and oauth role mappings? login
clients
are allowed to bypass entering username and password if the user has already logged in once and has
an existing authenticated session with the server. oauth
clients are always
required to enter username and password. You probably don't want to grant permission automatically to
an oauth client. A user will want to look at who is requesting permission. This role distinction
gives you this capability.
You must create a configuration file that holds all the configuration for OAuth2. This is json formatted
If you name it resteasy-oauth.json
and put it within the WEB-INF/
directory of your war, that's all you have to do. Otherwise, you must specify the full path
to this configuration file within a context-param within your web.xml file. The name of this param
is skeleton.key.config.file
. You can reference System properties within the
value of this context-param by enclosing them within ${VARIABLE}
. Here's an
example configuration:
{ "realm" : "mydomain", "admin-role" : "admin", "login-role" : "login", "oauth-client-role" : "oauth", "wildcard-role" : "*", "realm-keystore" : "${jboss.server.config.dir}/realm.jks", "realm-key-alias" : "mydomain", "realm-keystore-password" : "password", "realm-private-key-password" : "password", "access-code-lifetime" : "300", "token-lifetime" : "3600", "truststore" : "${jboss.server.config.dir}/client-truststore.ts", "truststore-password" : "password", "resources" : [ "https://example.com/customer-portal", "https://somewhere.com/product-portal" ] }
Let's go over what each of these config variables represent:
Name of the realm representing the users of your distributed applications and services
Admin role mapping used for admins. You must have this defined if you want to do distributed logout.
Role mapping for login clients.
Role mapping for regular oauth clients.
Role mapping for assigning all roles to an oauth client wishing to act on behalf of a user.
Absolute path pointing to the keystore that contains the realm's keypair. This keypair
is used to digitally sign access tokens. You may use ${VARIABLE}
to reference System properties. The example is referencing the Widlfly config dir.
Key alias for the key pair stored in your realm-keystore file.
Password to access the keystore.
Password to access the private realm key within the keystore
The access code is obtained via a browser redirect after you log into the central server. This access code is then transmitted in a separate request to the auth server to obtain an access token. This variable is the lifetime of this access code. In how many seconds will it expire. You want to keep this value short. The default is 300 seconds.
This is how long in seconds the access token is viable after it was first created. The default is one hour. Depending on your security requirements you may want to extend or shorten this default.
Used for outgoing client HTTPS communications. This contains one or more trusted host certificates or certificate authorities. This is OPTIONAL if you are not using distributed logout.
Password for the truststore keystore.
Root URLs of applications using this auth-server for SSO. This is OPTIONAL and only needed if you want to allow distributed logout.
Set up your security constraints however you like. You must though use FORM authentication.
In jboss-web.xml in your WEB-INF directory, point to your security domain as a normal secured web app does, and also use a specific valve.
<jboss-web> <security-domain>java:/jaas/commerce</security-domain> <valve> <class-name>org.jboss.resteasy.skeleton.key.as7.OAuthAuthenticationServerValve</class-name> </valve> </jboss-web>
You must import the skeleton key modules so that the classes are visible to this application. Include this file within WEB-INF
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/> <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/> <module name="org.jboss.resteasy.skeleton-key"/> </dependencies> </deployment> </jboss-deployment-structure>
The action url used by your login form is dependent on the oauth protocol. Skeleton key defines
a request attribute called OAUTH_FORM_ACTION
which is the URL you should
use for the form's action. Here's an example login.jsp page that uses this attribute:
<form action="<%= request.getAttribute("OAUTH_FORM_ACTION")%>" method=post> <p><strong>Please Enter Your User Name: </strong> <input type="text" name="j_username" size="25"> <p><p><strong>Please Enter Your Password: </strong> <input type="password" size="15" name="j_password"> <p><p> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form>
This section specifies how you can use the central auth-server for SSO. Following these directions will use the auth-server for browser log in. The server will also be able to do bearer token authentication as well.
The best way to create the config file for your application is to ask the central authentication server
you configured in the last section. So, boot up the auth server and go to https://auth-server-context-root/j_oauth_realm_info.html.
For example: https://localhost:8443/auth-server/j_oauth_realm_info.html
. This will
show template configurations depending on which valve you are using. You want the OAuthManagedResourceValve
config.
It will look something like this.
{ "realm" : "mydomain", "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCO8XXyi7oAq5ecsYy+tJrl54N2TtKAkxuWEDmzvSPU+mUA2/3qHcxucZakG74Z49410tn5IIu2CXXlk9CuKcpXvKh+cPBzmC1Nmbd+4MelRVVZnvogyPICs8h3sNTAMNdfI6hDc5/MfVQQ9m5OZrKbNR3dY50mTi/ExnJ5IWPqxQIDAQAB", "admin-role" : "admin", "auth-url" : "https://localhost:8443/auth-server/login.jsp", "code-url" : "https://localhost:8443/auth-server/j_oauth_resolve_access_code", "truststore" : "REQUIRED", "truststore-password" : "REQUIRED", "client-id" : "REQUIRED", "client-credentials" : { "password" : "REQUIRED" } }Let's go over what each of these config variables represent:
Name of the realm representing the users of your distributed applications and services
PEM format of public key.
Admin role mapping used for admins. You must have this defined if you want to do distributed logout.
URL of the auth server's login page.
URL to turn an access code into an access token. (Part of the OAuth2 protocol)
Used for outgoing client HTTPS communications. This contains one or more trusted host certificates or certificate authorities. This is REQUIRED as you must talk HTTPS to the auth server to turn an access code into an access token. You can create this truststore by extracting the public certificate of the auth server's SSL keystore. The google knows if you want to know how to do this.
Password for the truststore keystore.
Username of the login client. This server will send client-id and password when turning an access code into an access token. Internally, the server will do an HTTPS invocation to the auth-server passing this information using Basic AUTH.
Must specify the password of the oauth login client.
Set up your security constraints however you like. You must though use FORM authentication.
In jboss-web.xml in your WEB-INF directory you need to use a specific valve.
<jboss-web> <valve> <class-name>org.jboss.resteasy.skeleton.key.as7.OAuthManagedResourceValve</class-name> </valve> </jboss-web>
You must import the skeleton key modules so that the classes are visible to this application. Include this file within WEB-INF
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/> <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/> <module name="org.jboss.resteasy.skeleton-key"/> </dependencies> </deployment> </jboss-deployment-structure>
If you have a web app that you want only to allow Bearer token authentication, i.e. a set of JAX-RS services then follow these directions.
The best way to create the config file for your application is to ask the central authentication server
you configured in the last section. So, boot up the auth server and go to https://auth-server-context-root/j_oauth_realm_info.html.
For example: https://localhost:8443/auth-server/j_oauth_realm_info.html
. This will
show template configurations depending on which valve you are using. You want the BearerTokenAuthenticatorValve
config.
It will look something like this.
{ "realm" : "mydomain", "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCO8XXyi7oAq5ecsYy+tJrl54N2TtKAkxuWEDmzvSPU+mUA2/3qHcxucZakG74Z49410tn5IIu2CXXlk9CuKcpXvKh+cPBzmC1Nmbd+4MelRVVZnvogyPICs8h3sNTAMNdfI6hDc5/MfVQQ9m5OZrKbNR3dY50mTi/ExnJ5IWPqxQIDAQAB", }All that is needed is the realm name, and the public key of the realm. Let's go over what each of these config variables represent:
Name of the realm representing the users of your distributed applications and services
PEM format of the realm's public key. Used to verify tokens.
Set up your security constraints however you like. You must though use FORM authentication.
In jboss-web.xml in your WEB-INF directory you need to use a specific valve.
<jboss-web> <valve> <class-name>org.jboss.resteasy.skeleton.key.as7.BearerTokenAuthenticatorValve</class-name> </valve> </jboss-web>
You must import the skeleton key modules so that the classes are visible to this application. Include this file within WEB-INF
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/> <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/> <module name="org.jboss.resteasy.skeleton-key"/> </dependencies> </deployment> </jboss-deployment-structure>
You can request an access token from the auth-server by doing a simple HTTPS invocation. You must use BASIC authentication to identify your user, and you will get back a signed access token for that user. Here's an example using a JAX-RS 2.0 client:
ResteasyClient client = new ResteasyClientBuilder() .truststore(truststore) .build(); Form form = new Form().param("grant_type", "client_credentials"); ResteasyWebTarget target = client.target("https://localhost:8443/auth-server/j_oauth_token_grant"); target.configuration().register(new BasicAuthentication("bburke@redhat.com", "password")); AccessTokenResponse res = target.request() .post(Entity.form(form), AccessTokenResponse.class);
The above makes a simple POST to the context root of the auth server with j_oauth_token_grant
at the end of the target URL. This resource is responsible for creating access tokens.
try { Response response = client.target("https://localhost:8443/database/products").request() .header(HttpHeaders.AUTHORIZATION, "Bearer " + res.getToken()).get(); String xml = response.readEntity(String.class); } finally { client.close(); }
The access token is a simple string. To invoke on a service protected by bearer token auth, just
set the Authorization
header of your HTTPS request with a value of Bearer
and then the access token string.
If you have an application secured by one of the methods described in this chapter, you can obtain
the access token of the current web session so that you can use it to invoke on other remote services
securely using bearer token authentication. Each HttpServletRequest in a secure web session has an attribute
called org.jboss.resteasy.skeleton.key.SkeletonKeySession
which points to an instance
of a class with the same name. This class contains the access token and also points to the truststore
you configured. You can then extract this info and make secure remote invocations. Here's an example of that.
public List<String> getCustomers(HttpServletRequest request) { SkeletonKeySession session = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName()); ResteasyClient client = new ResteasyClientBuilder() .truststore(session.getMetadata().getTruststore()) .build(); try { Response response = client.target("https://localhost:8443/database/customers").request() .header(HttpHeaders.AUTHORIZATION, "Bearer " + session.getToken()).get(); return response.readEntity(new GenericType<List<String>>(){}); } finally { client.close(); } }
If you are within a JAX-RS environment you can inject a SkeletonKeySession
using the @Context
annotation.
The Resteasy distribution comes with an example project that shows all of these different features in action! Check it out!
For reference, here is the set of relative URL actions that the auth server will publish.
The is the url of your login page. OAuth clients will redirect to it. This is application specific.
Used by oauth clients to turn an access code into an access token.
Do a GET request to this URL and it will perform a distributed logout.
Do a POST with BASIC Auth to obtain an access token for a specific user.
Displays an HTML page with template configurations for using this realm.
Since Resteasy runs within a servlet container you can use most (all?) mechanism available in your servlet container for authentication. Basic and Digest authentication are probably the easiest to set up and fit nicely into REST's stateless principle. Form security can be used, but requires passing the session's cookie value with each request. We have done some preliminary work on OAuth and also plan to work on OpenID and SAML integration in the future.
RESTEasy has preliminary support for OAuth core 1.0a. This includes support for authenticating with OAuth (as described by the spec section 6) and OAuth authentication for protected resources (as described by the spec section 7).
This API is deprecated and will be removed in subsequent versions of Resteasy unless there is an outcry from the community. We're focusing on OAuth 2.0 protocols. Please see our OAuth 2.0 Work.
OAuth authentication is the process in which Users grant access to their Protected Resources without sharing their credentials with the Consumer.
OAuth Authentication is done in three steps:
The Consumer obtains an unauthorized Request Token. This part is handled by RESTEasy.
The User authorizes the Request Token. This part is not handled by RESTEasy because it requires a user interface where the User logs in and authorizes or denies the Request Token. This cannot be implemented automatically as it needs to be integrated with your User login process and user interface.
The Consumer exchanges the Request Token for an Access Token. This part is handled by RESTEasy.
In order for RESTEasy to provide the two URL endpoints where the Client will request unauthorized Request Tokens and exchange authorized Request Tokens for Access Tokens, you need to enable the OAuthServlet in your web.xml:
<!-- The OAuth Servlet handles token exchange -->
<servlet>
<servlet-name>OAuth</servlet-name>
<servlet-class>org.jboss.RESTEasy.auth.oauth.OAuthServlet</servlet-class>
</servlet>
<!-- This will be the base for the token exchange endpoint URL -->
<servlet-mapping>
<servlet-name>OAuth</servlet-name>
<url-pattern>/oauth/*</url-pattern>
</servlet-mapping>
The following configuration options are available using <context-param> elements
:
Option Name | Default | Description |
---|---|---|
oauth.provider.provider-class | *Required* | Defines the fully-qualified class name of your OAuthProvider implementation |
oauth.provider.tokens.request | /requestToken | This defines the endpoint URL for requesting unauthorized Request Tokens |
oauth.provider.tokens.access | /accessToken | This defines the endpoint URL for exchanging authorized Request Tokens for Access Tokens |
After successfully receiving the Access Token and Token Secret, the Consumer is able to access the Protected Resources on behalf of the User.
RESTEasy supports OAuth authentication for protected resources using a servlet filter which should be mapped in your web.xml for all protected resources:
<!-- The OAuth Filter handles authentication for protected resources -->
<filter>
<filter-name>OAuth Filter</filter-name>
<filter-class>org.jboss.RESTEasy.auth.oauth.OAuthFilter</filter-class>
</filter>
<!-- This defines the URLs which should require OAuth authentication for your protected resources -->
<filter-mapping>
<filter-name>OAuth Filter</filter-name>
<url-pattern>/rest/*</url-pattern>
</filter-mapping>
The following configuration options are available using <context-param> elements
:
Option Name | Default | Description |
---|---|---|
oauth.provider.provider-class | *Required* | Defines the fully-qualified class name of your OAuthProvider implementation |
Once authenticated, the OAuth Servlet Filter will set your request's Principal and Roles, which can then be accessed using the JAX-RS SecurityContext. You can also protect your resources using Roles as described in the section "Securing JAX-RS and RESTEasy".
In order for RESTEasy to implement OAuth it needs you to provide an instance of OAuthProvider
which
will provide access to the list of Consumer, Request and Access Tokens. Because one size doesn’t fit all we cannot
know if you wish to store your Tokens and Consumer credentials in a configuration file, in memory, or on persistent storage.
All you need to do is implement the OAuthProvider
interface:
public interface OAuthProvider { String getRealm(); OAuthConsumer getConsumer(String consumerKey)throws OAuthException; OAuthToken getRequestToken(String consumerKey, String requestToken) throws OAuthException; OAuthToken getAccessToken(String consumerKey, String accessToken) throws OAuthException; OAuthToken makeRequestToken(String consumerKey, String callback) throws OAuthException; OAuthToken makeAccessToken(String consumerKey, String requestToken, String verifier) throws OAuthException; String authoriseRequestToken(String consumerKey, String requestToken) throws OAuthException; void checkTimestamp(OAuthToken token, long timestamp) throws OAuthException; }
If a Consumer Key, or Token doesn’t exist, or if the timestamp is not valid,
simply throw an OAuthException
.
The rest of the interfaces used in OAuthProvider
are:
public interface OAuthConsumer { String getKey(); String getSecret(); } public interface OAuthToken { OAuthConsumer getConsumer(); String getToken(); String getSecret(); Principal getPrincipal(); Set<String> getRoles(); }
JSON Web Signature and Encryption (JOSE JWT) is a new specification that can be used to encode content as a string and either digitally sign or encrypt it. I won't go over the spec here Do a Google search on it if you're interested
To digitally sign content using JWS, use the org.jboss.resteasy.jose.jws.JWSBuilder
class.
To unpack and verify a JWS, use the org.jboss.resteasy.jose.jws.JWSInput
class. (TODO, write
more doco here!)
Here's an example:
@Test public void testRSAWithContentType() throws Exception { KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); String encoded = new JWSBuilder() .contentType(MediaType.TEXT_PLAIN_TYPE) .content("Hello World", MediaType.TEXT_PLAIN_TYPE) .rsa256(keyPair.getPrivate()); System.out.println(encoded); JWSInput input = new JWSInput(encoded, ResteasyProviderFactory.getInstance()); System.out.println(input.getHeader()); String msg = (String)input.readContent(String.class); Assert.assertEquals("Hello World", msg); Assert.assertTrue(RSAProvider.verify(input, keyPair.getPublic())); }
To encrypt content using JWE, use the org.jboss.resteasy.jose.jwe.JWEBuilder
class.
To decrypt content using JWE, use the org.jboss.resteasy.jose.jwe.JWEInput
class. (TODO, write
more doco here!)
Here's an example:
@Test public void testRSA() throws Exception { KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); String content = "Live long and prosper."; { String encoded = new JWEBuilder().contentBytes(content.getBytes()).RSA1_5((RSAPublicKey)keyPair.getPublic()); System.out.println("encoded: " + encoded); byte[] raw = new JWEInput(encoded).decrypt((RSAPrivateKey)keyPair.getPrivate()).getRawContent(); String from = new String(raw); Assert.assertEquals(content, from); } { String encoded = new JWEBuilder().contentBytes(content.getBytes()).RSA_OAEP((RSAPublicKey)keyPair.getPublic()); System.out.println("encoded: " + encoded); byte[] raw = new JWEInput(encoded).decrypt((RSAPrivateKey)keyPair.getPrivate()).getRawContent(); String from = new String(raw); Assert.assertEquals(content, from); } { String encoded = new JWEBuilder().contentBytes(content.getBytes()).A128CBC_HS256().RSA1_5((RSAPublicKey)keyPair.getPublic()); System.out.println("encoded: " + encoded); byte[] raw = new JWEInput(encoded).decrypt((RSAPrivateKey)keyPair.getPrivate()).getRawContent(); String from = new String(raw); Assert.assertEquals(content, from); } { String encoded = new JWEBuilder().contentBytes(content.getBytes()).A128CBC_HS256().RSA_OAEP((RSAPublicKey)keyPair.getPublic()); System.out.println("encoded: " + encoded); byte[] raw = new JWEInput(encoded).decrypt((RSAPrivateKey)keyPair.getPrivate()).getRawContent(); String from = new String(raw); Assert.assertEquals(content, from); } } @Test public void testDirect() throws Exception { String content = "Live long and prosper."; String encoded = new JWEBuilder().contentBytes(content.getBytes()).dir("geheim"); System.out.println("encoded: " + encoded); byte[] raw = new JWEInput(encoded).decrypt("geheim").getRawContent(); String from = new String(raw); Assert.assertEquals(content, from); }
Digital signatures allow you to protect the integrity of a message. They are used to verify that a message sent was sent by the actual user that sent the message and was modified in transit. Most web apps handle message integrity by using TLS, like HTTPS, to secure the connection between the client and server. Sometimes though, we have representations that are going to be forwarded to more than one recipient. Some representations may hop around from server to server. In this case, TLS is not enough. There needs to be a mechanism to verify who sent the original representation and that they actually sent that message. This is where digital signatures come in.
While the mime type multiple/signed exists, it does have drawbacks. Most importantly it requires the receiver of the message body to understand how to unpack. A receiver may not understand this mime type. A better approach would be to put signatures in an HTTP header so that receivers that don't need to worry about the digital signature, don't have to.
The email world has a nice protocol called Domain Keys Identified Mail (DKIM). Work is also being done to apply this header to protocols other than email (i.e. HTTP) through the DOSETA specifications. It allows you to sign a message body and attach the signature via a DKIM-Signature header. Signatures are calculated by first hashing the message body then combining this hash with an arbitrary set of metadata included within the DKIM-Signature header. You can also add other request or response headers to the calculation of the signature. Adding metadata to the signature calculation gives you a lot of flexibility to piggyback various features like expiration and authorization. Here's what an example DKIM-Signature header might look like.
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=burke; c=simple/simple; h=Content-Type; x=0023423111111; bh=2342322111; b=M232234=
As you can see it is a set of name value pairs delimited by a ';'. While its not THAT important to know the structure of the header, here's an explanation of each parameter:
Protocol version. Always 1.
Algorithm used to hash and sign the message. RSA signing and SHA256 hashing is the only supported algorithm at the moment by Resteasy.
Domain of the signer. This is used to identify the signer as well as discover the public key to use to verify the signature.
Selector of the domain. Also used to identify the signer and discover the public key.
Canonical algorithm. Only simple/simple is supported at the moment. Basically this allows you to transform the message body before calculating the hash
Semi-colon delimited list of headers that are included in the signature calculation.
When the signature expires. This is a numeric long value of the time in seconds since epoch. Allows signer to control when a signed message's signature expires
Timestamp of signature. Numeric long value of the time in seconds since epoch. Allows the verifier to control when a signature expires.
Base 64 encoded hash of the message body.
Base 64 encoded signature.
To verify a signature you need a public key. DKIM uses DNS text records to discover a public key. To find a public key, the verifier concatenates the Selector (s parameter) with the domain (d parameter)
<selector>._domainKey.<domain>
It then takes that string and does a DNS request to retrieve a TXT record under that entry. In our above example burke._domainKey.example.com would be used as a string. This is a every interesting way to publish public keys. For one, it becomes very easy for verifiers to find public keys. There's no real central store that is needed. DNS is a infrastructure IT knows how to deploy. Verifiers can choose which domains they allow requests from. Resteasy supports discovering public keys via DNS. It also instead allows you to discover public keys within a local Java KeyStore if you do not want to use DNS. It also allows you to plug in your own mechanism to discover keys.
If you're interested in learning the possible use cases for digital signatures, here's a blog you might find interesting.
You must include the resteasy-crypto project to use the digital signature framework.
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-crypto</artifactId> <version>3.0.22.Final</version> </dependency>
To sign a request or response using the Resteasy client or server framework you need to create an instance of org.jboss.resteasy.security.doseta.DKIMSignature. This class represents the DKIM-Signature header. You instantiate the DKIMSignature object and then set the "DKIM-Signature" header of the request or response. Here's an example of using it on the server-side:
import org.jboss.resteasy.security.doseta.DKIMSignature; import java.security.PrivateKey; @Path("/signed") public static class SignedResource { @GET @Path("manual") @Produces("text/plain") public Response getManual() { PrivateKey privateKey = ....; // get the private key to sign message DKIMSignature signature = new DKIMSignature(); signature.setSelector("test"); signature.setDomain("samplezone.org"); signature.setPrivateKey(privateKey); Response.ResponseBuilder builder = Response.ok("hello world"); builder.header(DKIMSignature.DKIM_SIGNATURE, signature); return builder.build(); } } // client example DKIMSignature signature = new DKIMSignature(); PrivateKey privateKey = ...; // go find it signature.setSelector("test"); signature.setDomain("samplezone.org"); signature.setPrivateKey(privateKey); ClientRequest request = new ClientRequest("http://..."); request.header("DKIM-Signature", signature); request.body("text/plain", "some body to sign"); ClientResponse response = request.put();
To sign a message you need a PrivateKey. This can be generated by KeyTool or manually using regular, standard JDK Signature APIs. Resteasy currently only supports RSA key pairs. The DKIMSignature class also allows you to add and control how various pieces of metadata are added to the DKIM-Signature header and the signature calculation. See the javadoc for more details.
If you are including more than one signature, then just add additional DKIMSignature instances to the headers of the request or response.
Instead of using the API, Resteasy also provides you an annotation alternative to the manual way of signing using a DKIMSignature instances is to use the @org.jboss.resteasy.annotations.security.doseta.Signed annotation. It is required that you configure a KeyRepository as described later in this chapter. Here's an example:
@GET @Produces("text/plain") @Path("signedresource") @Signed(selector="burke", domain="sample.com", timestamped=true, expires=@After(hours=24)) public String getSigned() { return "hello world"; }
The above example using a bunch of the optional annotation attributes of @Signed to create the following Content-Signature header:
DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; domain=sample.com; s=burke; t=02342342341; x=02342342322; bh=m0234fsefasf==; b=mababaddbb==
This annotation also works with the client proxy
framework.
If you want fine grain control over verification, this is an API to verify signatures manually. Its a little tricky because you'll need the raw bytes of the HTTP message body in order to verify the signature. You can get at an unmarshalled message body as well as the underlying raw bytes by using a org.jboss.resteasy.spi.MarshalledEntity injection. Here's an example of doing this on the server side:
import org.jboss.resteasy.spi.MarshalledEntity; @POST @Consumes("text/plain") @Path("verify-manual") public void verifyManual(@HeaderParam("Content-Signature") DKIMSignature signature, @Context KeyRepository repository, @Context HttpHeaders headers, MarshalledEntity<String> input) throws Exception { Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); verification.setStaleCheck(true); verification.setStaleSeconds(100); try { verifier.verifySignature(headers.getRequestHeaders(), input.getMarshalledBytes, signature); } catch (SignatureException ex) { } System.out.println("The text message posted is: " + input.getEntity()); }
MarshalledEntity is a generic interface. The template parameter should be the Java type you want the message body to be converted into. You will also have to configure a KeyRepository. This is describe later in this chapter.
The client side is a little bit different:
ClientRequest request = new ClientRequest("http://localhost:9095/signed")); ClientResponse<String> response = request.get(String.class); Verifier verifier = new Verifier(); Verification verification = verifier.addNew(); verification.setRepository(repository); response.getProperties().put(Verifier.class.getName(), verifier); // signature verification happens when you get the entity String entity = response.getEntity();
On the client side, you create a verifier and add
it as a property to the ClientResponse. This will trigger the verification
interceptors.
The easiest way to verify a signature sent in a HTTP request on the server side is to use the @@org.jboss.resteasy.annotations.security.doseta.Verify (or @Verifications which is used to verify multiple signatures). Here's an example:
@POST @Consumes("text/plain") @Verify public void post(String input) { }
In the above example, any DKIM-Signature headers attached to the posted message body will be verified. The public key to verify is discovered using the configured KeyRepository (discussed later in this chapter). You can also specify which specific signatures you want to verify as well as define multiple verifications you want to happen via the @Verifications annotation. Here's a complex example:
@POST @Consumes("text/plain") @Verifications( @Verify(identifierName="d", identiferValue="inventory.com", stale=@After(days=2)), @Verify(identifierName="d", identiferValue="bill.com") } public void post(String input) {...}
The above is expecting 2 different signature to be included within the DKIM-Signature header.
Failed verifications will throw an org.jboss.resteasy.security.doseta.UnauthorizedSignatureException. This causes a 401 error code to be sent back to the client. If you catch this exception using an ExceptionHandler you can browse the failure results.
Resteasy manages keys for you through a org.jboss.resteasy.security.doseta.KeyRepository. By default, the KeyRepository is backed by a Java KeyStore. Private keys are always discovered by looking into this KeyStore. Public keys may also be discovered via a DNS text (TXT) record lookup if configured to do so. You can also implement and plug in your own implementation of KeyRepository.
Use the Java keytool to generate RSA key pairs. Key aliases MUST HAVE the form of:
<selector>._domainKey.<domain>
For example:
$ keytool -genkeypair -alias burke._domainKey.example.com -keyalg RSA -keysize 1024 -keystore my-apps.jks
You can always import your own official certificates too. See the JDK documentation for more details.
Next you need to configure the KeyRepository in your web.xml file
so that it is created and made available to Resteasy to discover private
and public keys.You can reference a Java key store you want the Resteasy
signature framework to use within web.xml using either
resteasy.keystore.classpath
or
resteasy.keystore.filename
context parameters. You
must also specify the password (sorry its clear text) using the
resteasy.keystore.password
context parameter. The
resteasy.context.objects is used to create the instance of the
repository. For example:
<context-param> <param-name>resteasy.doseta.keystore.classpath</param-name> <param-value>test.jks</param-value> </context-param> <context-param> <param-name>resteasy.doseta.keystore.password</param-name> <param-value>geheim</param-value> </context-param> <context-param> <param-name>resteasy.context.objects</param-name> <param-value>org.jboss.resteasy.security.doseta.KeyRepository : org.jboss.resteasy.security.doseta.ConfiguredDosetaKeyRepository</param-value> </context-param>
You can also manually register your own instance of a KeyRepository within an Application class. For example:
import org.jboss.resteasy.core.Dispatcher; import org.jboss.resteasy.security.doseta.KeyRepository; import org.jboss.resteasy.security.doseta.DosetaKeyRepository; import javax.ws.rs.core.Application; import javax.ws.rs.core.Context; public class SignatureApplication extends Application { private HashSet<Class<?>> classes = new HashSet<Class<?>>(); private KeyRepository repository; public SignatureApplication(@Context Dispatcher dispatcher) { classes.add(SignedResource.class); repository = new DosetaKeyRepository(); repository.setKeyStorePath("test.jks"); repository.setKeyStorePassword("password"); repository.setUseDns(false); repository.start(); dispatcher.getDefaultContextObjects().put(KeyRepository.class, repository); } @Override public Set<Class<?>> getClasses() { return classes; } }
On the client side, you can load a KeyStore manually, by instantiating an instance of org.jboss.resteasy.security.doseta.DosetaKeyRepository. You then set a request attribute, "org.jboss.resteasy.security.doseta.KeyRepository", with the value of the created instance. Use the ClientRequest.getAttributes() method to do this. For example:
DosetaKeyRepository keyRepository = new DoestaKeyRepository(); repository.setKeyStorePath("test.jks"); repository.setKeyStorePassword("password"); repository.setUseDns(false); repository.start(); DKIMSignature signature = new DKIMSignature(); signature.setDomain("example.com"); ClientRequest request = new ClientRequest("http://..."); request.getAttributes().put(KeyRepository.class.getName(), repository); request.header("DKIM-Signature", signatures);
Public keys can also be discover by a DNS text record lookup. You must configure web.xml to turn this feature:
<context-param> <param-name>resteasy.doseta.use.dns</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>resteasy.doseta.dns.uri</param-name> <param-value>dns://localhost:9095</param-value> </context-param>
The resteasy.doseta.dns.uri context-param is optional and allows you to point to a specific DNS server to locate text records.
DNS TXT Records are stored via a format described by the DOSETA specification. The public key is defined via a base 64 encoding. You can obtain this text encoding by exporting your public keys from your keystore, then using a tool like openssl to get the text-based format. For example:
$ keytool -export -alias bill._domainKey.client.com -keystore client.jks -file bill.der $ openssl x509 -noout -pubkey -in bill.der -inform der > bill.pem
The output will look something like:
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCKxct5GHz8dFw0mzAMfvNju2b3 oeAv/EOPfVb9mD73Wn+CJYXvnryhqo99Y/q47urWYWAF/bqH9AMyMfibPr6IlP8m O9pNYf/Zsqup/7oJxrvzJU7T0IGdLN1hHcC+qRnwkKddNmD8UPEQ4BXiX4xFxbTj NvKWLZVKGQMyy6EFVQIDAQAB -----END PUBLIC KEY-----
The DNS text record entry would look like this:
test2._domainKey IN TXT "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIKFLFWuQfDfBug688BJ0dazQ/x+GEnH443KpnBK8agpJXSgFAPhlRvf0yhqHeuI+J5onsSOo9Rn4fKaFQaQNBfCQpHSMnZpBC3X0G5Bc1HWq1AtBl6Z1rbyFen4CmGYOyRzDBUOIW6n8QK47bf3hvoSxqpY1pHdgYoVK0YdIP+wIDAQAB; t=s"
Notice that the newlines are take out. Also, notice that the text record is a name value ';' delimited list of parameters. The p field contains the public key.
S/MIME (Secure/Multipurpose Internet Mail Extensions) is a standard for public key encryption and signing of MIME data. MIME data being a set of headers and a message body. Its most often seen in the email world when somebody wants to encrypt and/or sign an email message they are sending across the internet. It can also be used for HTTP requests as well which is what the RESTEasy integration with S/MIME is all about. RESTEasy allows you to easily encrypt and/or sign an email message using the S/MIME standard. While the API is described here, you may also want to check out the example projects that come with the RESTEasy distribution. It shows both Java and Python clients exchanging S/MIME formatted messages with a JAX-RS service.
You must include the resteasy-crypto project to use the smime framework.
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-crypto</artifactId> <version>3.0.22.Final</version> </dependency>
While HTTPS is used to encrypt the entire HTTP message, S/MIME
encryption is used solely for the message body of the HTTP request or
response. This is very useful if you have a representation that may be
forwarded by multiple parties (for example, HornetQ's REST Messaging
integration!) and you want to protect the message from prying eyes as it
travels across the network. RESTEasy has two different interfaces for
encrypting message bodies. One for output, one for input. If your client
or server wants to send an HTTP request or response with an encrypted
body, it uses the
org.jboss.resteasy.security.smime.EnvelopedOutput
type.
Encrypting a body also requires an X509 certificate which can be generated
by the Java keytool command-line interface, or the openssl tool that comes
installed on many OS's. Here's an example of using the
EnvelopedOutput
interface:
// server side @Path("encrypted") @GET public EnvelopedOutput getEncrypted() { Customer cust = new Customer(); cust.setName("Bill"); X509Certificate certificate = ...; EnvelopedOutput output = new EnvelopedOutput(cust, MediaType.APPLICATION_XML_TYPE); output.setCertificate(certificate); return output; } // client side X509Certificate cert = ...; Customer cust = new Customer(); cust.setName("Bill"); EnvelopedOutput output = new EnvelopedOutput(cust, "application/xml"); output.setCertificate(cert); Response res = target.request().post(Entity.entity(output, "application/pkcs7-mime").post();
An EnvelopedOutput instance is created passing in the entity you want to marshal and the media type you want to marshal it into. So in this example, we're taking a Customer class and marshalling it into XML before we encrypt it. RESTEasy will then encrypt the EnvelopedOutput using the BouncyCastle framework's SMIME integration. The output is a Base64 encoding and would look something like this:
Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name="smime.p7m" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7m" MIAGCSqGSIb3DQEHA6CAMIACAQAxgewwgekCAQAwUjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMK U29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkAgkA7oW81OriflAw DQYJKoZIhvcNAQEBBQAEgYCfnqPK/O34DFl2p2zm+xZQ6R+94BqZHdtEWQN2evrcgtAng+f2ltIL xr/PiK+8bE8wDO5GuCg+k92uYp2rLKlZ5BxCGb8tRM4kYC9sHbH2dPaqzUBhMxjgWdMCX6Q7E130 u9MdGcP74Ogwj8fNl3lD4sx/0k02/QwgaukeY7uNHzCABgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE CDRozFLsPnSgoIAEQHmqjSKAWlQbuGQL9w4nKw4l+44WgTjKf7mGWZvYY8tOCcdmhDxRSM1Ly682 Imt+LTZf0LXzuFGTsCGOUo742N8AAAAAAAAAAAAA
Decrypting an S/MIME encrypted message requires using the org.jboss.resteasy.security.smime.EnvelopedInput interface. You also need both the private key and X509Certificate used to encrypt the message. Here's an example:
// server side @Path("encrypted") @POST public void postEncrypted(EnvelopedInput<Customer> input) { PrivateKey privateKey = ...; X509Certificate certificate = ...; Customer cust = input.getEntity(privateKey, certificate); } // client side ClientRequest request = new ClientRequest("http://localhost:9095/smime/encrypted"); EnvelopedInput input = request.getTarget(EnvelopedInput.class); Customer cust = (Customer)input.getEntity(Customer.class, privateKey, cert);
Both examples simply call the getEntity() method passing in the PrivateKey and X509Certificate instances requires to decrypt the message. On the server side, a generic is used with EnvelopedInput to specify the type to marshal to. On the server side this information is passed as a parameter to getEntity(). The message is in MIME format: a Content-Type header and body, so the EnvelopedInput class now has everything it needs to know to both decrypt and unmarshall the entity.
S/MIME also allows you to digitally sign a message. It is a bit different than the Doseta Digital Signing Framework. Doseta is an HTTP header that contains the signature. S/MIME uses the multipart/signed data format which is a multipart message that contains the entity and the digital signature. So Doseta is a header, S/MIME is its own media type. Generally I would prefer Doseta as S/MIME signatures require the client to know how to parse a multipart message and Doseta doesn't. Its up to you what you want to use.
Resteasy has two different interfaces for creating a
multipart/signed message. One for input, one for output. If your client or
server wants to send an HTTP request or response with an multipart/signed
body, it uses the
org.jboss.resteasy.security.smime.SignedOutput
type.
This type requires both the PrivateKey and X509Certificate to create the
signature. Here's an example of signing an entity and sending a
multipart/signed entity.
// server-side @Path("signed") @GET @Produces("multipart/signed") public SignedOutput getSigned() { Customer cust = new Customer(); cust.setName("Bill"); SignedOutput output = new SignedOutput(cust, MediaType.APPLICATION_XML_TYPE); output.setPrivateKey(privateKey); output.setCertificate(certificate); return output; } // client side Client client = new ResteasyClient(); WebTarget target = client.target("http://localhost:9095/smime/signed"); Customer cust = new Customer(); cust.setName("Bill"); SignedOutput output = new SignedOutput(cust, "application/xml"); output.setPrivateKey(privateKey); output.setCertificate(cert); Response res = target.request().post(Entity.entity(output, "multipart/signed");
An SignedOutput instance is created passing in the entity you want to marshal and the media type you want to marshal it into. So in this example, we're taking a Customer class and marshalling it into XML before we sign it. RESTEasy will then sign the SignedOutput using the BouncyCastle framework's SMIME integration. The output iwould look something like this:
Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="----=_Part_0_1083228271.1313024422098" ------=_Part_0_1083228271.1313024422098 Content-Type: application/xml Content-Transfer-Encoding: 7bit <customer name="bill"/> ------=_Part_0_1083228271.1313024422098 Content-Type: application/pkcs7-signature; name=smime.p7s; smime-type=signed-data Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="smime.p7s" Content-Description: S/MIME Cryptographic Signature MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAMYIBVzCCAVMC AQEwUjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJu ZXQgV2lkZ2l0cyBQdHkgTHRkAgkA7oW81OriflAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzEL BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTExMDgxMTAxMDAyMlowIwYJKoZIhvcNAQkEMRYE FH32BfR1l1vzDshtQvJrgvpGvjADMA0GCSqGSIb3DQEBAQUABIGAL3KVi3ul9cPRUMYcGgQmWtsZ 0bLbAldO+okrt8mQ87SrUv2LGkIJbEhGHsOlsgSU80/YumP+Q4lYsVanVfoI8GgQH3Iztp+Rce2c y42f86ZypE7ueynI4HTPNHfr78EpyKGzWuZHW4yMo70LpXhk5RqfM9a/n4TEa9QuTU76atAAAAAA AAA= ------=_Part_0_1083228271.1313024422098--
To unmarshal and verify a signed message requires using the
org.jboss.resteasy.security.smime.SignedInput
interface. You only need the X509Certificate to verify the message. Here's
an example of unmarshalling and verifying a multipart/signed
entity.
// server side @Path("signed") @POST @Consumes("multipart/signed") public void postSigned(SignedInput<Customer> input) throws Exception { Customer cust = input.getEntity(); if (!input.verify(certificate)) { throw new WebApplicationException(500); } } // client side Client client = new ResteasyClient(); WebTarget target = client.target("http://localhost:9095/smime/signed"); SignedInput input = target.request().get(SignedInput.class); Customer cust = (Customer)input.getEntity(Customer.class) input.verify(cert);
application/pkcs7-signature is a data format that includes both the data and the signature in one ASN.1 binary encoding.
SignedOutput and SignedInput can be used to return application/pkcs7-signature format in binary form. Just change the @Produces or @Consumes to that media type to send back that format.
Also, if your @Produces or @Consumes is text/plain instead, SignedOutput will be base64 encoded and sent as a string.
To integrate with EJB you must first modify your EJB's published interfaces. Resteasy currently only has simple portable integration with EJBs so you must also manually configure your Resteasy WAR.
Resteasy currently only has simple integration with EJBs. To make an EJB a JAX-RS resource, you must annotate an SLSB's @Remote or @Local interface with JAX-RS annotations:
@Local @Path("/Library") public interface Library { @GET @Path("/books/{isbn}") public String getBook(@PathParam("isbn") String isbn); } @Stateless public class LibraryBean implements Library { ... }
Next, in RESTEasy's web.xml file you must manually register the EJB with RESTEasy using the resteasy.jndi.resources <context-param>
<web-app> <display-name>Archetype Created Web Application</display-name> <context-param> <param-name>resteasy.jndi.resources</param-name> <param-value>LibraryBean/local</param-value> </context-param> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
This is the only portable way we can offer EJB integration. Future versions of RESTEasy will have tighter integration with WildFly so you do not have to do any manual registrations or modifications to web.xml. For right now though, we're focusing on portability.
If you're using Resteasy with an EAR and EJB, a good structure to have is:
my-ear.ear |------myejb.jar |------resteasy-jaxrs.war | ----WEB-INF/web.xml ----WEB-INF/lib (nothing) |------lib/ | ----All Resteasy jar files
From the distribution, remove all libraries from WEB-INF/lib and place them in a common EAR lib. OR. Just place the Resteasy jar dependencies in your application server's system classpath. (i.e. In JBoss put them in server/default/lib)
An example EAR project is available from our testsuite here.
RESTEasy integrates with Spring 3.0.x. We are interested in other forms of Spring integration, so please help contribute.
For Maven users, you must use the resteasy-spring artifact. Otherwise, the jar is available in the downloaded distribution.
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-spring</artifactId> <version>whatever version you are using</version> </dependency>
RESTEasy comes with its own Spring ContextLoaderListener that registers a RESTEasy specific BeanPostProcessor that processes JAX-RS annotations when a bean is created by a BeanFactory. What does this mean? RESTEasy will automatically scan for @Provider and JAX-RS resource annotations on your bean class and register them as JAX-RS resources.
Here is what you have to do with your web.xml file
<web-app> <display-name>Archetype Created Web Application</display-name> <listener> <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class> </listener> <listener> <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
The SpringContextLoaderListener must be declared after ResteasyBootstrap as it uses ServletContext attributes initialized by it.
If you do not use a Spring ContextLoaderListener to create your bean factories, then you can manually register the RESTEasy BeanFactoryPostProcessor by allocating an instance of org.jboss.resteasy.plugins.spring.SpringBeanProcessor. You can obtain instances of a ResteasyProviderFactory and Registry from the ServletContext attributes org.jboss.resteasy.spi.ResteasyProviderFactory and org.jboss.resteasy.spi.Registry. (Really the string FQN of these classes). There is also a org.jboss.resteasy.plugins.spring.SpringBeanProcessorServletAware, that will automatically inject references to the Registry and ResteasyProviderFactory from the Servlet Context. (that is, if you have used RestasyBootstrap to bootstrap Resteasy).
Our Spring integration supports both singletons and the "prototype" scope. RESTEasy handles injecting @Context references. Constructor injection is not supported though. Also, with the "prototype" scope, RESTEasy will inject any @*Param annotated fields or setters before the request is dispatched.
NOTE: You can only use auto-proxied beans with our base Spring integration. You will have undesirable affects if you are doing handcoded proxying with Spring, i.e., with ProxyFactoryBean. If you are using auto-proxied beans, you will be ok.
RESTEasy can also integrate with the Spring DispatcherServlet. The advantages of using this are that you have a simpler web.xml file, you can dispatch to either Spring controllers or Resteasy from under the same base URL, and finally, the most important, you can use Spring ModelAndView objects as return arguments from @GET resource methods. Setup requires you using the Spring DispatcherServlet in your web.xml file, as well as importing the springmvc-resteasy.xml file into your base Spring beans xml file. Here's an example web.xml file:
<web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>Spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Spring</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
Then within your main Spring beans xml, import the springmvc-resteasy.xml file
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- Import basic SpringMVC Resteasy integration --> <import resource="classpath:springmvc-resteasy.xml"/> ....
You can specify resteasy configuration options by overriding the resteasy.deployment bean which is an instance of org.jboss.resteasy.spi.ResteasyDeployment. Here's an example of adding media type suffix mappings as well as enabling the Resteasy asynchronous job service.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- Import basic SpringMVC Resteasy integration --> <import resource="classpath:springmvc-resteasy.xml" /> <!-- override the bean definition for deployment --> <bean id="resteasy.deployment" class="org.jboss.resteasy.spi.ResteasyDeployment" init-method="start" destroy-method="stop"> <property name="asyncJobServiceEnabled" value="true"/> <property name="mediaTypeMappings"> <map> <entry key="json" value="application/json" /> <entry key="xml" value="application/xml" /> </map> </property> </bean> ...
A JAX-RS Application subclass can be combined with a Spring DispatcherServlet and used in the same web application. An application combined in this way allows you to dispatch to either the Spring controller or the JAX-RS resource using the same base URL. In addition you can use the Spring ModelAndView objects as return arguments from @GET resource methods.
Configuring a web application of this type requires a web.xml and spring-servlet.xml file and a reference to springmvc-resteasy.xml. A servlet definition is required for both the Spring DispatcherServlet and the Application subclass in the web.xml, as well as RESTEasy Configuration Switch, resteasy.scan.resources. Here is an example of the minimum configuration information needed in the web.xml.
<web-app> <servlet> <servlet-name>mySpring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>mySpring</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>myAppSubclass</servlet-name> <servlet-class>org.my.app.EntryApplicationSubclass</servlet-class> </servlet> <servlet-mapping> <servlet-name>myAppSubclass</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <!-- required Resteasy Configuration Switch directs auto scanning of the archive for JAX-RS resource files --> <context-param> <param-name>resteasy.scan.resources</param-name> <param-value>true</param-value> </context-param> </web-app>
If your web application contains JAX-RS provider classes the RESTEasy Configuration Switch, resteasy.scan.providers, will also be needed. And if the url-pattern for the JAX-RS Application subclass is other than /* you will need to declare the RESTEasy Configuration Switch, resteasy.servlet.mapping.prefix. This switch can be declare either as a context-param or as a servlet init-param. It's value must be the text that preceeds the /*. Here is an example of such a web.xml.
<web-app> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>myAppSubclass</servlet-name> <servlet-class>org.my.app.EntryApplicationSubclass</servlet-class> <init-param> <param-name>resteasy.servlet.mapping.prefix</param-name> <param-value>/resources</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>myAppSubclass</servlet-name> <url-pattern>/resources/*</url-pattern> </servlet-mapping> <context-param> <param-name>resteasy.scan.resources</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>resteasy.scan.providers</param-name> <param-value>true</param-value> </context-param> </web-app>
The spring-servlet.xml file must import springmvc-resteasy.xml, however this file does not need to be present in the archive. In addition a component-scan, declaration of the packages that contain you application classes is needed. At minimum your spring-servlet.xml should contain these statements.
<beans> <import resource="classpath:springmvc-resteasy.xml"/> <context:component-scan base-package="org.my.app"/> </beans>
Note. As noted in Section 3.1.2, “Upgrading Resteasy within WildFly”, the The Resteasy distribution comes with a zip file called resteasy-jboss-modules-<version>.zip, which can be unzipped into the modules/system/layers/base/ directory of WildFly to upgrade to a new version of Resteasy. Because of the way resteasy-spring is used in WildFly, after unzipping the zip file, it is also necessary to remove the old resteasy-spring jar from modules/system/layers/base/org/jboss/resteasy/resteasy-spring/main/bundled/resteasy-spring-jar.
This module provides integration with JSR-299 (Contexts and Dependency Injection for the Java EE platform)
Both the JAX-RS and CDI specifications introduce their own
component
model. On the one hand, every class placed in a CDI archive that
fulfills a set of basic constraints is implicitly a CDI bean. On
the
other hand, explicit decoration of your Java class with
@Path
or
@Provider
is required for it to become a JAX-RS component.
Without the
integration code, annotating a class suitable for being a CDI bean
with
JAX-RS annotations leads into a faulty result (JAX-RS component
not managed by CDI)
The
resteasy-cdi module is a bridge that
allows
RESTEasy
to work with class
instances obtained from the CDI
container.
During a web service invocation, resteasy-cdi asks the CDI container for the managed instance of a JAX-RS component. Then, this instance is passed to RESTEasy. If a managed instance is not available for some reason (the class is placed in a jar which is not a bean deployment archive), RESTEasy falls back to instantiating the class itself.
As a result, CDI services like injection, lifecycle management, events, decoration and interceptor bindings can be used in JAX-RS components.
A CDI bean that does not explicitly define a scope is
@Dependent
scoped by default.
This pseudo scope means that the bean adapts to the
lifecycle of
the bean it is
injected into. Normal scopes (request,
session,
application) are more suitable for JAX-RS components as they
designate component's lifecycle boundaries explicitly. Therefore, the
resteasy-cdi
module alters the default
scoping in
the following way:
If a JAX-RS root resource does not define a scope explicitly, it is bound to the Request scope.
If a JAX-RS Provider or
javax.ws.rs.Application
subclass
does not define a scope explicitly, it is bound to the
Application
scope.
Since the scope of all beans that do not declare a scope is modified by resteasy-cdi, this affects session beans as well. As a result, a conflict occurs if the scope of a stateless session bean or singleton is changed automatically as the spec prohibits these components to be @RequestScoped. Therefore, you need to explicitly define a scope when using stateless session beans or singletons. This requirement is likely to be removed in future releases.
CDI integration is provided with no additional configuration with WildFly.
Provided you have an
existing RESTEasy application, all that needs to
be done is to
add the
resteasy-cdi jar into your project's
WEB-INF/lib
directory.
When using
maven, this can be achieve by defining the
following
dependency.
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-cdi</artifactId> <version>${project.version}</version> </dependency>
Furthermore, when running a pre-Servlet 3 container, the following context parameter needs to be specified in web.xml. (This is done automatically via web-fragment in a Servlet 3 environment)
<context-param>
<param-name>resteasy.injector.factory</param-name>
<param-value>org.jboss.resteasy.cdi.CdiInjectorFactory</param-value>
</context-param>
When deploying an application to a Servlet container that does not support CDI out of the box (Tomcat, Jetty, Google App Engine), a CDI implementation needs to be added first. Weld-servlet module can be used for this purpose.
RESTEasy integrates quite nicely with the JBoss Seam framework. This integration is maintained by the Seam developers and documented there as well. Check out Seam documentation.
RESTEasy has some simple integration with Guice 3.0. RESTEasy will scan the binding types for a Guice Module for @Path and @Provider annotations. It will register these bindings with RESTEasy. The guice-hello project that comes in the RESTEasy examples/ directory gives a nice example of this.
@Path("hello") public class HelloResource { @GET @Path("{name}") public String hello(@PathParam("name") final String name) { return "Hello " + name; } }
First you start off by specifying a JAX-RS resource class. The HelloResource is just that. Next you create a Guice Module class that defines all your bindings:
import com.google.inject.Module; import com.google.inject.Binder; public class HelloModule implements Module { public void configure(final Binder binder) { binder.bind(HelloResource.class); } }
You put all these classes somewhere within your WAR WEB-INF/classes or in a JAR within WEB-INF/lib. Then you need to create your web.xml file. You need to use the GuiceResteasyBootstrapServletContextListener as follows
<web-app> <display-name>Guice Hello</display-name> <context-param> <param-name>resteasy.guice.modules</param-name> <param-value>org.jboss.resteasy.examples.guice.hello.HelloModule</param-value> </context-param> <listener> <listener-class> org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener </listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class> org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher </servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
GuiceResteasyBootstrapServletContextListener is a subclass of ResteasyBootstrap, so you can use any other RESTEasy configuration option within your web.xml file. Also notice that there is a resteasy.guice.modules context-param. This can take a comma delimited list of class names that are Guice Modules.
Add the RequestScopeModule to your modules to allow objects to be scoped to the HTTP request by adding the @RequestScoped annotation to your class. All the objects injectable via the @Context annotation are also injectable, except ServletConfig and ServletContext.
import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.Context; import org.jboss.resteasy.plugins.guice.RequestScoped; @RequestScoped public class MyClass { @Inject @Context private HttpRequest request; }
Add the JaxrsModule to bind javax.ws.rs.ext.RuntimeDelegate, javax.ws.rs.core.Response.ResponseBuilder, javax.ws.rs.core.UriBuilder, javax.ws.rs.core.Variant.VariantListBuilder and org.jboss.resteasy.client.ClientExecutor.
You can configure the stage Guice uses to deploy your modules by specific a context param, resteasy.guice.stage. If this value is not specified, Resteasy uses whatever Guice's default is.
<web-app> <display-name>Guice Hello</display-name> <context-param> <param-name>resteasy.guice.modules</param-name> <param-value>org.jboss.resteasy.examples.guice.hello.HelloModule</param-value> </context-param> <context-param> <param-name>resteasy.guice.stage</param-name> <param-value>PRODUCTION</param-value> </context-param> <listener> <listener-class> org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener </listener-class> </listener> <servlet> <servlet-name>Resteasy</servlet-name> <servlet-class> org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher </servlet-class> </servlet> <servlet-mapping> <servlet-name>Resteasy</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
GuiceResteasyBootstrapServletContextListener can be extended to allow more flexibility in the way the Injector and Modules are created. Three methods can be overridden: getModules(), withInjector() and getStage(). Register your subclass as the listener in the web.xml.
Override getModules() when you need to pass arguments to your modules' constructor or perform more complex operations.
Override withInjector(Injector) when you need to interact with the Injector after it has been created.
Override getStage(ServletContext) to set the Stage yourself.
<web-app> <!-- other tags omitted --> <listener> <listener-class> org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener </listener-class> </listener> </web-app> public class MyServletContextListener extends GuiceResteasyBootstrapServletContextListener { @Override protected List<? extends Module> getModules(ServletContext context) { return Arrays.asList(new JpaPersistModule("consulting_hours"), new MyModule()); } @Override public void withInjector(Injector injector) { injector.getInstance(PersistService.class).start(); } }
JAX-RS 2.0 introduces a new client API so that you can make http requests to your remote RESTful web services. It is a 'fluent' request building API with really 3 main classes: Client, WebTarget, and Response. The Client interface is a builder of WebTarget instances. WebTarget represents a distinct URL or URL template from which you can build more sub-resource WebTargets or invoke requests on.
There are really two ways to create a Client. Standard way, or you can use the ResteasyClientBuilder class. The advantage of the latter is that it gives you a few more helper methods to configure your client.
Client client = ClientBuilder.newClient(); ... or... Client client = ClientBuilder.newBuilder().build(); WebTarget target = client.target("http://foo.com/resource"); Response response = target.request().get(); String value = response.readEntity(String.class); response.close(); // You should close connections! ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://foo.com/resource");
Resteasy will automatically load a set of default providers. (Basically all classes listed in all META-INF/services/javax.ws.rs.ext.Providers files). Additionally, you can manually register other providers, filters, and interceptors through the Configuration object provided by the method call Client.configuration(). Configuration also lets you set various configuration properties that may be needed.
Each WebTarget has its own Configuration instance which inherits the components and properties registered with its parent. This allows you to set specific configuration options per target resource. For example, username and password.
The Resteasy Proxy Framework is the mirror opposite of the JAX-RS server-side specification. Instead of using JAX-RS annotations to map an incoming request to your RESTFul Web Service method, the client framework builds an HTTP request that it uses to invoke on a remote RESTful Web Service. This remote service does not have to be a JAX-RS service and can be any web resource that accepts HTTP requests.
Resteasy has a client proxy framework that allows you to use JAX-RS annotations to invoke on a remote HTTP resource. The way it works is that you write a Java interface and use JAX-RS annotations on methods and the interface. For example:
public interface SimpleClient { @GET @Path("basic") @Produces("text/plain") String getBasic(); @PUT @Path("basic") @Consumes("text/plain") void putBasic(String body); @GET @Path("queryParam") @Produces("text/plain") String getQueryParam(@QueryParam("param")String param); @GET @Path("matrixParam") @Produces("text/plain") String getMatrixParam(@MatrixParam("param")String param); @GET @Path("uriParam/{param}") @Produces("text/plain") int getUriParam(@PathParam("param")int param); }
Resteasy has a simple API based on Apache HttpClient. You generate a proxy then you can invoke methods on the proxy. The invoked method gets translated to an HTTP request based on how you annotated the method and posted to the server. Here's how you would set this up:
Client client = ClientBuilder.newClient(); WebTarget target = client.target("http://example.com/base/uri"); ResteasyWebTarget rtarget = (ResteasyWebTarget)target; SimpleClient simple = rtarget.proxy(SimpleClient.class); client.putBasic("hello world");
Alternatively you can use the Resteasy client extension interfaces directly:
ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target("http://example.com/base/uri"); SimpleClient simple = target.proxy(SimpleClient.class); client.putBasic("hello world");
@CookieParam works the mirror opposite of its server-side counterpart and creates a cookie header to send to the server. You do not need to use @CookieParam if you allocate your own javax.ws.rs.core.Cookie object and pass it as a parameter to a client proxy method. The client framework understands that you are passing a cookie to the server so no extra metadata is needed.
The framework also supports the JAX-RS locator pattern, but on the client side. So, if you have a method annotated only with @Path, that proxy method will return a new proxy of the interface returned by that method.
Sometimes you are interested not only in the response body of a client request, but also either the response code and/or response headers. The Client-Proxy framework has two ways to get at this information
You may return a javax.ws.rs.core.Response.Status enumeration from your method calls:
@Path("/") public interface MyProxy { @POST Response.Status updateSite(MyPojo pojo); }
Internally, after invoking on the server, the client proxy internals will convert the HTTP response code into a Response.Status enum.
If you are interested in everything, you can get it with the javax.ws.rs.core.Response class:
@Path("/") public interface LibraryService { @GET @Produces("application/xml") Response getAllBooks(); }
It is generally possible to share an interface between the client and server. In this scenario, you just have your JAX-RS services implement an annotated interface and then reuse that same interface to create client proxies to invoke on the client-side.
Network communication between the client and server is handled in Resteasy,
by default, by HttpClient (4.x) from the Apache HttpComponents project. In general,
the interface between the Resteasy Client Framework and the network is
found in an implementation of
org.jboss.resteasy.client.jaxrs.ClientHttpEngine
, and
org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine
,
which uses HttpClient (4.x), is the default implementation. Resteasy
also ships with the following client engines, all found in the
org.jboss.resteasy.client.jaxrs.engines
package:
java.net.HttpURLConnection
;
and a client executor may be passed to a specific ClientRequest
:
ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();
Resteasy and HttpClient make reasonable default decisions so that it is possible to use the client
framework without ever referencing HttpClient, but for some applications it may be necessary to drill
down into the HttpClient details. ApacheHttpClient4Engine
can
be supplied with an instance of org.apache.http.client.HttpClient
and
an instance of org.apache.http.protocol.HttpContext
, which can carry
additional configuration details into the HttpClient layer. For example, authentication
may be configured as follows:
// Configure HttpClient to authenticate preemptively // by prepopulating the authentication data cache. // 1. Create AuthCache instance AuthCache authCache = new BasicAuthCache(); // 2. Generate BASIC scheme object and add it to the local auth cache AuthScheme basicAuth = new BasicScheme(); authCache.put(new HttpHost("sippycups.bluemonkeydiamond.com"), basicAuth); // 3. Add AuthCache to the execution context BasicHttpContext localContext = new BasicHttpContext(); localContext.setAttribute(ClientContext.AUTH_CACHE, authCache); // 4. Create client executor and proxy DefaultHttpClient httpClient = new DefaultHttpClient(); ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient, localContext); ResteasyClient client = new ResteasyClientBuilder().httpEngine(engine).build();
One default decision made by HttpClient and adopted by Resteasy is the use of
org.apache.http.impl.conn.SingleClientConnManager
,
which manages a single socket at any given time and which
supports the use case in which one or more invocations are made serially
from a single thread. For multithreaded applications,
SingleClientConnManager
may be replaced by
org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager
:
ClientConnectionManager cm = new ThreadSafeClientConnManager(); HttpClient httpClient = new DefaultHttpClient(cm); ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
For more information about HttpClient (4.x), see the documentation at http://hc.apache.org/httpcomponents-client-ga/tutorial/html/.
Note. It is important to understand the difference between "releasing" a connection and "closing" a connection. Releasing a connection makes it available for reuse. Closing a connection frees its resources and makes it unusable.
SingleClientConnManager
manages
a single socket, which it allocates to at most a single invocation
at any given time. Before that socket can be reused, it has to be
released from its current use, which can occur in one of two ways. If
an execution of a request or a call on
a proxy returns a class other than Response
,
then Resteasy will take care of releasing the connection. For example,
in the fragments
WebTarget target = client.target("http://localhost:8081/customer/123"); String answer = target.request().get(String.class);
or
ResteasyWebTarget target = client.target("http://localhost:8081/customer/123"); RegistryStats stats = target.proxy(RegistryStats.class); RegistryData data = stats.get();
Resteasy will release the connection under the covers. The only counterexample is the case
in which the response is an instance of InputStream
, which must
be closed explicitly.
On the other hand, if the result of an invocation is an instance of
Response
, then Response.close() method must be used to released the connection.
WebTarget target = client.target("http://localhost:8081/customer/123"); Response response = target.request().get(); System.out.println(response.getStatus()); response.close();
You should probably execute this in a try/finally block. Again, releasing a connection only makes it available for another use. It does not normally close the socket.
On the other hand,
ApacheHttpClient4Engine.finalize()
will close any open
sockets, but only if it created the HttpClient
it has been
using. If an HttpClient
has been passed into the
ApacheHttpClient4Executor
, then the user is responsible
for closing the connections:
HttpClient httpClient = new DefaultHttpClient(); ApacheHttpClient4Engine executor = new ApacheHttpClient4Engine(httpClient); ... httpClient.getConnectionManager().shutdown();
Note that if ApacheHttpClient4Engine
has created its own
instance of HttpClient
, it is not necessary to wait
for finalize()
to close open sockets. The
ClientHttpEngine
interface has a close()
method for this purpose.
Finally, if your javax.ws.rs.client.Client class has created the engine automatically for you, you should call Client.close() and this will clean up any socket connections.
RESTEasy resources can be accessed in JavaScript using AJAX using a proxy API generated by RESTEasy.
RESTEasy can generate a JavaScript API that uses AJAX calls to invoke JAX-RS operations.
Example 50.1. First JAX-RS JavaScript API example
Let's take a simple JAX-RS API:
@Path("orders") public interface Orders { @Path("{id}") @GET public String getOrder(@PathParam("id") String id){ return "Hello "+id; } }
The preceding API would be accessible using the following JavaScript code:
var order = Orders.getOrder({id: 23});
In order to enable the JavaScript API servlet you must configure it in your web.xml file as such:
<servlet>
<servlet-name>RESTEasy JSAPI</servlet-name>
<servlet-class>org.jboss.resteasy.jsapi.JSAPIServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RESTEasy JSAPI</servlet-name>
<url-pattern>/rest-js</url-pattern>
</servlet-mapping>
Each JAX-RS resource class will generate a JavaScript object of the same name as the declaring class (or interface), which will contain every JAX-RS method as properties.
Example 50.2. Structure of JAX-RS generated JavaScript
For example, if the JAX-RS resource X defines methods Y and Z:
@Path("/")
public interface X{
@GET
public String Y();
@PUT
public void Z(String entity);
}
Then the JavaScript API will define the following functions:
var X = { Y : function(params){…}, Z : function(params){…} };
Each JavaScript API method takes an optional object as single parameter where each property is a cookie, header, path, query or form parameter as identified by their name, or the following special parameters:
The following special parameter names are subject to change.
Property name | Default | Description |
---|---|---|
$entity | The entity to send as a PUT, POST request. | |
$contentType | As determined by @Consumes. | The MIME type of the body entity sent as the Content-Type header. |
$accepts | Determined by @Provides, defaults to */*. | The accepted MIME types sent as the Accept header. |
$callback | Set to a function(httpCode, xmlHttpRequest, value) for an asynchronous call. If not present, the call will be synchronous and return the value. | |
$apiURL | Determined by container | Set to the base URI of your JAX-RS endpoint, not including the last slash. |
$username | If username and password are set, they will be used for credentials for the request. | |
$password | If username and password are set, they will be used for credentials for the request. |
Example 50.3. Using the API
Here is an example of JAX-RS API:
@Path("foo")
public class Foo{
@Path("{id}")
@GET
public String get(@QueryParam("order") String order, @HeaderParam("X-Foo") String header,
@MatrixParam("colour") String colour, @CookieParam("Foo-Cookie") String cookie){
&
}
@POST
public void post(String text){
}
}
We can use the previous JAX-RS API in JavaScript using the following code:
var text = Foo.get({order: 'desc', 'X-Foo': 'hello', colour: 'blue', 'Foo-Cookie': 123987235444}); Foo.put({$entity: text});
@Form is a RESTEasy specific annotation that allows you to re-use any @*Param annotation within an injected class. The generated JavaScript API will expand the parameters for use automatically. Support we have the following form:
public class MyForm {
@FormParam("stuff")
private String stuff;
@FormParam("number")
private int number;
@HeaderParam("myHeader")
private String header;
}
And the resource is like:
@Path("/")
public class MyResource {
@POST
public String postForm(@Form MyForm myForm) {...}
}
Then we could call the method from JavaScript API like following:
MyResource.postForm({stuff:"A", myHeader:"B", number:1});
Also, @Form supports prefix mappings for lists and maps:
public static class Person {
@Form(prefix="telephoneNumbers") List<TelephoneNumber> telephoneNumbers;
@Form(prefix="address") Map<String, Address> addresses;
}
public static class TelephoneNumber {
@FormParam("countryCode") private String countryCode;
@FormParam("number") private String number;
}
public static class Address {
@FormParam("street") private String street;
@FormParam("houseNumber") private String houseNumber;
}
@Path("person")
public static class MyResource {
@POST
public void postForm(@Form Person p) {...}
}
From JavaScript we could call the API like this:
MyResource.postForm({ telephoneNumbers:[ {"telephoneNumbers[0].countryCode":31}, {"telephoneNumbers[0].number":12345678}, {"telephoneNumbers[1].countryCode":91}, {"telephoneNumbers[1].number":9717738723} ], address:[ {"address[INVOICE].street":"Main Street"}, {"address[INVOICE].houseNumber":2}, {"address[SHIPPING].street":"Square One"}, {"address[SHIPPING].houseNumber":13} ] });
The Accept header sent by any client JavaScript function is controlled by the $accepts parameter, which overrides the @Produces annotation on the JAX-RS endpoint. The returned value however is controlled by the Content-Type header sent in the response as follows:
MIME | Description |
---|---|
text/xml,application/xml,application/*+xml | The response entity is parsed as XML before being returned. The return value is thus a DOM Document. |
application/json | The response entity is parsed as JSON before being returned. The return value is thus a JavaScript Object. |
Anything else | The response entity is returned raw. |
Example 50.4. Unmarshalling example
The RESTEasy JavaScript client API can automatically unmarshall JSON and XML:
@Path("orders") public interface Orders { @XmlRootElement public static class Order { @XmlElement private String id; public Order(){} public Order(String id){ this.id = id; } } @Path("{id}/xml") @GET @Produces("application/xml") public Order getOrderXML(@PathParam("id") String id){ return new Order(id); } @Path("{id}/json") @GET @Produces("application/json") public Order getOrderJSON(@PathParam("id") String id){ return new Order(id); } }
Let us look at what the preceding JAX-RS API would give us on the client side:
// this returns a JSON object var orderJSON = Orders.getOrderJSON({id: "23"}); orderJSON.id == "23"; // this one returns a DOM Document whose root element is the order, with one child (id) // whose child is the text node value var orderXML = Orders.getOrderXML({id: "23"}); orderXML.documentElement.childNodes[0].childNodes[0].nodeValue == "23";
The Content-Type header sent in the request is controlled by the $contentType parameter which overrides the @Consumes annotation on the JAX-RS endpoint. The value passed as entity body using the $entity parameter is marshalled according to both its type and content type:
Type | MIME | Description |
---|---|---|
DOM Element | Empty or text/xml,application/xml,application/*+xml | The DOM Element is marshalled to XML before being sent. |
JavaScript Object (JSON) | Empty or application/json | The JSON object is marshalled to a JSON string before being sent. |
Anything else | Anything else | The entity is sent as is. |
Example 50.5. Marshalling example
The RESTEasy JavaScript client API can automatically marshall JSON and XML:
@Path("orders") public interface Orders { @XmlRootElement public static class Order { @XmlElement private String id; public Order(){} public Order(String id){ this.id = id; } } @Path("{id}/xml") @PUT @Consumes("application/xml") public void putOrderXML(Order order){ // store order } @Path("{id}/json") @PUT @Consumes("application/json") public void putOrderJSON(Order order){ // store order } }
Let us look at what the preceding JAX-RS API would give us on the client side:
// this saves a JSON object Orders.putOrderJSON({$entity: {id: "23"}}); // It is a bit more work with XML var order = document.createElement("order"); var id = document.createElement("id"); order.appendChild(id); id.appendChild(document.createTextNode("23")); Orders.putOrderXML({$entity: order});
The RESTEasy JavaScript API can also be used to manually construct your requests.
The REST object contains the following read-write properties:
Property | Description |
---|---|
apiURL | Set by default to the JAX-RS root URL, used by every JavaScript client API functions when constructing the requests. |
log | Set to a function(string) in order to receive RESTEasy client API logs. This is useful if you want to debug your client API and place the logs where you can see them. |
Example 50.6. Using the REST object
The REST object can be used to override RESTEasy JavaScript API client behaviour:
// Change the base URL used by the API: REST.apiURL = "http://api.service.com"; // log everything in a div element REST.log = function(text){ jQuery("#log-div").append(text); };
The REST.Request class is used to build custom requests. It has the following members:
Member | Description |
---|---|
execute(callback) | Executes the request with all the information set in the current object. The value is never returned but passed to the optional argument callback. |
setAccepts(acceptHeader) | Sets the Accept request header. Defaults to */*. |
setCredentials(username, password) | Sets the request credentials. |
setEntity(entity) | Sets the request entity. |
setContentType(contentTypeHeader) | Sets the Content-Type request header. |
setURI(uri) | Sets the request URI. This should be an absolute URI. |
setMethod(method) | Sets the request method. Defaults to GET. |
setAsync(async) | Controls whether the request should be asynchronous. Defaults to true. |
addCookie(name, value) | Sets the given cookie in the current document when executing the request. Beware that this will be persistent in your browser. |
addQueryParameter(name, value) | Adds a query parameter to the URI query part. |
addMatrixParameter(name, value) | Adds a matrix parameter (path parameter) to the last path segment of the request URI. |
addHeader(name, value) | Adds a request header. |
Example 50.7. Using the REST.Request class
The REST.Request class can be used to build custom requests:
var r = new REST.Request(); r.setURI("http://api.service.com/orders/23/json"); r.setMethod("PUT"); r.setContentType("application/json"); r.setEntity({id: "23"}); r.addMatrixParameter("JSESSIONID", "12309812378123"); r.execute(function(status, request, entity){ log("Response is "+status); });
RESTEasy AJAX Client works well with server side caching features. But the buggy browsers cache will always prevent the function to work properly. If you'd like to use RESTEasy's caching feature with its AJAX client, you can enable 'antiBrowserCache' option:
REST.antiBrowserCache = true;
The above setting should be set once before you call any APIs.
RESTEasy has its own support to generate WADL for its resources, and it supports several different containers. The following text will show you how to use this feature in different containers.
RESTEasy WADL uses ResteasyWadlServlet to support servlet container. It can be registered into web.xml to enable WADL feature. Here is an example to show the usages of ResteasyWadlServlet in web.xml:
<servlet> <servlet-name>RESTEasy WADL</servlet-name> <servlet-class>org.jboss.resteasy.wadl.ResteasyWadlServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RESTEasy WADL</servlet-name> <url-pattern>/application.xml</url-pattern> </servlet-mapping>
The preceding configuration in web.xml shows how to enable ResteasyWadlServlet and mapped it to /application.xml. And then the WADL can be accessed from the configured URL:
/application.xml
RESTEasy has provided a ResteasyWadlDefaultResource to generate WADL info for its embedded containers. Here is and example to show how to use it with RESTEasy's Sun JDK HTTP Server container:
com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(port), 10); org.jboss.resteasy.plugins.server.sun.http.HttpContextBuilder contextBuilder = new org.jboss.resteasy.plugins.server.sun.http.HttpContextBuilder(); contextBuilder.getDeployment().getActualResourceClasses() .add(ResteasyWadlDefaultResource.class); contextBuilder.bind(httpServer); ResteasyWadlDefaultResource.getServices() .put("/", ResteasyWadlGenerator .generateServiceRegistry(contextBuilder.getDeployment())); httpServer.start();
From the above code example, we can see how ResteasyWadlDefaultResource is registered into deployment:
contextBuilder.getDeployment().getActualResourceClasses() .add(ResteasyWadlDefaultResource.class);
Another important thing is to use ResteasyWadlGenerator to generate the WADL info for the resources in deployment at last:
ResteasyWadlDefaultResource.getServices() .put("/", ResteasyWadlGenerator .generateServiceRegistry(contextBuilder.getDeployment()));
After the above configuration is set, then users can access "/application.xml" to fetch the WADL info, because ResteasyWadlDefaultResource has @PATH set to "/application.xml" as default:
@Path("/application.xml") public class ResteasyWadlDefaultResource
RESTEasy WADL support for Netty Container is simliar to the support for JDK HTTP Server. It also uses ResteasyWadlDefaultResource to serve '/application.xml' and ResteasyWadlGenerator to generate WADL info for resources. Here is the sample code:
ResteasyDeployment deployment = new ResteasyDeployment(); netty = new NettyJaxrsServer(); netty.setDeployment(deployment); netty.setPort(port); netty.setRootResourcePath(""); netty.setSecurityDomain(null); netty.start(); deployment.getRegistry() .addPerRequestResource(ResteasyWadlDefaultResource.class); ResteasyWadlDefaultResource.getServices() .put("/", ResteasyWadlGenerator.generateServiceRegistry(deployment));
Please note for all the embedded containers like JDK HTTP Server and Netty Container, if the resources in the deployment changes at runtime, the ResteasyWadlGenerator.generateServiceRegistry() need to be re-run to refresh the WADL info.
The RESTEasy Undertow Container is a embedded Servlet Container, and RESTEasy WADL provides a connector to it. To use RESTEasy Undertow Container together with WADL support, you need to add these three components into your maven dependencies:
<dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-wadl</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-wadl-undertow-connector</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-undertow</artifactId> <version>${project.version}</version> </dependency>
The resteasy-wadl-undertow-connector provides a WadlUndertowConnector to help you to use WADL in RESTEasy Undertow Container. Here is the code example:
UndertowJaxrsServer server = new UndertowJaxrsServer().start(); WadlUndertowConnector connector = new WadlUndertowConnector(); connector.deployToServer(server, MyApp.class);
The MyApp class shown in above code is a standard JAX-RS 2.0 Application class in your project:
@ApplicationPath("/base") public static class MyApp extends Application { @Override public Set<Class<?>> getClasses() { HashSet<Class<?>> classes = new HashSet<Class<?>>(); classes.add(YourResource.class); return classes; } }
After the Application is deployed to the UndertowJaxrsServer via WadlUndertowConnector, you can access the WADL info at "/application.xml" prefixed by the @ApplicationPath in your Application class. If you want to override the @ApplicationPath, you can use the other method in WadlUndertowConnector:
public UndertowJaxrsServer deployToServer(UndertowJaxrsServer server, Class<? extends Application> application, String contextPath)
The "deployToServer" method shown above accepts a "contextPath" parameter, which you can use to override the @ApplicationPath value in the Application class.
RESTEasy provides the support for validation mandated by the JAX-RS: Java API for RESTful Web Services 2.0 , given the presence of an implementation of the Bean Validation specification 1.1 such as Hibernate Validator 5.x.
Validation provides a declarative way of imposing constraints on fields and properties of beans, bean classes, and the parameters and return values of bean methods. For example, in
@Path("all") @TestClassConstraint(5) public class TestResource { @Size(min=2, max=4) @PathParam("s") String s; private String t; @Size(min=3) public String getT() { return t; } @PathParam("t") public void setT(String t) { this.t = t; } @POST @Path("{s}/{t}/{u}") @Pattern(regexp="[a-c]+") public String post(@PathParam("u") String u) { return u; } }
the field s
is constrained by the Bean Validation built-in annotation @Size
to have between 2 and 4 characters, the property t
is constrained to have at least 3
characters, and the TestResource
object is constrained by the application defined
annotation @TestClassConstraint
to have the combined lengths of s
and
t
less than 5:
@Constraint(validatedBy = TestClassValidator.class) @Target({TYPE}) @Retention(RUNTIME) public @interface TestClassConstraint { String message() default "Concatenation of s and t must have length > {value}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int value(); } public class TestClassValidator implements ConstraintValidator<TestClassConstraint, TestResource> { int length; public void initialize(TestClassConstraint constraintAnnotation) { length = constraintAnnotation.value(); } public boolean isValid(TestResource value, ConstraintValidatorContext context) { boolean b = value.retrieveS().length() + value.getT().length() < length; } }
See the links above for more about how to create validation annotations.
Also, the method parameter u
is constrained to have no more than 5 characters,
and the return value of method post
is constrained by the built-in annotation
@Pattern
to match the regular expression "[a-c]+".
The sequence of validation constraint testing is as follows:
If a validation problem occurs, either a problem with the validation definitions or a constraint violation,
Resteasy will set the return header org.jboss.resteasy.api.validation.Validation.VALIDATION_HEADER
("validation-exception") to "true".
If Resteasy detects a structural validation problem, such as a validation annotation with a
missing validator class, it will return a String representation of a
javax.validation.ValidationException
. For example
javax.validation.ValidationException: HV000028: Unexpected exception during isValid call.[org.jboss.resteasy.test.validation.TestValidationExceptions$OtherValidationException]
If any constraint violations are detected, Resteasy will return a report in one of a variety
of formats. If one of "application/xml" or "application/json" occur in the "Accept" request header,
Resteasy will return an appropriately marshalled instance of
org.jboss.resteasy.api.validation.ViolationReport
:
@XmlRootElement(name="violationReport") @XmlAccessorType(XmlAccessType.FIELD) public class ViolationReport { ... public ArrayList<ResteasyConstraintViolation> getFieldViolations() { return fieldViolations; } public ArrayList<ResteasyConstraintViolation> getPropertyViolations() { return propertyViolations; } public ArrayList<ResteasyConstraintViolation> getClassViolations() { return classViolations; } public ArrayList<ResteasyConstraintViolation> getParameterViolations() { return parameterViolations; } public ArrayList<ResteasyConstraintViolation> getReturnValueViolations() { return returnValueViolations; } ... }
where org.jboss.resteasy.api.validation.ResteasyConstraintViolation
is defined:
@XmlRootElement(name="resteasyConstraintViolation") @XmlAccessorType(XmlAccessType.FIELD) public class ResteasyConstraintViolation implements Serializable { ... /** * @return type of constraint */ public ConstraintType.Type getConstraintType() { return constraintType; } /** * @return description of element violating constraint */ public String getPath() { return path; } /** * @return description of constraint violation */ public String getMessage() { return message; } /** * @return object in violation of constraint */ public String getValue() { return value; } /** * @return String representation of violation */ public String toString() { return "[" + type() + "]\r[" + path + "]\r[" + message + "]\r[" + value + "]\r"; } /** * @return String form of violation type */ public String type() { return constraintType.toString(); } }
and org.jboss.resteasy.api.validation.ConstraintType
is the enumeration
public class ConstraintType { public enum Type {CLASS, FIELD, PROPERTY, PARAMETER, RETURN_VALUE}; }
If both "application/xml" or "application/json" occur in the "Accept" request header, the media type is chosen according to the ranking given by implicit or explicit "q" parameter values. In the case of a tie, the returned media type is indeterminate.
If neither "application/xml" or "application/json" occur in the "Accept" request header,
Resteasy returns a report with a String representation of each
ResteasyConstraintViolation
, where
each field is delimited by '[' and ']', followed by a '\r', with a final '\r' at the end.
For example,
[FIELD] [s] [size must be between 2 and 4] [a] [PROPERTY] [t] [size must be between 3 and 5] [z] [CLASS] [] [Concatenation of s and t must have length > 5] [org.jboss.resteasy.validation.TestResource@68467a6f] [PARAMETER] [test.<cross-parameter>] [Parameters must total <= 7] [[5, 7]] [RETURN_VALUE] [g.<return value>] [size must be between 2 and 4] [abcde]
where the four fields are
The ViolationReport
can be reconsititued from the String
as follows:
ResteasyClient client = new ResteasyClientBuilder().build(); Invocation.Builder request = client.target(...).request(); Response response = request.get(); if (Boolean.valueOf(response.getHeaders().getFirst(Validation.VALIDATION_HEADER))) { String s = response.getEntity(String.class); ViolationReport report = new ViolationReport(s); }
If the path field is considered to be too much server side information, it can be surpressed by setting the context parameter "resteasy.validation.suppress.path" to "true". In that case, "*" will be returned in the path fields.
The form of validation mandated by the JAX-RS 2.0 specification, based on Bean Validation 1.1, is supported by the RESTEasy module resteasy-validator-provider-11, which produces the artifact resteasy-validator-provider-11-<version>.jar. Validation is turned on by default (assuming resteasy-validator-provider-11-<version>.jar is available), though parameter and return value validation can be turned off or modified in the validation.xml configuration file. See the Hibernate Validator documentation for the details. WildFly 8+ ships with Hibernate Validator 5.x.
RESTEasy obtains a bean validation implemenation by looking in the available
META-INF/services/javax.ws.rs.Providers files for an implementation
of ContextResolver<GeneralValidator>
, where
org.jboss.resteasy.spi.GeneralValidator
is
public interface GeneralValidator { /** * Validates all constraints on {@code object}. * * @param object object to validate * @param groups the group or list of groups targeted for validation (defaults to * {@link Default}) * @return constraint violations or an empty set if none * @throws IllegalArgumentException if object is {@code null} * or if {@code null} is passed to the varargs groups * @throws ValidationException if a non recoverable error happens * during the validation process */ public abstract void validate(HttpRequest request, Object object, Class<?>... groups); /** * Validates all constraints placed on the parameters of the given method. * * @param <T> the type hosting the method to validate * @param object the object on which the method to validate is invoked * @param method the method for which the parameter constraints is validated * @param parameterValues the values provided by the caller for the given method's * parameters * @param groups the group or list of groups targeted for validation (defaults to * {@link Default}) * @return a set with the constraint violations caused by this validation; * will be empty if no error occurs, but never {@code null} * @throws IllegalArgumentException if {@code null} is passed for any of the parameters * or if parameters don't match with each other * @throws ValidationException if a non recoverable error happens during the * validation process */ public abstract void validateAllParameters(HttpRequest request, Object object, Method method, Object[] parameterValues, Class<?>... groups); /** * Validates all return value constraints of the given method. * * @param <T> the type hosting the method to validate * @param object the object on which the method to validate is invoked * @param method the method for which the return value constraints is validated * @param returnValue the value returned by the given method * @param groups the group or list of groups targeted for validation (defaults to * {@link Default}) * @return a set with the constraint violations caused by this validation; * will be empty if no error occurs, but never {@code null} * @throws IllegalArgumentException if {@code null} is passed for any of the object, * method or groups parameters or if parameters don't match with each other * @throws ValidationException if a non recoverable error happens during the * validation process */ public abstract void validateReturnValue( HttpRequest request, Object object, Method method, Object returnValue, Class<?>... groups); /** * Indicates if validation is turned on for a class. * * @param clazz Class to be examined * @return true if and only if validation is turned on for clazz */ public abstract boolean isValidatable(Class<?> clazz); /** * Indicates if validation is turned on for a method. * * @param method method to be examined * @return true if and only if validation is turned on for method */ public abstract boolean isMethodValidatable(Method method); void checkViolations(HttpRequest request); }
The methods and the javadoc are adapted from the Bean Validation 1.1 classes
javax.validation.Validator
and
javax.validation.executable.ExecutableValidator
.
RESTEasy module resteasy-validator-provider-11 supplies an implementation
of GeneralValidator
.
An alternative implementation may be supplied by implementing
ContextResolver<GeneralValidator>
and
org.jboss.resteasy.spi.validation.GeneralValidator
.
A validator intended to function in the presence of CDI must also implement the subinterface
public interface GeneralValidatorCDI extends GeneralValidator { /** * Indicates if validation is turned on for a class. * * This method should be called from the resteasy-jaxrs module. It should * test if injectorFactor is an instance of CdiInjectorFactory, which indicates * that CDI is active. If so, it should return false. Otherwise, it should * return the same value returned by GeneralValidator.isValidatable(). * * @param clazz Class to be examined * @param injectorFactory the InjectorFactory used for clazz * @return true if and only if validation is turned on for clazz */ public boolean isValidatable(Class<?> clazz, InjectorFactory injectorFactory); /** * Indicates if validation is turned on for a class. * This method should be called only from the resteasy-cdi module. * * @param clazz Class to be examined * @return true if and only if validation is turned on for clazz */ public abstract boolean isValidatableFromCDI(Class<?> clazz); /** * Throws a ResteasyViolationException if any validation violations have been detected. * The method should be called only from the resteasy-cdi module. * @param request */ public void checkViolationsfromCDI(HttpRequest request); /** * Throws a ResteasyViolationException if either a ConstraintViolationException or a * ResteasyConstraintViolationException is embedded in the cause hierarchy of e. * * @param request * @param e */ public void checkForConstraintViolations(HttpRequest request, Exception e); }
The validator in resteasy-validator-provider-11 implements GeneralValidatorCDI.
With the help of the JBoss Logging project, all log and exception messages in Resteasy are internationalized. That is, they have a default value in English which can be overridden in any given locale by a file which gives translated values. For more information about internationalization and localization in Java, see, for example, http://docs.oracle.com/javase/tutorial/i18n. For more about JBoss Logging, see https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6/html/Development_Guide/, Chapters 4 and 5.
Each module in Resteasy that produces any text in the form of logging messages or exception messages has an interface named org.jboss.resteasy...i18n.Messages which contains the default messages. Those modules which do any logging also have an interface named org.jboss.resteasy...i18n.LogMessages which gives access to an underlying logger. With the exception of the resteasy-jaxrs module, all messages are in the Messages class. resteasy-jaxrs has exception messages in the Messages class and log messages in the LogMessages class.
Each message is prefixed by the project code "RESTEASY" followed by an ID which is unique to Resteasy. These IDs belong to the following ranges:
Table 53.1.
Range | Module |
---|---|
2000-2999 | resteasy-jaxrs log messages |
3000-4499 | resteasy-jaxrs exception messages |
4500-4999 | resteasy-client |
5000-5499 | providers/resteasy-atom |
5500-5999 | providers/fastinfoset |
6000-6499 | providers/resteasy-html |
6500-6999 | providers/jaxb |
7000-7499 | providers/jettison |
7500-7999 | providers/multipart |
8000-8499 | providers/resteasy-hibernatevalidator-provider |
8500-8999 | providers/resteasy-validator-provider-11 |
9000-9499 | providers/yaml |
9500-9999 | async-http-servlet-3.0 |
10000-10499 | resteasy-cache-core |
10500-10999 | resteasy-cdi |
11000-11499 | resteasy-guice |
11500-11999 | resteasy-jsapi |
12000-12499 | resteasy-links |
12500-12999 | resteasy-servlet-initializer |
13000-13499 | resteasy-spring |
13500-13999 | security/resteasy-crypto |
14000-14499 | security/jose-jwt |
14500-14999 | security/keystone/keystone-as7 |
15000-15499 | security/keystone/keystone-core |
15500-15999 | security/resteasy-oauth |
16000-16499 | security/skeleton-key-idm/skeleton-key-as7 |
16500-16999 | security/skeleton-key-idm/skeleton-key-core |
17000-17499 | security/skeleton-key-idm/skeleton-key-idp |
17500-17999 | server-adapters/resteasy-jdk-http |
18000-18499 | server-adapters/resteasy-netty |
18500-18999 | server-adapters/resteasy-netty4 |
For example, the jaxb provider contains the interface
org.jboss.resteasy.plugins.providers.jaxb.i18.Messages
which looks like
@MessageBundle(projectCode = "RESTEASY") public interface Messages { Messages MESSAGES = org.jboss.logging.Messages.getBundle(Messages.class); int BASE = 6500; @Message(id = BASE + 00, value = "Collection wrapping failed, expected root element name of {0} got {1}", format=Format.MESSAGE_FORMAT) String collectionWrappingFailedLocalPart(String element, String localPart); @Message(id = BASE + 05, value = "Collection wrapping failed, expect namespace of {0} got {1}", format=Format.MESSAGE_FORMAT) String collectionWrappingFailedNamespace(String namespace, String uri); ...
The value of a message is retrieved by referencing a method and passing the appropriate parameters. For example,
throw new JAXBUnmarshalException(Messages.MESSAGES.collectionWrappingFailedLocalPart(wrapped.element(), ele.getName().getLocalPart()));
When Resteasy is built with the "i18n" profile, a template properties file containing the default messages is created in a subdirectory of target/generated-translation-files. In the jaxb provider, for example, the
Messages.i18n_locale_COUNTRY_VARIANT.properties
goes in the
org/jboss/resteasy/plugins/providers/jaxb/i18n
directory, and the first few lines are
# Id: 6500 # Message: Collection wrapping failed, expected root element name of {0} got {1} # @param 1: element - # @param 2: localPart - collectionWrappingFailedLocalPart=Collection wrapping failed, expected root element name of {0} got {1} # Id: 6505 # Message: Collection wrapping failed, expect namespace of {0} got {1} # @param 1: namespace - # @param 2: uri - collectionWrappingFailedNamespace=Collection wrapping failed, expect namespace of {0} got {1}
To provide the translation of the messages for a particular locale, the file should be renamed, replacing "locale", "COUNTRY", and "VARIANT" as appropriate (possibly omitting the latter two), and copied to the src/main/resources directory. In the jaxb provider, it would go in
src/main/resources/org/jboss/resteasy/plugins/providers/jaxb/i18n
For testing purposes, each module containing a Messages interface has two sample properties files, for the locale "en" and the imaginary locale "xx", in the src/test/resources directory. They are copied to src/main/resources when the module is built and deleted when it is cleaned.
The Messages.i18n_xx.properties file in the jaxb provider, for example, looks like
# Id: 6500 # Message: Collection wrapping failed, expected root element name of {0} got {1} # @param 1: element - # @param 2: localPart - collectionWrappingFailedLocalPart=Collection wrapping failed, expected root element name of {0} got {1} # Id: 6505 # Message: Collection wrapping failed, expect namespace of {0} got {1} # @param 1: namespace - # @param 2: uri - collectionWrappingFailedNamespace=aaa {0} bbb {1} ccc ...
Note that the value of collectionWrappingFailedNamespace is modified.
JBoss's Maven Repository is at: http://repository.jboss.org/nexus/content/groups/public/
Here's the pom.xml fragment to use. Resteasy is modularized into various components. Mix and max as you see fit. Please replace 3.0.22.Final with the current Resteasy version you want to use.
<repositories> <repository> <id>jboss</id> <url>http://repository.jboss.org/nexus/content/groups/public/</url> </repository> </repositories> <dependencies> <!-- core library --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxrs</artifactId> <version>3.0.22.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-client</artifactId> <version>3.0.22.Final</version> </dependency> <!-- optional modules --> <!-- JAXB support --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-jaxb-provider</artifactId> <version>3.0.22.Final</version> </dependency> <!-- multipart/form-data and multipart/mixed support --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-multipart-provider</artifactId> <version>3.0.22.Final</version> </dependency> <!-- Resteasy Server Cache --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-cache-core</artifactId> <version>3.0.22.Final</version> </dependency> <!-- Ruby YAML support --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-yaml-provider</artifactId> <version>3.0.22.Final</version> </dependency> <!-- JAXB + Atom support --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-atom-provider</artifactId> <version>3.0.22.Final</version> </dependency> <!-- Spring integration --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-spring</artifactId> <version>3.0.22.Final</version> </dependency> <!-- Guice integration --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-guice</artifactId> <version>3.0.22.Final</version> </dependency> <!-- Asynchronous HTTP support with Servlet 3.0 --> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>async-http-servlet-3.0</artifactId> <version>3.0.22.Final</version> </dependency> </dependencies>
There is also a pom that can be imported so the versions of the individual modules do not have to be specified. Note that maven 2.0.9 is required for this.
<dependencyManagement> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-bom</artifactId> <version>3.0.22.Final</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
There's a great javadoc engine that allows you to generate javadocs for JAX-RS and JAXB called JAX-Doclet. Follow the link for more details.
In a minor release like 3.1.0.Final, as opposed to a point release like 3.0.19.Final, the scope of permissible changes is broadened. User discernible changes in Resteasy 3.1.0.Final include
In this chapter we focus on changes that might cause existing code to fail or behave in new ways. The audience for this discussion may be partitioned into three subsets, depending on the version of Resteasy currently in use, the API currently in use, and the API to be used after an upgrade to Resteasy 3.1. The following APIs are available:
Resteasy 2: Resteasy 2 conforms to the JAX-RS 1 specification, and adds a variety of additional facilities, such as a client API, a caching system, an interceptor framework, etc. All of these user facing classes and interfaces comprise the Resteasy 2 API.
Resteasy 3: Resteasy 3 conforms to the JAX-RS 2 specification, and adds some additional facilities. Many of the non-spec facilities from the Resteasy 2 API are formalized, in altered form, in JAX-RS 2, in which case the older facilites are deprecated. The non-deprecated user facing classes and interfaces in Resteasy 3 comprise the Resteasy 3 API.
These definitions are rather informal and imprecise, since the user facing classes / interfaces in Resteasy 3.0.19.Final, for example, are a proper superset of the user facing classes / interfaces in Resteasy 3.0.1.Final. For this discussion, we identify the API with the version currently in use in a given project.
Now, there are three potential target audiences of users planning to upgrade to Resteasy 3.1.0.Final:
Those currently using Resteasy API 3 with some Resteasy 3.0.x release
Those currently using Resteasy API 2 with some Resteasy 2.x or 3.0.x release and planning to upgrade to Resteasy API 3
Those currently using Resteasy API 2 with some Resteasy 2.x or 3.0.x release and planning to continue to use Resteasy API 2
Of these, users in Group 2 have the most work to do in upgrading from Resteasy API 2 to Resteasy API 3. They should consult the separate guide Upgrading from Resteasy 2 to Resteasy 3.
Ideally, users in Groups 1 and 3 might make some changes to take advantage of new features but would have no changes forced on them by reorganization or altered behavior. Indeed, that is almost the case, but there are a few changes that they should be aware of.
All Resteasy changes are documented in JIRA issues. Issues that describe detectable changes in release 3.1.0.Final that might impact existing applications include
When a build()
method from
org.jboss.resteasy.client.jaxrs.internal.ClientInvocationBuilder
in resteasy-client,org.jboss.resteasy.specimpl.LinkBuilderImpl
in resteasy-jaxrs,org.jboss.resteasy.specimpl.ResteasyUriBuilder
in resteasy-jaxrsis called, it will return a new object. This behavior might be seen indirectly. For example,
Builder builder = client.target(generateURL(path)).request(); ... Link link = new LinkBuilderImpl().uri(href).build(); ... URI uri = uriInfo.getBaseUriBuilder().path("test").build();
As it says. Depending on the application, it might be necessary to recompile with a target of JDK 1.8 so that calls to Resteasy code can work.
Prior to release 3.1.0.Final, the default behavior of Resteasy was to use GZIP to compress and decompress messages whenever "gzip" appeared in the Content-Encoding header. However, decompressing messages can lead to security issues, so, as of release 3.1.0.Final, GZIP compression has to be enabled explicitly. For details, see Chapter GZIP Compression/Decompression.
Note. Because of some package reorganization due to RESTEASY-1531 (see below),
the GZIP interceptors, which used to be in package org.jboss.resteasy.plugins.interceptors.encoding
are
now in org.jboss.resteasy.plugins.interceptors
.
This issue is related to refactoring deprecated elements of the Resteasy 2 API into a separate module, and, ideally, would have no bearing at all on Resteasy 3. However, a reorganization of packages has led to moving some non-deprecated API elements in the resteasy-jaxrs module:
org.jboss.resteasy.client.ClientURI
is now
org.jboss.resteasy.annotations.ClientURI
org.jboss.resteasy.core.interception.JaxrsInterceptorRegistryListener
is now
org.jboss.resteasy.core.interception.jaxrs.JaxrsInterceptorRegistryListener
org.jboss.resteasy.spi.interception.DecoratorProcessor
is now
org.jboss.resteasy.spi.DecoratorProcessor
All of the dynamic features and interceptors in the package
org.jboss.resteasy.plugins.interceptors.encoding
are now in
org.jboss.resteasy.plugins.interceptors
Most of the deprecated classes and interfaces from Resteasy 2 have been segregated in a separate module, resteasy-legacy, as of release 3.1.0.Final. A few remain in module resteasy-jaxrs for technical reasons. Eventually, all such classes and interfaces will be removed from Resteasy. Most of the relocated elements are internal, so ensuring that resteasy-legacy is on the classpath will make most changes undetectable. One way to do that, of course, is to include it in an application's WAR. In the context of WildFly, it is also possible to use a jboss-deployment-structure.xml file in the WEB-INF directory of your WAR file. For example:
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-legacy"/> </dependencies> </deployment> </jboss-deployment-structure>
There are a few API classes and interfaces from resteasy-jaxrs that have moved to a new package in resteasy-legacy. These are
org.jboss.resteasy.annotations.ClientResponseType
is now
org.jboss.resteasy.annotations.legacy.ClientResponseType
org.jboss.resteasy.spi.Link
is now
org.jboss.resteasy.client.Link
org.jboss.resteasy.spi.LinkHeader
is now
org.jboss.resteasy.client.LinkHeader
Many facilities from Resteasy 2 appear in a different form in Resteasy 3. For example, much of the client framework in Resteasy 2 is formalized, in modified form, in JAX-RS 2.0. Resteasy versions 3.0.x implement both the older deprecated form and the newer conformant form. The deprecated form is finally removed in Resteasy 3.1.0.Final. For more information on upgrading from various deprecated facilities in Resteasy 2, see http://docs.jboss.org/resteasy/docs/resteasy-upgrade-guide-en-US.pdf
as()
method for casting to subinterfaces.
For more information see
https://issues.jboss.org/browse/RESTEASY-1332.
org.jboss.resteasy.plugins.server.netty.NettyJaxrsServer
in module resteasy-netty4
can now be configured to close idle connections. For more information see
https://issues.jboss.org/browse/RESTEASY-1325.
MessageBodyWriter<Number>
. For more information see
https://issues.jboss.org/browse/RESTEASY-1282.
org.jboss.resteasy.Links.RESTServiceDiscovery
in resteasy-links module.
See https://issues.jboss.org/browse/RESTEASY-833 for more information.
@QueryParam
annotation which allows capturing the name of
a query parameter. See https://issues.jboss.org/browse/RESTEASY-715
for more information.
org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder
has a new method,
asyncExecutor(ExecutorService asyncExecutor, boolean cleanupExecutor)
, with a new
second parameter which tells the Client
to shut down the Executor upon closing. See
https://issues.jboss.org/browse/RESTEASY-1181 for more information.
org.jboss.resteasy.client.jaxrs.ResteasyClient.property()
will now
remove a property when the value is set to null. See
https://issues.jboss.org/browse/RESTEASY-1184 for more information.
org.jboss.resteasy.plugins.server.netty.NettyJaxrsServer
in module resteasy-netty4
has a new method, setHttpChannelHandlers()
, that allows adding channel handlers that are
processed after the HTTP handlers. See
https://issues.jboss.org/browse/RESTEASY-1171 for more information.
org.jboss.resteasy.api.validation.ResteasyViolationException
is now a subclass of
javax.validation.ConstraintViolationException
instead of
javax.validation.ValidationException
. Now, an ExceptionMapper
for
ConstraintViolationException
will catch ResteasyViolationException
s.
See https://issues.jboss.org/browse/RESTEASY-1137 for more information.
There is a new context parameter, "resteasy.buffer.exception.entity". See
Section 3.4, “Configuration switches” for more information. Also, see
https://issues.jboss.org/browse/RESTEASY-981.
context.setRequestUri(URI.create("https://foo.com/base"), URI.create("https://foo.com/base/path")); // legal context.setRequestUri(URI.create("https://foo.com/base"), URI.create("/path")); // "path" is ignored // if base uri is "http://foo.com/base" context.setRequestUri(URI.create("http://foo.com/base/path")); // legal context.setRequestUri(URI.create("/path")); // ignored
ServletContainerInitializer
.
This allows tighter integration with Resteasy much like you have within JBoss/WildFly. Check out
Chapter 3, Installation/Configuration for more information.
createPerRequestInstance
parameter has been removed from
ClientRequest.setDefaultExecutorClass()
.
Methods @Deprecated within 1.2.GA have been removed. This is in the Client Framework and has to do with all references to Apache HTTP Client. You must now create an ClientExecutor if you want to manage your Apache HTTP Client sessions.
There are a number of great books that you can learn REST and JAX-RS from