package org.mule.transport.jersey;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.PathParam;
@Path("/helloworld")
public class HelloWorldResource {
@POST
@Produces("text/plain")
@Path("/{name}")
public String sayHelloWithUri(@PathParam("name") String name) {
return "Hello " + name;
}
}
Jersey Module Reference - Mule 3
Jersey is a framework built over JAX-RS (JSR-311) implementation. JAX-RS is a specification that provides a series of annotations and classes which make it possible to build RESTful services. The Mule Jersey transport makes it possible to deploy these annotated classes inside Mule.
Our recommended approach to REST APIs is using RAML and exposing APIs through the API Manager. Jersey might still in some cases be the path of minimum resistance for when the API you want to expose was built in Java and lacks a RAML file that describes how to interface to it.
In addition to the annotation capabilities, Jersey contains many useful features:
-
The ability to integrate with XML data-binding frameworks such as JAXB.
-
The ability to produce/consume JSON easily.
-
The ability to integrate with the JSP presentation tier.
-
Integration with Abdera for Atom support.
Mule 3.6 makes use of Jersey v2.11, while older versions of Mule used Jersey 1.6. If you want to update an existent API built using Jersey 1.x, this might require you to perform some code changes, since the latest JAX-RS and Jersey versions aren’t backwards compatible with the 1.x versions. |
Currently implicit views are not supported. |
Writing a Service
Writing JAX-RS services is an expansive topic and is not covered in this guide. However, the Jersey website has an excellent set of samples, and the JAX-RS specification is helpful as well.
This guide takes a look at a simple hello world service. This example requires the installation of Apache Xalan JARs.
The first step to create a JAX-RS service is to create a class which represents your HTTP resource. In our case we create a "HelloWorldResource" class. Methods in this class are called in response to GET, POST, DELETE, and PUT invocations on specific URLs.
The @Path annotation allows you to bind a class/resource to a specific URL. In the sample below we’re binding the HelloWorldResource class to the "/helloworld" URL.
Looking at the "sayHelloWithUri" method we see several annotations involved:
-
@POST specifies that this method is only called on @POST requests to the URL.
-
@Produces specifies that this method is producing a resource with a mime type of "text/plain".
-
@Path binds this method to the URL
/helloworld/{name}
. The{name}
is a URI template. Anything in this portion of the URL maps to a URI parameter named "name" (see below). -
@PathParam binds the first parameter of the method to the URI parameter in that path named "name".
Deploy the Web Service
Once you’ve written your service, you can create a jersey:resources
component which contains a set of Jersey resources. URL. Below is a very simple configuration which does this:
<http:listener-config name="HTTP_Listener_Configuration" host="localhost" port="${port}" doc:name="HTTP Listener Configuration"/>
<flow name="HelloWorld">
<http:listener config-ref="HTTP_Listener_Configuration" path="/*" doc:name="HTTP"/>
<jersey:resources>
<component class="org.mule.module.jersey.HelloWorldResource"/>
</jersey:resources>
</flow>
Consume a RESTful Web Service
After you run this configuration in Mule, use a URL similar to the following. You should see this response in your browser: 'Hello Me':
http://localhost:8080/jersey/helloworld/Me
Exception Mappers
It is possible to register exception mappers inside the resources
element. Exception mappers allow mapping generic exceptions that may be thrown in the component class to HTTP response codes, you can add as many of these as you want.
The following configuration maps a HelloWorldException
that may be thrown during the execution of HelloWorldResource
to HTTP error 503 (Service Unavailable):
<jersey:resources>
<component class="org.mule.module.jersey.HelloWorldResource"/>
<jersey:exception-mapper class="org.mule.module.jersey.exception.HelloWorldExceptionMapper" />
</jersey:resources>
HelloWorldExceptionMapper.java
public class HelloWorldExceptionMapper implements ExceptionMapper<HelloWorldException>
{
public Response toResponse(HelloWorldException exception)
{
int status = Response.Status.SERVICE_UNAVAILABLE.getStatusCode();
return Response.status(status).entity(exception.getMessage()).type("text/plain").build();
}
}
Context Resolvers
Context resolvers are injected into resource classes, and they provide context information to them, which can be useful in certain cases when you need specific metadata that is not available by default.
When you use JAXB for your XML/JSON serialization, JAXB provides some annotations in case you would need to change the output format. A simple example of such annotations is @XmlElement where you can provide the name of the field as a property on the annotation itself: @XmlElement(name="PersonName").
Some configuration however is not possible to achieve using annotations. For example by default when using JAXB for JSON serialization, the numbers (int, long …) are surrounded by double quotes, making them look like strings. This might be good for some projects, but other projects might want to remove those double quotes. This can be done by configuring a ContextResolver on the Jersey resource. Let’s take a quick example. If we have a class called Person which internally contains an age property, and we would want this Person object to be returned as a JSON object with the age without quotes, first create the custom context resolver.
CustomContextResolver.java
@Provider
public class CustomContextResolver implements ContextResolver<JAXBContext>
{
private JAXBContext context;
private Class[] types = {Person.class};
public JAXBContextResolver() throws Exception
{
this.context = new JSONJAXBContext(
JSONConfiguration.natural().build(), types);
}
public JAXBContext getContext(Class<?> objectType)
{
for (Class type : types)
{
if (type == objectType)
{
return context;
}
}
return null;
}
}
In the above CustomContextResolver, we are specifying that for class of type Person, we return a JAXBContext which is configured using JSONConfiguration class using the natural notation. Once we have our custom Jersey ContextResolver, we need to configure that in Mule.
<jersey:resources>
<component class="org.mule.module.jersey.HelloWorldResource"/>
<jersey:context-resolver class="org.mule.module.jersey.context.CustomContextResolver" />
</jersey:resources>
Without the custom context resolver, the output would look like the following:
{"name":"Alan","age":"26"}
With the custom context resolver, the output changes to the following:
{"name":"Alan","age":26}
ContextResolvers can also be used to configure other XML/JSON libraries such as Jackson. The following is a custom context resolver to configure Jackson to return numbers in quotes.
"CustomJacksonContextResolver"
@Provider
public class CustomJacksonContextResolver implements ContextResolver<ObjectMapper>
{
public ObjectMapper getContext(Class<?> type)
{
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(Feature.WRITE_NUMBERS_AS_STRINGS, true);
objectMapper.configure(Feature.QUOTE_NON_NUMERIC_NUMBERS, true);
return objectMapper;
}
}
For more information about context resolvers, check out the Jersey user guide.
Sending a Jersey Response to Other Flows
You can use interface bindings to invoke completely separate Mule flows from your Jersey resource.
XML Configuration
<http:listener-config name="HTTP_Listener_Configuration" host="localhost" port="8081" doc:name="HTTP Listener Configuration" />
<flow name="test">
<http:listener config-ref="HTTP_Listener_Configuration" path="/*" doc:name="HTTP" />
<jersey:resources>
<component class="org.example.JerseyHelloWorldComponent">
<binding interface="org.example.JerseyHelloWorldComponent.HelloWorldInterface">
<vm:outbound-endpoint path="bindingQueue" exchange-pattern="request-response" />
</binding>
</component>
</jersey:resources>
</flow>
<flow name="TransformationFlow">
<vm:inbound-endpoint path="bindingQueue" exchange-pattern="request-response" />
<set-payload value="Hello World!" />
</flow>
Java Class
@Path("/")
public class JerseyHelloWorldComponent {
private HelloWorldInterface helloWorldBinding;
@GET
@Path("/sayHello")
@Produces("text/plain")
public String sayHelloFromBinding() {
return helloWorldBinding.sayHello("s");
}
public void setHelloWorldBinding(HelloWorldInterface helloWorldBinding) {
this.helloWorldBinding = helloWorldBinding;
}
public HelloWorldInterface getHelloWorldBinding() {
return this.helloWorldBinding;
}
public static interface HelloWorldInterface {
public String sayHello(String s);
}
}
To test, browse to http://localhost:8081/sayHello
.
The result is: Hello World!
by virtue of the Set Payload from the XML Configuration:
<flow name="TransformationFlow">
<vm:inbound-endpoint path="bindingQueue" exchange-pattern="request-response" />
<set-payload value="Hello World!" />
</flow>
Adding Custom Properties
You can execute resources passing your own set of server properties. For example, the following configuration specifies its very own set of language mappings:
<http:listener-config name="HTTP_Listener_Configuration" host="localhost" port="${port}" doc:name="HTTP Listener Configuration"/>
<flow name="helloWorld">
<http:listener config-ref="HTTP_Listener_Configuration" path="/*" doc:name="HTTP"/>
<jersey:resources>
<component class="org.mule.module.jersey.HelloWorldResource"/>
<jersey:property key="jersey.config.server.languageMappings" value="english : en, french : fr" />
</jersey:resources>
</flow>
Extension Autodiscovery
Jersey owns a very extensible Java API that allows developers to modify almost every aspect of its inner working. Because Jersey provides so many extension points, these are exposed in Mule through auto discovery capabilities. Per Jersey’s own API, every class that you annotate with the @Provider annotation can be used as an extension point. A list of Java packages that contain this annotation and exist in the Mule namespace is shown, every discovered class automatically registers in the resource’s context.
Here’s an example of how to register your own JAXB body writers and readers for an hypothetical Person class:
<http:listener-config name="HTTP_Listener_Configuration" host="localhost" port="${port}" doc:name="HTTP Listener Configuration"/>
<flow name="helloWorldResource">
<http:listener config-ref="HTTP_Listener_Configuration" path="/*" doc:name="HTTP"/>
<jersey:resources>
<component class="org.mule.module.jersey.HelloWorldResource"/>
<jersey:package packageName="com.my.project.jersey.readers" />
<jersey:package packageName="com.my.project.jersey.writers" />
</jersey:resources>
</flow>
Here, the packages com.my.project.jersey.readers
and com.my.project.jersey.writers
are being scanned and, for example, the following providers would be discovered:
package com.my.project.jersey.writers;
@Produces("application/xml")
public class MyBeanMessageBodyWriter implements MessageBodyWriter<MyBean> {
@Override
public boolean isWriteable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == Person.class;
}
@Override
public long getSize(MyBean myBean, Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
// deprecated by JAX-RS 2.0 and ignored by Jersey runtime
return 0;
}
@Override
public void writeTo(Person person,
Class<?> type,
Type genericType,
Annotation[] annotations,
MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders,
OutputStream entityStream)
throws IOException, WebApplicationException {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Person.class);
jaxbContext.createMarshaller().marshal(person, entityStream);
} catch (JAXBException jaxbException) {
throw new ProcessingException(
"Error serializing a Person to the output stream", jaxbException);
}
}
}
package com.my.project.jersey.readers;
public static class MyBeanMessageBodyReade implements MessageBodyReader<MyBean> {
@Override
public boolean isReadable(Class<?> type, Type genericType,
Annotation[] annotations, MediaType mediaType) {
return type == Person.class;
}
@Override
public MyBean readFrom(Class<MyBean> type,
Type genericType,
Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, String> httpHeaders,
InputStream entityStream)
throws IOException, WebApplicationException {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(MyBean.class);
return (Person) jaxbContext.createUnmarshaller()
.unmarshal(entityStream);
return myBean;
} catch (JAXBException jaxbException) {
throw new ProcessingException("Error deserializing a Person.",
jaxbException);
}
}
}