Resteasy Reference Guide en US Java Guide
Resteasy Reference Guide en US Java Guide
Preface ............................................................................................................................. ix
1. Overview ...................................................................................................................... 1
2. License ........................................................................................................................ 3
3. Installation/Configuration ............................................................................................ 5
3.1. Upgrading Resteasy Within JBoss AS 7 ............................................................... 5
3.2. Upgrading Resteasy Within JBoss EAP 6.1 .......................................................... 5
3.3. Upgrading Resteasy Within Wildfly ...................................................................... 5
3.4. Configuring in JBoss AS 7, EAP, and Wildfly ........................................................ 5
3.4.1. Resteasy Modules in AS7, EAP6.1, Wildfly ................................................ 6
3.5. Standalone Resteasy in Servlet 3.0 Containers .................................................... 7
3.6. Standalone Resteasy in Older Servlet Containers ................................................. 8
3.7. Configuration Switches ........................................................................................ 9
3.8. javax.ws.rs.core.Application ............................................................................... 11
3.9. RESTEasy as a ServletContextListener .............................................................. 13
3.10. RESTEasy as a servlet Filter ........................................................................... 14
3.11. RESTEasyLogging .......................................................................................... 15
4. Using @Path and @GET, @POST, etc. ...................................................................... 17
4.1. @Path and regular expression mappings ...........................................................
5. @PathParam ..............................................................................................................
5.1. Advanced @PathParam and Regular Expressions ..............................................
5.2. @PathParam and PathSegment ........................................................................
6. @QueryParam ............................................................................................................
7. @HeaderParam ..........................................................................................................
8. Linking resources ......................................................................................................
8.1. Link Headers ....................................................................................................
8.2. Atom links in the resource representations .........................................................
8.2.1. Configuration ..........................................................................................
8.2.2. Your first links injected ...........................................................................
8.2.3. Customising how the Atom links are serialised .........................................
8.2.4. Specifying which JAX-RS methods are tied to which resources ..................
8.2.5. Specifying path parameter values for URI templates .................................
8.2.6. Securing entities ....................................................................................
8.2.7. Extending the UEL context .....................................................................
8.2.8. Resource facades ..................................................................................
9. @MatrixParam ............................................................................................................
10. @CookieParam ........................................................................................................
11. @FormParam ...........................................................................................................
12. @Form .....................................................................................................................
13. @DefaultValue ..........................................................................................................
14. @Encoded and encoding .........................................................................................
15. @Context .................................................................................................................
16. JAX-RS Resource Locators and Sub Resources .....................................................
17. JAX-RS Content Negotiation ....................................................................................
17.1. URL-based negotiation ....................................................................................
18
21
22
22
25
27
29
29
29
29
29
32
32
33
36
36
38
41
43
45
47
51
53
55
57
61
63
iii
RESTEasy JAX-RS
20.
21.
22.
23.
iv
65
65
66
68
71
72
73
74
74
76
81
81
84
85
87
115
115
116
116
119
121
123
125
125
126
127
129
131
133
135
137
137
138
139
141
141
142
142
143
143
145
147
147
148
148
151
151
153
154
155
157
159
163
164
164
164
164
24.
25.
26.
27.
28.
29.
30.
31.
RESTEasy JAX-RS
40.
41.
42.
43.
44.
45.
vi
167
167
168
168
168
170
170
170
170
170
171
171
171
172
173
174
174
175
175
175
176
177
179
179
179
183
185
185
186
187
188
189
189
189
191
193
193
193
195
197
199
201
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
205
206
206
207
209
210
211
211
212
215
215
215
218
223
223
223
224
226
227
229
230
230
231
232
233
234
238
239
240
245
249
251
253
255
255
255
255
255
256
256
256
257
vii
RESTEasy JAX-RS
viii
258
258
258
258
259
259
259
259
261
Preface
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:
Is really:
Let's pretend to have an extremely long line that does not fit
This one is short
ix
Chapter 1.
Chapter 1. Overview
JAX-RS, JSR-311, 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 JBoss Application Server is also available to make
the user experience nicer in that environment. While JAX-RS is only a server-side specification,
Resteasy has innovated to bring JAX-RS to the client through the RESTEasy JAX-RS Client
Framework. This client-side framework allows you to map outgoing HTTP requests to remote
servers using JAX-RS annotations and interface proxies.
JAX-RS implementation
Portable to any app-server/Tomcat that runs on JDK 5 or higher
Embeddable server implementation for junit testing
EJB and Spring integration
Client framework to make writing HTTP clients easy (JAX-RS only define server bindings)
Chapter 2.
Chapter 2. License
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.
Chapter 3.
Chapter 3. Installation/
Configuration
RESTEasy is installed and configured in different ways depending on which environment you
are running in. If you are running in JBoss AS 6-M4 (milestone 4) or higher, resteasy is already
bundled and integrated completely so there is very little you have to do. If you are running in a
different distribution, there is some manual installation and configuration you will have to do.
Chapter 3. Installation/Confi...
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://
java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
</web-app>
Since we're not using a jax-rs servlet mapping, we must define an Application class that is
annotated with the @ApplicationPath annotation. If you return any empty set for by classes and
singletons, your WAR will be scanned for JAX-RS annotation resource and provider classes.
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/root-path")
public class MyApplication extends Application
{
}
The Resteasy distribution has ported the "Restful Java" O'Reilly workbook examples to AS7. You
can find these under the directory examples/oreilly-workbook-as7.
<jboss-deployment-structure>
<deployment>
<dependencies>
<module
services="import"/>
</dependencies>
</deployment>
</jboss-deployment-structure>
name="org.jboss.resteasy.resteasy-yaml-provider"
The services attribute must be set to import for modules that have default providers that must
be registered. The following table specifies which modules 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.resteasyjaxrs
yes
org.jboss.resteasy.resteasycdi
yes
org.jboss.resteasy.resteasyjaxb-provider
yes
org.jboss.resteasy.resteasyatom-provider
yes
org.jboss.resteasy.resteasyjackson-provider
yes
org.jboss.resteasy.resteasyjsapi
yes
org.jboss.resteasy.resteasymultipart-provider
yes
org.jboss.resteasy.resteasy-
no
crypto
with
org.jboss.resteasy.jose-jwt
no
org.jboss.resteasy.resteasyjettison-provider
no
org.jboss.resteasy.resteasyyaml-provider
no
YAML marshalling.
org.jboss.resteasy.skeletonkey
no
Chapter 3. Installation/Confi...
integration interface. To use this, 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>3.0.9.Final</version>
</dependency>
We strongly suggest that you use Maven to build your WAR files as RESTEasy is split into a
bunch of different modules. You can see an example Maven project in one of the examples in the
examples/ directory. If you are not using Maven,when you download RESTeasy and unzip it you
will see a lib/ directory that contains the libraries needed by resteasy. Copy these into your /WEBINF/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.
<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</paramvalue>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Configuration Switches
</web-app>
The Resteasy servlet is responsible for initializing some basic components of RESTeasy.
Table 3.2.
Option Name
Default Value
resteasy.servlet.mapping.prefix no default
Description
If the url-pattern for the
Resteasy servlet-mapping is
not /*
resteasy.scan
false
resteasy.scan.providers
false
resteasy.scan.resources
false
resteasy.providers
no default
resteasy.use.builtin.providers
true
resteasy.resources
no default
resteasy.jndi.resources
no default
Chapter 3. Installation/Confi...
Option Name
Default Value
Description
javax.ws.rs.Application
no default
resteasy.media.type.mappings no default
resteasy.language.mappings
no default
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
resteasy.document.secure.disableDTDs
true
Prohibit
DTDs
org.w3c.dom.Document
in
10
javax.ws.rs.core.Application
Option Name
Default Value
resteasy.use.container.form.params
true
Description
Will
use
the
HttpServletRequest.getParameterMap()
method to obtain form
parameters. Use this switch
if you are calling this method
within a servlet filter or eating
the input stream within the
filter.
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/restful-services/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/restful-services</param-value>
</context-param>
3.8. javax.ws.rs.core.Application
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
11
Chapter 3. Installation/Confi...
12
RESTEasy as a ServletContextListener
To use Application you must set a servlet init-param, javax.ws.rs.Application with a fully qualified
class that implements Application. For example:
<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</paramvalue>
</init-param>
</servlet>
If you have this set, you should probably turn off automatic scanning as this will probably result
in duplicate classes being registered.
<web-app>
<listener>
<listener-class>
org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
</listener-class>
</listener>
13
Chapter 3. Installation/Confi...
<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</paramvalue>
</init-param>
</filter>
<filter-mapping>
<filter-name>Resteasy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
14
RESTEasyLogging
</web-app>
3.11. RESTEasyLogging
RESTEasy supports logging via java.util.logging, Log4j, or Slf4j. How it picks which framework to
delegate to is expressed in the following algorithm:
Table 3.3.
Category
Function
org.jboss.resteasy.core
org.jboss.resteasy.plugins.providers
org.jboss.resteasy.plugins.server
org.jboss.resteasy.specimpl
org.jboss.resteasy.mock
15
16
Chapter 4.
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:
GET http://myhost.com/services/library/books
GET http://myhost.com/services/library/book/333
PUT http://myhost.com/services/library/book/333
DELETE http://myhost.com/services/library/book/333
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.
17
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.
@Path("/resources)
public class MyResource {
@GET
@Path("{var:.*}/stuff")
public String get() {...}
}
GET /resources/stuff
GET /resources/foo/stuff
GET /resources/on/and/on/stuff
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
"([]*)"
18
For example:
@Path("/resources/{var}/stuff")
will match these:
GET /resources/foo/stuff
GET /resources/bar/stuff
GET /resources/a/bunch/of/stuff
19
20
Chapter 5.
Chapter 5. @PathParam
@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) {...}
21
Chapter 5. @PathParam
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:
Table 5.1.
Request
param
many
GET /aaabb/some/stuff
bb
some
GET /aaab/a/lot/of/stuff
a/lot/of
22
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.
23
24
Chapter 6.
Chapter 6. @QueryParam
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 paramters. Like PathParam, your parameter type can be an String,
primitive, or class that has a String constructor or static valueOf() method.
25
26
Chapter 7.
Chapter 7. @HeaderParam
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, ...)
27
28
Chapter 8.
Link
See
and
The main advantage of Link headers over Atom links in the resource is that those links are
available without parsing the entity body.
Warning
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.
8.2.1. Configuration
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:
Artifact
Version
org.jboss.resteasy
resteasy-links
3.0.9.Final
29
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);
}
30
@Mapped(namespaceMap = @XmlNsMap(jsonName
www.w3.org/2005/Atom"))
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Book {
@XmlAttribute
private String author;
"atom",
namespace
"http://
@XmlID
@XmlAttribute
private String title;
@XmlElementRef
private RESTServiceDiscovery rest;
}
If you do a GET /order/foo you will then get this XML representation:
{
"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"}
]
}
}
31
Table 8.2.
@LinkResource
parameters
Parameter
Type
Function
Default
value
Class
rel
String
self
For GET methods
returning a nonCollection
remove
For
DELETE
methods
update
For PUT methods
add
For POST methods
32
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.
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;
}
@Path("/")
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
public interface BookStore {
33
@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 commentI
@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 commentI
@LinkResource(Comment.class)
@DELETE
@Path("book/{id}/comment/{cid}")
public void deleteComment(@PathParam("id") String bookId, @PathParam("cid") String commentI
}
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".
Table 8.3.
34
@LinkResource
Parameter
Type
Function
Default
pathParameters
String[]
Defaults
to
@ResourceID,
using
@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 commentI
@LinkResource(pathParameters = {"${book.title}", "${id}"})
@POST
@Path("book/{id}/comments")
public void addComment(@PathParam("id") String bookId, Comment comment);
35
Table 8.4.
@LinkResource
security restrictions
Parameter
Type
Function
Default
constraint
String
@LinkELProvider(SeamELProvider.class)
package org.jboss.resteasy.links.test;
import org.jboss.resteasy.links.*;
36
package org.jboss.resteasy.links.test;
import
import
import
import
javax.el.ELContext;
javax.el.ELResolver;
javax.el.FunctionMapper;
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();
}
};
}
}
@Path("/")
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
37
@AddLinks
@LinkResource(constraint = "${s:hasPermission(this, 'read')}")
@GET
@Path("book/{id}/comment/{cid}")
public Comment getComment(@PathParam("id") String bookId, @PathParam("cid") String commentI
@LinkResource(constraint = "${s:hasPermission(this, 'insert')}")
@POST
@Path("book/{id}/comments")
public void addComment(@PathParam("id") String bookId, Comment comment);
38
Resource facades
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 Comments:
@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;
}
}
39
40
Chapter 9.
Chapter 9. @MatrixParam
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.
41
42
Chapter 10.
@GET
public String getBooks(@CookieParam("sessionid") int id) {
...
}
@GET
publi cString 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.
43
44
Chapter 11.
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")
@FormParam("lastname") String last) {...}
String
first,
@Path("/")
public class NameRegistry {
@Path("/resources/service")
@POST
@Consumes("application/x-www-form-urlencoded")
public
void
addName(@FormParam("firstname")
MultivaluedMap<String, String> form) {...}
String
first,
45
46
Chapter 12.
@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:
47
@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:
48
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");
49
50
Chapter 13.
@GET
public String getBooks(@QueryParam("num") @DefaultValue("10") int num) {...}
51
52
Chapter 14.
@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.
53
@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)
54
Chapter 15.
55
56
Chapter 16.
@Path("/")
public class ShoppingStore {
@Path("/customers/{id}")
public Customer getCustomer(@PathParam("id") int id) {
Customer cust = ...; // Find a customer object
return cust;
}
}
Resource methods that have a @Path annotation, but no HTTP method are considered subresource 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
57
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;
}
}
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}")
58
59
60
Chapter 17.
@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
thsi sis anice 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
61
@Produces("text/*")
@Path("/library")
public class Library {
@GET
@Produces("application/json")
public String getJSON() {...}
@GET
public String get() {...}
GET /library
Accept: application/json
62
URL-based negotiation
<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>
<servletclass>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>
GET /foo/bar
Accept: application/xml
63
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.
<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>
<servletclass>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'.
64
Chapter 18.
Table 18.1.
Media Types
Java Type
org.w3c.dom.Document
*/*
java.lang.String
*/*
java.io.InputStream
text/plain
*/*
javax.activation.DataSource
*/*
java.io.File
*/*
byte[]
application/x-www-form-urlencoded
javax.ws.rs.core.MultivaluedMap
65
@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);
/**
66
* 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
67
* 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);
}
@Provider
@Consumes("multipart/fixed")
public class MultipartProvider implements MessageBodyReader {
private @Context Providers providers;
...
}
<?xml version="1.0"?>
<!DOCTYPE foo
[<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<search>
68
<user>bill</user>
<file>&xxe;<file>
</search>
<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
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
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
to "false".
69
70
Chapter 19.
text/*+xml
application/*+xml
application/*+fastinfoset
application/*+json
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:
71
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
import
import
import
import
72
javax.xml.bind.Marshaller;
javax.xml.bind.PropertyException;
javax.ws.rs.core.MediaType;
javax.ws.rs.Produces;
java.lang.annotation.Annotation;
/**
* @author <a href="mailto:[email protected]">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
73
@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.
or
@XmlRootElement
public static class Thing
{
private String name;
74
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)
75
{
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;
}
}
@XmlRootElement(name = "book")
public class Book
{
private String author;
private String ISBN;
private String title;
76
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
77
{"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:
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(...) {...}
78
@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:
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:
79
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
80
@POST
public void newBook(@Mapped(attributeAsElements={"title"}) Book book) {...}
@XmlRootElement(name = "customer")
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer
{
@XmlElement
private String name;
public Customer()
{
}
public Customer(String name)
{
81
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
82
<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";
}
<foo:list xmlns:foo="http://foo.org">
<customer><name>bill</name></customer>
<customer><name>monica</name></customer>
</foo:list>
@GET
@Path("list")
@Produces("application/xml")
@Wrapped(element="list", namespace="http://foo.org", prefix="foo")
public List<Customer> getCustomerSet()
{
83
@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:
84
[{"foo":{"@test":"bill"}},{"foo":{"@test":"monica}"}}]
@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"));
85
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";
/**
86
<hashmap>
<hashentry hashkey="bill" xmlns:foo="http://foo.com">
<foo:foo name="bill"/>
</hashentry>
</map>
@Path("/map")
public static class MyResource
{
@GET
@Produces("application/xml")
@WrappedMap(map="hashmap", entry="hashentry", key="hashkey")
public Map<String, Foo> get()
{
...
return map;
}
87
@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:
88
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.
89
90
Chapter 20.
import
import
import
import
import
org.jboss.resteasy.plugins.providers.atom.Content;
org.jboss.resteasy.plugins.providers.atom.Entry;
org.jboss.resteasy.plugins.providers.atom.Feed;
org.jboss.resteasy.plugins.providers.atom.Link;
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"));
91
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")
@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;
92
}
}
@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);
}
}
93
94
Chapter 21.
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>3.0.9.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
95
<version>3.0.9.Final</version>
</dependency>
<jboss-deployment-structure>
<deployment>
<exclusions>
<module name="org.jboss.resteasy.resteasy-jackson-provider"/>
</exclusions>
<dependencies>
<module name="org.jboss.resteasy.resteasy-jackson2-provider"
services="import"/>
</dependencies>
</deployment>
</jboss-deployment-structure>
@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.
96
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) {...}
}
97
GET /resources/stuff?callback=processStuffResponse
org.jboss.resteasy.annotations.providers.jackson.Formatted
Here is an example:
98
@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".
99
100
Chapter 22.
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-json-p-provider</artifactId>
<version>3.0.9.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.
101
102
Chapter 23.
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();
}
103
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();
}
}
104
Use of GenericType is required because it is really the only way to obtain generic type information
at runtime.
@Path("/multipart")
public class MyService
{
@PUT
@Consumes("multipart/mixed")
public void put(List<Customer> customers)
{
...
}
}
105
It works in much the same way as MultipartInput described earlier in this chapter.
@Path("/multipart")
public class MyService
{
@PUT
@Consumes("multipart/form-data")
public void put(Map<String, Customer> customers)
{
...
}
}
106
InputPart getRootPart();
Map<String, InputPart> getRelatedMap();
}
It works in much the same way as MultipartInput described earlier in this chapter.
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()
}
107
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;
}
@Path("/multipart")
public class MyService
{
@GET
@Produces("multipart/mixed")
@PartType("application/xml")
public List<Customer> get()
{
...
}
}
108
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()
}
@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;
}
109
@Path("/multipart")
public class MyService
{
@GET
@Produces("multipart/form-data")
@PartType("application/xml")
public Map<String, Customer> get()
{
...
}
}
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)
}
110
@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),
"<[email protected]>", "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;
}
}
111
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);
}
112
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...
}
}
@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 ...
}
113
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:
@Path("/mime")
public class XopService {
@PUT
@Path("xop")
@Consumes(MultipartConstants.MULTIPART_RELATED)
public void putXopWithMultipartRelated(@XopWithMultipartRelated Xop xop) {
// do very important things here
}
114
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;
}
}
115
@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);
...
}
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 {
116
request.setAttribute(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8");
return null;
}
}
If
both
InputPart.DEFAULT_CONTENT_TYPE_PROPERTY
are
set,
then
the
InputPart.DEFAULT_CHARSET_PROPERTY will override any charset in
InputPart.DEFAULT_CONTENT_TYPE_PROPERTY.
InputPart.DEFAULT_CHARSET_PROPERTY
and
value
of
the value of
117
118
Chapter 24.
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
text/x-yaml
text/yaml
application/x-yaml
import
import
import
import
javax.ws.rs.Consumes;
javax.ws.rs.GET;
javax.ws.rs.Path;
javax.ws.rs.Produces;
@Path("/yaml")
public class YamlResource
119
{
@GET
@Produces("text/x-yaml")
public MyObject getMyObject() {
return createMyObject();
}
...
}
120
Chapter 25.
121
122
Chapter 26.
123
124
Chapter 27.
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
ExceptionMapper<javax.ejb.EJBException>
EJBExceptionMapper
implements
125
{
Response toResponse(EJBException exception) {
return Response.status(500).build();
}
}
Table 27.1.
Exception
HTTP Code
Description
ReaderException
400
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
(XML
JAXB
providers
and Jettison) throw
126
Exception
HTTP Code
Description
JAXBExceptions. This class
extends ReaderException
o.j.r.plugins.providers.jaxb.JAXBMarshalException
500
The
(XML
JAXB
providers
and Jettison) throw
N/A
Failure
N/A
LoggableFailure
N/A
Internal
Logged
DefaultOptionsMethodExceptionN/A
Resteasy
error.
127
128
Chapter 28.
<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:
129
{
public void contextInitialized(ServletContextEvent event)
{
Registry
registry
=
event.getServletContext().getAttribute(Registry.class.getName());
(Registry)
}
...
}
Please also take a look at our Spring Integration as well as the Embedded Container's Spring
Integration
130
Chapter 29.
@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() {...}
}
131
132
Chapter 30.
133
134
Chapter 31.
@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("ContentRange"));
135
136
Chapter 32.
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 {};
}
137
@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
138
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);
}
@Context
ServerCache cache;
139
@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.9.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>
140
Chapter 33.
141
142
@NameBinding
public @interface DoIt {}
@DoIt
public class MyFilter implements ContainerRequestFilter {...}
@Path("/root")
public class MyResource {
@GET
@DoIt
public String get() {...}
}
33.5. Ordering
Ordering is accomplished by using the @BindingPriority annotation on your filter or interceptor
class.
143
144
Chapter 34.
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()
{
145
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.
146
Chapter 35.
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.
/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.
147
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.
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.
<web-app>
<!-- enable the Asynchronous Job Service -->
<context-param>
<param-name>resteasy.async.job.service.enabled</param-name>
148
<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>
149
150
Chapter 36.
36.1. Undertow
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
import
import
import
io.undertow.servlet.api.DeploymentInfo;
org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;
org.jboss.resteasy.test.TestPortProvider;
org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import
import
import
import
import
import
import
import
import
javax.ws.rs.ApplicationPath;
javax.ws.rs.GET;
javax.ws.rs.Path;
javax.ws.rs.Produces;
javax.ws.rs.client.Client;
javax.ws.rs.client.ClientBuilder;
javax.ws.rs.core.Application;
java.util.HashSet;
java.util.Set;
/**
* @author <a href="mailto:[email protected]">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";
}
151
}
@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
152
implementation
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>
153
<version>3.0.9.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>tjws</artifactId>
<version>3.0.9.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"; }
154
Netty
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
36.4. Netty
Resteasy has integration with the popular Netty project as well..
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-netty</artifactId>
<version>3.0.9.Final</version>
155
</dependency>
156
Chapter 37.
import org.jboss.resteasy.mock.*;
...
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
POJOResourceFactory
noDefaults
POJOResourceFactory(LocatingResource.class);
dispatcher.getRegistry().addResourceFactory(noDefaults);
new
{
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.
157
158
Chapter 38.
/{pathparam1}/foo/bar/{pathparam2}
/*/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 JAXRS 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
159
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>
<listenerclass>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listenerclass>
</listener>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servletclass>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>
160
<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>
161
162
Chapter 39.
Important
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.
163
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 JBoss AS7 distribution.
164
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.
{
"realm" : "mydomain",
"admin-role" : "admin",
"login-role" : "login",
"oauth-client-role" : "oauth",
"wildcard-role" : "*",
"realm-keystore" : "${jboss.server.config.dir}/realm.jks",
165
"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"
]
}
realm
Name of the realm representing the users of your distributed applications and services
admin-role
Admin role mapping used for admins. You must have this defined if you want to do distributed
logout.
login-role
Role mapping for login clients.
oauth-client-role
Role mapping for regular oauth clients.
wildcard-role
Role mapping for assigning all roles to an oauth client wishing to act on behalf of a user.
realm-keystore
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 JBoss config dir.
realm-key-alias
Key alias for the key pair stored in your realm-keystore file.
realm-keystore-password
Password to access the keystore.
realm-private-key-password
Password to access the private realm key within the keystore
access-code-lifetime
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
166
Set up web.xml
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.
token-lifetime
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.
truststore
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.
truststore-password
Password for the truststore keystore.
resources
Root URLs of applications using this auth-server for SSO. This is OPTIONAL and only needed
if you want to allow distributed logout.
<jboss-web>
<security-domain>java:/jaas/commerce</security-domain>
<valve>
<classname>org.jboss.resteasy.skeleton.key.as7.OAuthAuthenticationServerValve</
class-name>
</valve>
</jboss-web>
<jboss-deployment-structure>
<deployment>
<dependencies>
167
{
"realm" : "mydomain",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCO8XXyi7oAq5ecsYy
+tJrl54N2TtKAkxuWEDmzvSPU+mUA2/3qHcxucZakG74Z49410tn5IIu2CXXlk9CuKcpXvKh
168
+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"
}
}
169
<jboss-web>
<valve>
<classname>org.jboss.resteasy.skeleton.key.as7.OAuthManagedResourceValve</classname>
</valve>
</jboss-web>
<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>
170
Set up web.xml
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:
realm
Name of the realm representing the users of your distributed applications and services
realm-public-key
PEM format of the realm's public key. Used to verify tokens.
<jboss-web>
<valve>
<classname>org.jboss.resteasy.skeleton.key.as7.BearerTokenAuthenticatorValve</classname>
</valve>
</jboss-web>
171
<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 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();
172
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 are within a JAX-RS environment you can inject a SkeletonKeySession using the @Context
annotation.
173
login page
The is the url of your login page. OAuth clients will redirect to it. This is application specific.
j_oauth_resolve_access_code
Used by oauth clients to turn an access code into an access token.
j_oauth_logout
Do a GET request to this URL and it will perform a distributed logout.
j_oauth_token_grant
Do a POST with BASIC Auth to obtain an access token for a specific user.
j_oauth_realm_info.html
Displays an HTML page with template configurations for using this realm.
174
Chapter 40.
Important
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.
175
Default
Description
oauth.provider.provider-class
*Required*
oauth.provider.tokens.request /requestToken
oauth.provider.tokens.access
/accessToken
<!-- 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>
176
Implementing an OAuthProvider
</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>
Default
Description
oauth.provider.provider-class
*Required*
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".
consumerKey,
String
callback)
throws
177
If a Consumer Key, or Token doesnt exist, or if the timestamp is not valid, simply throw an
OAuthException.
The rest of the interfaces used in OAuthProvider are:
178
Chapter 41.
@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()));
}
179
@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.get
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.g
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
180
181
182
Chapter 42.
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:
v
Protocol version. Always 1.
183
a
Algorithm used to hash and sign the message. RSA signing and SHA256 hashing is the only
supported algorithm at the moment by Resteasy.
d
Domain of the signer. This is used to identify the signer as well as discover the public key to
use to verify the signature.
s
Selector of the domain. Also used to identify the signer and discover the public key.
c
Canonical algorithm. Only simple/simple is supported at the moment. Basically this allows you
to transform the message body before calculating the hash
h
Semi-colon delimited list of headers that are included in the signature calculation.
x
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
t
Timestamp of signature. Numeric long value of the time in seconds since epoch. Allows the
verifier to control when a signature expires.
bh
Base 64 encoded hash of the message body.
b
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 [http://
bill.burkecentral.com/2011/02/21/multiple-uses-for-content-signature/] you might find interesting.
184
Maven settings
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-crypto</artifactId>
<version>3.0.9.Final</version>
</dependency>
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
185
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.
@GET
@Produces("text/plain")
@Path("signedresource")
@Signed(selector="burke",
expires=@After(hours=24))
public String getSigned()
{
return "hello world";
}
domain="sample.com",
timestamped=true,
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;
186
t=02342342341;
x=02342342322;
bh=m0234fsefasf==;
b=mababaddbb==
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:
187
On the client side, you create a verifier and add it as a property to the
ClientResponse. This will trigger the verification interceptors.
@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")
}
188
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.
You can always import your own official certificates too. See the JDK documentation for more
details.
<context-param>
<param-name>resteasy.doseta.keystore.classpath</param-name>
<param-value>test.jks</param-value>
</context-param>
189
<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
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;
}
}
190
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:
<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.
191
test2._domainKey
IN
TXT
"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIKFLFWuQfDfBug688BJ0dazQ/x
+GEnH443KpnBK8agpJXSgFAPhlRvf0yhqHeuI
+J5onsSOo9Rn4fKaFQaQNBfCQpHSMnZpBC3X0G5Bc1HWq1AtBl6Z1rbyFen4CmGYOyRzDBUOIW6n8QK47bf3hvoSxqpY1pH
+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.
192
Chapter 43.
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-crypto</artifactId>
<version>3.0.9.Final</version>
</dependency>
// server side
@Path("encrypted")
@GET
public EnvelopedOutput getEncrypted()
193
{
Customer cust = new Customer();
cust.setName("Bill");
X509Certificate certificate = ...;
EnvelopedOutput
output
MediaType.APPLICATION_XML_TYPE);
output.setCertificate(certificate);
return output;
new
EnvelopedOutput(cust,
// 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/pkcs7mime").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:
194
// 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.
// server-side
@Path("signed")
@GET
195
@Produces("multipart/signed")
public SignedOutput getSigned()
{
Customer cust = new Customer();
cust.setName("Bill");
SignedOutput
output
MediaType.APPLICATION_XML_TYPE);
output.setPrivateKey(privateKey);
output.setCertificate(certificate);
return output;
}
new
SignedOutput(cust,
// 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:
micalg=sha1;
multipart/signed;
protocol="application/pkcs7-signature";
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=signeddata
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
Content-Description: S/MIME Cryptographic Signature
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAMYIBVzCCAVMC
196
application/pkcs7-signature
AQEwUjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJu
ZXQgV2lkZ2l0cyBQdHkgTHRkAgkA7oW81OriflAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzEL
BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTExMDgxMTAxMDAyMlowIwYJKoZIhvcNAQkEMRYE
FH32BfR1l1vzDshtQvJrgvpGvjADMA0GCSqGSIb3DQEBAQUABIGAL3KVi3ul9cPRUMYcGgQmWtsZ
0bLbAldO+okrt8mQ87SrUv2LGkIJbEhGHsOlsgSU80/YumP+Q4lYsVanVfoI8GgQH3Iztp+Rce2c
y42f86ZypE7ueynI4HTPNHfr78EpyKGzWuZHW4yMo70LpXhk5RqfM9a/n4TEa9QuTU76atAAAAAA
AAA=
------=_Part_0_1083228271.1313024422098--
To
unmarshal
and
verify
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);
43.4. application/pkcs7-signature
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.
197
198
Chapter 44.
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>
<listenerclass>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listenerclass>
</listener>
199
<servlet>
<servlet-name>Resteasy</servlet-name>
<servletclass>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 JBoss AS 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.
200
Chapter 45.
<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 JAXRS 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>
<listenerclass>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listenerclass>
</listener>
<listener>
<listenerclass>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listenerclass>
</listener>
<servlet>
<servlet-name>Resteasy</servlet-name>
201
<servletclass>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>
<web-app>
202
</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"
www.w3.org/2001/XMLSchema-instance"
xmlns:xsi="http://
203
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
www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/util
www.springframework.org/schema/util/spring-util-2.5.xsd
http://www.springframework.org/schema/beans
www.springframework.org/schema/beans/spring-beans.xsd
">
http://
http://
http://
204
Chapter 46.
Warning
Since the scope of all beans that do not declare a scope is modified by resteasycdi, 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.
205
<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 [http://docs.jboss.org/weld/reference/latest/en-US/html/environments.html] can be used
for this purpose.
206
Chapter 47.
207
208
Chapter 48.
@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>
209
<param-name>resteasy.guice.modules</param-name>
<param-value>org.jboss.resteasy.examples.guice.hello.HelloModule</paramvalue>
</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>
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;
import org.jboss.resteasy.plugins.guice.RequestScoped;
@RequestScoped
210
<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</paramvalue>
</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>
211
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
<web-app>
<!-- other tags omitted -->
<listener>
<listener-class>
org.jboss.resteasy.plugins.guice.GuiceResteasyBootstrapServletContextListener
</listener-class>
</listener>
</web-app>
public
class
MyServletContextListener
GuiceResteasyBootstrapServletContextListener
extends
{
@Override
protected List<? extends Module> getModules(ServletContext context)
{
return Arrays.asList(new JpaPersistModule("consulting_hours"), new
MyModule());
}
@Override
public void withInjector(Injector injector)
{
212
injector.getInstance(PersistService.class).start();
}
}
213
214
Chapter 49.
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.
215
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:
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:
216
Abstract Responses
Alternatively you can use the Resteasy client extension interfaces directly:
@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.
1. Abstract Responses
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
@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:
217
@Path("/")
public interface LibraryService {
@GET
@Produces("application/xml")
Response getAllBooks();
}
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:
218
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:
For more information about HttpClient (4.x), see the documentation at http://hc.apache.org/
httpcomponents-client-ga/tutorial/html/ [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
219
or
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.
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:
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.
220
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.
221
222
Chapter 50.
@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:
<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>
223
@Path("/")
public interface X{
@GET
public String Y();
@PUT
public void Z(String entity);
}
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:
Warning
The following special parameter names are subject to change.
Default
$entity
Description
The entity to send as a PUT,
POST request.
$contentType
As
determined
@Consumes.
$accepts
224
Property name
Default
$callback
Description
Set to a function(httpCode,
xmlHttpRequest, value)
an asynchronous call. If
present, the call will
synchronous and return
value.
$apiURL
Determined by container
for
not
be
the
$username
$password
@Path("foo")
public class Foo{
@Path("{id}")
@GET
public String get(@QueryParam("order") String order, @HeaderParam("XFoo") String header,
@MatrixParam("colour") String colour, @CookieParam("FooCookie") String cookie){
&
}
@POST
public void post(String text){
}
}
We can use the previous JAX-RS API in JavaScript using the following code:
225
@Path("/")
public class MyResource {
@POST
public String postForm(@Form MyForm myForm) {...}
}
Then we could call the method from JavaScript API like following:
226
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}
]
});
Description
text/xml,application/xml,application/*+xml
application/json
Anything else
227
@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:
228
MIME
Description
DOM Element
Empty or application/json
The
JSON
object
is
marshalled to a JSON string
before being sent.
Anything else
Anything else
@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")
229
Let us look at what the preceding JAX-RS API would give us on the client side:
Description
apiURL
log
230
REST.log = function(text){
jQuery("#log-div").append(text);
};
Description
execute(callback)
setAccepts(acceptHeader)
setCredentials(username, password)
setEntity(entity)
setContentType(contentTypeHeader)
setURI(uri)
setMethod(method)
setAsync(async)
addCookie(name, value)
addQueryParameter(name, value)
addMatrixParameter(name, value)
addHeader(name, value)
231
REST.antiBrowserCache = true;
The above setting should be set once before you call any APIs.
232
Chapter 51.
@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:
233
@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
ConstraintValidator<TestClassConstraint, TestResource>
{
int length;
implements
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:
1. Create the resource and validate field, property, and class constraints.
2. Validate the resource method parameters.
3. If no violations have been detected, call the resource method and validate the return value
234
a
problem
with
the
validation
Resteasy
will
set
the
return
Violation reporting
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()
{
235
return returnValueViolations;
}
...
}
@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;
}
236
Violation reporting
/**
* @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();
}
}
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]
237
[]
[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]
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.
238
validation can be turned off or modified in the validation.xml configuration file. See the Hibernate
Validator [http://docs.jboss.org/hibernate/validator/5.0/reference/en-US/html/] documentation for
the details. Wildfly 8.x will ship with Hibernate Validator 5.x.
@Path("resourcePath")
@ValidateRequest
public interface Resource {
@POST
@Path("insert")
public String insert(...
@GET
@Path("list")
public String list(...
}
@Path("resourcePath")
public interface Resource {
@POST
@Path("insert")
239
@ValidateRequest
public String insert(...
@GET
@Path("list")
public String list(...
}
This way RESTEasy will only trigger validation in insert method. It's possible to say what methods
you don't want to be validated:
@Path("resourcePath")
@ValidateRequest
public interface Resource {
@POST
@Path("insert")
public String insert(...
@GET
@Path("list")
@DoNotValidateRequest
public String list(...
}
240
241
/**
* 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 supplies two implementations of GeneralValidator, in the modules
resteasy-validator-provider-11 and resteasy-hibernatevalidator-provider. 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
242
/**
* 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);
}
243
244
Chapter 52.
<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.9.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>3.0.9.Final</version>
</dependency>
<!-- optional modules -->
<!-- JAXB support -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxb-provider</artifactId>
<version>3.0.9.Final</version>
</dependency>
<!-- multipart/form-data and multipart/mixed support -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
<version>3.0.9.Final</version>
</dependency>
<!-- Resteasy Server Cache -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-cache-core</artifactId>
<version>3.0.9.Final</version>
245
</dependency>
<!-- Ruby YAML support -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-yaml-provider</artifactId>
<version>3.0.9.Final</version>
</dependency>
<!-- JAXB + Atom support -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-atom-provider</artifactId>
<version>3.0.9.Final</version>
</dependency>
<!-- Spring integration -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring</artifactId>
<version>3.0.9.Final</version>
</dependency>
<!-- Guice integration -->
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-guice</artifactId>
<version>3.0.9.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.9.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.9.Final</version>
246
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
247
248
Chapter 53.
249
250
Chapter 54.
http://
</web-app>
251
252
Chapter 55.
253
254
Chapter 56.
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
255
The JAX-RS TCK has become very strict with a ton more tests. I can't remember them all, but
there are a number of edge cases which earlier Resteasy releases misinterpreted.
Any Failure exceptions in the SPI now have a corresponding JAX-RS 2.0 exception, so they
have been deprecated Resteasy no longer uses these old SPI exceptions internally. It now uses
the JAX-RS 2.0 ones.
A number of SPIs have changed. Shouldn't be an issue for those of you who use Restasy asis. Specifically InjectorFactory and Registry have changed.
256
Filter execution and exception handling now matches the JAX-RS 2.0 spec. Exceptions
thrown from filters/interceptors can now be mapped if possible. Responses returned from
ExceptionMappers are now filtered.
257
258
allows non-string header objects to propagate through the MessageBodyWriter interceptor and
ClientExecutor interceptor chains.
259
Jettison and Fastinfoset have been broken out of the resteasy-jaxb-provider maven module.
You will now need to include resteasy-jettison-provider or resteasy-fastinfoset-provider if you
use either of these libraries.
The constructors for ClientRequest that have a HttpClient parameter (Apache Http Client 3.1
API) are now deprecated. They will be removed in the final release of 1.2. You must create a
Apache hTTP Client Executor and pass it in as a parameter if you want to re-use existing Apache
HttpClient sessions or do any special configuration. The same is true for the ProxyFactoyr
methods.
Apache HttpClient 4.0 support is available if you want to use it. I've had some trouble with it so
it is not the default implementation yet for the client framework.
It is no longer required to call RegisterBuiltin.register() to initialize the set of providers. Too
many users forgot to do this (include myself!). You can turn this off by calling the static method
ResteasyProviderFactory.setRegisterBuiltinByDefault(false)
The
Embedded
Container's
API
has
changed
to
use
260
Chapter 57.
261
262