JBoss.orgCommunity Documentation
Resteasy can automatically marshal and unmarshal a few different message bodies.
Table 17.1.
Media Types | Java Type |
---|---|
application/*+xml, text/*+xml, application/*+json, application/*+fastinfoset, application/atom+* | JaxB annotated classes |
*/* | java.lang.String |
*/* | java.io.InputStream |
text/plain | primtives, java.lang.String, or any type that has a String constructor, or static valueOf(String) method for input, toString() for output |
*/* | javax.activation.DataSource |
*/* | java.io.File |
*/* | byte[] |
application/x-www-form-urlencoded | javax.ws.rs.core.MultivaluedMap |
The JAX-RS specification allows you to plug in your own request/response body reader and writers. To do this, you annotate a class with @Provider and specify the @Produces types for a writer and @Consumes types for a reader. You must also implement a MessageBodyReader/Writer interface respectively. Here is an example.
The Resteasy ServletContextLoader will automatically scan your WEB-INF/lib and classes directories for classes annotated with @Provider or you can manually configure them in web.xml. See Installation/Configuration
javax.ws.rs.ext.MessageBodyWorks is a simple injectable interface that allows you to look up MessageBodyReaders and Writers. It is very useful, for instance, for implementing multipart providers. Content types that embed other random content types.
/** * An injectable interface providing lookup of {@link MessageBodyReader} and * {@link MessageBodyWriter} instances. * * @see javax.ws.rs.core.Context * @see MessageBodyReader * @see MessageBodyWriter */ public interface MessageBodyWorkers { /** * Get a message body reader that matches a set of criteria. * * @param mediaType the media type of the data that will be read, this will * be compared to the values of {@link javax.ws.rs.Consumes} for * each candidate reader and only matching readers will be queried. * @param type the class of object to be produced. * @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. */ public abstract <T> MessageBodyReader<T> getMessageBodyReader(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); /** * Get a message body writer that matches a set of criteria. * * @param mediaType the media type of the data that will be written, this will * be compared to the values of {@link javax.ws.rs.Produces} for * each candidate writer and only matching writers will be queried. * @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. */ public abstract <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type, Type genericType, Annotation annotations[], MediaType mediaType); }
MessageBodyWorkers are injectable into MessageBodyReader or Writers:
@Provider @Consumes("multipart/fixed") public class MultipartProvider implements MessageBodyReader { private @Context MessageBodyWorkers workers; ... }
As required by the specification, RESTEasy JAX-RS includes support for (un)marshalling JAXB annotated classes. RESTEasy provides multiple JAXB Providers to address some subtle differences between classes generated by XJC and classes which are simply annotated with @XmlRootElement, or working with JAXBElement classes directly.
For the most part, developers using the JAX-RS API, the selection of which provider is invoked will be completely transparent. For developers wishing to access the providers directly (which most folks won't need to do), this document describes which provider is best suited for different configurations.
A JAXB Provider is selected by RESTEasy when a parameter or return type is an object that is annotated with JAXB annotations (such as @XmlRootEntity or @XmlType) or if the type is a JAXBElement. Additionally, the resource class or resource method will be annotated with either a @Consumes or @Produces annotation and contain one or more of the following values:
RESTEasy will select a different provider based on the return type or parameter type used in the resource. This section decribes how the selection process works.
@XmlRootEntity When a class is annotated with a @XmlRootElement annotation, RESTEasy will select the JAXBXmlRootElementProvider. This provider handles basic marhaling and 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, of the target type is called "Contact", then the ObjectFactory class will have a method:
public JAXBElement createContact(Contact value) {..
JAXBElement<?> If your resource works with the JAXBElement class directly, the RESTEasy runtime will select the JAXBElementProvider. This provider examines the ParameterizedType value of the JAXBElement in order to select the appropriate JAXBContext.
You should not use this feature unless you know what you're doing.
Based on the class you are marshalling/unmarshalling, RESTEasy will, by default create and cache JAXBContext instances per class type. If you do not want RESTEasy to create JAXBContexts, you can plug-in your own by implementing an instance of javax.ws.rs.ext.ContextResolver
public interface ContextResolver<T> { T getContext(Class<?> type); } @Provider @Produces("application/xml") public class MyJAXBContextResolver implements ContextResolver<JAXBContext> { JAXBContext getContext(Class<?> type) { if (type.equals(WhateverClassIsOverridedFor.class)) return JAXBContext.newInstance()...; } }
You must provide a @Produces annotation to specify the media type the context is meant for. You must also make sure to implement ContextResolver<JAXBContext>. This helps the runtime match to the correct context resolver. You must also annotate the ContextResolver class with @Provider.
There are multiple ways to make this ContextResolver available.
RESTEasy allows you to marshall JAXB annotated POJOs to and from JSON. This provider wraps the Jettison JSON library to accomplish this. You can obtain more information about Jettison and how it works from:
http://jettison.codehaus.org/
Jettison has two mapping formats. One is BadgerFish the other is a Jettison Mapped Convention format. The Mapped Convention is the default mapping.
For example, consider this JAXB class:
@XmlRootElement(name = "book") public class Book { private String author; private String ISBN; private String title; public Book() { } public Book(String author, String ISBN, String title) { this.author = author; this.ISBN = ISBN; this.title = title; } @XmlElement public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } @XmlElement public String getISBN() { return ISBN; } public void setISBN(String ISBN) { this.ISBN = ISBN; } @XmlAttribute public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
This is how the JAXB Book class would be marshalled to JSON using the BadgerFish Convention
{"book": { "@title":"EJB 3.0", "author":{"$":"Bill Burke"}, "ISBN":{"$":"596529260"} } }
Notice that element values have a map associated with them and to get to the value of the element, you must access the "$" variable. Here's an example of accessing the book in Javascript:
var data = eval("(" + xhr.responseText + ")"); document.getElementById("zone").innerHTML = data.book.@title; document.getElementById("zone").innerHTML += data.book.author.$;
To use the BadgerFish Convention you must use the @org.jboss.resteasy.annotations.providers.jaxb.json.BadgerFish annotation on the JAXB class you are marshalling/unmarshalling, or, on the JAX-RS resource method or parameter:
@BadgerFish @XmlRootElement(name = "book") public class Book {...}
If you are returning a book on the JAX-RS method and you don't want to (or can't) pollute your JAXB classes with RESTEasy annotations, add the annotation to the JAX-RS method:
@BadgerFish @GET public Book getBook(...) {...}
If a Book is your input then you put it on the parameter:
@POST public void newBook(@BadgerFish Book book) {...}
The default Jettison Mapped Convention would return JSON that looked like this:
{ "book" : { "@title":"EJB 3.0", "author":"Bill Burke", "ISBN":596529260 } }
Notice that the @XmlAttribute "title" is prefixed with the '@' character. Unlike BadgerFish, the '$' does not represent the value of element text. This format is a bit simpler than the BadgerFish convention which is why it was chose as a default. Here's an example of accessing this in Javascript:
var data = eval("(" + xhr.responseText + ")"); document.getElementById("zone").innerHTML = data.book.@title; document.getElementById("zone").innerHTML += data.book.author;
The Mapped Convention allows you to fine tune the JAXB mapping using the @org.jboss.resteasy.annotations.providers.jaxb.json.Mapped annotation. You can provide an XML Namespace to JSON namespace mapping. For example, if you defined your JAXB namespace within your package-info.java class like this:
@javax.xml.bind.annotation.XmlSchema(namespace="http://jboss.org/books") package org.jboss.resteasy.test.books;
You would have to define a JSON to XML namespace mapping or you would receive an exception of something like this:
java.lang.IllegalStateException: Invalid JSON namespace: http://jboss.org/books at org.codehaus.jettison.mapped.MappedNamespaceConvention.getJSONNamespace(MappedNamespaceConvention.java:151) at org.codehaus.jettison.mapped.MappedNamespaceConvention.createKey(MappedNamespaceConvention.java:158) at org.codehaus.jettison.mapped.MappedXMLStreamWriter.writeStartElement(MappedXMLStreamWriter.java:241)
To fix this problem you need another annotation, @Mapped. You use the @Mapped annotation on your JAXB classes, on your JAX-RS resource method, or on the parameter you are unmarshalling
import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped; import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap; ... @GET @Produces("application/json") @Mapped(namespaceMap = { @XmlNsMap(namespace = "http://jboss.org/books", jsonName = "books") }) public Book get() {...}
Besides mapping XML to JSON namespaces, you can also force @XmlAttribute's to be marshaled as XMLElements.
@Mapped(attributeAsElements={"title"}) @XmlRootElement(name = "book") public class Book {...}
If you are returning a book on the JAX-RS method and you don't want to (or can't) pollute your JAXB classes with RESTEasy annotations, add the annotation to the JAX-RS method:
@Mapped(attributeAsElements={"title"}) @GET public Book getBook(...) {...}
If a Book is your input then you put it on the parameter:
@POST public void newBook(@Mapped(attributeAsElements={"title"}) Book book) {...}
RESTEasy supports the FastinfoSet mime type with JAXB annotated classes. Fast infoset documents are faster to serialize and parse, and smaller in size, than logically equivalent XML documents. Thus, fast infoset documents may be used whenever the size and processing time of XML documents is an issue. It is configured the same way the XML JAXB provider is so really no other documentation is needed here.
In combination with the @org.jboss.resteasy.annotations.providers.jaxb.Wrapped annotation on your methods and parameters, you can marshal arrays, java.util.Set's, and java.util.List's of JAXB objects to and from XML, JSON, Fastinfoset (or any other new JAXB mapper Restasy comes up with).
@XmlRootElement(name = "customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement private String name; public Customer() { } public Customer(String name) { this.name = name; } public String getName() { return name; } } @Path("/") public class MyResource { @PUT @Path("array") @Consumes("application/xml") public void putCustomers(@Wrapped Customer[] customers) { Assert.assertEquals("bill", customers[0].getName()); Assert.assertEquals("monica", customers[1].getName()); } @GET @Path("set") @Produces("application/xml") @Wrapped 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(@Wrapped 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 in the http://jboss.org/resteasy namespace.
<resteasy:collection xmlns:resteasy="http://jboss.org/resteasy"> <customer><name>bill</name></customer> <customer><name>monica</name></customer> </resteasy:collection>
Since Beta 6, resteasy comes with built in support for YAML using the Jyaml library. To enable YAML support, you need to drop in the jyaml-1.3.jar in RestEASY's classpath.
Jyaml jar file can either be downloaded from sourceforge: https://sourceforge.net/project/showfiles.php?group_id=153924
Or if you use maven, the jyaml jar is available through the main repositories and included using this dependency:
<dependency> <groupId>org.jyaml</groupId> <artifactId>jyaml</artifactId> <version>1.3</version> </dependency>
When starting resteasy look out in the logs for a line stating that the YamlProvider has been added - this indicates that resteasy has found the Jyaml jar:
2877 Main INFO org.jboss.resteasy.plugins.providers.RegisterBuiltin - Adding YamlProvider
The Yaml provider recognises three mime types:
This is an example of how to use Yaml in a resource method.
import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @Path("/yaml") public class YamlResource { @GET @Produces("text/x-yaml") public MyObject getMyObject() { return createMyObject(); } ... }
Resteasy has rich support for the "multipart/*" and "multipart/form-data" mime types. The multipart mime format is used to pass lists of content bodies. Multiple content bodies are embedded in one message. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it.
RESTEasy provides a custom API for reading and writing multipart types as well as marshalling arbitrary List (for any multipart type) and Map (multipart/form-data only) objects
When writing a JAX-RS service, RESTEasy provides an interface that allows you to read in any multipart mime type. org.jboss.resteasy.plugins.providers.multipart.MultipartInput
package org.jboss.resteasy.plugins.providers.multipart; public interface MultipartInput { List<InputPart> getParts(); String getPreamble(); } 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(); }
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); } } }
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<<() {}); } } }
Use of GenericType is required because it is really the only way to obtain generic type information at runtime.
If your body parts are uniform, you do not have to manually unmarshall each and every part. You can just provide a java.util.List as your input parameter. It must have the type it is unmarshalling with the generic parameter of the List type declaration. Here's an example again of unmarshalling a list of customers.
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/mixed") public void put(List<Customer> customers) { ... } }
When writing a JAX-RS service, RESTEasy provides an interface that allows you to read in multipart/form-data mime type. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it. The interface used for form-data input is org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput
public interface MultipartFormDataInput extends MultipartInput { Map<String, InputPart> getFormData(); <T> T getFormDataPart(String key, Class<T> rawType, Type genericType) throws IOException; <T> T getFormDataPart(String key, GenericType<T> type) throws IOException; }
It works in much the same way as MultipartInput described earlier in this chapter.
With form-data, if your body parts are uniform, you do not have to manually unmarshall each and every part. You can just provide a java.util.Map as your input parameter. It must have the type it is unmarshalling with the generic parameter of the List type declaration. Here's an example of of unmarshalling a Map of Customer objects which are JAXB annotated classes.
@Path("/multipart") public class MyService { @PUT @Consumes("multipart/form-data") public void put(Map<String, Customer> customers) { ... } }
RESTEasy provides a simple API to output multipart data.
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartOutput { public OutputPart addPart(Object entity, MediaType mediaType) public OutputPart addPart(Object entity, GenericType type, MediaType mediaType) public OutputPart addPart(Object entity, Class type, Type genericType, MediaType mediaType) public List<OutputPart> getParts() public String getBoundary() public void setBoundary(String boundary) } public class OutputPart { public MultivaluedMap<String, Object> getHeaders() public Object getEntity() public Class getType() public Type getGenericType() public MediaType getMediaType() }
When you want to output multipart data it is as simple as creating a MultipartOutput object and calling addPart() methods. Resteasy will automatically find a MessageBodyWriter to marshall your entity objects. Like MultipartInput, sometimes you may have marshalling which is sensitive to generic type metadata. In that case, use GenericType. Most of the time though passing in an Object and its MediaType is enough. In the example below, we are sending back a "multipart/mixed" format back to the calling client. The parts are Customer objects which are JAXB annotated and will be marshalling into "application/xml".
@Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") public MultipartOutput get() { MultipartOutput output = new MultipartOutput(); output.addPart(new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart(new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; }
If your body parts are uniform, you do not have to manually marshall each and every part or even use a MultipartOutput object.. You can just provide a java.util.List. It must have the generic type it is marshalling with the generic parameter of the List type declaration. You must also annotate the method with the @PartType annotation to specify what media type each part is. Here's an example of sending back a list of customers back to a client. The customers are JAXB objects
@Path("/multipart") public class MyService { @GET @Produces("multipart/mixed") @PartType("application/xml") public List<Customer> get() { ... } }
RESTEasy provides a simple API to output multipart/form-data.
package org.jboss.resteasy.plugins.providers.multipart; public class MultipartFormDataOutput extends MultipartOutput { public OutputPart addFormData(String key, Object entity, MediaType mediaType) public OutputPart addFormData(String key, Object entity, GenericType type, MediaType mediaType) public OutputPart addFormData(String key, Object entity, Class type, Type genericType, MediaType mediaType) public Map<String, OutputPart> getFormData() }
When you want to output multipart/form-data it is as simple as creating a MultipartFormDataOutput object and calling addFormData() methods. Resteasy will automatically find a MessageBodyWriter to marshall your entity objects. Like MultipartInput, sometimes you may have marshalling which is sensitive to generic type metadata. In that case, use GenericType. Most of the time though passing in an Object and its MediaType is enough. In the example below, we are sending back a "multipart/form-data" format back to the calling client. The parts are Customer objects which are JAXB annotated and will be marshalling into "application/xml".
@Path("/form") public class MyService { @GET @Produces("multipart/form-data") public MultipartFormDataOutput get() { MultipartFormDataOutput output = new MultipartFormDataOutput(); output.addPart("bill", new Customer("bill"), MediaType.APPLICATION_XML_TYPE); output.addPart("monica", new Customer("monica"), MediaType.APPLICATION_XML_TYPE); return output; }
If your body parts are uniform, you do not have to manually marshall each and every part or even use a MultipartFormDataOutput object.. You can just provide a java.util.Map. It must have the generic type it is marshalling with the generic parameter of the Map type declaration. You must also annotate the method with the @PartType annotation to specify what media type each part is. Here's an example of sending back a list of customers back to a client. The customers are JAXB objects
@Path("/multipart") public class MyService { @GET @Produces("multipart/form-data") @PartType("application/xml") public Map<String, Customer> get() { ... } }
If you have a exact knowledge of your multipart/form-data packets, you can map them to and from a POJO class to and from multipart/form-data using the @org.jboss.resteasy.annotations.providers.multipart.MultipartForm annotation and the JAX-RS @FormParam annotation. You simple define a POJO with at least a default constructor and annotate its fields and/or properties with @FormParams. These @FormParams must also be annotated with @org.jboss.resteasy.annotations.providers.multipart.PartType if you are doing output. For example:
public class CustomerProblemForm { @FormData("customer") @PartType("application/xml") private Customer customer; @FormData("problem") @PartType("text/plain") private String problem; public Customer getCustomer() { return customer; } public void setCustomer(Customer cust) { this.customer = cust; } public String getProblem() { return problem; } public void setProblem(String problem) { this.problem = problem; } }
After defining your POJO class you can then use it to represent multipart/form-data. Here's an example of sending a CustomerProblemForm using the RESTEasy client framework
@Path("portal") public interface CustomerPortal { @Path("issues/{id}") @Consumes("multipart/form-data") @PUT public void putProblem(@MultipartForm CustomerProblemForm, @PathParam("id") int id); } { CustomerPortal portal = ProxyFactory.create(CustomerPortal.class, "http://example.com"); CustomerProblemForm form = new CustomerProblemForm(); form.setCustomer(...); form.setProblem(...); portal.putProblem(form, 333); }
You see that the @MultipartForm annotation was used to tell RESTEasy that the object has @FormParam and that it should be marshalled from that. You can also use the same object to receive multipart data. Here is an example of the server side counterpart of our customer portal.
@Path("portal") public class CustomerPortalServer { @Path("issues/{id}) @Consumes("multipart/form-data") @PUT public void putIssue(@MultipartForm CustoemrProblemForm, @PathParm("id") int id) { ... write to database... } }
From W3.org (http://tools.ietf.org/html/rfc4287):
"Atom is an XML-based document format that describes lists of related information known as "feeds". Feeds are composed of a number of items, known as "entries", each with an extensible set of attached metadata. For example, each entry has a title. The primary use case that Atom addresses is the syndication of Web content such as weblogs and news headlines to Web sites as well as directly to user agents."
Atom is the next-gen RSS feed. Although it is used primarily for the syndication of blogs and news, many are starting to use this format as the envelope for Web Services, for example, distributed notifications, job queues, or simply a nice format for sending or receiving data in bulk from a service.
RESTEasy has defined a simple object model in Java to represent Atom and uses JAXB to marshal and unmarshal it. The main classes are in the org.jboss.resteasy.plugins.providers.atom package and are Feed, Entry, Content, and Link. If you look at the source, you'd see that these are annotated with JAXB annotations. The distribution contains the javadocs for this project and are a must to learn the model. Here is a simple example of sending an atom feed using the Resteasy API.
import org.jboss.resteasy.plugins.providers.atom.Content; import org.jboss.resteasy.plugins.providers.atom.Feed; import org.jboss.resteasy.plugins.providers.atom.Link; import org.jboss.resteasy.plugins.providers.atom.Person; @Path("atom") public class MyAtomService { @GET @Path("feed") @Produces("application/atom+xml") public Feed getFeed() { Feed feed = new Feed(); feed.setId(new URI("http://example.com/42")); feed.setTitle("My Feed"); feed.setUpdated(new Date()); Link link = new Link(); link.setHref(new URI("http://localhost")); link.setRel("edit"); feed.getLinks().add(link); feed.getAuthors().add(new Person("Bill Burke")); Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setType(MediaType.TEXT_HTML_TYPE); content.setText("Nothing much"); feed.getEntries().add(content); return feed; } }
Because Resteasy's atom provider is JAXB based, you are not limited to sending atom objects using XML. You can automatically re-use all the other JAXB providers that Resteasy has like JSON and fastinfoset. All you have to do is have "atom+" in front of the main subtype. i.e. @Produces("application/atom+json") or @Consumes("application/atom+fastinfoset")
The org.jboss.resteasy.plugins.providers.atom.Content class allows you to unmarshal and marshal JAXB annotated objects that are the body of the content. Here's an example of sending an Entry with a Customer object attached as the body of the entry's content.
@XmlRootElement(namespace = "http://jboss.org/Customer") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement private String name; public Customer() { } public Customer(String name) { this.name = name; } public String getName() { return name; } } @Path("atom") public static class AtomServer { @GET @Path("entry") @Produces("application/atom+xml") public Entry getEntry() { Entry entry = new Entry(); entry.setTitle("Hello World"); Content content = new Content(); content.setJAXBObject(new Customer("bill")); entry.setContent(content); return entry; } }
The Content.setJAXBObject() method is used to tell the content object you are sending back a Java JAXB object and want it marshalled appropriately. If you are using a different base format other than XML, i.e. "application/atom+json", this attached JAXB object will be marshalled into that same format.
If you have an atom document as your input, you can also extract JAXB objects from Content using the Content.getJAXBObject(Class clazz) method. Here is an example of an input atom document and extracting a Customer object from the content.
@Path("atom") public static class AtomServer { @PUT @Path("entry") @Produces("application/atom+xml") public void putCustomer(Entry entry) { Content content = entry.getContent(); Customer cust = content.getJAXBObject(Customer.class); } }
Resteasy provides support for Apache Abdera, an implementation of the Atom protocol and data format. http://incubator.apache.org/abdera/
Abdera is a full-fledged Atom server. Resteasy only supports integration with JAX-RS for Atom data format marshalling and unmarshalling to and from the Feed and Entry interface types in Abdera. Here's a simple example:
The Abdera provider is not included with the Resteasy distribution. To include the Abdera provider in your WAR poms, include the following. Please change the version to be the version of resteasy you are working with. Also, Resteasy may be coded to pick up an older version of Abdera than what you want. You're on your own with fixing this one, sorry.
<repository> <id>jboss</id> <url>http://repository.jboss.org/maven2</url> </repository> ... <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>abdera-atom-provider</artifactId> <version>...version...</version> </dependency>
import org.apache.abdera.Abdera; import org.apache.abdera.factory.Factory; import org.apache.abdera.model.Entry; import org.apache.abdera.model.Feed; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.jboss.resteasy.plugins.providers.atom.AbderaEntryProvider; import org.jboss.resteasy.plugins.providers.atom.AbderaFeedProvider; import org.jboss.resteasy.test.BaseResourceTest; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; import javax.xml.bind.JAXBContext; import java.io.StringReader; import java.io.StringWriter; import java.util.Date; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class AbderaTest extends BaseResourceTest { @Path("atom") public static class MyResource { private static final Abdera abdera = new Abdera(); @GET @Path("feed") @Produces(MediaType.APPLICATION_ATOM_XML) public Feed getFeed(@Context UriInfo uri) throws Exception { Factory factory = abdera.getFactory(); Assert.assertNotNull(factory); Feed feed = abdera.getFactory().newFeed(); feed.setId("tag:example.org,2007:/foo"); feed.setTitle("Test Feed"); feed.setSubtitle("Feed subtitle"); feed.setUpdated(new Date()); feed.addAuthor("James Snell"); feed.addLink("http://example.com"); Entry entry = feed.addEntry(); entry.setId("tag:example.org,2007:/foo/entries/1"); entry.setTitle("Entry title"); entry.setUpdated(new Date()); entry.setPublished(new Date()); entry.addLink(uri.getRequestUri().toString()); Customer cust = new Customer("bill"); JAXBContext ctx = JAXBContext.newInstance(Customer.class); StringWriter writer = new StringWriter(); ctx.createMarshaller().marshal(cust, writer); entry.setContent(writer.toString(), "application/xml"); return feed; } @PUT @Path("feed") @Consumes(MediaType.APPLICATION_ATOM_XML) public void putFeed(Feed feed) throws Exception { String content = feed.getEntries().get(0).getContent(); JAXBContext ctx = JAXBContext.newInstance(Customer.class); Customer cust = (Customer) ctx.createUnmarshaller().unmarshal(new StringReader(content)); Assert.assertEquals("bill", cust.getName()); } @GET @Path("entry") @Produces(MediaType.APPLICATION_ATOM_XML) public Entry getEntry(@Context UriInfo uri) throws Exception { Entry entry = abdera.getFactory().newEntry(); entry.setId("tag:example.org,2007:/foo/entries/1"); entry.setTitle("Entry title"); entry.setUpdated(new Date()); entry.setPublished(new Date()); entry.addLink(uri.getRequestUri().toString()); Customer cust = new Customer("bill"); JAXBContext ctx = JAXBContext.newInstance(Customer.class); StringWriter writer = new StringWriter(); ctx.createMarshaller().marshal(cust, writer); entry.setContent(writer.toString(), "application/xml"); return entry; } @PUT @Path("entry") @Consumes(MediaType.APPLICATION_ATOM_XML) public void putFeed(Entry entry) throws Exception { String content = entry.getContent(); JAXBContext ctx = JAXBContext.newInstance(Customer.class); Customer cust = (Customer) ctx.createUnmarshaller().unmarshal(new StringReader(content)); Assert.assertEquals("bill", cust.getName()); } } @Before public void setUp() throws Exception { dispatcher.getProviderFactory().registerProvider(AbderaFeedProvider.class); dispatcher.getProviderFactory().registerProvider(AbderaEntryProvider.class); dispatcher.getRegistry().addPerRequestResource(MyResource.class); } @Test public void testAbderaFeed() throws Exception { HttpClient client = new HttpClient(); GetMethod method = new GetMethod("http://localhost:8081/atom/feed"); int status = client.executeMethod(method); Assert.assertEquals(200, status); String str = method.getResponseBodyAsString(); PutMethod put = new PutMethod("http://localhost:8081/atom/feed"); put.setRequestEntity(new StringRequestEntity(str, MediaType.APPLICATION_ATOM_XML, null)); status = client.executeMethod(put); Assert.assertEquals(200, status); } @Test public void testAbderaEntry() throws Exception { HttpClient client = new HttpClient(); GetMethod method = new GetMethod("http://localhost:8081/atom/entry"); int status = client.executeMethod(method); Assert.assertEquals(200, status); String str = method.getResponseBodyAsString(); PutMethod put = new PutMethod("http://localhost:8081/atom/entry"); put.setRequestEntity(new StringRequestEntity(str, MediaType.APPLICATION_ATOM_XML, null)); status = client.executeMethod(put); Assert.assertEquals(200, status); } }