I just finished implementing some “multipart/*” and multipart/form-data support within RESTEasy. It will be out with the next release. Highlights?
A JAX-RS compatable API
Our current support for multipart is through the MimeMultipart classes within the Java Mail library. They are limited because they do not use the MessageBodyReader/Writers available in JAX-RS. I have written two parallel APIs that provide multipart support in a JAX-RS way:
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); } } }
There’s a similar API for outputing multipart.
Multipart with vanilla Lists and Maps
Another feature I’ve added is the ability to use regular instances of java.util.List(any multipart format) or Map (form-data only) to represent multipart data. Its only usable when your parts are uniform though.
Here’s an example of using it:
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) { ... } }
That’s using input. The problem with output is that RESTEasy has no idea what mime type you want to marshal or List or Map into. So, the @PartType annotation is used. Here’s an example of outputing multipart/form-data with a map:
@Path("/multipart") public class MyService { @GET @Produces("multipart/form-data") @PartType("application/xml") public Map<String, Customer> get() { ... }
Multipart-Formdata with POJOs
Finally, I added the ability to map POJO form classes to multipart/form-data. You re-use @FormParam on fields and/or properties of a user-provided POJO. Here’s an 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... } }
Oct 31, 2008 @ 20:37:48
Since the registration at JBoss.com is onerous, and required for access to Jira for submitting bugs… I figured I’d stop by here and let you know about a couple I encountered with RESTeasy while I was evaluating the resteasy-jaxrs-1.0-beta-8 download.
I’m deploying into a Tomcat 6.0.14 container.
1. When I set the following in the web.xml deployment descriptor:
resteasy.scan
true
Upon starting the server I get the following error:
Oct 31, 2008 4:09:31 PM org.apache.catalina.core.StandardContext listenerStart
SEVERE: Exception sending context initialized event to listener instance of class org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
java.lang.RuntimeException: java.lang.ClassNotFoundException: org.jboss.resteasy.test.smoke.SimpleResource
Looks like a smoke test class is somehow being looked for – and not found by the bootstrapper.
2. The download appears to be missing a required .jar file. When starting up – RESTeasy looks for the javassist.jar; I went ahead and downloaded a copy and dropped into the WEB-INF/lib directory of my project and everything worked fine.
In general though – things are looking pretty good – I’m liking the JAX-RS specification (annotations) in general, and RESTeasy in particular. Looking at Jersey and CXF as well; both seem to be pushing EJB/JEE and/or Spring heavily. I’m still one of those people who likes to use the API separately – get comfy with it – and then see how I want to work persistence, and dependency injection into the mix.
Sep 09, 2009 @ 17:50:29
A blog isn’t a place to post user questsions. Please see the developer list at jboss.org/resteasy.
Oct 31, 2008 @ 20:58:49
Also it seems to ignore at least one of the context-parameters..
resteasy.scan.providers
false
And, yet I still see the following emitted to the console when starting Tomcat.
Oct 31, 2008 5:02:05 PM org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.14
47 [main] INFO org.jboss.resteasy.plugins.providers – Added DataSourceProvider
47 [main] INFO org.jboss.resteasy.plugins.providers – Added DefaultTextPlain
78 [main] INFO org.jboss.resteasy.plugins.providers – Added JAXBXmlRootElementProvider
78 [main] INFO org.jboss.resteasy.plugins.providers – Added JAXBElementProvider
94 [main] INFO org.jboss.resteasy.plugins.providers – Added JAXBXmlTypeProvider
94 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.IIOImageProvider
94 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.jaxb.json.JsonJAXBElementProvider
109 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.jaxb.json.JsonXmlTypeProvider
109 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.jaxb.json.JsonXmlRootElementProvider
172 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.MimeMultipartProvider
172 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.YamlProvider
187 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.jaxb.fastinfoset.FastinfoSetXmlRootElementProvider
187 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.jaxb.fastinfoset.FastinfoSetJAXBElementProvider
187 [main] INFO org.jboss.resteasy.plugins.providers – Adding org.jboss.resteasy.plugins.providers.jaxb.fastinfoset.FastinfoSetXmlTypeProvider
Nov 03, 2008 @ 13:11:11
Porter, those are the built-in providers. You can turn them off with by setting this context param to false:
resteasy.use.builtin.providers
As for your 1st problem, yup there is no javassist jar provided with distribution. So much for trusting maven to pull in transitive dependencies. I can’t believe I missed that, not sure what changed because it used to pull in the dependencies.
Sep 09, 2009 @ 15:39:53
Hi there.
I’ve been working away with the RestEasy Library for a while now and have run into a problem with unmarshalling vanilla java.util.list. I used the RestEasy documentation as my source of examples, but have apparently misunderstood something. I have dropped my code below in hopes that you might be able to inform me as to what I have done wrong.
this is the driving code:
public List getSearchResults( String searchType, String uriParam )
{
List concepts = null;
try
{
ListInterface proxy =
ProxyFactory.create( ListInterface.class, uriRoot + “/exactmatch” + “/skin”);
ClientResponse<List> response = (ClientResponse) proxy.getList();
concepts =
(List)
response.getEntity( new GenericType<List>(){} );
}
catch( Exception e )
{
e.printStackTrace();
}
return concepts;
}
this is the interface
public interface ListInterface
{
@GET
@Produces(“multipart/*”)
@PartType(“application/xml”)
public Response getList();
}
What I feel are the two most significant elements of the output
110 [main] INFO org.jboss.resteasy.plugins.providers – Adding built in provider org.jboss.resteasy.plugins.providers.multipart.ListMultipartReader
org.jboss.resteasy.client.ClientResponseFailure: Unable to find a MessageBodyReader of content-type text/html;charset=utf-8 and type java.util.List
Feb 26, 2010 @ 07:54:50
org.jboss.resteasy.client.ClientResponseFailure: Unable to find a MessageBodyReader of content-type text/html;charset=utf-8 and type java.util.List
You have to create a message body reader, like in this example (this solved that problem):
ClientResponse response = request.get(String.class);
ResteasyProviderFactory.getInstance().addBuiltInMessageBodyReader(new DefaultTextPlain());
if (response.getStatus() == 200) {// OK!
String str = response.getEntity();
System.out.println(str);
}
Mar 12, 2010 @ 17:34:25
Hi,
I also have a strange problem with MessageBodyReader.
I want to test a client which is defined like:
@POST
@Path(“{membershipExternalId}/query”)
@Consumes(MediaType.APPLICATION_XML)
@Produces(“multipart/mixed”)
public MultipartOutput query(@PathParam(“membershipExternalId”) final String membershipExternalId,
//others param ){
//code}
I receive this strange error:
org.jboss.resteasy.client.ClientResponseFailure: Unable to find a MessageBodyReader of content-type multipart/mixed;boundary=”8265ccfc-6ff2-49b7-ba57-6a8149020e4d” and type class org.jboss.resteasy.plugins.providers.multipart.MultipartOutput
when i try to take the response in a MultipartOutput variable.
Any help will be higly appreciate.
Thanks.