Creating a message processing or output node in C

Before you start

WebSphere Message Broker provides the source for two sample user-defined nodes called SwitchNode and TransformNode. You can use these nodes in their current state, or you can modify them. In addition you can view the User-defined Extension sample which demonstrate the use of user-defined nodes, including a message processing node written in C.

A loadable implementation library, or a LIL, is the implementation module for a C node (or parser). A LIL is implemented as a dynamic link library (DLL). It has the file extension .lil, not .dll.

The implementation functions that have to be written by the developer are listed in C node implementation functions. The utility functions that are provided by WebSphere Message Broker to aid this process are listed in C node utility functions.

WebSphere Message Broker provides the source for two sample user-defined nodes called SwitchNode and TransformNode. You can use these nodes in their current state, or you can modify them. There is also the User-defined Extension sample sample for you to use.

Conceptually, a message processing node is used to process a message in some way, and an output node is used to output a message as a bit stream. However, when you code a message processing node or an output node, they are essentially the same thing. You can perform message processing within an output node, and likewise you can output a message to a bit stream using a message processing node. For simplicity, this topic mainly refers to the node as a message processing node, however, it discusses the functionality of both types of node.

The functions of both types of node are covered in this topic. It outlines the following steps:
  1. Declaring and defining your node
  2. Creating an instance of the node
  3. Setting attributes
  4. Implementing the node functionality
  5. Deleting an instance of the node

Declaring and defining your node

To declare and define a user-defined node to the broker you must include an initialization function, bipGetMessageflowNodeFactory, in your LIL. The following steps take place on the configuration thread and outline how the broker calls your initialization function and how your initialization function declares and defines the user-defined node:

  1. The initialization function, bipGetMessageflowNodeFactory, is called by the broker after the LIL has been loaded and initialized by the operating system. The broker calls this function to understand what your LIL is able to do and how the broker should call the LIL. For example:
    CciFactory LilFactoryExportPrefix * LilFactoryExportSuffix
    bipGetMessageflowNodeFactory()
  2. The bipGetMessageflowNodeFactory function must then call the utility function cniCreateNodeFactory. This function passes back a factory name (or group name) for all the nodes that your LIL supports. The factory name (or group name) must be unique throughout all the LILs in the broker.
  3. The LIL must then call the utility function cniDefineNodeClass to pass the unique name of each node and a virtual function table of the addresses of the implementation functions.
    For example, the following code declares and defines a single node called MessageProcessingxNode:
    {
    	CciFactory* factoryObject;
    	int rc = 0;
    	CciChar factoryName[] = L"MyNodeFactory";
    	CCI_EXCEPTION_ST exception_st;
    
    	/* Create the Node Factory for this node */
    	factoryObject = cniCreateNodeFactory(0, factoryName);
    	if (factoryObject == CCI_NULL_ADDR) {
    		/* Any local error handling can go here */
    	}
    	else {
    		/* Define the nodes supported by this factory */
    	static CNI_VFT vftable = {CNI_VFT_DEFAULT};
    
    	/* Setup function table with pointers to node implementation functions */
    	vftable.iFpCreateNodeContext = _createNodeContext;
    	vftable.iFpDeleteNodeContext = _deleteNodeContext;
    	vftable.iFpGetAttributeName2 = _getAttributeName2;
    	vftable.iFpSetAttribute      = _setAttribute;
    	vftable.iFpGetAttribute2     = _getAttribute2;
    	vftable.iFpEvaluate          = _evaluate;
    
    	cniDefineNodeClass(0, factoryObject, L"MessageProcessingxNode", &vftable);
    
    	}
    
    	/* Return address of this factory object to the broker */
    	return(factoryObject);
    }

    A user-defined node identifies itself as providing the capability of a message processing or output node by implementing the cniEvaluate function. User-defined nodes have to either implement a cniEvaluate or a cniRun implementation function, otherwise the broker does not load the user-defined node, and the cniDefineNodeClass utility function fails, returning CCI_MISSING_IMPL_FUNCTION.

    When a message flow containing a user-defined message processing node is deployed successfully, the node's cniEvaluate function is called for each message propagated to the node.

    Message flow data is received at the input terminal of the node, that is, the message, global environment, local environment, and exception list.

    For example:
    void cniEvaluate(                
      CciContext* context,                
      CciMessage* destinationList,        
      CciMessage* exceptionList,          
      CciMessage* message                 
    ){                                    
      ...
    }
    For the minimum code required to compile a C user-defined node, see C skeleton code.

Creating an instance of the node

The following procedure shows you how to instantiate your node:

  1. When the broker has received the table of function pointers, it calls the function cniCreateNodeContext for each instantiation of the user-defined node. If you have three message flows that are using your user-defined node, your cniCreateNodeContext function is called for each of them. This function should allocate memory for that instantiation of the user-defined node to hold the values for the configured attributes. For example:
    1. The user function cniCreateNodeContext is called:
      CciContext* _Switch_createNodeContext(
        CciFactory* factoryObject,
        CciChar*    nodeName,
        CciNode*    nodeObject
      ){
        static char* functionName = (char *)"_Switch_createNodeContext()";
        NODE_CONTEXT_ST* p;
        CciChar          buffer[256];
      
      
    2. Allocate a pointer to the local context and clear the context area:
        p = (NODE_CONTEXT_ST *)malloc(sizeof(NODE_CONTEXT_ST));
      
        if (p) {
           memset(p, 0, sizeof(NODE_CONTEXT_ST));
    3. Save the node object pointer in the context:
         p->nodeObject = nodeObject;
    4. Save the node name:
       CciCharNCpy((CciChar*)&p->nodeName, nodeName, MAX_NODE_NAME_LEN);
    5. Return the node context:
      return (CciContext*) p;
  2. The broker calls the appropriate utility functions to find out about the node's input terminals and output terminals. A node has a number of input terminals and output terminals associated with it. Within the user function cniCreateNodeContext, calls should be made to cniCreateInputTerminal and cniCreateOutputTerminal to define the user node's terminals. These functions must be invoked within the cniCreateNodeContext implementation function. For example, to define a node with one input terminal and two output terminals:
        {
          const CciChar* ucsIn = CciString("in", BIP_DEF_COMP_CCSID) ;
          insInputTerminalListEntry(p, (CciChar*)ucsIn);
          free((void *)ucsIn) ;
        }
        {
          const CciChar* ucsOut = CciString("out", BIP_DEF_COMP_CCSID) ;
          insOutputTerminalListEntry(p, (CciChar*)ucsOut);
          free((void *)ucsOut) ;
        }
        {
          const CciChar* ucsFailure = CciString("failure", BIP_DEF_COMP_CCSID) ;
          insOutputTerminalListEntry(p, (CciChar*)ucsFailure);
          free((void *)ucsFailure) ;
        }
    For the minimum code required to compile a C user-defined node, see C skeleton code.

Setting attributes

Attributes are set whenever you start the broker, or when you redeploy a message flow with new values. Attributes are set by the broker calling user code on the configuration thread. The user code needs to store these attributes in its node context area, for use when processing messages later.

Following the creation of input and output terminals, the broker calls the cniSetAttribute function to pass the values for the configured attributes for this instantiation of the user-defined node. For example:
    {
      const CciChar* ucsAttr = CciString("nodeTraceSetting", BIP_DEF_COMP_CCSID) ;
      insAttrTblEntry(p, (CciChar*)ucsAttr, CNI_TYPE_INTEGER);
      _setAttribute(p, (CciChar*)ucsAttr, (CciChar*)constZero);
      free((void *)ucsAttr) ;
    }
    {
      const CciChar* ucsAttr = CciString("nodeTraceOutfile", BIP_DEF_COMP_CCSID) ;
      insAttrTblEntry(p, (CciChar*)ucsAttr, CNI_TYPE_STRING);
      _setAttribute(p, (CciChar*)ucsAttr, (CciChar*)constSwitchTraceLocation);
      free((void *)ucsAttr) ;
    }
Start of changeThere is no limit to the number of configuration attributes that a node can have. However, a plug-in node must not implement an attribute that is already implemented as a base configuration attribute. These base attributes are:
  • label
  • userTraceLevel
  • traceLevel
  • userTraceFilter
  • traceFilter
End of change

Implementing the node functionality

When the broker retrieves a message from the queue and that message arrives at the input terminal of your user-defined message processing or output node, the broker calls the implementation function cniEvaluate. This function is called on the message processing thread and it should decide what to do with the message. This function might be called on multiple threads, especially if additional instances are used.

Deleting an instance of the node

In the event of a node being deleted, the broker calls the cniDeleteNodeContext function. This function must free up all resources used by your user-defined node. For example:

void _deleteNodeContext(
  CciContext* context
){
  static char* functionName = (char *)"_deleteNodeContext()";
  free ((void*) context);  return;
}