Wednesday, November 2, 2016

Implementing a Custom Message Builder in WSO2 ESB 4.9.0

Message Builder's in ESB


In WSO2 ESB message builders are used to convert the message into SOAP. Whenever a message came into ESB, the receiving transport will select a message builder from axis2.xml based on the content type of the message and convert it to SOAP and it will be used throughout the mediation.

There are predefined message builders inside ESB and you can refer [1] for more details on it.

In some circumstances, we need to change the logic inside the predefined message builders to achieve some custom tasks while the message get build. Here, for example, consider that there is a message with invalid namespace configuration or malformed xml message comes into the ESB and at that time while building at axiom level it will throw exception and the message will not propagate
to synapse level. For some validation purpose or guranteed delivery we may need the message to be
get into synapse level, even when there is a exception occur at message builder.

In these cases we need to create a custom message builder to capture the exception and handling it and passing a custom payload with the original message to the synapse level. Below is the sample custom message builder, which will capture the exception while message building and send a custom payload back to synapse level. In this sample used the Builder interface to implement the custom message builder and as the expected message content type is application/xml used the same logic of the ApplicationXMLBuilder and handled the exception. Based on your need you can modify which contentType you want to handle and use the Builder interface to implement your custom use case.


When using this if you want to use this for a specific proxy service only in a JMS listener you need to first add this to axis2.xml

<messageBuilder contentType="application/test"
 class="com.custom.messagebuilder.CustomApplicationXMLMessageBuilder"/>

Then add the content type parameter in the proxy as below:

<parameter name="transport.jms.ContentType">application/test</parameter>

The Java Sample Message Builder


package com.custom.messagebuilder;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.io.StringWriter;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.OMNodeEx;
import org.apache.axiom.om.impl.builder.StAXBuilder;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axiom.om.util.StAXParserConfiguration;
import org.apache.axiom.om.util.StAXUtils;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.builder.Builder;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.Constants;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/* This is a custom message builder to escape the build error's at axis2 level due to invalid messages and forward a custom error message with the actual message as value for <originalMessage>
 * tag to the synapse level. 
 * */

public class CustomApplicationXMLMessageBuilder implements Builder {

 private static final Log log = LogFactory
   .getLog(CustomApplicationXMLMessageBuilder.class);

 private final XMLInputFactory inputFactory = XMLInputFactory.newInstance();

 public CustomApplicationXMLMessageBuilder() {
  inputFactory.setProperty("javax.xml.stream.supportDTD", Boolean.FALSE);
  inputFactory.setProperty(
    "javax.xml.stream.isReplacingEntityReferences", Boolean.FALSE);
  inputFactory.setProperty(
    "javax.xml.stream.isSupportingExternalEntities", Boolean.FALSE);
 }

 public OMElement processDocument(InputStream inputStream,
   String contentType, MessageContext messageContext) throws AxisFault {

  if (log.isInfoEnabled()) {
   log.info("[CustomApplicationXMLMessageBuilder]" + "- Invoked");
   log.info("[CustomApplicationXMLMessageBuilder]"
     + "- Content Type Received: " + contentType);
  }
  // Cloning the input stream to get the original message into exception.
  byte[] byteArray = null;
  try {
   byteArray = IOUtils.toByteArray(inputStream);
  } catch (IOException e2) {
   e2.printStackTrace();
  }

  InputStream input1 = new ByteArrayInputStream(byteArray);
  InputStream input2 = new ByteArrayInputStream(byteArray);

  SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory();
  SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();

  if (input1 != null) {
   if (log.isInfoEnabled()) {
    log.info("[CustomApplicationXMLMessageBuilder] Inputstream not null.");
   }
   try {
    PushbackInputStream pushbackInputStream = new PushbackInputStream(
      input1);
    int b;
    if ((b = pushbackInputStream.read()) > 0) {
     pushbackInputStream.unread(b);
     javax.xml.stream.XMLStreamReader xmlReader;

     if ("true"
       .equals(messageContext
         .getProperty(Constants.Configuration.APPLICATION_XML_BUILDER_ALLOW_DTD))
       || ((messageContext
         .getParameter(Constants.Configuration.APPLICATION_XML_BUILDER_ALLOW_DTD) != null) && ("true"
         .equals(messageContext
           .getParameter(
             Constants.Configuration.APPLICATION_XML_BUILDER_ALLOW_DTD)
           .getValue())))) {
      xmlReader = inputFactory
        .createXMLStreamReader(
          pushbackInputStream,
          (String) messageContext
            .getProperty(Constants.Configuration.CHARACTER_SET_ENCODING));
     } else {
      xmlReader = StAXUtils
        .createXMLStreamReader(
          StAXParserConfiguration.SOAP,
          pushbackInputStream,
          (String) messageContext
            .getProperty(Constants.Configuration.CHARACTER_SET_ENCODING));
     }
     // Handling the exception thrown, when a invalid message received to StAXOMBuilder
     try {
      
      StAXBuilder builder = new StAXOMBuilder(xmlReader);
      OMNodeEx documentElement = (OMNodeEx) builder
        .getDocumentElement();
      documentElement.setParent(null);
      SOAPBody body = soapEnvelope.getBody();
      body.addChild(documentElement);
      
     } catch (Exception e) {
      if (log.isWarnEnabled()) {
       log.warn("[CustomApplicationXMLMessageBuilder] Exception thrown due to Invalid Message and creating a custom message to forward to synapse level.");
      }
      
      // Reading the original message by copying the input stream to writer
      StringWriter writer = new StringWriter();
      String originalPayload;
      IOUtils.copy(input2, writer);
      originalPayload = writer.toString();
      
      if (log.isDebugEnabled()) {
       log.debug("[CustomApplicationXMLMessageBuilder] Original Payload Received at Input Stream: "
         + originalPayload);
      }
      
      //Appending the custom payload to the soap body
      SOAPBody body = soapEnvelope.getBody();
      OMElement documentElementInvalidMessage = null;
      OMElement documentElementOriginalPayload = null;
      documentElementInvalidMessage = AXIOMUtil.stringToOM("<messageType>" + "INVALID_MESSAGE" + "</messageType>");
      documentElementOriginalPayload = AXIOMUtil.stringToOM("<originalMessage>"+ "<![CDATA[" + originalPayload + "]]>" +"</originalMessage>");           
      body.addChild(documentElementInvalidMessage);
      body.addChild(documentElementOriginalPayload); 
      
     }
    }
   } catch (XMLStreamException e) {
    if (log.isWarnEnabled()) {
     log.warn("[CustomApplicationXMLMessageBuilder]"
       + "- Entered XML Stream Exception.");
    }
    throw AxisFault.makeFault(e);
   } catch (IOException e) {
    if (log.isWarnEnabled()) {
     log.warn("[CustomApplicationXMLMessageBuilder]"
       + "- Entered IO Exception.");
    }
    throw AxisFault.makeFault(e);
   }
  }
  if (log.isInfoEnabled()) {
   log.info("[CustomApplicationXMLMessageBuilder]"
     + "- Returned SOAP Envelope: " + soapEnvelope);
  }
  return soapEnvelope;
 }
}


The pom.xml


<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemalocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelversion>4.0.0</modelversion>
 <groupid>CustomApplicationXMLMessageBuilder</groupid>
 <artifactid>CustomApplicationXMLMessageBuilder</artifactid>
 <version>0.0.1</version>
 <name>CustomApplicationXMLMessageBuilder</name>
 <dependencies>
  <dependency>
   <groupid>org.apache.axis2.wso2</groupid>
   <artifactid>axis2</artifactid>
   <version>1.6.1.wso2v14</version>
  </dependency>
  <dependency>
   <groupid>org.apache.ws.commons.axiom.wso2</groupid>
   <artifactid>axiom</artifactid>
   <version>1.2.11.wso2v6</version>
  </dependency>
  <dependency>
   <groupid>commons-logging</groupid>
   <artifactid>commons-logging</artifactid>
   <version>1.1.1</version>
  </dependency>
  <dependency>
   <groupid>org.apache.commons</groupid>
   <artifactid>commons-io</artifactid>
   <version>1.3.2</version>
  </dependency>
 </dependencies>
 <repositories>
  <repository>
   <releases>
    <enabled>true</enabled>
    <updatepolicy>daily</updatepolicy>
    <checksumpolicy>ignore</checksumpolicy>
   </releases>
   <id>wso2-nexus</id>
   <url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url>
  </repository>
 </repositories>
 <pluginrepositories>
  <pluginrepository>
   <releases>
    <enabled>true</enabled>
    <updatepolicy>daily</updatepolicy>
    <checksumpolicy>ignore</checksumpolicy>
   </releases>
   <id>wso2-nexus</id>
   <url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url>
  </pluginrepository>
 </pluginrepositories>
 <build>
  <plugins>
   <plugin>
    <artifactid>maven-eclipse-plugin</artifactid>
    <version>2.9</version>
    <configuration>
     <buildcommands>
      <buildcommand>org.eclipse.jdt.core.javabuilder</buildcommand>
     </buildcommands>
     <projectnatures>
      <projectnature>org.wso2.developerstudio.eclipse.artifact.mediator.project.nature</projectnature>
      <projectnature>org.eclipse.jdt.core.javanature</projectnature>
     </projectnatures>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>






No comments:

Post a Comment