Mule, show me the exception, please

Do you know the feeling that you think what you want is really simple, but can't get it to work? You start searching the forums and google is your best friend. But even a best friend doesn't always know the answer. The worst part of it all is that most of the time the answer IS really simple. You just needed to figure it out.


The setting
A couple of month ago, I was struggling with how to send a proper response to the client in case an exception occurs in mule (or any of the services attached to it). We use mule as a proxy for our external webservices. Mule handles among others authentication/authorization, logging, auditing and transformation and routing features. If for example authentication fails or the connection to an external webservice is down, a SoapFault needed to be returned to the calling party.


Easy, right? (well actually it is, I'll come to that later).


Since I like to visualize things, let's take a look at an example config file, below:


<mule>
   ...
   <custom-transformer name="HttpRequestToParamMap"
      class="org.mule.transport....HttpRequestBodyToParamMap"/>
   
   <expression-transformer name="MapToServiceParams">
      <return-argument evaluator="map-payload"
         expression="text"/>
      <return-argument evaluator="map-payload"
         expression="delim" optional="true"/>
   </expression-transformer>
   
   <model name="ExceptionHandling">
      <service name="inputService">
         <inbound>
            <inbound-endpoint
               address="http://localhost:7777/textcasing"
               transformer-refs="HttpRequestToParamMap"
               synchronous="true"/>
         </inbound>
         <outbound>
            <pass-through-router>
               <outbound-endpoint
                  transformer-refs="MapToServiceParams"
                  address="wsdl-cxf:
                     http://www.dataaccess.com/
                     webservicesserver/textcasing.wso?
                     wsdl&method=TitleCaseWordsWithToken"
                  synchronous="true"/>
            </pass-through-router>
         </outbound>
      </service>
   </model>
</mule>


In the example a service is defined with an http inbound-endpoint, which takes two parameters: text and delim. The parameters will be transformed and the SOAP service TitleCaseWordsWithToken will be called with these parameters. So if you go to your browser and type:


http://localhost:7777/textcasing?text=This%20is%20a%20text&delim=-


The service will return:


This-Is-A-Test


The Problem
So far, so good. But what if, for example, You forget to specify the parameter delim, which is required. The webservice will return a SOAP Fault. If you don't configure anything, the caller will either see nothing (NullPayload) or the incoming message, depending on the point where the exception is thrown. But you want to inform the requester. I'm not gonna bore you with the different solutions I've tried (one of them was mentioned 'most innovative approach I have ever seen' ;-) ), but unfortunately they all failed.


The Solution
The solution brought to me by Puneet Gupta is as simple as elegant. Since the exception is stored in the MuleMessage (in the property exceptionPayload), use a transformer to transform the exception to the proper response message.


public class ErrorTransformer extends
               AbstractMessageAwareTransformer {
   private ExceptionHandlerStrategy
            exceptionHandlerStrategy;

   @Override
   public Object transform(MuleMessage message,
         String outputEncoding) throws
            TransformerException {
      if (message.getExceptionPayload() != null) {
         // Remove HTTP_STATUS
         message.removeProperty
               (HttpConnector.HTTP_STATUS_PROPERTY);

         ExceptionPayload exceptionPayload =
               message.getExceptionPayload();

         // Strategy builds custom Error Message
         Object payload =
               exceptionHandlerStrategy.
                  handleException(
                     message,
                     exceptionPayload.getException(),
                     exceptionPayload.getRootException());

         // Set the error message / soap fault as payload
         message.setPayload(payload);

         // Remove exception payload
         message.setExceptionPayload(null);
      }

      return message;
   }
}


Now you only need to configure this transformer as a responseTransformer in your inbound endpoint:


<custom-transformer name="ErrorTransformer"
   class="nl.endpoint.mule.exception.ErrorTransformer" />
...
<inbound>
   <inbound-endpoint
      address="http://localhost:7777/textcasing" ...
      responseTransformer-refs="ErrorTransformer"/>
</inbound>


Other solutions
If you are working with SOAP messages, another good alternative would be to use the cxf transport instead of the http transport. The cxf transport automatically translates the Exceptions to proper SOAP Faults.


As I start seeing the same question over and over on the mule forum, I decided to spend a blogpost on it. So, for all those people searching for the same answer, I hope your search stops here. Otherwise leave me a note.



|

  • Digg
  • Del.icio.us
  • StumbleUpon
  • Reddit
  • Twitter
  • RSS