Creating an input node in Java

Before you start

Ensure that you have read and understood the following topics:

Creating a new Java project

You can create Java nodes from within the workbench. To do this, you must create a new Java project, as follows:
  1. Switch to the Java perspective.
  2. Click File > New > Project. Select Java from the left menu, and then select Java Project from the right menu.
  3. Give the project a name.

    The Java Settings panel is displayed.

  4. Select the Libraries tab, and click Add External JARs.
  5. Select install_dir\classes\jplugin2.jar.
  6. Follow the prompts on the other tabs to define any other build settings.
  7. Click Finish.
You can then develop the source for your Java node within this project.

Declaring the input node class

Any class that implements MbInputNodeInterface and is contained in the broker's LIL path is registered with the broker as an input node. When you implement MbInputNodeInterface, you also need to implement a run method for this class. The run method represents the start of the message flow, contains the data that formulates the message, and propagates it down the flow. The broker calls the run method when threads become available in accordance with your specified threading model.

For example, to declare the input node class:

package com.ibm.jplugins;

import com.ibm.broker.plugin.*;

public class BasicInputNode extends MbInputNode implements MbInputNodeInterface
{
...
You can do this in the workbench as follows:
  1. Click File > New > Class.
  2. Set the package and class name fields to appropriate values.
  3. Delete the text in the Superclass text field and click the Browse button
  4. Select MbInputNode.
  5. Click the Add button next to Interfaces text field, and select MbInputNodeInterface.
  6. Click Finish.

Defining the node constructor

When the node is instantiated, the constructor of the user's node class is called. This is where you create the terminals of the node, and initialize any default values for the attributes.

An input node has a number of output terminals associated with it, but does not typically have any input terminals. Use the createOutputTerminal method to add output terminals to a node when the node is instantiated. For example, to create a node with three output terminals:

public BasicInputNode() throws MbException
{
	createOutputTerminal ("out");
	createOutputTerminal ("failure");
	createOutputTerminal ("catch");
	setAttribute ("firstParserClassName","myParser");
	attributeVariable  = "none"; 
}

Receiving external data into a buffer

An input node can receive data from any type of external source, such as a file system, a queue or a database, in the same way as any other Java program, as long as the output from the node is in the correct format.

You provide an input buffer (or bit stream) to contain input data, and associate it with a message object. You create a message from a byte array using the createMessage method of the MbInputNode class, and then generate a valid message assembly from this message. For details of these methods, see theJava API. For example, to read the input from a file:

  1. Create an input stream to read from the file:
    FileInputStream inputStream = new FileInputStream("myfile.msg");
  2. Create a byte array the size of the input file:
    byte[] buffer = new byte[inputStream.available()];
  3. Read from the file into the byte array:
    inputStream.read(buffer);
  4. Close the input stream:
    inputStream.close();
  5. Create a message to put on the queue:
    MbMessage msg = createMessage(buffer);
  6. Create a new message assembly to hold this message:
    msg.finalizeMessage(MbMessage.FINALIZE_VALIDATE);
    MbMessageAssembly newAssembly =
         new MbMessageAssembly(assembly, msg);

Propagating the message

When you have created a message assembly, you can then propagate it to one of the node's terminals.

For example, to propagate the message assembly to the "out" terminal :
MbOutputTerminal out = getOutputTerminal("out");
out.propagate(newAssembly);
To delete the message:
msg.clearMessage();

To clear the memory that is allocated for the message tree, call the clearMessage() function within the final try/catch block.

Controlling threading and transactionality

The broker infrastructure handles transaction issues such as controlling the commit of any WebSphere MQ or database unit of work when message processing has completed. However, resources modified from within a user-defined node will not necessarily be under the transactional control of the broker.

Each message flow thread is allocated from a pool of threads maintained for each message flow, and starts execution in the run method.

The user-defined node uses return values to indicate whether a transaction has been successful, to control whether transactions are committed or rolled-back, and to control when the thread is returned to the pool. Any unhandled exceptions are caught by the broker infrastructure, and the transaction is rolled back.

You determine the behavior of transactions and threads using an appropriate return value from the following:

MbInputNode.SUCCESS_CONTINUE
The transaction is committed and the broker calls the run method again using the same thread.
MbInputNode.SUCCESS_RETURN
The transaction is committed and the thread is returned to the thread pool, assuming that it is not the only thread for this message flow.
MbInputNode.FAILURE_CONTINUE
The transaction is rolled back and the broker calls the run method again using the same thread.
MbInputNode.FAILURE_RETURN
The transaction is rolled back and the thread is returned to the thread pool, assuming that it is not the only thread for this message flow.
MbInputNode.TIMEOUT
The run method must not block indefinitely while waiting for input data to arrive. While the flow is blocked by user code, you cannot shutdown or reconfigure the broker. The run method must yield control to the broker periodically by returning from the run method. If input data has not been received after a certain period (for example, 5 seconds), the method should return with the TIMEOUT return code. Assuming that the broker does not need to reconfigure or shutdown, the input node's run method gets called again straight away.
To create multithreaded message flows, you call the dispatchThread method after a message has been created, but before the message is propagated to an output terminal. This ensures that only one thread is waiting for data while other threads are processing the message. New threads are obtained from the thread pool up to the maximum limit specified by the additionalInstances attribute of the message flow. For example:
public int run( MbMessageAssembly assembly ) throws MbException
{
  byte[] data = getDataWithTimeout();  // user supplied method
                                       // returns null if timeout
  if( data == null )
    return TIMEOUT;

  MbMessage msg = createMessage( data );
  msg.finalizeMessage( MbMessage.FINALIZE_VALIDATE );
  MbMessageAssembly newAssembly =
       new MbMessageAssembly( assembly, msg );

  dispatchThread();

  getOutputTerminal( "out" ).propagate( newAssembly );

  return SUCCESS_RETURN;
}

Declaring the node name

You need to declare the name of the node as it will be identified by the workbench. All node names must end with "Node". You declare the name using the following method:

public static String getNodeName()
{
   return "BasicInputNode";
}
If this method is not declared, the Java API framework creates a default node name using the following rules:
  • The class name is appended to the package name.
  • The dots are removed, and the first letter of each part of the package and class name are capitalized.
For example, by default, the following class is assigned the node name "ComIbmPluginsamplesBasicInputNode":
package com.ibm.pluginsamples;
public class BasicInputNode extends MbInputNode implements MbInputNodeInterface
{
   ...

Declaring attributes

You declare node attributes in the same way as Java Bean properties. You are responsible for writing getter and setter methods for the attributes, and the API framework infers the attribute names using the Java Bean introspection rules. For example, if you declare the following two methods:

private String attributeVariable;

public String getFirstAttribute()
{
  return attributeVariable;
}

publc void setFirstAttribute(String value)
{
  attributeVariable = value;
}

The broker infers that this node has an attribute called firstAttribute. This name is derived from the names of the get or set methods, not from any internal class member variable names. Attributes can only be exposed as strings, so you must convert any numeric types to and from strings in the get or set methods. For example, the following method defines an attribute called timeInSeconds:

int seconds;

public String getTimeInSeconds()
{
  return Integer.toString(seconds);
}

public void setTimeInSeconds(String value)
{
  seconds = Integer.parseInt(value);
}

Implementing the node functionality

As already described, the run method is called by the broker to create the input message. This method should provide all the processing function for the input node.

Overriding default message parser attributes (optional)

An input node implementation normally determines what message parser initially parses an input message. For example, the primitive MQInput node dictates that an MQMD parser is required to parse the MQMD header. A user-defined input node can select an appropriate header or message parser, and the mode in which the parsing is controlled, by using the following attributes that are included as default, which you can override:

rootParserClassName
Defines the name of the root parser that parses message formats supported by the user-defined input node. It defaults to GenericRoot, a supplied root parser that causes the broker to allocate and chain parsers together. It is unlikely that a node would need to modify this attribute value.
firstParserClassName
Defines the name of the first parser, in what might be a chain of parsers that are responsible for parsing the bitstream. It defaults to XML.
messageDomainProperty
An optional attribute that defines the name of the message parser required to parse the input message. The supported values are the same as those supported by the MQInput node. (See MQInput node for more information about the MQInput node.)
messageSetProperty
An optional attribute that defines the message set identifier, or the message set name, in the Message Set field, only if the MRM parser was specified by the messageDomainProperty attribute.
messageTypeProperty
An optional attribute that defines the identifier of the message in the MessageType field, only if the MRM parser was specified by the messageDomainProperty attribute.
messageFormatProperty
An optional attribute that defines the format of the message in the Message Format field, only if the MRM parser was specified by the messageDomainProperty attribute.

Deleting an instance of the node

An instance of the node is deleted when either:
  • You shutdown the broker.
  • You remove the node or the message flow containing the node, and redeploy the configuration.
During node deletion, the node might want to be informed so that it can perform any cleanup operations, such as closing sockets. If the node implements the optional onDelete method, this is called by the broker just before the node is deleted.

You implement the onDelete method as follows:

public void onDelete()
{
  // perform node cleanup if necessary
}
Related information
Java API