Showing posts with label response. Show all posts
Showing posts with label response. Show all posts

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

Mule: Log request and response messages of synchronous call

Suppose you have a SOAP webservice running and you want to log all requests and responses to the webservice. How can you establish this using Mule ESB?


I have seen this question a lot on the mule mailinglist, and so far I have only seen answers that partially describe the solution, but I have never seen a complete example. So, this will be the topic of my first technical blog.


A typical way to do this in Mule is with the wire-tap-router. This inbound router allows you to route certain events to a different endpoint as well as to the component (see figure below).



So, what's the problem, you might think. Well, the router only taps incoming events, so this will not work for the response messages of a synchronous call. The solution is to transform the synchronous call to an asynchronous one. This can be accomplished by the async-reply router.



<model name="LogExample">
<service name="WebServiceBridge">
<inbound>
<inbound-endpoint address="${inbound.uri}" synchronous="true" />
</inbound>
<outbound>
<pass-through-router>
<vm:outbound-endpoint path="request-channel"
transformer-refs="ObjectToString SetRequestMessageProperty"/>
</pass-through-router>
</outbound>
<async-reply>
<vm:inbound-endpoint path="back-channel" />
<single-async-reply-router />
</async-reply>
</service>


As you can see, the inbound endpoint is synchronous, while the outbound endpoint isn't. The service further defines a single-async-reply-router, which means that the first message that is received is returned to the inbound-endpoint. The outbound router routes the events to the request-channel. Don't mind the transformers that are defined yet, those are explained later.


The next service that is defined is the request flow:



<service name="RequestFlow">
<inbound>
<vm:inbound-endpoint path="request-channel"/>
<wire-tap-router>
<vm:outbound-endpoint path="log-channel"/>
</wire-tap-router>
</inbound>
<outbound>
<chaining-router>
<outbound-endpoint name="webservice" address="${outbound.uri}"
synchronous="true"/>
<vm:outbound-endpoint path="response-channel"
transformer-refs="ObjectToString SetResponseMessageProperty"/>
</chaining-router>
</outbound>
</service>


A wire-tap-router is configured on the inbound-endpoint. All events are passed to the log-channel. A chaining-router is used to first send the request to the actual webservice and then send the response from the webservice to the response-channel.


The next service is the response flow:



<service name="ResponseFlow">
<inbound>
<vm:inbound-endpoint path="response-channel" />
<wire-tap-router>
<vm:outbound-endpoint path="log-channel"/>
</wire-tap-router>
</inbound>
<outbound>
<pass-through-router>
<vm:outbound-endpoint path="back-channel"/>
</pass-through-router>
</outbound>
</service>


As you can see, the event is again send to the log-channel through the wire-tap-router as well as to the back-channel (and thus to the client).


The last service we have to define is the log flow:



<service name="LogFlow">
<inbound>
<vm:inbound-endpoint path="log-channel"/>
<custom-inbound-router
class="nl.endpoint.mule.router.RequestResponseAggregator"/>
</inbound>
<component>
<spring-object bean="LogComponent" />
</component>
</service>
</model>


An aggregator is used to collect both the request and the response and hand them as a set to the LogComponent. This is only necessary if you want to handle the request and response as one message (you can also choose to log them separately). So, how can the aggregator make a distinction between the request and the response message? This is done by the transformers SetRequestMessageProperty and SetResponseMessageProperty. Both set a property to the MuleMessage indicating whether the message contains the request event or the response event. The former is shown below:



<message-properties-transformer
name="SetRequestMessageProperty">
<add-message-properties>
<spring:entry key="MessageType" value="Request"/>
</add-message-properties>
</message-properties-transformer>


The SetResponseMessageProperty is configured the same way.


Now the aggregator is pretty straight forward and listed below. The RequestResponseAggregator extends AbstractEventAggregator. Therefore the method getCorrelatorCallback has to be implemented. This method returns a new EventCorrelatorCallback. In the method aggregateEvents, the property 'MessageType' is checked.



public class RequestResponseAggregator extends AbstractEventAggregator {
@Override
protected EventCorrelatorCallback getCorrelatorCallback() {
return new EventCorrelatorCallback() {
@SuppressWarnings("unchecked")
public MuleMessage aggregateEvents(EventGroup events)
throws RoutingException {
Iterator<MuleEvent> iterator = events.iterator();
while(iterator.hasNext()) {
MuleEvent event = iterator.next();
MuleMessage message = event.getMessage();
// Check message type
if ("Request".equals(message.getProperty("MessageType"))) {
// This is the request
} else {
// This is the response
}
}

return new DefaultMuleMessage(...);
}

public EventGroup createEventGroup(MuleEvent event, Object correlationId) {
return new EventGroup(correlationId, 2);
}

public boolean shouldAggregateEvents(EventGroup events) {
return events.expectedSize() == events.size();
}
};
}
}

Hope this clarifies how to deal with logging of request and response messages in synchronous flows.


My next blog will be about another common question in the Mule mailing list: Exception Handling.



|

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