In previous part of this article I've presented two ways of building REST API on Mule ESB: using Mule REST Module and handling HTTP properties manually.
This time I want to present achieving the same result using Mule Jersey Module with addition of
Component Bindings - feature of Mule, which enables us to leave Java and go back to Mule flow processing.
Presented example was tested against Mule ESB 3.4.0 EE.
3. Jersey REST
Flow overview:
Let's go back to our example. Staring with the simple Jersey service class:
Nothing fancy here. JAX-RS plain and simple. Interesting part is the FlowProcessing interface. It looks like it's wrapping whole business logic processing. By using Component Bindings we can make any Mule's outbound endpoint behave as an implementation of that interface. Hence, we can make use of VM endpoints and move the processing from Java back to Mule flows:
Flows with the "business logic" for POST and GET methods processing are described below:
<custom-transformer class="pl.poznachowski.jerseyrest.PopulateVariables" name="populateVariables" doc:name="Java" />
and reusing it in the flows (<transformer ref="populateVariables" />).
PopulateVariables Java transformer:
We have everything in place to make it work. One thing I don't like is that Jersey for requests with URL not met is returning 404 HTTP status (Not Found). I find 400 status (Bad Request) more appropriate in such case and I'd like to keep 404 reserved for situations where URL was met, but resource was not found. Satysfying that requirement is fairly simple. We need to add a custom exception mapper in Jersey definition:
<jersey:exception-mapper class="pl.poznachowski.jerseyrest.BadURIExceptionMapper" />
with implementation:
Note: It is possible to catch exceptions thrown in the binded outbound endpoints. To make that possible we just need to declare exception in binding interface method.
To test described solution I used the same set of tests as in previous post. However I was having problems testing successful scenarios using MUnit:
It seems that muleContext is not propagated correctly with MUnit and reflective proxy classes. Switching test class to use 'official' FunctionalTestCase instead of MUnit worked like a charm.
Full example at GitHub: JerseyREST
That were all the solutions for exposing REST Services I can think of.
In my work project, I'm using the REST Module and it's doing its job well. Thus, if you want build REST API and use all of the nifty Mule features I would suggest going with the Router Module.
If you prefer to be more Java-centric, make use of Mule for handling integration matter only or to use Mule flows to a smaller extent then Jersey approach will fit in.
For more information about Component Bindings I encourage you to read Mule's well-written blog post explaining the feature.
This time I want to present achieving the same result using Mule Jersey Module with addition of
Component Bindings - feature of Mule, which enables us to leave Java and go back to Mule flow processing.
Presented example was tested against Mule ESB 3.4.0 EE.
3. Jersey REST
Flow overview:
Unfortunately, not much to see in this graphical overview - devil is in the details :)
Jersey REST Component is the official recommendation for exposing REST Services on Mule ESB. Usually, this reference implementation of JAX-RS would be everything we need to achieve the goal. However, in terms of Mule, it ties us heavily to Java code, which is not what we are especially looking for. To overcome this we can use Component Bindings.
@Path(value = "/") public class JerseyRestService { private FlowProcessing flowProcessing; @GET @Path(value = "/client/{accountID}/{userID}/get") public Response processGET(@PathParam("accountID") String accountId, @PathParam("userID") String userId) { String result = flowProcessing.processGET(accountId, userId); return Response.ok(result).build(); } @POST @Path(value = "/client/{accountID}/{userID}/get") public Response processPOST(@PathParam("accountID") String accountId, @PathParam("userID") String userId, String body) { String result = flowProcessing.processPOST(accountId, userId, body); return Response.ok(result).build(); } public void setFlowProcessing(FlowProcessing flowProcessing) { this.flowProcessing = flowProcessing; } }
Nothing fancy here. JAX-RS plain and simple. Interesting part is the FlowProcessing interface. It looks like it's wrapping whole business logic processing. By using Component Bindings we can make any Mule's outbound endpoint behave as an implementation of that interface. Hence, we can make use of VM endpoints and move the processing from Java back to Mule flows:
<jersey:resources doc:name="REST"> <component class="pl.poznachowski.jerseyrest.JerseyRestService"> <binding interface="pl.poznachowski.jerseyrest.FlowProcessing" method="processGET"> <vm:outbound-endpoint exchange-pattern="request-response" path="vmProcessGET" /> </binding> <binding interface="pl.poznachowski.jerseyrest.FlowProcessing" method="processPOST"> <vm:outbound-endpoint exchange-pattern="request-response" path="vmProcessPOST" /> </binding> </component> <jersey:exception-mapper class="pl.poznachowski.jerseyrest.BadURIExceptionMapper" /> </jersey:resources>As seen above, everything we need to make it work is to provide binding element specifying the interface, interface's method and outbound endpoint, which should be called. Few things to remember:
- Don't forget to write setter for the interface in the Jersey class.
- Make sure that request and response of the interface and endpoint matches
- It is possible to have the method return MuleMessage. It lets Java component have access to whole message, not only payload.
Flows with the "business logic" for POST and GET methods processing are described below:
<flow name="ProcessGetFlow" doc:name="ProcessGetFlow"> <vm:inbound-endpoint exchange-pattern="request-response" path="vmProcessGET" doc:name="VM" /> <transformer ref="populateVariables" doc:name="Populate variables"/> <set-payload value="Processing GET with account id: #[accountID] and user id: #[userID]" doc:name="Set Payload" /> </flow> <flow name="ProcessPostFlow" doc:name="ProcessPostFlow"> <vm:inbound-endpoint exchange-pattern="request-response" path="vmProcessPOST" doc:name="VM" /> <transformer ref="populateVariables" doc:name="Populate variables"/> <set-payload value="Processing POST with account id: #[accountID] and user id: #[userID] and body: #[payload]" doc:name="Set Payload" /> </flow>Input parameters of the FlowProcessing interface methods comes in the Mule flows as an Object array. We can map them into Mule parameters by writting and setting global transformer:
<custom-transformer class="pl.poznachowski.jerseyrest.PopulateVariables" name="populateVariables" doc:name="Java" />
and reusing it in the flows (<transformer ref="populateVariables" />).
PopulateVariables Java transformer:
public class PopulateVariables extends AbstractMessageTransformer { @Override public MuleMessage transformMessage(MuleMessage message, String outputEncoding) throws TransformerException { Object[] args = message.getPayload(Object[].class); message.setInvocationProperty("accountID", args[0]); message.setInvocationProperty("userID", args[1]); // For POST method if (args.length > 2) { message.setPayload(args[2]); } return message; } }
We have everything in place to make it work. One thing I don't like is that Jersey for requests with URL not met is returning 404 HTTP status (Not Found). I find 400 status (Bad Request) more appropriate in such case and I'd like to keep 404 reserved for situations where URL was met, but resource was not found. Satysfying that requirement is fairly simple. We need to add a custom exception mapper in Jersey definition:
<jersey:exception-mapper class="pl.poznachowski.jerseyrest.BadURIExceptionMapper" />
with implementation:
public class BadURIExceptionMapper implements ExceptionMapper<NotFoundException> { public Response toResponse(NotFoundException exception){ return Response.status(Response.Status.BAD_REQUEST).entity("Unknown resource: " + exception.getNotFoundUri().toString()).build(); } }
Note: It is possible to catch exceptions thrown in the binded outbound endpoints. To make that possible we just need to declare exception in binding interface method.
To test described solution I used the same set of tests as in previous post. However I was having problems testing successful scenarios using MUnit:
ERROR 2013-10-17 22:30:02,434 [main] org.mule.exception.DefaultMessagingExceptionStrategy:
********************************************************************************
Message : Failed to invoke JerseyResourcesComponent$$EnhancerByCGLIB$$8a425d6{JerseyRestFlow.component.206873183}. Component that caused exception is: JerseyResourcesComponent$$EnhancerByCGLIB$$8a425d6{JerseyRestFlow.component.206873183}. Message payload is of type: String
Code : MULE_ERROR--2
--------------------------------------------------------------------------------
Exception stack is:
1. The required object/property "muleContext" is null (java.lang.IllegalArgumentException)
org.mule.DefaultMuleMessage:292 (null)
2. Failed to invoke JerseyResourcesComponent$$EnhancerByCGLIB$$8a425d6{JerseyRestFlow.component.206873183}. Component that caused exception is: JerseyResourcesComponent$$EnhancerByCGLIB$$8a425d6{JerseyRestFlow.component.206873183}. Message payload is of type: String (org.mule.component.ComponentException)
org.mule.component.AbstractComponent:148 (http://www.mulesoft.org/docs/site/current3/apidocs/org/mule/component/ComponentException.html)
It seems that muleContext is not propagated correctly with MUnit and reflective proxy classes. Switching test class to use 'official' FunctionalTestCase instead of MUnit worked like a charm.
Full example at GitHub: JerseyREST
That were all the solutions for exposing REST Services I can think of.
In my work project, I'm using the REST Module and it's doing its job well. Thus, if you want build REST API and use all of the nifty Mule features I would suggest going with the Router Module.
If you prefer to be more Java-centric, make use of Mule for handling integration matter only or to use Mule flows to a smaller extent then Jersey approach will fit in.
For more information about Component Bindings I encourage you to read Mule's well-written blog post explaining the feature.