Extending the capability of a Java message processing or output node

Before you start

Ensure that you have read and understood the following topic:

Accessing message data

In many cases, the user-defined node needs to access the contents of the message received on its input terminal. The message is represented as a tree of syntax elements. Groups of utility functions are provided for message management, message buffer access, syntax element navigation, and syntax element access.

The MbElement class provides the interface to the syntax elements. For further details of the Java API, see the Javadoc.

For example:

  1. To navigate to the relevant syntax element in the XML message:
        MbElement rootElement = assembly.getMessage().getRootElement();
        MbElement switchElement = 
    			rootElement.getLastChild().getFirstChild().getFirstChild();
  2. To select the terminal indicated by the value of this element:
        String terminalName;
        String elementValue = (String)switchElement.getValue();
        if(elementValue.equals("add"))
          terminalName = "add";
        else if(elementValue.equals("change"))
          terminalName = "change";
        else if(elementValue.equals("delete"))
          terminalName = "delete";
        else if(elementValue.equals("hold"))
          terminalName = "hold";
        else
          terminalName = "failure";
        
        MbOutputTerminal out = getOutputTerminal(terminalName);

Transforming a message object

The received input message is read-only, so before a message can be transformed, you must write it to a new output message. You can copy elements from the input message, or you can create new elements and attach them to the message. New elements are generally in a parser's domain.

The MbMessage class provides the copy constructors, and methods to get the root element of the message. The MbElement class provides the interface to the syntax elements.

For example, where you have an incoming message assembly with embedded messages:
  1. Create a new copy of the message assembly and its embedded messages:
        MbMessage newMsg = new MbMessage(assembly.getMessage());
        MbMessageAssembly newAssembly = new MbMessageAssembly(assembly, newMsg);
  2. Navigate to the relevant syntax element in the XML message:
        MbElement rootElement = newAssembly.getMessage().getRootElement();
        MbElement switchElement = 
    			rootElement.getFirstElementByPath("/XML/data/action");
  3. Change the value of an existing element:
      String elementValue = (String)switchElement.getValue();
        if(elementValue.equals("add"))
          switchElement.setValue("change");
        else if(elementValue.equals("change"))
          switchElement.setValue("delete");
        else if(elementValue.equals("delete"))
          switchElement.setValue("hold");
        else
          switchElement.setValue("failure");
  4. Add a new tag as a child of the switch tag:
        MbElement tag = switchElement.createElementAsLastChild(MbElement.TYPE_NAME,
                                                               "PreviousValue",
                                                               elementValue);
  5. Add an attribute to this new tag:
        tag.createElementAsFirstChild(MbElement.TYPE_NAME_VALUE,
                                      "NewValue",
                                      switchElement.getValue());
    
        MbOutputTerminal out = getOutputTerminal("out");
As part of the transformation it might be necessary to create a new message body. To create a new message body, the following methods are available:
createElementAfter
createElementAsFirstChild
createElementAsLastChild
createElementBefore
createElementAsLastChildFromBitstream
These methods should be used because they are specific for assigning a parser to a message tree folder.
When creating a message body, do not use the following methods because they do not associate an owning parser with the folder:
createElementAfter
createElementAfter
createElementAsFirstChild
createElementAsFirstChild
createElementAsLastChild
createElementAsLastChild
createElementBefore
createElementBefore 

Accessing ESQL

Nodes can invoke ESQL expressions using Compute node ESQL syntax. You can create and modify the components of the message using ESQL expressions, and you can refer to elements of both the input message and data from an external database.

The following procedure demonstrates how to control transactions in your user-defined node using ESQL:
  1. Set the name of the ODBC data source to use. For example:
    String dataSourceName = "myDataSource";
  2. Set the ESQL statement to execute:
    String statement = 
       "SET OutputRoot.XML.data = 
              (SELECT Field2 FROM Database.Table1 WHERE Field1 = 1);";
    Or, if you want to execute a statement that returns no result:
    String statement = "PASSTHRU(
                            'INSERT INTO Database.Table1 VALUES(
                                 InputRoot.XML.DataField1,
                                 InputRoot.XML.DataField2)');";
  3. Select the type of transaction you want from the following:
    MbSQLStatement.SQL_TRANSACTION_COMMIT
    Immediately commit the transaction upon execution of the ESQL statement.
    MbSQLStatement.SQL_TRANSACTION_AUTO
    Commit the transaction when the message flow has completed. (Rollbacks are performed if necessary.)
    For example:
    int transactionType = MbSQLStatement.SQL_TRANSACTION_AUTO;
  4. Get the ESQL statement. For example:
    MbSQLStatement sql = 
           createSQLStatement(dataSourceName, statement, transactionType);
    You can use the method createSQLStatement(dataSource, statement) to default the transaction type to MbSQLStatement.SQL_TRANSACTION_AUTO).
  5. Create the new message assembly to be propagated:
    MbMessageAssembly newAssembly = 
           new MbMessageAssembly(assembly, assembly.getMessage());
  6. Execute the ESQL statement:
    sql.select(assembly, newAssembly);
    Or, if you want to execute an ESQL statement that returns no result:
    sql.execute(assembly);

For more information about ESQL, see ESQL overview.

Handling exceptions

You use the mbException class to catch and access exceptions. The mbException class returns an array of exception objects representing the children of an exception in the broker exception list. Each element returned specifies its exception type. An empty array is returned if an exception has no children. The following code sample shows an example of the usage of the MbException class.

public void evaluate(MbMessageAssembly assembly, MbInputTerminal inTerm) throws MbException
  {
    try
      {

        // plug-in functionality

      }
    catch(MbException ex)
      {
        traverse(ex, 0);

        throw ex; // if re-throwing, it must be the original exception that was caught
      }
  }

  void traverse(MbException ex, int level)
  {
    if(ex != null)
      {
        // Do whatever action here
        System.out.println("Level: " + level);
        System.out.println(ex.toString());
        System.out.println("traceText:  " + ex.getTraceText());

        // traverse the hierarchy
        MbException e[] = ex.getNestedExceptions();
        int size = e.length;
        for(int i = 0; i < size; i++)
          {
            traverse(e[i], level + 1);
          }
      }
  }

Refer to the javadoc for more details of using the mbException class.

You can develop a user-defined message processing or output node in such a way that it can access all current exceptions. For example, to catch database exceptions you can use the MbSQLStatement class. This class sets the value of the 'throwExceptionOnDatabaseError' attribute, which determines broker behavior when it encounters a database error. When it is set to true, if an exception is thrown, it can be caught and handled by the user-defined extension.

The following code sample shows an example of how to use the MbSQLStatement class.

public void evaluate(MbMessageAssembly assembly, MbInputTerminal inTerm) throws MbException
  {
    MbMessage newMsg = new MbMessage(assembly.getMessage());
    MbMessageAssembly newAssembly = new MbMessageAssembly(assembly, newMsg);

    String table = 
       assembly.getMessage().getRootElement().getLastChild().getFirstChild().getName();

    MbSQLStatement state = createSQLStatement( "dbName", 
       "SET OutputRoot.XML.integer[] = PASSTHRU('SELECT * FROM " + table + "');" );

    state.setThrowExceptionOnDatabaseError(false);
    state.setTreatWarningsAsErrors(true);

    state.select( assembly, newAssembly );

    int sqlCode = state.getSQLCode();
    if(sqlCode != 0)
      {
        // Do error handling here

        System.out.println("sqlCode = " + sqlCode);
        System.out.println("sqlNativeError = " + state.getSQLNativeError());
        System.out.println("sqlState = " + state.getSQLState());
        System.out.println("sqlErrorText = " + state.getSQLErrorText());
      }

    getOutputTerminal("out").propagate(assembly);
  }

Propagating the message

Before you propagate a message, you have to decide what message flow data you want to propagate, and which of the node's terminals is to receive the data. You should finalize the message before you propagate it. After propagating a message, you must delete the output message.

For example:
  1. To propagate the message to the output terminal "out":
    MbOutputTerminal out = getOutputTerminal("out");
            out.propagate(newAssembly);
  2. To delete the output message:
      newMsg.clearMessage();	

Writing to an output device

To write to an output device, the logical (hierarchical) message needs to be converted back into a bitstream. You do this using the getBuffer method in MbMessage, as follows:

public void evaluate( MbMessageAssembly assembly, MbInputTerminal in)
                                                     throws MbException
{
  MbMessage msg = assembly.getMessage();
  byte[] bitstream = msg.getBuffer();

  // write the bitstream out somewhere
  writeBitstream( bitstream );   // user method

 }

Typically, for an output node the message is not propagated to any output terminal, so you can just return at this point.

Note: You must use the supplied MQOutput node when writing to WebSphere MQ queues, because the broker internally maintains a WebSphere MQ connection and open queue handles on a thread-by-thread basis, and these are cached to optimize performance. In addition, the broker handles recovery scenarios when certain WebSphere MQ events occur, and this would be adversely affected if WebSphere MQ MQI calls were used in a user-defined output node.
Related reference
Exception list structure
Related information
Java user-defined node API