此内容没有您所选择的语言版本。
Chapter 47. Passing Information into Resource Classes and Methods
Abstract
JAX-RS specifies a number of annotations that allow the developer to control where the information passed into resources come from. The annotations conform to common HTTP concepts such as matrix parameters in a URI. The standard APIs allow the annotations to be used on method parameters, bean properties, and resource class fields. Apache CXF provides an extension that allows for the injection of a sequence of parameters to be injected into a bean.
47.1. Basics of injecting data
Overview
Parameters, fields, and bean properties that are initialized using data from the HTTP request message have their values injected into them by the runtime. The specific data that is injected is specified by a set of annotations described in Section 47.2, “Using JAX-RS APIs”.
The JAX-RS specification places a few restrictions on when the data is injected. It also places a few restrictions on the types of objects into which request data can be injected.
When data is injected
Request data is injected into objects when they are instantiated due to a request. This means that only objects that directly correspond to a resource can use the injection annotations. As discussed in Chapter 46, Creating Resources, these objects will either be a root resource decorated with the @Path
annotation or an object returned from a sub-resource locator method.
Supported data types
The specific set of data types that data can be injected into depends on the annotation used to specify the source of the injected data. However, all of the injection annotations support at least the following set of data types:
-
primitives such as
int
,char
, orlong
-
Objects that have a constructor that accepts a single
String
argument -
Objects that have a static
valueOf()
method that accepts a singleString
argument - List<T>, Set<T>, or SortedSet<T> objects where T satisfies the other conditions in the list
Where injection annotations have different requirements for supported data types, the differences will be highlighted in the discussion of the annotation.
47.2. Using JAX-RS APIs
47.2.1. JAX-RS Annotation Types
The standard JAX-RS API specifies annotations that can be used to inject values into fields, bean properties, and method parameters. The annotations can be split up into three distinct types:
47.2.2. Injecting data from a request URI
Overview
One of the best practices for designing a RESTful Web service is that each resource should have a unique URI. A developer can use this principle to provide a good deal of information to the underlying resource implementation. When designing URI templates for a resource, a developer can build the templates to include parameter information that can be injected into the resource implementation. Developers can also leverage query and matrix parameters for feeding information into the resource implementations.
Getting data from the URI’s path
One of the more common mechanisms for getting information about a resource is through the variables used in creating the URI templates for a resource. This is accomplished using the javax.ws.rs.PathParam
annotation. The @PathParam
annotation has a single parameter that identifies the URI template variable from which the data will be injected.
In Example 47.1, “Injecting data from a URI template variable” the @PathParam
annotation specifies that the value of the URI template variable color
is injected into the itemColor
field.
Example 47.1. Injecting data from a URI template variable
import javax.ws.rs.Path; import javax.ws.rs.PathParam ... @Path("/boxes/{shape}/{color}") class Box { ... @PathParam("color") String itemColor; ... }
The data types supported by the @PathParam
annotation are different from the ones described in the section called “Supported data types”. The entity into which the @PathParam
annotation injects data must be of one of the following types:
PathSegment
The value will be the final segment of the matching part of the path.
List<PathSegment>
The value will be a list of PathSegment objects corresponding to the path segment(s) that matched the named template parameter.
-
primitives such as
int
,char
, orlong
-
Objects that have a constructor that accepts a single
String
argument -
Objects that have a static
valueOf()
method that accepts a singleString
argument
Using query parameters
A common way of passing information on the Web is to use query parameters in a URI. Query parameters appear at the end of the URI and are separated from the resource location portion of the URI by a question mark(?
). They consist of one, or more, name value pairs where the name and value are separated by an equal sign(=
). When more than one query parameter is specified, the pairs are separated from each other by either a semicolon(;
) or an ampersand(&
). Example 47.2, “URI with a query string” shows the syntax of a URI with query parameters.
Example 47.2. URI with a query string
http://fusesource.org?name=value;name2=value2;...
You can use either the semicolon or the ampersand to separate query parameters, but not both.
The javax.ws.rs.QueryParam
annotation extracts the value of a query parameter and injects it into a JAX-RS resource. The annotation takes a single parameter that identifies the name of the query parameter from which the value is extracted and injected into the specified field, bean property, or parameter. The @QueryParam
annotation supports the types described in the section called “Supported data types”.
Example 47.3, “Resource method using data from a query parameter” shows a resource method that injects the value of the query parameter id
into the method’s id
parameter.
Example 47.3. Resource method using data from a query parameter
import javax.ws.rs.QueryParam; import javax.ws.rs.PathParam; import javax.ws.rs.POST; import javax.ws.rs.Path; ... @Path("/monstersforhire/") public class MonsterService { ... @POST @Path("/{type}") public void updateMonster(@PathParam("type") String type, @QueryParam("id") String id) { ... } ... }
To process an HTTP POST
to /monstersforhire/daikaiju?id=jonas the updateMonster()
method’s type
is set to daikaiju
and the id
is set to jonas
.
Using matrix parameters
URI matrix parameters, like URI query parameters, are name/value pairs that can provide additional information selecting a resource. Unlike query parameters, matrix parameters can appear anywhere in a URI and they are separated from the hierarchical path segments of the URI using a semicolon(;
). /mostersforhire/daikaiju;id=jonas has one matrix parameter called id
and /monstersforhire/japan;type=daikaiju/flying;wingspan=40 has two matrix parameters called type
and wingspan
.
Matrix parameters are not evaluated when computing a resource’s URI. So, the URI used to locate the proper resource to handle the request URI /monstersforhire/japan;type=daikaiju/flying;wingspan=40 is /monstersforhire/japan/flying.
The value of a matrix parameter is injected into a field, parameter, or bean property using the javax.ws.rs.MatrixParam
annotation. The annotation takes a single parameter that identifies the name of the matrix parameter from which the value is extracted and injected into the specified field, bean property, or parameter. The @MatrixParam
annotation supports the types described in the section called “Supported data types”.
Example 47.4, “Resource method using data from matrix parameters” shows a resource method that injects the value of the matrix parameters type
and id
into the method’s parameters.
Example 47.4. Resource method using data from matrix parameters
import javax.ws.rs.MatrixParam; import javax.ws.rs.POST; import javax.ws.rs.Path; ... @Path("/monstersforhire/") public class MonsterService { ... @POST public void updateMonster(@MatrixParam("type") String type, @MatrixParam("id") String id) { ... } ... }
To process an HTTP POST
to /monstersforhire;type=daikaiju;id=whale the updateMonster()
method’s type
is set to daikaiju
and the id
is set to whale
.
JAX-RS evaluates all of the matrix parameters in a URI at once, so it cannot enforce constraints on a matrix parameters location in a URI. For example /monstersforhire/japan;type=daikaiju/flying;wingspan=40 , /monstersforhire/japan/flying;type=daikaiju;wingspan=40, and /monstersforhire/japan;type=daikaiju;wingspan=40/flying are all treated as equivalent by a RESTful Web service implemented using the JAX-RS APIs.
Disabling URI decoding
By default all request URIs are decoded. So the URI /monster/night%20stalker and the URI /monster/night stalker are equivalent. The automatic URI decoding makes it easy to send characters outside of the ASCII character set as parameters.
If you do not wish to have URI automatically decoded, you can use the javax.ws.rs.Encoded
annotation to deactivate the URI decoding. The annotation can be used to deactivate URI decoding at the following levels:
-
class level—Decorating a class with the
@Encoded
annotation deactivates the URI decoding for all parameters, field, and bean properties in the class. -
method level—Decorating a method with the
@Encoded
annotation deactivates the URI decoding for all parameters of the class. -
parameter/field level—Decorating a parameter or field with the
@Encoded
annotation deactivates the URI decoding for all parameters of the class.
Example 47.5, “Disabling URI decoding” shows a resource whose getMonster()
method does not use URI decoding. The addMonster()
method only disables URI decoding for the type
parameter.
Example 47.5. Disabling URI decoding
@Path("/monstersforhire/") public class MonsterService { ... @GET @Encoded @Path("/{type}") public Monster getMonster(@PathParam("type") String type, @QueryParam("id") String id) { ... } @PUT @Path("/{id}") public void addMonster(@Encoded @PathParam("type") String type, @QueryParam("id") String id) { ... } ... }
Error handling
If an error occurs when attempting to inject data using one of the URI injection annotations a WebApplicationException exception wrapping the original exception is generated. The WebApplicationException exception’s status is set to 404
.
47.2.3. Injecting data from the HTTP message header
Overview
In normal usage the HTTP headers in a request message pass along generic information about the message, how it is to be handled in transit, and details about the expected response. While a few standard headers are commonly recognized and used, the HTTP specification allows for any name/value pair to be used as an HTTP header. The JAX-RS APIs provide an easy mechanism for injecting HTTP header information into a resource implementation.
One of the most commonly used HTTP headers is the cookie. Cookies allow HTTP clients and servers to share static information across multiple request/response sequences. The JAX-RS APIs provide an annotation inject data directly from a cookie into a resource implementation.
Injecting information from the HTTP headers
The javax.ws.rs.HeaderParam
annotation is used to inject the data from an HTTP header field into a parameter, field, or bean property. It has a single parameter that specifies the name of the HTTP header field from which the value is extracted and injected into the resource implementation. The associated parameter, field, or bean property must conform to the data types described in the section called “Supported data types”.
Injecting the If-Modified-Since header shows code for injecting the value of the HTTP If-Modified-Since
header into a class' oldestDate
field.
Injecting the If-Modified-Since header
import javax.ws.rs.HeaderParam; ... class RecordKeeper { ... @HeaderParam("If-Modified-Since") String oldestDate; ... }
Injecting information from a cookie
Cookies are a special type of HTTP header. They are made up of one or more name/value pairs that are passed to the resource implementation on the first request. After the first request, the cookie is passes back and forth between the provider and consumer with each message. Only the consumer, because they generate requests, can change the cookie. Cookies are commonly used to maintain session across multiple request/response sequences, storing user settings, and other data that can persist.
The javax.ws.rs.CookieParam
annotation extracts the value from a cookie’s field and injects it into a resource implementation. It takes a single parameter that specifies the name of the cookie’s field from which the value is to be extracted. In addition to the data types listed in the section called “Supported data types”, entities decorated with the @CookieParam
can also be a Cookie
object.
Example 47.6, “Injecting a cookie” shows code for injecting the value of the handle
cookie into a field in the CB
class.
Example 47.6. Injecting a cookie
import javax.ws.rs.CookieParam; ... class CB { ... @CookieParam("handle") String handle; ... }
Error handling
If an error occurs when attempting to inject data using one of the HTTP message injection annotations a WebApplicationException exception wrapping the original exception is generated. The WebApplicationException exception’s status is set to 400
.
47.2.4. Injecting data from HTML forms
Overview
HTML forms are an easy means of getting information from a user and they are also easy to create. Form data can be used for HTTP GET
requests and HTTP POST
requests:
- GET
-
When form data is sent as part of an HTTP
GET
request the data is appended to the URI as a set of query parameters. Injecting data from query parameters is discussed in the section called “Using query parameters”. - POST
-
When form data is sent as part of an HTTP
POST
request the data is placed in the HTTP message body. The form data can be handled using a regular entity parameter that supports the form data. It can also be handled by using the@FormParam
annotation to extract the data and inject the pieces into resource method parameters.
Using the @FormParam annotation to inject form data
The javax.ws.rs.FormParam
annotation extracts field values from form data and injects the value into resource method parameters. The annotation takes a single parameter that specifies the key of the field from which it extracts the values. The associated parameter must conform to the data types described in the section called “Supported data types”.
The JAX-RS API Javadoc states that the @FormParam
annotation can be placed on fields, methods, and parameters. However, the @FormParam
annotation is only meaningful when placed on resource method parameters.
Example
Injecting form data into resource method parameters shows a resource method that injects form data into its parameters. The method assumes that the client’s form includes three fields—title
, tags
, and body
—that contain string data.
Injecting form data into resource method parameters
import javax.ws.rs.FormParam; import javax.ws.rs.POST; ... @POST public boolean updatePost(@FormParam("title") String title, @FormParam("tags") String tags, @FormParam("body") String post) { ... }
47.2.5. Specifying a default value to inject
Overview
To provide for a more robust service implementation, you may want to ensure that any optional parameters can be set to a default value. This can be particularly useful for values that are taken from query parameters and matrix parameters since entering long URI strings is highly error prone. You may also want to set a default value for a parameter extracted from a cookie since it is possible for a requesting system not have the proper information to construct a cookie with all the values.
The javax.ws.rs.DefaultValue
annotation can be used in conjunction with the following injection annotations:
-
@PathParam
-
@QueryParam
-
@MatrixParam
-
@FormParam
-
@HeaderParam
-
@CookieParam
The @DefaultValue
annotation specifies a default value to be used when the data corresponding to the injection annotation is not present in the request.
Syntax
Syntax for setting the default value of a parameter shows the syntax for using the @DefaultValue
annotation.
Syntax for setting the default value of a parameter
import javax.ws.rs.DefaultValue; ... void resourceMethod(@MatrixParam("matrix") @DefaultValue("value) int someValue, ... ) ...
The annotation must come before the parameter, bean, or field, it will effect. The position of the @DefaultValue
annotation relative to the accompanying injection annotation does not matter.
The @DefaultValue
annotation takes a single parameter. This parameter is the value that will be injected into the field if the proper data cannot be extracted based on the injection annotation. The value can be any String
value. The value should be compatible with type of the associated field. For example, if the associated field is of type int
, a default value of blue
results in an exception.
Dealing with lists and sets
If the type of the annotated parameter, bean or field is List, Set, or SortedSet then the resulting collection will have a single entry mapped from the supplied default value.
Example
Setting default values shows two examples of using the @DefaultValue
to specify a default value for a field whose value is injected.
Setting default values
import javax.ws.rs.DefaultValue; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.GET; import javax.ws.rs.Path; @Path("/monster") public class MonsterService { @Get public Monster getMonster(@QueryParam("id") @DefaultValue("42") int id, @QueryParam("type") @DefaultValue("bogeyman") String type) { ... } ... }
The getMonster()
method in Setting default values is invoked when a GET
request is sent to baseURI/monster. The method expects two query parameters, id
and type
, appended to the URI. So a GET
request using the URI baseURI/monster?id=1&type=fomóiri would return the Fomóiri with the id of one.
Because the @DefaultValue
annotation is placed on both parameters, the getMonster()
method can function if the query parameters are omitted. A GET
request sent to baseURI/monster is equivalent to a GET
request using the URI baseURI/monster?id=42&type=bogeyman.
47.2.6. Injecting Parameters into a Java Bean
Overview
When posting HTML forms over REST, a common pattern on the server side is to create a Java bean to encapsulate all of the data received in the form (and possibly data from other parameters and HTML headers, as well). Normally, creating this Java bean would be a two step process: a resource method receives the form values by injection (for example, by adding @FormParam
annotations to its method parameters), and the resource method then calls the bean’s constructor, passing in the form data.
Using the JAX-RS 2.0 @BeanParam
annotation, it is possible to implement this pattern in a single step. The form data can be injected directly into the fields of the bean class and the bean itself is created automatically by the JAX-RS runtime. This is most easily explained by example.
Injection target
The @BeanParam
annotation can be attached to resource method parameters, resource fields, or bean properties. A parameter target is the only kind of target that can be used with all resource class lifecycles, however. The other kinds of target are restricted to the per-request lifecycle. This situation is summarized in Table 47.1, “@BeanParam Injection Targets”.
Target | Resource Class Lifecycles |
---|---|
| All |
| Per-request (default) |
| Per-request (default) |
Example without BeanParam annotation
The following example shows how you might go about capturing form data in a Java bean using the conventional approach (without using @BeanParam
):
// Java import javax.ws.rs.POST; import javax.ws.rs.FormParam; import javax.ws.rs.core.Response; ... @POST public Response orderTable(@FormParam("orderId") String orderId, @FormParam("color") String color, @FormParam("quantity") String quantity, @FormParam("price") String price) { ... TableOrder bean = new TableOrder(orderId, color, quantity, price); ... return Response.ok().build(); }
In this example, the orderTable
method processes a form that is used to order a quantity of tables from a furniture Web site. When the order form is posted, the form values are injected into the parameters of the orderTable
method, and the orderTable
method explicitly creates an instance of the TableOrder
class, using the injected form data.
Example with BeanParam annotation
The previous example can be refactored to take advantage of the @BeanParam
annotation. When using the @BeanParam
approach, the form parameters can be injected directly into the fields of the bean class, TableOrder
. In fact, you can use any of the standard JAX-RS parameter annotations in the bean class: including @PathParam
, @QueryParam
, @FormParam
, @MatrixParam
, @CookieParam
, and @HeaderParam
. The code for processing the form can be refactored as follows:
// Java import javax.ws.rs.POST; import javax.ws.rs.FormParam; import javax.ws.rs.core.Response; ... public class TableOrder { @FormParam("orderId") private String orderId; @FormParam("color") private String color; @FormParam("quantity") private String quantity; @FormParam("price") private String price; // Define public getter/setter methods // (Not shown) ... } ... @POST public Response orderTable(@BeanParam TableOrder orderBean) { ... // Do whatever you like with the 'orderBean' bean ... return Response.ok().build(); }
Now that the form annotations have been added to the bean class, TableOrder, you can replace all of the @FormParam
annotations in the signature of the resource method with just a single @BeanParam
annotation, as shown. Now, when the form is posted to the orderTable
resource method, the JAX-RS runtime automatically creates a TableOrder
instance, orderBean
, and injects all of the data specified by the parameter annotations on the bean class.
47.3. Parameter Converters
Overview
Using parameter converters, it is possible to inject a parameter (of String
type) into any type of field, bean property, or resource method argument. By implementing and binding a suitable parameter converter, you can extend the JAX-RS runtime so that it is capable of converting the parameter String value to the target type.
Automatic conversions
Parameters are received as instances of String
, so you can always inject them directly into fields, bean properties, and method parameters of String
type. In addition, the JAX-RS runtime has the capability to convert parameter strings automatically to the following types:
- Primitive types.
-
Types that have a constructor that accepts a single
String
argument. -
Types that have a static method named
valueOf
orfromString
with a single String argument that returns an instance of the type. -
List<T>
,Set<T>
, orSortedSet<T>
, ifT
is one of the types described in 2 or 3.
Parameter converters
In order to inject a parameter into a type not covered by automatic conversion, you can define a custom parameter converter for the type. A parameter converter is a JAX-RS extension that enables you to define conversion from String
to a custom type, and also in the reverse direction, from the custom type to a String
.
Factory pattern
The JAX-RS parameter converter mechanism uses a factory pattern. So, instead of registering a parameter converter directly, you must register a parameter converter provider (of type, javax.ws.rs.ext.ParamConverterProvider
), which creates a parameter converter (of type, javax.ws.rs.ext.ParamConverter
) on demand.
ParamConverter interface
The javax.ws.rs.ext.ParamConverter
interface is defined as follows:
// Java package javax.ws.rs.ext; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.ws.rs.DefaultValue; public interface ParamConverter<T> { @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public static @interface Lazy {} public T fromString(String value); public String toString(T value); }
To implement your own ParamConverter
class, you must implement this interface, overriding the fromString
method (to convert the parameter string to your target type) and the toString
method (to convert your target type back to a string).
ParamConverterProvider interface
The javax.ws.rs.ext.ParamConverterProvider
interface is defined as follows:
// Java package javax.ws.rs.ext; import java.lang.annotation.Annotation; import java.lang.reflect.Type; public interface ParamConverterProvider { public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation annotations[]); }
To implement your own ParamConverterProvider
class, you must implement this interface, overriding the getConverter
method, which is a factory method that creates ParamConverter
instances.
Binding the parameter converter provider
To bind the parameter converter provider to the JAX-RS runtime (thus making it available to your application), you must annotate your implementation class with the @Provider
annotation, as follows:
// Java ... import javax.ws.rs.ext.ParamConverterProvider; import javax.ws.rs.ext.Provider; @Provider public class TargetTypeProvider implements ParamConverterProvider { ... }
This annotation ensures that your parameter converter provider is automatically registered during the scanning phase of deployment.
Example
The following example shows how to implement a ParamConverterProvider
and a ParamConverter
which has the capability to convert parameter strings to and from the TargetType
type:
// Java import java.lang.annotation.Annotation; import java.lang.reflect.Type; import javax.ws.rs.ext.ParamConverter; import javax.ws.rs.ext.ParamConverterProvider; import javax.ws.rs.ext.Provider; @Provider public class TargetTypeProvider implements ParamConverterProvider { @Override public <T> ParamConverter<T> getConverter( Class<T> rawType, Type genericType, Annotation[] annotations ) { if (rawType.getName().equals(TargetType.class.getName())) { return new ParamConverter<T>() { @Override public T fromString(String value) { // Perform conversion of value // ... TargetType convertedValue = // ... ; return convertedValue; } @Override public String toString(T value) { if (value == null) { return null; } // Assuming that TargetType.toString is defined return value.toString(); } }; } return null; } }
Using the parameter converter
Now that you have defined a parameter converter for TargetType
, it is possible to inject parameters directly into TargetType
fields and arguments, for example:
// Java import javax.ws.rs.FormParam; import javax.ws.rs.POST; ... @POST public Response updatePost(@FormParam("target") TargetType target) { ... }
Lazy conversion of default value
If you specify default values for your parameters (using the @DefaultValue
annotation), you can choose whether the default value is converted to the target type right away (default behaviour), or whether the default value should be converted only when required (lazy conversion). To select lazy conversion, add the @ParamConverter.Lazy
annotation to the target type. For example:
// Java import javax.ws.rs.FormParam; import javax.ws.rs.POST; import javax.ws.rs.DefaultValue; import javax.ws.rs.ext.ParamConverter.Lazy; ... @POST public Response updatePost( @FormParam("target") @DefaultValue("default val") @ParamConverter.Lazy TargetType target) { ... }
47.4. Using Apache CXF extensions
Overview
Apache CXF provides an extension to the standard JAX-WS injection mechanism that allows developers to replace a sequence of injection annotations with a single annotation. The single annotation is placed on a bean containing fields for the data that is extracted using the annotation. For example, if a resource method is expecting a request URI to include three query parameters called id
, type
, and size
, it could use a single @QueryParam
annotation to inject all of the parameters into a bean with corresponding fields.
Consider using the @BeanParam
annotation instead (available since JAX-RS 2.0). The standardized @BeanParam
approach is more flexible than the proprietary Apache CXF extension, and is thus the recommended alternative. For details, see Section 47.2.6, “Injecting Parameters into a Java Bean”.
Supported injection annotations
This extension does not support all of the injection parameters. It only supports the following ones:
-
@PathParam
-
@QueryParam
-
@MatrixParam
-
@FormParam
Syntax
To indicate that an annotation is going to use serial injection into a bean, you need to do two things:
-
Specify the annotation’s parameter as an empty string. For example
@PathParam("")
specifies that a sequence of URI template variables are to be serialized into a bean. - Ensure that the annotated parameter is a bean with fields that match the values being injected.
Example
Example 47.7, “Injecting query parameters into a bean” shows an example of injecting a number of Query parameters into a bean. The resource method expect the request URI to include two query parameters: type
and id
. Their values are injected into the corresponding fields of the Monster
bean.
Example 47.7. Injecting query parameters into a bean
import javax.ws.rs.QueryParam; import javax.ws.rs.PathParam; import javax.ws.rs.POST; import javax.ws.rs.Path; ... @Path("/monstersforhire/") public class MonsterService { ... @POST public void updateMonster(@QueryParam("") Monster bean) { ... } ... } public class Monster { String type; String id; ... }