...
Span |
---|
Wiki Markup | |||
---|---|---|---|
|
| ||
} JAX-RS : Data Bindings {span} |
Table of Contents |
---|
JAXB support
The request and response can be marshalled and unmarshalled to/from a Java object using JAXB.
There 's are a number of ways to tell to the JAXB provider how objects can be serialized. The simplest way is to mark a given type with @XmlRootElement annotation.
...
In the example below, the Customer object returned by getCustomer is marshaled marshalled using the JAXB data binding:
Code Block | ||||
---|---|---|---|---|
| ||||
@Path("/customerservice/") public class CustomerService { @GET @Path("/customers/{customerId}/") public Customer getCustomer(@PathParam("customerId") String id) { .... } } |
The wire representation of the Customer object is:
Code Block | ||||
---|---|---|---|---|
| ||||
<Customer> <id>123</id> <name>John</name> </Customer> |
The simplest way to work with the collections is to define a type representing a collection. For example:
Code Block |
---|
@XmlRootElement(name = "Customers") public class Customers { private Collection<Customer> customers; public Collection<Customer> getCustomer() { return customers; } public void setCustomer(Collection<Customer> c) { this.customers = c; } } @Path("/customerservice/") public class CustomerService { @GET @Path("/customers/") public Customers getCustomers() { .... } } |
Alternatively As an alternative to using @XmlRootElement and Collection wrappers, one can provide an Object factory which will tell JAXB how to marshal a given type (in case of Collections - its template type). Another option is to return/accept a JAXBElement directly from/in a given method.
...
Finally, JAXBProvider provides a support for serializing response types and deserializing parameters of methods annotated with @XmlJavaTypeAdapter annotations.
Configuring the JAXB provider
The default JAXB provider can be configured in a number of ways. For example, here's how to set up marshall configure marshal properties :
Code Block | ||||
---|---|---|---|---|
| ||||
<beans xmlns:util="http://www.springframework.org/schema/util"> <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"> <property name="marshallerProperties" ref="propertiesMap"/> </bean> <util:map id="propertiesMap"> <entry key="jaxb.formatted.output"> <value type="java.lang.Boolean">true</value> </entry> </util:map> /<beans> |
Individual marshal properties can be injected as simple properties. At the moment, Marshaller.JAXB_SCHEMA_LOCATION can be injected as the "schemaLocation" property. Schema validation can be enabled and custom @Consume and @Produce media types can be injected, see this example and the "Customizing media types for message body providers" and "Schema Validation" sections for more information.
...
Starting from CXF 2.4.3 it is possible to have specific prefixes associated with XML namespaces. This might be needed to make the legacy consumers able to consume the resulting XML. Use a "namespacePrefixes" map property (namespace is a key, corresponding prefix is a value).
...
For JAXBElementProvider to support Moxy a custom Moxy-aware JAX-RS ContextProvider implementation needs to be registered in jaxrs:providers. For example:
Code Block | ||||
---|---|---|---|---|
| ||||
import javax.xml.bind.JAXBContext; import javax.wsxml.rs.ext.bind.JAXBException; import javax.ws.rs.ext.ContextResolver; import org.eclipse.persistence.jaxb.JAXBContextFactory; public class MoxyJaxbContextResolved implements ContextResolver<JAXBContext> { org.eclipse.persistence.jaxb.JAXBContextFactory factory = new JAXBContextFactory(); public JAXBContext getContext(Class<?> cls) { try { return factoryJAXBContextFactory.createContext(new Class[] {cls}, null); } catch (JAXBException ex) { throw new RuntimeException(ex); } } } |
Alternatively, add a "jaxb.properties" file with the entry "javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory" to a package where the JAXB beans are located.
If Moxy is used to handle beans without JAXB annotations then setting a 'skipJaxbChecks' property on JAXBElementProvider to 'true' will be needed.
Use JAXBElementProvider "namespaceMapperPropertyName" and "xmlPiPropertyName" properties if you need to customize namespace prefixes or add XML processing instructions with Moxy. JSONProvider will also recognize "namespaceMapperPropertyName" in cases when the namespaces are not ignored.
JSON support
Following The following code returns a Customer object that is marshaled to JSON format:
Code Block |
---|
@Path("/customerservice/")
public class CustomerService {
@Produces("application/json")
@GET
@Path("/customers/{customerId}/")
public Customer getCustomer(@PathParam("customerId") String id) {
....
}
} |
Jettison
Configuring JSON provider
Default The default JSON provider relies on Jettison 1.4.3 0 and it expects the types it deals with to follow the same techniques as described above in the JAXB support section for them to be handled properly.
...
Code Block | ||||
---|---|---|---|---|
| ||||
<beans xmlns:util="http://www.springframework.org/schema/util"> <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.json.JSONProvider"> <property name= <property name="namespaceMap" ref="jsonNamespaceMap"/> </bean> <util:map id="jsonNamespaceMap" map-class="java.util.Hashtable"> <entry key="http://www.example.org/books" value="b"/> </util:map> /<beans> |
Note that starting from CXF 2.3.0 it may not be needed to set up a custom namespace map on the write side with JSONProvider making a 'best' effort to retrieve them during the JAXB Marshaller calls. Starting from CXF 2.6.1 and 2.5.4 setting the namespace maps will additionally configure a Marshaller namespace mapper.
Schema validation can be enabled and custom @Consume and @Produce media types can be injected, see this example and "Customizing media types for message body providers" and "Schema Validation" sections for more information.
Note that JSONProvider is Jettison based and supports only JAXB annotated beans. You should use Jackson provider to process POJOs without JAXB annotations.
Dealing with Jettison array serialization issues
By default, Jettison wrongly serializes List objects containing a single value only. To work around this issue, one needs to enable a 'serializeAsArray' feature on a JSONProvider, with the additional option of specifying the individual fields which needs to be processed accordingly using an 'arrayKeys' property. Please see this example for more information.
Note that 'serializeAsArray' and 'arrayKeys' can be combined to produce so called natural convention sequences. For example, given the following two class definitions :
Code Block | ||||
---|---|---|---|---|
| ||||
@XmlRootElement() @XmlType(name = "", propOrder = {"title", "comments" }) public static class Post { private String title; private List<Comment> comments = new ArrayList<Comment>ArrayList<>(); public void setTitle(String title) { this.title = title; } public String getTitle() { return title; } public void setComments(List<Comment> comments) { this.comments = comments; } public List<Comment> getComments() { return comments; } } public static class Comment { private String title; public void setTitle(String title) { this.title = title; } public String getTitle() { return title; } } |
...
One other property which might help during the serialization is a boolean "ignoreMixedContent" property which lets allows you to bypass a Jettison issue to do with outputting '$' properties when dealing with empty strings typically encountered in mixed content trees.
...
Code Block | ||||
---|---|---|---|---|
| ||||
<dependency> <groupId>org<groupId>com.fasterxml.codehausjackson.jackson<jaxrs</groupId> <artifactId>jackson-jaxrs-json-jaxrs<provider</artifactId> <version>1<version>2.9.0<3</version> </dependency> |
Common JAXB and JSON configuration
...
JAXB and JSON providers can handle explicit collections like List, Set or base Collection.
By default they will try to deduce the name of the collection root element from a collection member class. For example, given a Book.class whose @XmlRootElement value is 'Book', the name of the collection name will be 'Books'.
One can override it by setting a 'collectionWrapperName' string property, like 'Books' or '{http://books}Book'.
There's also a 'collectionWrapperMap' property available for use in more advanced cases, when collections of different types are used, for example, when mixed collections of objects descended from abstract classes having no @XmlRootElement tags are returned :
Code Block | ||||
---|---|---|---|---|
| ||||
<!-- Configure JAXB Provider --> <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"> <property name="collectionWrapperMap"> <map> <entry> <key><value>com.foo.bar.MyObject</value></key> <value>MyObjects</value> <value>MyObjects</value> </entry> </map> </property> </bean> |
JSONProvider can only serialize explicit collections at the moment. If needed, it can be told to drop a collection element name using a boolean 'dropCollectionWrapperElementName'. For example, a 'dropCollectionWrapperElementName' and 'serializeAsArray' properties can be used to make the Dojo Pojo JSON RestStore consume the resulting JSON sequence (in CXF 2.2.5).
...
Sometimes you may want to adapt an incoming XML request or outgoing XML response. For example, your application has changed but a lot of legacy clients have not been updated yet.
When dealing with XML, the easiest and fastest option is to register a custom STAX XMLStreamWriter or XMLStreamReader and modify XML as needed. You can register a custom STAX
handler from RequestHandler or ResponseHandler filters or input/output CXF interceptors. For example, see XMLStreamWriterProvider and CustomXmlStreamWriter.
Another option is to register a custom JAXB or JSON provider extending CXF JAXBElementProvider or JSONProvider and overriding a method like createStreamWriter().
Typically one would delegate to a super class first and then wrap the returned writer in a custom writer, see CustomXmlStreamWriter for an example.
One can also use XSLTJaxbProvider to produce or modify the incoming XML. In fact, XSLTJaxbProvider can be used to adapt formats like JSON for legacy consumers.
...
Starting from CXF 2.6.0 it is possible to control the depth of large XML and JSON payloads on the per-endpoint basis in order to limit the risk of the denial of service attacks. Please see this section on how to use a new DepthRestrictingInterceptor in order to control XML payloads which are read either by JAXBElementProvider or SourceProvider (which supports JAXP Source and DOM Document types).
Additionally it is possible to configure JAXBElementProvider or JSONProvider with contextual properties or DocumentDepthProperties:
Code Block | ||||
---|---|---|---|---|
| ||||
<bean id="depthProperties" class="org.apache.cxf.staxutils.DocumentDepthProperties">
<property name="innerElementCountThreshold" value="500"/>
</bean>
<bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider">
<property name="depthProperties" ref="depthProperties"/>
</bean>
<bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.json.JSONProvider">
<property name="depthProperties" ref="depthProperties"/>
</bean>
<jaxrs:server id="endpoint1">
<jaxrs:serviceBeans>
<bean class="my.package.RootResource"/>
</jaxrs:serviceBeans>
<jaxrs:providers>
<ref bean="jaxbProvider"/>
<ref bean="jsonProvider"/>
</jaxrs:providers>
</jaxrs:server>
<jaxrs:server id="endpoint2">
<jaxrs:serviceBeans>
<bean class="my.package.RootResource"/>
</jaxrs:serviceBeans>
<jaxrs:properties>
<entry key="depthInnerElementCountThreshold" value="500"/
</jaxrs:properties>
</jaxrs:server>
|
JSR-353 JSON Processing
As per JAX-RS 2.0 specification, the support of JSR-353 Java API for JSON Processing is mandatory requirement and implies the support of presence of message body reader(s)/writer(s) for following types: JsonStructure, JsonArray and JsonObject. The Apache CXF provides such a support in a form of JsrJsonpProvider provider distributed by Apache CXF JAX-RS Extensions Providers module (cxf-rt-rs-extension-providers).
...
Adding JsrJsonpProvider provider alone (or in a combination with other providers) allows JAX-RS resources to natively use JsonStructure, JsonArray, JsonObject as input parameters or return resultsvalues. For example:
Code Block | ||||
---|---|---|---|---|
| ||||
@GET @Path("/books") @Produces(MediaType.APPLICATION_JSON) public JsonArray getBooks() { // Implementation here } @GET @Path("/books/{bookId}") @Produces(MediaType.APPLICATION_JSON) public JsonObject getBook(@PathParam("bookId") Long id) { // Implementation here } @POST @Path("/books") @Consumes(MediaType.APPLICATION_JSON) public Response addBook(@Context final UriInfo uriInfo, JsonObject obj) { // Implementation here } |
The JsrJsonpProvider provider could be used on server side or on a client side. By default, the provider uses JSR-353 Java API for JSON Processing reference implementation to read/write message bodies.
JSON With Padding (JSONP)
One option for supporting "JSON With Padding" (JSONP) is to extend the default JSONProvider and override its writeTo method as follows:
JSR-367 JSON Binding
As per JAX-RS 2.1 specification, the support of JSR 367: Java API for JSON Binding (JSON-B) is desired requirement and implies the presence of message body reader(s)/writer(s) for Java types supported by JSON-B. Apache CXF provides such a support in a form of JsrJsonbProvider provider distributed by Apache CXF JAX-RS Extensions Providers module (cxf-rt-rs-extension-providers).
Code Block | ||||
---|---|---|---|---|
| ||||
<jaxrs:providers>
<bean class="org.apache.cxf.jaxrs.provider.jsrjsonb.JsrJsonbProvider"/>
</jaxrs:providers>
|
Adding JsrJsonbProvider providers also covers JsonStructure, JsonArray, JsonObject as input parameters or return values.
Simple JsonMapObject support
org.apache.cxf.jaxrs.provider.json.JsonMapObjectProvider is available starting from CXF 3.0.3 and CXF 3.1.0. This provider can read or write JSON into/from org.apache.cxf.jaxrs.provider.json.JsonMapObject which is a simple Map wrapper.
JsonMapObject can detect duplicate JSON sibling properties which can be useful in certain applications.
JSON With Padding (JSONP)
One option for supporting "JSON With Padding" (JSONP) is to extend the default JSONProvider and override its writeTo method as follows:
Code Block | ||||
---|---|---|---|---|
| ||||
@Produces("application/javascript") class JsonpProvider extends JSONProvider { @Override public void writeTo(Object obj, Class<?> cls, Type genericType, Annotation[] anns, MediaType m, writeTo(Object obj, Class<?> cls, Type genericType, Annotation[] anns, MediaType m, MultivaluedMap<String, Object> headers, OutputStream os) throws IOException { String prefix = getContext().getHttpServletRequest().getParameter("_jsonp"); boolean hasPrefix = !isEmpty(prefix); if(hasPrefix) { os.write(prefix.getBytes(HttpUtils.getSetEncoding(m, headers, "UTF-8"))); os.write('('); } super.writeTo(obj, cls, genericType, anns, m, headers, os); if(hasPrefix) { os.write(')'); } } } |
Similar A similar approach can work when Jackson is used.
Alternatively, a custom servlet filter can be used to support JSONP. Please read the Supporting JSONP blog post for more information.
Starting from CXF 2.3.4 and 2.4.0, JSONP can be supported with the help of CXF in/out interceptors:
...
Code Block | ||||
---|---|---|---|---|
| ||||
<jaxrs:server id="bookJsonp" address="/jsonp"> <jaxrs:serviceBeans> <ref bean="serviceBean" /> </jaxrs:serviceBeans> <jaxrs:inInterceptors> <bean class="org.apache.cxf.jaxrs.provider.jsonp.JsonpInInterceptor"> <property name="callbackParam" value="myjsonp"/> </bean> </jaxrs:inInterceptors> <jaxrs:outInterceptors> <bean class="org.apache.cxf.jaxrs.provider.jsonp.JsonpPreStreamInterceptor"> <property name="mediaType" value="text/x+javascript"/> </bean> <bean class="org.apache.cxf.jaxrs.provider.jsonp.JsonpPostStreamInterceptor"> <!-- default is ');' --> <property name="paddingEnd" value=")"/> </bean> </jaxrs:outInterceptors> </jaxrs:server> |
Form payloads
A Form payload is a sequence of name and value pairs, example, "name=Barry&age=20".
One can capture the form data by using either JAX-RS FormParam annotation or MultivaluedMap, for example:
...
Both AtomFeedProvider and AtomEntryProvider support a 'formattedOutput' (pretty-printing) property.
2. Register an AtomPojoProvider injected with either AtomElementWriter or AtomElementReader implementations parameterized by either Abdera Feed or Entry and type of object which will have to be converted to/read from Feed/Entry.
...
Code Block | ||||
---|---|---|---|---|
| ||||
@Path("books") public class BooksRootResource { private Books books; @GET @Produces({"application/xml", "application/json", "application/atom+xml;type=feed"}) public Books getCollectionOfBooks() { return books; } @GET @Produces({"application/xml", "application/json", "application/atom+xml;type=entry"}) @Path("{id}") public Book getBook(@PathParam("id") Long id) { return books.get(id); } } |
Note that when an object such as Books is about to be converted to/read from Feed (which in our case is essentially a collection of entries each of them representing an individual Book) the AtomPojoProvider needs to know about the collection getters and/or setters so that it can create individual Entries. The "collectionGetters" and "collectionSetters" map properties with keys being the names of collection classes and values being the method names need to be used for providing this information, example a pair "org.apache.cxf.systest.jaxrs.Books:getBooks" tells AtomPojoProvider that when creating a Books Feed, the objects representing individual entries can be retrieved from Book with the help of "getBooks". If these properties are not set then AtomPojoProvider will try to get a method adding the simple class name to either 'get' or 'set', for example, an "org.apache.cxf.systest.jaxrs.Books:getBooks" pair is redundant if the Books class has a getBooks method.
3. This option is nearly identical to the option 2, except that users configure AtomPojoProvider with concrete implementations of either AbstractFeedBuilder or AbstractEntryBuilder.
The difference is that in this case users have no explicit dependencies in their code on Atom-aware libraries such as Abdera - it may make it easier to experiment with various Atom libraries.
...
Starting from CXF 2.2.3 it is possible to register a CXF DataBinding bean using a jaxrs:databinding element and it will be wrappped as a JAXRS MessageBodyReader/Writer DataBindingProvider capable of dealing with XML-based content. It can be of special interest to users combining JAX-RS and JAXWS. Thus CXF JAXB, JIBX, Aegis, SDO and XMLBeans DataBindings can be plugged in.
...
JSON support is also provided for all these databindings by DataBindingJSONProvider.
Please see this configuration file for some examples.
Similarly to the default JSONProvider the DataBindingJSONProvider JSON provider can have "namespaceMap", "serializeAsArray", "arrayKeys", "dropRootElement" and "writeXsiType" properties set. Additionally it may also have an "ignoreMixedContent" property set.
...
org.apache.cxf.jaxrs.provider.JAXRSDataBinding is a CXF DataBinding implementation which wraps JAX-RS providers and can be used by CXF JAX-WS endpoints thus making it possible to use JAX-RS providers for reading and writing XML payloads during SOAP and RESTful invocations. Example :
Code Block | ||||
---|---|---|---|---|
| ||||
<jaxrs:server id="hello_rest" address="/hello-rest"> <jaxrs:serviceBeans> <bean class="org.apache.cxf.systest.jaxrs.jaxws.HelloWorldImpl"/> </jaxrs:serviceBeans> <jaxrs:providers> <ref bean="jaxbProviderSoap"/> </jaxrs:providers> </jaxrs:server> <jaxws:endpoint xmlns:s="http://hello.com" serviceName="s:HelloWorld" endpointName="s:HelloWorldPort" id="hello_soap-databinding" implementor="org.apache.cxf.systest.jaxrs.jaxws.HelloWorldImpl" address="/hello-soap-databinding"> <jaxws:dataBinding> <ref bean="jaxrs-data-binding"/> </jaxws:dataBinding> </jaxws:endpoint> <bean id="jaxrs-data-binding" class="org.apache.cxf.jaxrs.provider.JAXRSDataBinding"> <property name="provider" ref="jaxbProviderSoap"/> </bean> <bean id="jaxbProviderSoap" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"/> |
...
1. Using jaxrs:schemaLocations element
Code Block | ||||
---|---|---|---|---|
| ||||
<beans> <jaxrs:server address="/" serviceClass="org.apache.cxf.systest.jaxrs.BookStore"> <jaxrs:schemaLocations> <jaxrs:schemaLocation>classpath:/schemas/a.xsd</jaxrs:schemaLocation> <jaxrs:schemaLocation>classpath:/schemas/b.xsd</jaxrs:schemaLocation> <!-- Possible from CXF 3.0.0 milestone2: --> <!-- <jaxrs:schemaLocation>classpath:/schemas/</jaxrs:schemaLocation> --> </jaxrs:schemaLocations> </jaxrs:server> </beans> |
Using this option is handy when you have multiple bindings involved which support the schema validation. In this case
individual MessageBodyReader implementations which have a method setSchemas(List<Sring> schemaLocations) have it called and locations to schema resources injected. Default JAXBElementProvider and JSONProvider which rely on JAXB can be enabled to do the validation this way. In the above example two schema documents are provided, with b.xsd schema importing a.xsd
...
Code Block | ||||
---|---|---|---|---|
| ||||
<beans xmlns:util="http://www.springframework.org/schema/util"> <jaxrs:server address="/" serviceClass="org.apache.cxf.systest.jaxrs.BookStore"> <jaxrs:providers> <ref bean="jaxbProvider"/> </jaxrs:providers> </jaxrs:server> <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"> <property name="schemaLocations" ref="schemaHolder"/> </bean> <util:list id="theSchemas"> <value>classpath:/WEB-INF/schemas/bookid.xsd</value> <value>classpath:/org/apache/cxf/systest/jaxrs/resources/book.xsd</value> </util:list> </beans> |
If you have both JAXB and JSON providers validating the input data then you can get schemas shared between them, for example:
Code Block | ||||
---|---|---|---|---|
| ||||
<beans xmlns:util="http://www.springframework.org/schema/util"> <jaxrs:server address="/" serviceClass="org.apache.cxf.systest.jaxrs.BookStore"> <jaxrs:providers> <ref bean="jaxbProvider"/> <ref bean="jsonProvider"/> </jaxrs:providers> </jaxrs:server> <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"> <property name="schemaHandler" ref="schemaHolder"/> </bean> <bean id="jsonProvider" class="org.apache.cxf.jaxrs.provider.json.JSONProvider"> <property name="schemaHandler" ref="schemaHolder"/> </bean> <bean id="schemaHolder" class="org.apache.cxf.jaxrs.utils.schemas.SchemaHandler"> <property name="schemas" ref="theSchemas"/> </bean> <util:list id="theSchemas"> <value>classpath:/WEB-INF/schemas/bookid.xsd</value> <value>classpath:/org/apache/cxf/systest/jaxrs/resources/book.xsd</value> </util:list> </beans> |
3. Using SchemaValidation annotation
...
Code Block | ||||
---|---|---|---|---|
| ||||
<bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider">
<property name="catalogLocation" value="classpath:/schemas/mycatalog.xml"/>
</bean>
|
where mycatalog.xml may look like this:
...
Fast Infoset
You can enable the FastInfoset by explicitly registering CXF FastInfoset interceptors with a JAXRS endpoint and configuring JAXBElementProvider to support an "application/fastinfoset" media type :
for example :
Code Block | ||||
---|---|---|---|---|
| ||||
<jaxrs:server id="restservice3" address="/rest3"> <jaxrs:serviceBeans> <ref bean="bookstore"/> </jaxrs:serviceBeans> <jaxrs:providers> <ref bean="jaxbProvider"/> </jaxrs:providers> <jaxrs:outInterceptors> <ref bean="fastInfosetOutInterceptor"/> </jaxrs:outInterceptors> <jaxrs:inInterceptors> <ref bean="fastInfosetInInterceptor"/> </jaxrs:inInterceptors> <jaxrs:properties> <entry key="org.apache.cxf.endpoint.private" value="true"/> </jaxrs:properties> </jaxrs:server> <util:list id="fastinfosetType"> <value>application/fastinfoset</value> </util:list> <bean id="jaxbProvider" class="org.apache.cxf.jaxrs.provider.JAXBElementProvider"> <property name="produceMediaTypes" ref="fastinfosetType"/> <property name="consumeMediaTypes" ref="fastinfosetType"/> </bean> <bean id="fastInfosetOutInterceptor" class="org.apache.cxf.interceptor.FIStaxOutInterceptor"/> <bean id="fastInfosetInInterceptor" class="org.apache.cxf.interceptor.FIStaxInInterceptor"/> |
...