IBM WebSphere Developer Technical Journal: A practical introduction to message mediation -- Part 3

Modifying messages with mediations

Part 3 of this article series explains how to access and modify messages contents using mediations. Sample administrative scripts to create a test environment and Java™ source code for the mediation handlers are included.

Share:

Dan Murphy (d_murphy@uk.ibm.com), Software Engineer, IBM Hursley, UK

Author photoDan Murphy works in IBM Hursley in the United Kingdom. He has been working at IBM for more than 11 years and is currently a test designer and developer for WebSphere messaging components. In his spare time Dan is trying to renovate the house he bought about about three years ago. The current task is laying a herringbone parquet floor in the lounge, like doing giant jigsaw puzzle and having to make some of the pieces yourself !



15 June 2005

Introduction

If you read the previous two articles in this series, The basics of message mediation (Part 1) and Routing messages with mediations (Part 2), then you should now be comfortable with the basics of message mediation, a new programmable extension to the messaging capabilities of IBM® WebSphere® Application Server that can simplify connecting systems, services, applications, or components that use messaging. These articles, along with others recently published by IBM colleagues, have primarily focused on routing messages and simple logging. Here in Part 3, we will see how to access and manipulate the actual content of a JMS message using a mediation.

The mediations programming model enables you to access the message body using the Service Data Objects (SDO) interface, which simplifies the exchange of data between multiple tiers and different data sources. The SDO specification has been submitted to the Java Community Process through JSR 235. (To understand why SDO exists and how to develop a simple application that uses SDO, see Introduction to Service Data Objects.) In this article, we will take the practice approach of using SDO to read and write the contents of a message within a mediation.

Before you continue, make sure you have understood the previous articles. This article assumes that you understand how to develop, deploy, and test a simple mediation handler. If you haven't read the previous articles, you should do so now.

Preparation

To develop and test the mediations contained in this article, you must have installed one of the following combinations:

  • IBM WebSphere Application Server Toolkit Version 6.0 and WebSphere Application Server V6.
  • IBM Rational® Application Developer V6, including the integrated WebSphere Application Server V6 test environment.
  • IBM Rational Application Developer V6 and WebSphere Application Server V6.
  • IBM Rational Software Architect V6.0, including the integrated WebSphere Application Server V6 test environment.

You should also download and apply the latest service levels for the products you install. At the time of writing, WebSphere Application Server V6 Refresh Pack 1 (V6.0.1) is the latest fixpack to be released. See Resources for details on WebSphere Application Server fixpacks and how to obtain them. Updates to the Rational products can be obtained using the Rational Product Updater, which is installed automatically when you install the any Rational products. Alternatively, you can manually download fixpacks from the IBM Rational Support Web site

The screen images that appear in this article are from Rational Application Developer V6. If you use the WebSphere Application Server Toolkit, you can still use the steps documented here, as they are identical for both Rational Application Developer and the toolkit. You can install the WebSphere Application Server Toolkit from the WebSphere Application Server installation media or images.


Accessing message contents with mediations

Part 2 of this series explained how and why to use mediations to route messages. The other major capability of mediations is to access and modify the contents of in-flight messages. For example:

  • A mediation could be used to convert the message contents between two different -- and otherwise incompatible -- formats.
  • Mediations can augment message contents with data obtained from a database.
  • Log the message contents for debug or auditing purposes.

All of these uses depend on obtaining and potentially changing the data contained in the message. The most obvious use of a mediation is to log the contents of a message for auditing or debug purposes; for example logging before and after images of a message that is transformed. Later, we will develop a mediation with this capability, but for now, let's look at how to gain access to the message content.

The mediation handler framework lets you access the message using an SDO representation of the message. To access the message SDO, you first obtain the SIMessage from the SIMessageContext by invoking getSIMessage() method. Having obtained a reference to the SIMessage, you can use the SIMessage.getDataGraph() method to obtain an SDO DataGraph. The message SDO DataObject is contained in the DataGraph root object.

This may sound complicated, but as shown in Listing 1, given SIMessageContext, it is simple to obtain the DataObject SDO representation of a message.

Listing 1. Obtain the message SDO DataObject
private DataObject getMessageDataObject(SIMessageContext ctx){
        DataObject result = null;
        SIMessage msg = ctx.getSIMessage();
        try {
            result = msg.getDataGraph().getRootObject();
        } catch (SIDataGraphSchemaNotFoundException e) {
            e.printStackTrace();
        } catch (SIMessageException e) {
            e.printStackTrace();
        }
        return result;
}

Using the DataObject, you can navigate the structure using an XPATH syntax. JMS messages have a very simple structure; the payload of the message is contained in the data/value element of the SDO message DataObject. The type and contents of the property will depend on the type of JMS message sent to the mediation. The type of the JMS message can be obtained using SIMessage.getFormat(), which returns a string, as shown in this table:

JMS Message Type SIMessage.getFormat() value
TextMessageJMS:text
BytesMessageJMS:bytes
ObjectMessageJMS:object
StreamMessageJMS:stream

You will notice that the JMS MapMessage is missing from the table above. The current release of the mediation framework does not support the MapMessage type, and so attempting to mediate one will result in an exception. The JMS message type formats are defined in the com.ibm.websphere.sib.SIApiConstants class for your reference.

Having obtained the DataObject, you can retrieve the message contents of text and bytes messages simply by invoking either getString(data/value) or getBytes(data/value). Retrieving object and stream messages is slightly more complicated.

Retrieving object messages

You can de-serialize object messages by constructing a ByteArrayInputStream from the payload, then use it to create an ObjectInputStream and invoke the readObject() method. To successfully de-serialize the message, you will need to make sure that the mediation handler has access to the appropriate classes. Listing 2 illustrates how to de-serialize JMS ObjectMessages.

Listing 2. De-serializing the contents of a JMS ObjectMessage
private Object getObject(SIMessageContext ctx) {
   Object result = null;
   byte[] msgBodyBytes = null;
   DataObject msgDataObj = getMessageDataObject(ctx);
   String format = ctx.getSIMessage().getFormat();
   if (msgDataObj !=null && msgDataObj.isSet("data/value") {
       msgBodyBytes = msgDataObj.getBytes("data/value");
   }
   if ((format.equals(SIApiConstants.JMS_FORMAT_OBJECT)) &&
      (msgBodyBytes != null)) {
       try {
           ObjectInputStream in = new ObjectInputStream(
                   new ByteArrayInputStream(msgBodyBytes));
           result = in.readObject();
       } catch (Throwable e) {
           // Error de-serialising object
           e.printStackTrace();
       }
   }
   return result;
}

Retrieving stream messages

Stream messages contain a sequence of Java primitives or arrays of bytes. This means that the data/value element will contain a list. You can either:

  • Use getList("data/value"), which returns a java.util.List containing the stream elements, or
  • Access specific items in the list using get("data/value[n]") where n is the nth element in the list; SDO indexes start from 1.

As an example, Listing 3 shows how you could print the contents of a JMS StreamMessage.

Listing 3. Printing the contents of a JMS StreamMessage
private void printStreamMessage(SIMessageContext ctx) {
   DataObject msgDataObj = getMessageDataObject(ctx);
   String msgFormat = ctx.getSIMessage().getFormat();
   List msgBodyStream = null;
   Object streamItem;
   if (msgDataObj != null && (msgFormat.equals(SIApiConstants.JMS_FORMAT_STREAM)){
       if msgDataObj.isSet("data/value"){
           msgBodyStream = msgDataObj.getList("data/value");
       }
   }
   if (msgBodyStream != null) {
       ListIterator iterator = msgBodyStream.listIterator();
       for (int i = 1; iterator.hasNext(); i++) {
           streamItem = iterator.next();
           System.out.print("Item index: " + i);
           System.out.print(" type: ");
           if (streamItem instanceof byte[]) {
               System.out.print("byte[] data: ");
               System.out.println(new String(
                 (byte[]) streamItem));
           } else {
               System.out.print(streamItem.getClass().getName());
               System.out.print(" value: ");
               System.out.println(streamItem.toString());
           }
       }
   }
}

You can see that, from the above examples, reading message bodies is simple for JMS messages. Web service messages, because of their origin in XML, have more complex structures. This makes writing mediations that access Web service messages more complex. As mentioned earlier, the SIMessage contains an SDO data graph. From the root of the data graph, obtained by invoking getRootObject(), you can navigate the structure using XPATH. The SDO programming model also provides access to metadata; for example, you can use getType() and getProperties() to discover the runtime types and properties of any given DataObject.

(Programming Web service message mediations is beyond the scope of this article, but information can be found in the WebSphere Application Server Information Center.)


LoggingMediation: DebugMediation revisited

In Part 1, we introduced the concept of meditations with a simple debug mediation. You will recall that this mediation simply wrote session and context data about the message to the log. Based on the information in the previous section, we will now see how to rewrite the DebugMediation to make it more reusable, and to extend its capabilities to include logging the message content.

Rather than write a Java class that implements the required MediationHandler interface, we will instead separate the log writing functionality into different classes to the mediation handler. This aids reuse, as it enables other handlers to log without forcing the inheritance of the DebugMediation class.

For this example, we will create a MediationLogWriter class for writing entries to the application server log using log name and level specific to its construction. The MediationLogWriter will make use of a MessageBodyFormatter class, which we will also code. Finally, we will code a simple mediation that will enable us to test the log writer. The steps that follow are documented assuming you have followed the examples in the previous articles.

  1. Start your development environment with the same workspace used for the previous articles.
  2. Import the source code for the mediation.handlers.MediationLogWriter class from the download file to the MessageMediation project. For reference, the source code is shown in Listing 4. The majority of this code is based on the DebugMediation from the first article. In addition to some formatting improvements, and light refactoring, the MediationLogWriter now invokes a MessageBodyFormatter to format JMS message bodies before they are written to the log.

    Listing 4. MediationLogWriter

    package mediation.handlers;
    
    import java.util.Iterator;
    import java.util.List;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import com.ibm.websphere.sib.SIMessage;
    import com.ibm.websphere.sib.mediation.messagecontext.SIMessageContext;
    import com.ibm.websphere.sib.mediation.session.SIMediationSession;
    
    public class MediationLogWriter {
        private Logger logger;
        private Level level;
        private MessageBodyFormatter msgBodyFmt = new MessageBodyFormatter();
    
        private MediationLogWriter() {
            super();
        }
    
        public MediationLogWriter(Logger loggerToUse, Level levelToLogAt) {
            this();
            logger = loggerToUse;
            level = levelToLogAt;
        }
    
        public void writeMediationInvoked(SIMessageContext ctx) {
            StringBuffer buf = new StringBuffer("Mediation invoked (");
            appendCommonHeading(buf, ctx);
            buf.append(')');
            logger.log(level, buf.toString());
        }
    
        public void writeAll(SIMessageContext ctx) {
            StringBuffer buf = new StringBuffer();
            String indent = "    ";
            appendCommonHeading(buf, ctx);
            appendContext(buf, ctx, indent);
            appendSession(buf, ctx, indent);
            appendMessageHeaders(buf, ctx, indent);
            appendMessageBody(buf, ctx, indent);
            logger.log(level, buf.toString());
        }
    
        public void writeSession(SIMessageContext ctx) {
            StringBuffer buf = new StringBuffer();
            appendCommonHeading(buf, ctx);
            appendSession(buf, ctx, "");
            logger.log(level, buf.toString());
        }
    
        private void appendSession(StringBuffer buf, SIMessageContext ctx,
                String indent) {
            String newLine = '\n' + indent;
            SIMediationSession session = ctx.getSession();
            buf.append("\nMediation Session:" + newLine);
            buf.append("Mediation = " + session.getMediationName() + newLine);
            buf.append("Destination = " + session.getDestinationName() + newLine);
            buf.append("Service Bus = " + session.getBusName() + newLine);
            buf.append("Messaging Engine = " + session.getMessagingEngineName());
            if (session.getDiscriminator() != null
                    && !session.getDiscriminator().equals("")) {
                buf.append(newLine + "Discriminator = ");
                buf.append(session.getDiscriminator());
            }
            if (session.getMessageSelector() != null
                    && !session.getMessageSelector().equals("")) {
                buf.append(newLine + "Message selector = ");
                buf.append(session.getDiscriminator());
            }
        }
    
        public void writeContext(SIMessageContext ctx) {
            StringBuffer buf = new StringBuffer();
            appendCommonHeading(buf, ctx);
            appendContext(buf, ctx, "");
            logger.log(level, buf.toString());
        }
    
        private void appendContext(StringBuffer buf, SIMessageContext ctx,
                String indent) {
            Iterator i = ctx.getPropertyNames();
            String newLine = '\n' + indent;
            buf.append("\nMessage Context:");
            if (i != null && !i.hasNext()) {
                String propName = null;
                Object propValue;
                for (; i.hasNext();) {
                    propName = (String) i.next();
                    if (propName != null && !propName.equals("")) {
                        buf.append(newLine + indent);
                        buf.append(propName);
                        buf.append(" = ");
                        try {
                            propValue = ctx.getProperty(propName);
                            buf.append('(' + propValue.getClass().getName() + ") ");
                            buf.append(propValue.toString());
                        } catch (Exception e) {
                            buf.append("exception ");
                            buf.append(e.toString());
                            buf.append("getting value");
                        }
                    }
                }
                if (propName != null && !propName.equals("")) {
                    buf.append("[]");
                    buf.append(newLine);
                }
            } else {
                buf.append("[]");
                buf.append(newLine);
            }
        }
    
        public void writeMessageHeaders(SIMessageContext ctx) {
            StringBuffer buf = new StringBuffer();
            appendCommonHeading(buf, ctx);
            appendMessageHeaders(buf, ctx, "");
            logger.log(level, buf.toString());
        }
    
        private void appendMessageHeaders(StringBuffer buf, SIMessageContext ctx,
                String indent) {
            String newLine = '\n' + indent;
            SIMessage msg = ctx.getSIMessage();
            buf.append("Message Headers:" + newLine);
            buf.append("API message id = " + msg.getApiMessageId() + newLine);
            buf.append("System message id = " + msg.getSystemMessageId() + newLine);
            buf.append("Correlation id = " + msg.getCorrelationId() + newLine);
            buf.append("Message format = \"" + msg.getFormat() + '\"' + newLine);
            buf.append("Message descriminator = " + msg.getDiscriminator()
                    + newLine);
            List list = msg.getUserPropertyNames();
            buf.append("User properties: ");
            Iterator i;
            if (list != null && !list.isEmpty()) {
                i = list.iterator();
                String propName;
                Object propValue;
                for (; i.hasNext();) {
                    buf.append(newLine + indent);
                    propName = (String) i.next();
                    buf.append(propName);
                    buf.append(" = ");
                    try {
                        propValue = msg.getUserProperty(propName);
                        buf.append('(' + propValue.getClass().getName() + ") ");
                        buf.append(propValue.toString());
                    } catch (Exception e) {
                        buf.append("exception ");
                        buf.append(e.toString());
                        buf.append("getting value");
                    }
                }
                buf.append(newLine);
            } else {
                buf.append("[]");
                buf.append(newLine);
            }
            buf.append("Forward routing path = " + msg.getForwardRoutingPath()
                    + newLine);
            buf.append("Reverse routing path = " + msg.getReverseRoutingPath()
                    + newLine);
            buf.append("Reliability = " + msg.getReliability() + newLine);
            buf.append("Priority = " + msg.getPriority() + newLine);
            buf
                    .append("Redelivered Count = " + msg.getRedeliveredCount()
                            + newLine);
            buf.append("User id = " + msg.getUserId());
        }
    
        public void writeMessageBody(SIMessageContext ctx) {
            StringBuffer buf = new StringBuffer();
            appendCommonHeading(buf, ctx);
            appendMessageBody(buf, ctx, "");
            logger.log(level, buf.toString());
        }
    
        public void appendCommonHeading(StringBuffer buf, SIMessageContext ctx) {
            buf.append("Msg ");
            buf.append(ctx.getSIMessage().getApiMessageId());
            buf.append(" Mediation: ");
            buf.append(ctx.getSession().getMediationName());
            buf.append(" Destination: ");
            buf.append(ctx.getSession().getDestinationName());
        }
    
        private void appendMessageBody(StringBuffer buf, SIMessageContext ctx,
                String indent) {
            msgBodyFmt.formatJMSMessage(buf, ctx.getSIMessage(), indent);
        }
    }
  3. Create a new class, mediation.handlers.MessageBodyFormatter, in the MediationHandlers project and add the required import statements (Listing 5).

    Listing 5. Import statements

    import java.io.ByteArrayInputStream;
    import java.io.ObjectInputStream;
    import java.util.List;
    import com.ibm.websphere.sib.SIApiConstants;
    import com.ibm.websphere.sib.SIMessage;
    import commonj.sdo.DataObject;
  4. Add a method, formatJMSMessage(StringBuffer buf, SIMessage msg, String indent), which will be invoked by theMediationLogWriter (Listing 6).

    Listing 6. formatJMSMessage

    public void formatJMSMessage(StringBuffer buf, SIMessage msg, String indent) {
        String msgfmt = msg.getFormat();
        DataObject msgRoot;
        String newLine = '\n' + indent;
        try {
            msgRoot = msg.getDataGraph().getRootObject();
            if (msgfmt.equals(SIApiConstants.JMS_FORMAT_TEXT)) {
                buf.append("\nJMS TextMessage :" + newLine);
                String msgText = msgRoot.getString("data/value");
                buf.append(msgText.replaceAll("\n", newLine));
                buf.append("\nJMS TextMessage, as bytes :");
                byte[] msgBytes = msgRoot.getString("data/value").getBytes();
                appendBytes(buf, msgBytes, indent);
            } else if (msgfmt.equals(SIApiConstants.JMS_FORMAT_BYTES)) {
                buf.append("\nJMS BytesMessage : ");
                appendBytes(buf, msgRoot.getBytes("data/value"), indent);
            } else if (msgfmt.equals(SIApiConstants.JMS_FORMAT_OBJECT)) {
                buf.append("\nJMS ObjectMessage : (");
                appendObjectMessage(buf, indent, msgRoot, newLine);
            } else if (msgfmt.equals(SIApiConstants.JMS_FORMAT_STREAM)) {
                buf.append("\nJMS StreamMessage :");
                appendStreamMessage(buf, indent, msgRoot, newLine);
            }
        } catch (Exception e) {
            buf.append("Cannot format message (" + msg.getApiMessageId());
            buf.append(") body using format " + msgfmt + " due to " + e);
        }
    }
  5. Add a method, appendStreamMessage(StringBuffer buf, String indent, DataObject msgRoot, String newLine) to format stream messages (Listing 7).

    Listing 7. appendStreamMessage

    private void appendStreamMessage(StringBuffer buf, String indent,
            DataObject msgRoot, String newLine) {
     List streamList = msgRoot.getList("data/value");
     Object streamItem;
     if (streamList != null && !streamList.isEmpty()) {
         for (int i = 1; i <= streamList.size(); i++) {
             streamItem = msgRoot.get("data/value[" + i + "]");
             buf.append(newLine);
             buf.append("Stream item " + i + " : (");
             if (streamItem instanceof byte[]) {
                 buf.append("byte[])");
                 appendBytes(buf, (byte[]) streamItem, "    " + indent);
             } else {
                 buf.append(streamItem.getClass().getName());
                 buf.append(") " + streamItem.toString());
             }
         }
     } else {
         buf.append("[]");
     }
    }
  6. Add two methods, appendObjectMessage(StringBuffer buf, String indent, DataObject msgRoot, String newLine) and getObject(DataObject msgRoot, String string) to format ObjectMessages (Listing 8).

    Listing 8. appendObjectMessage and getObject

    private void appendObjectMessage(StringBuffer buf, String indent,
            DataObject msgRoot, String newLine) {
     Object obj = null;
     try {
         obj = getObject(msgRoot, "data/value");
         buf.append(obj.getClass().getName());
         String objString = obj.toString();
         if (objString.indexOf('\n')>0){
             buf.append(")\n");
             objString = indent + objString.replaceAll("\n", newLine);
             buf.append(objString);
         } else {
             buf.append(") " + objString);    
         }
     } catch (RuntimeException e) {
         buf.append(e.getMessage() + " due to " + e.getCause());
     }
    }
    
    private Object getObject(DataObject msgRoot, String string) {
        Object result = null;
        byte[] msgBodyBytes = msgRoot.getBytes("data/value");
        try {
            ObjectInputStream in = new ObjectInputStream(
                    new ByteArrayInputStream(msgBodyBytes));
            result = in.readObject();
        } catch (Throwable e) {
            // Error de-serialising object
            throw new RuntimeException("Error deserialising object", e);
        }
        return result;
    }
  7. Finally add a method, appendBytes(StringBuffer buf, byte[] bytes, String indent), which formats byte arrays to the familiar columns of hex numbers and associated ASCII characters (Listing 9).

    Listing 9. appendBytes

    private void appendBytes(StringBuffer buf, byte[] bytes,
            String indent) {
        int length = bytes.length;
        String newLine = '\n' + indent;      
        for (int lineStart = 0; lineStart<length; lineStart += 16) {
            int lineEnd = Math.min(lineStart+16, length);
            StringBuffer hex=new StringBuffer();
            StringBuffer ascii=new StringBuffer();
            for (int i=lineStart; i<lineEnd; i++) {
              int b = bytes[i];
              b=(b+256)%256;
              int c1=b/16;
              int c2=b%16;
              hex.append((char)(c1<10 ? '0'+c1 : 'a'+c1-10));
              hex.append((char)(c2<10 ? '0'+c2 : 'a'+c2-10));
              if (i%2 == 1) hex.append(' ');
              if ((b>=0x20 && b<=0x7e)) ascii.append((char)b);
              else ascii.append('.');
            }
            int pad=16-(lineEnd-lineStart);
            int spaces=(pad*5 + pad%2)/2;
            spaces+=3;
            for (int i=0; i<spaces; i++) hex.append(' ');       
            String offset="0000"+Integer.toHexString(lineStart);
            offset=offset.substring(offset.length()-4);
            buf.append(newLine);
            buf.append(offset);
            buf.append("  ");
            buf.append(hex.toString());
            buf.append(ascii.toString());
          }
    }
  8. Save and compile the two classes before continuing.
  9. We now code a LoggingMediation handler class that will use the MediationLogWriter. Add the following mediation to the MediationHandlers project (Listing 10).

    Listing 10. New mediation

    package mediation.handlers;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.xml.rpc.handler.MessageContext;
    import com.ibm.websphere.sib.mediation.handler.MediationHandler;
    import com.ibm.websphere.sib.mediation.handler.MessageContextException;
    import com.ibm.websphere.sib.mediation.messagecontext.SIMessageContext;
    
    public class LoggingMediation implements MediationHandler {  
     private static final String className = LoggingMediation.class.getName(); 
     private final Logger logger = Logger.getLogger(className);
     private MediationLogWriter log;
    
     public boolean handle(MessageContext ctx) throws MessageContextException {
         getLog().writeSession((SIMessageContext) ctx);
         getLog().writeContext((SIMessageContext) ctx);
         getLog().writeMessageHeaders((SIMessageContext) ctx);
         getLog().writeMessageBody((SIMessageContext) ctx);
         // Alternatively you could log everything in one go using :
         // getLog().writeAll((SIMessageContext) ctx);
         // Or just log a "Mediation invoked" message using
         // getLog().writeMediationInvoked((SIMessageContext)ctx);
         return true;
        }
    
     private MediationLogWriter getLog(){
         if (log == null){
             log = new MediationLogWriter(logger,Level.INFO);
         }
         return log;
     }
    }
  10. Assemble this into the DeployableMediation project using the Mediation Handlers tab of the EJB deployment descriptor editor, as shown in Figure 1.
    Figure 1. The assembled mediation handler class
    Assembled mediation handler class
  11. Publish the enterprise application to the application server and then run the supplied LoggingMediationAdmin.jacl script to configure the mediation and other messaging resources. You should see messages similar to those shown in Listing 11.

    Listing 11. LoggingMediationAdmin messages

    Create SIB Queue
    $AdminTask createSIBDestination -bus SimpleBus -name loggingQueue
        -type queue -node neodogNode -server server1
    $AdminTask createSIBJMSConnectionFactory server1(cells/....) -name CF4
        -jndiName jms/CF4 -busName SimpleBus -type queue
    $AdminTask createSIBJMSQueue server1(cells/....) -name messageQueue
        -jndiName jms/loggingQueue -queueName loggingQueue
    $AdminTask createSIBMediation -bus SimpleBus -mediationName
        LoggingMediation -handlerListName LoggingMediation
    $AdminTask mediateSIBDestination -bus SimpleBus -destinationName
        loggingQueue -mediationName LoggingMediation -node neodogNode
        -server server1
    Configuration saved
  12. Restart the application server to update JNDI.
  13. Send a message using the Universal Test Client to destination jms/loggingQueue using connection factoryjms/CF4. This should result a set of log entries similar to those shown in Listing 12.

    Listing 12. Log entries

    [26/05/05 12:26:32:804 BST] 0000007a LoggingMediat I   
         Msg ID:ab6a8a35d4c176a305d19602110a134f0000000000000001 
         Mediation: LoggingMediation Destination: loggingQueue
    Mediation Session:
    Mediation = LoggingMediation
    Destination = loggingQueue
    Service Bus = SimpleBus
    Messaging Engine = neodogNode.server1-SimpleBus
    [26/05/05 12:26:32:805 BST] 0000007a LoggingMediat I   
        Msg ID:ab6a8a35d4c176a305d19602110a134f0000000000000001 
        Mediation: LoggingMediation Destination: loggingQueue
    Message Context:
    [26/05/05 12:26:32:811 BST] 0000007a LoggingMediat I   
         Msg ID:ab6a8a35d4c176a305d19602110a134f0000000000000001 
         Mediation: LoggingMediation Destination: loggingQueueMessage Headers:
    API message id = ID:ab6a8a35d4c176a305d19602110a134f0000000000000001
    System message id = BAC54242D3B04F6F_9000001
    Correlation id = null
    Message format = "JMS:text"
    Message descriminator = null
    User properties: []
    Forward routing path = []
    Reverse routing path = []
    Reliability = ReliablePersistent
    Priority = 4
    Redelivered Count = 0
    User id = 
    [26/05/05 12:26:32:815 BST] 0000007a LoggingMediat I   
         Msg ID:ab6a8a35d4c176a305d19602110a134f0000000000000001 
         Mediation: LoggingMediation Destination: loggingQueue
    JMS TextMessage :
    Sample text message contains a
    new line character
    JMS TextMessage, as bytes :
    0000:  5361 6d70 6c65 2074 6578 7420 6d65 7373    Sample text mess
    0010:  6167 6520 636f 6e74 6169 6e73 2061 0d0a    age contains a..
    0020:  6e65 7720 6c69 6e65 2063 6861 7261 6374    new line charact
    0030:  6572                                       er

Changing message contents

As we have seen in the above example, reading message bodies is not complex, especially if you are only using JMS byte or text messages. You will be glad to know that changing message bodies is no more complicated. If you recall that to obtain the message body, you invoke get("data/value") on the DataObject reference you get from the SIMessage, then you will not be surprised to learn that SIMessage has a corresponding setDataGraph(DataGraph dg, String format) method. Similarly, the DataGraph and DataObject classes also have setter methods. A code sample below illustrates how you could set the contents of a JMS text message.

Listing 13. Set contents of JMS text message
try {
    DataGraph dg = siMsg.getDataGraph();                
    dg.getRootObject().setString("data/value", msgText);
    siMsg.setDataGraph(dg, siMsg.getFormat();
    } catch (Exception e){
	// The actual exceptions that can be throw by the setDataGraph method are :
         // SIMessageDomainNotSupportedException, SIDataGraphSchemaNotFoundException,
         // SIDataGraphFormatMismatchException, SIMessageException
    }

We will now put this concept into practice by writing a simple mediation handler to modify messages; the mediation will perform a simple search and replace of text messages. The text to be replaced and actual replacement text will be configured in the deployment descriptor. (If we were to use a similar mediation in a production environment, it would be better to configure the mediation using context properties.) Our example also makes use of our MediationLogWriter to log before and after images of the message.

  1. Add the mediation handler class, shown in Listing 14, to the MediationHandlers project.

    Listing 14. Mediation handler class

    Click to see code listing

    Listing 14. Mediation handler class

    package mediation.handlers;
    
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.xml.rpc.handler.MessageContext;
    import com.ibm.websphere.sib.SIApiConstants;
    import com.ibm.websphere.sib.SIMessage;
    import com.ibm.websphere.sib.mediation.handler.MediationHandler;
    import com.ibm.websphere.sib.mediation.handler.MessageContextException;
    import com.ibm.websphere.sib.mediation.messagecontext.SIMessageContext;
    import commonj.sdo.DataGraph;
    
    public class ReplaceTextMediation implements MediationHandler {
        
      private final static String className = ReplaceTextMediation.class.getName();
      private final Logger logger = Logger.getLogger(className);
      private final MediationLogWriter log = new MediationLogWriter(logger, Level.INFO);
      private String textToReplace = "Message";
      private String replacementText = "MediatedMessage";
    
      public boolean handle(MessageContext ctx) throws MessageContextException {
        log.writeMediationInvoked((SIMessageContext)ctx);
        SIMessage siMsg = ((SIMessageContext)ctx).getSIMessage();
        if (siMsg.getFormat().equals(SIApiConstants.JMS_FORMAT_TEXT)) {
           try {
              DataGraph dg = siMsg.getDataGraph();                
              if (dg.getRootObject().isSet("data/value")){
                  String msgText = dg.getRootObject().getString("data/value");
                  if (msgText.indexOf(textToReplace) >= 0) {
                      log.writeMessageBody((SIMessageContext)ctx);
                      msgText = msgText.replaceAll(textToReplace, replacementText);
                      dg.getRootObject().setString("data/value", msgText);
                      siMsg.setDataGraph(dg, siMsg.getFormat());
                      log.writeMessageBody((SIMessageContext)ctx);
                  }
              }
            } catch (Exception e) {
              logger.log(Level.SEVERE, "Could not process message due to exception", e);
            }
        }else{
          logger.log(Level.INFO, "Message {0} not changed becuase it is not a text message", siMsg.getApiMessageId());
        }
        return true;
      }
    
        public String getReplacementText() {
            return replacementText;
        }
        public void setReplacementText(String replacementText) {
            this.replacementText = replacementText;
        }
        public String getTextToReplace() {
            return textToReplace;
        }
        public void setTextToReplace(String textToReplace) {
            this.textToReplace = textToReplace;
        }
    }
  2. Compile and assemble the handler class into the DeployableMediation project as a new mediation handler, called ReplaceTextMediation.
  3. Publish the project and run the supplied jacl script, ReplaceTextMediationAdmin.jacl, to create the mediation and required messaging resources.
  4. Restart the server.
  5. Send a message to jms/replaceTextQueue using jms/CF5 and include the text Message in the message body. The application server log should contain three log entries similar to those below:

    Listing 15. Application server log entries
    [27/05/05 12:14:43:165 BST] 00000064 ReplaceTextMe I   Mediation invoked 
    	(Msg ID:82f4131b80304e398e1dfbbb110a134f0000000000000001 
    	Mediation: ReplaceTextMediation Destination: sampleMediationQueue)
    [27/05/05 12:14:49:346 BST] 00000064 ReplaceTextMe I   
    	Msg ID:82f4131b80304e398e1dfbbb110a134f0000000000000001 
    	Mediation: ReplaceTextMediation Destination:sampleMediationQueue
    JMS TextMessage : Message text
    JMS TextMessage, as bytes :
    0000:  4d65 7373 6167 6520 7465 7874              Message text
    [27/05/05 12:14:49:388 BST] 00000064 ReplaceTextMe I   
    	Msg ID:82f4131b80304e398e1dfbbb110a134f0000000000000001 
    	Mediation: ReplaceTextMediation Destination:sampleMediationQueue
    JMS TextMessage : MediatedMessage text
    JMS TextMessage, as bytes :
    0000:  4d65 6469 6174 6564 4d65 7373 6167 6520    MediatedMessage
    0010:  7465 7874                                  text

Conclusion

Part 3 of this series of articles explained how to read and change message payloads using a mediation. The ability to transform messages as they are routed between applications is a very powerful one, enabling you to connect two applications that require messages in different formats and possibly even different types of JMS messages.

The simple ReplaceTextMediation example illustrates a simply way to change text messages. Exactly how you change your message bodies and what you change them to will depend on the types and contents of JMS messages you use.

This article did not show you how to mediate a Web service message. To do so would have required us to configure the Web Services Gateway and other resources. The WebSphere Application Server Information Center contains information about the mapping of Web service messages to SDO messages. Keep an eye out for an article on Web service message mediation coming soon.

In the next installment, we will see how to perform more complex message transformations using an XSLT-based mediation.


Acknowledgements

Once again, I would like to thank my colleagues Alasdair Nottingham and Dave Vines for helping with the review of this article, and for donating the byte formatting algorithm.


Download

DescriptionNameSize
Code samplemediatingMessages3.zip  ( HTTP | FTP )8 KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into WebSphere on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=WebSphere
ArticleID=85591
ArticleTitle=IBM WebSphere Developer Technical Journal: A practical introduction to message mediation -- Part 3
publish-date=06152005