Creación de un nodo de entrada en C

Antes de empezar

Una biblioteca de implementación cargable, o una LIL, es el módulo de implementación para un nodo C (o analizador). Una LIL se implementa como una biblioteca de enlace dinámico (DLL). No tiene la extensión de archivo .dll, sino la extensión .lil.

Las funciones de implementación que debe escribir el desarrollador están enumeradas en el apartado Funciones de implementación de nodo de lenguaje C. Las funciones de programa de utilidad que proporciona WebSphere Business Integration Message Broker para facilitar este proceso están enumeradas en el apartado Funciones de programa de utilidad de nodo de lenguaje C.

WebSphere Business Integration Message Broker proporciona el origen para dos nodos de ejemplo definidos por el usuario, llamados SwitchNode y TransformNode. Puede utilizar estos nodos en su estado actual, o puede modificarlos.

Recepción de datos externos en un almacenamiento intermedio

Un nodo de entrada puede recibir datos procedentes de cualquier tipo de origen externo, como un sistema de archivos o conexiones FTP, siempre que la salida del nodo tenga el formato correcto. Para las conexiones a colas o bases de datos, debe utilizar los nodos primitivos de IBM y las llamadas API suministradas, principalmente porque los nodos primitivos ya están configurados para el manejo de errores. No utilice los mandatos mqget o mqput para acceder directamente a las tablas de base de datos.

Debe proporcionar un almacenamiento intermedio de entrada (o corriente de bits) para contener datos de entrada y asociarlos con un objeto de mensaje. En la API C, el almacenamiento intermedio está conectado al objeto CciMessage que representa el mensaje de entrada mediante la función de programa de utilidad cniSetInputBuffer. Por ejemplo:
{
  static char* functionName = (char *)"_Input_run()";
  void*        buffer;
  CciTerminal* terminalObject;
  int          buflen = 4096;
  int          rc = CCI_SUCCESS;
  int          rcDispatch = CCI_SUCCESS;
  char         xmlData[] = "<A>data</A>";

  buffer = malloc(buflen);
  memcpy(buffer, &xmlData, sizeof(xmlData));
  cniSetInputBuffer(&rc, message, buffer, buflen);
}
/*propagar, etc*/

free(buffer);
El ejemplo anterior ilustra un área de memoria que se va a asignar (buffer = malloc(buflen);). Cuando programe en C, siempre que asigne memoria deberá liberarla cuando ya no la necesite. El intermediario puede intentar acceder a esta memoria en cualquier momento mientras el mensaje se propaga a través del flujo, así que debe liberar la memoria sólo después de invocar cniPropagate en el mismo objeto CciMessage.

Control del trabajo con hebras y las transacciones

Un nodo de entrada tiene la responsabilidad de finalizar el proceso de mensajes correctamente cuando un mensaje se ha propagado a través de un flujo de mensajes. Específicamente, el nodo de entrada debe hacer que se confirmen o restituyan las transacciones, y debe devolver las hebras a la agrupación de hebras.

Cada hebra de flujo de mensajes se asigna desde una agrupación de hebras mantenida para cada flujo de mensajes, y empieza a ejecutarse en la función cniRun. El usuario determina el comportamiento de una hebra utilizando la función de programa de utilidad cniDispatchThread junto con el valor de retorno adecuado.

El término transacción se utiliza aquí de forma genérica para describir una transacción coordinada globalmente o una transacción controlada por intermediario. Las transacciones coordinadas globalmente se coordinan mediante WebSphere MQ como gestor de transacciones compatible con XA , o como servicio de recuperación de recursos (RRS) en z/OS. WebSphere Business Integration Message Broker controla las transacciones confirmando (o restituyendo) los recursos de base de datos y, a continuación, confirmando las unidades de trabajo de WebSphere MQ; no obstante, si se utiliza un nodo definido por el usuario, el intermediario no podrá confirmar automáticamente las actualizaciones de recursos. El nodo definido por el usuario utiliza valores de retorno para indicar si una transacción se ha procesado correctamente, y para controlar si las transacciones se confirman o restituyen. Las excepciones que no se hayan manejado las recibe la infraestructura de intermediario, y la transacción se restituye.

En la tabla siguiente se describe cada uno de los valores de retorno a los que se da soporte, el efecto que tienen en las transacciones, y las acciones que realiza el intermediario con la hebra actual.

Valor de retorno Efecto en transacción Acción realizada en la hebra por intermediario
CCI_SUCCESS_CONTINUE Confirmado Vuelve a invocar la misma hebra en la función cniRun.
CCI_SUCCESS_RETURN Confirmado Devuelve la hebra a la agrupación de hebras.
CCI_FAILURE_CONTINUE Restituido Vuelve a invocar la misma hebra en la función cniRun.
CCI_FAILURE_RETURN Restituido Devuelve la hebra a la agrupación de hebras.
CCI_TIMEOUT No aplicable. La función sobrepasa periódicamente su tiempo de espera mientras espera un mensaje de entrada. Vuelve a invocar la misma hebra en la función cniRun.
A continuación figura un ejemplo de cómo utilizar el código de retorno SUCCESS_RETURN con la función cniDispatchThread:
{
  ...
  cniDispatchThread(&rcDispatch, ((NODE_CONTEXT_ST *)context)->nodeObject);
  ...
  if (rcDispatch == CCI_NO_THREADS_AVAILABLE) return CCI_SUCCESS_CONTINUE;  
  else return CCI_SUCCESS_RETURN;
}     

Propagación del mensaje

Antes de propagar un mensaje, debe decidir qué datos de flujo de mensajes desea propagar, y qué terminal va a recibir los datos.

El objeto terminalObject se deriva de una lista que el nodo definido por el usuario mantiene para sí mismo.

Por ejemplo, para propagar el mensaje al terminal de salida, debe utilizar la función cniPropagate:
  if (terminalObject) {
    if (cniIsTerminalAttached(&rc, terminalObject)) {
      if (rc == CCI_SUCCESS) {
        cniPropagate(&rc, terminalObject, destinationList, exceptionList, message);
      }
    }

En el ejemplo anterior, la función cniIsTerminalAttached se utiliza para probar si el mensaje puede propagarse al terminal especificado. Si no utiliza la función cniIsTerminalAttached, y el terminal no está conectado a otro nodo mediante un conector, el mensaje no se propagará. Si utiliza esta función, podrá modificar el comportamiento del nodo cuando un terminal no esté conectado.

Inicialización del nodo

El procedimiento siguiente muestra cómo inicializar el nodo:

  1. El intermediario invoca la función de inicialización bipGetMessageflowNodeFactory una vez que el sistema operativo ha cargado e inicializado la LIL. El intermediario invoca esta función para saber qué puede hacer la LIL y cómo debe invocarla. Por ejemplo:
    CciFactory LilFactoryExportPrefix * LilFactoryExportSuffix
    bipGetMessageflowNodeFactory()
  2. A continuación, la función bipGetMessageflowNodeFactory invoca la función de programa de utilidad cniCreateNodeFactory. Esta función vuelve a pasar un nombre de fábrica (o nombre de grupo) para todos los nodos a los que la LIL da soporte. Por ejemplo:
    {
    	CciFactory* factoryObject;
    	int rc = 0;
    	CciChar factoryName[] = L"SwitchNodeFactory";
    	CCI_EXCEPTION_ST exception_st;
    
    /* Crear la fábrica del nodo para este nodo                      */
    	factoryObject = cniCreateNodeFactory(0, factoryName);
    	if (factoryObject == CCI_NULL_ADDR) {
    		if (rc == CCI_EXCEPTION) {
    /* Obtener detalles de la excepción                              */
    			cciGetLastExceptionData(&rc, &exception_st);
    
    /* El manejo de errores local puede incluirse aquí               */
    
    /* Volver a emitir la excepción                                  */
    			cciRethrowLastException(&rc);
    		}
    
    /* El manejo de errores local adicional puede incluirse aquí     */
    	}
    	else {
    /* Definir los nodos a los que da soporte esta fábrica           */
    		defineSwitchNode(factoryObject);
    	}
    
    /* Devolver dirección de este objeto de fábrica al intermediario */
    	return(factoryObject);
    }
  3. A continuación, la LIL debe invocar la función de programa de utilidad cniDefineNodeClass para pasar el nombre de cada nodo y una tabla de funciones virtuales de las direcciones de las funciones de implementación. Por ejemplo, para definir un solo nodo, llamado InputxNode, y su tabla de funciones correspondiente:
    void defineInputxNode(void* factoryObject){
    	static CNI_VFT vftable = {CNI_VFT_DEFAULT};
    
    	/* Configurar tabla de funciones con punteros a las funciones   */
     /* de implementación de nodo                                    */
    	vftable.iFpCreateNodeContext = _createNodeContext;
    	vftable.iFpDeleteNodeContext = _deleteNodeContext;
    	vftable.iFpGetAttributeName = _getAttributeName;
    	vftable.iFpSetAttribute = _setAttribute;
    	vftable.iFpGetAttribute = _getAttribute;
    	vftable.iFpRun = _run;
    
    	cniDefineNodeClass(0, factoryObject, L"InputxNode", &vftable);
    
    	return;
    }

Definición del nodo como nodo de entrada

Un nodo definido por el usuario se identifica como nodo que proporciona la capacidad de nodo de entrada mediante la implementación de la función de implementación cniRun. Los nodos de entrada definidos por el usuario deben implementar una función cniRun, de lo contrario, el intermediario no carga el nodo definido por el usuario y la función de programa de utilidad cniDefineNodeClass no se ejecuta correctamente y devuelve CCI_MISSING_IMPL_FUNCTION. Cuando un flujo de mensajes que contiene un nodo de entrada definido por el usuario se difunde correctamente, el intermediario invoca la función de implementación cniRun del nodo a intervalos regulares.

Por ejemplo:
int cniRun(                       
  CciContext* context,                
  CciMessage* destinationList,        
  CciMessage* exceptionList,          
  CciMessage* message
){          
  ...
  /* Obtener datos de origen externo                             */
  return CCI_SUCCESS_CONTINUE;                                    
}
El valor de retorno debe utilizarse para devolver, periódicamente, el control al intermediario.

Cuando un flujo de mensajes que contiene un nodo de entrada definido por el usuario se difunde correctamente, se invoca la función cniRun del nodo para cada mensaje que se pasa a través del flujo de mensajes.

Los nodos de entrada también pueden implementar la función cniEvaluate, no obstante, esto no es recomendable.

Creación de una instancia del nodo

El procedimiento siguiente muestra cómo crear una instancia del nodo:

  1. Cuando el intermediario recibe la tabla de punteros de función, invoca la función cniCreateNodeContext para cada instancia creada del nodo definido por el usuario. Si el usuario tiene tres flujos de mensajes que utilizan el nodo definido por el usuario, la función cniCreateNodeContext se invocará para cada uno de ellos. Esta función debe asignar memoria a dicha instancia del nodo definido por el usuario para que retenga los valores de los atributos configurados. Por ejemplo:
    1. Invoque la función cniCreateNodeContext:
      CciContext* _Switch_createNodeContext(
        CciFactory* factoryObject,
        CciChar*    nodeName,
        CciNode*    nodeObject
      ){
        static char* functionName = (char *)"_Switch_createNodeContext()";
        NODE_CONTEXT_ST* p;
        CciChar          buffer[256];
      
    2. Asigne un puntero al contexto local y borre el área de contexto:
        p = (NODE_CONTEXT_ST *)malloc(sizeof(NODE_CONTEXT_ST));
      
        if (p) {
           memset(p, 0, sizeof(NODE_CONTEXT_ST));
    3. Guarde el puntero de objeto de nodo en el contexto:
         p->nodeObject = nodeObject;
    4. Guarde el nombre de nodo:
       CciCharNCpy((CciChar*)&p->nodeName, nodeName, MAX_NODE_NAME_LEN);
  2. Un nodo de entrada está asociado a varios terminales de salida, pero normalmente no tiene ningún terminal de entrada. Utilice la función de programa de utilidad cniCreateOutputTerminal para añadir terminales de salida a un nodo de entrada cuando se creen instancias para dicho nodo. Estas funciones deben invocarse desde la función de implementación cniCreateNodeContext. Por ejemplo, para definir un nodo de entrada con tres terminales de salida:
       {
          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) ;
        }    
        {
          const CciChar* ucsCatch = CciString("catch", BIP_DEF_COMP_CCSID) ;
          insOutputTerminalListEntry(p, (CciChar*)ucsCatch);
          free((void *)ucsCatch) ;    }

Configuración de atributos

Los atributos se configuran siempre que se inicia el intermediario, o al volver a difundir el flujo de mensajes con nuevos valores.

Tras la creación de los terminales de salida, el intermediario invoca la función cniSetAttribute para pasar los valores para los atributos configurados del nodo definido por el usuario. Por ejemplo:
    {
      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) ;
    }

Implementación de la funcionalidad del nodo

Cuando el intermediario sabe que tiene un nodo de entrada, invoca la función cniRun de dicho nodo a intervalos regulares. La función cniRun debe entonces decidir las acciones que va a llevar a cabo. Si hay datos disponibles para procesarse, la función cniRun debe invocar cniDispatchThread y procesar el mensaje, o devolver CCI_TIMEOUT para que el intermediario pueda continuar procesando otros mensajes en otras hebras. Si no se envía una hebra, el intermediario invierte todo su tiempo en esta hebra, la cual le impide hacer ninguna otra cosa.

Por ejemplo, para configurar el nodo para que invoque cniDispatchThread y procese el mensaje, o devuelva CCI_TIMEOUT:
If ( anything to do )
	CniDispatchThread;

   /* hacer el trabajo                                           */

	If ( work done O.K.)
		Return CCI_SUCCESS_CONTINUE;
	Else
		Return CCI_FAILURE_CONTINUE;
Else
  Return CCI_TIMEOUT;

Alteración temporal de los atributos de analizador de mensajes por omisión (opcional)

Normalmente, la implementación de un nodo de entrada determina qué analizador de mensajes analiza inicialmente un mensaje de entrada. Por ejemplo, el nodo MQInput primitivo dicta que es necesario un analizador MQMD para analizar la cabecera MQMD. Un nodo de entrada definido por el usuario puede seleccionar una cabecera o un analizador de mensajes adecuado, y la modalidad en que se controlará el análisis, utilizando los atributos siguientes que se incluyen por omisión, pero que pueden alterarse temporalmente:

rootParserClassName
Define el nombre del analizador de raíz que analiza los formatos de mensaje a los que da soporte el nodo de entrada definido por el usuario. Este atributo toma por omisión GenericRoot, un analizador de raíz suministrado que hace que el intermediario asigne analizadores y los encadene juntos. Es improbable que un nodo necesite modificar este valor de atributo.
firstParserClassName
Define el nombre del primer analizador, en lo que puede ser una cadena de analizadores que son responsables de analizar la corriente de bits. Este atributo toma por omisión XML.
messageDomainProperty
Atributo opcional que define el nombre del analizador de mensajes necesario para analizar el mensaje de entrada. Los valores a los que se da soporte son los mismos que soporta el nodo MQInput (consulte el apartado Nodo MQInput si desea ver más información acerca del nodo MQInput).
messageSetProperty
Atributo opcional que define el identificador de conjunto de mensajes, o el nombre de conjunto de mensajes, en el campo Message Set, sólo si se ha especificado el analizador MRM mediante el atributo messageDomainProperty.
messageTypeProperty
Atributo opcional que define el identificador del mensaje en el campo MessageType, sólo si se ha especificado el analizador MRM mediante el atributo messageDomainProperty.
messageFormatProperty
Atributo opcional que define el formato del mensaje en el campo Message Format, sólo si se ha especificado el analizador MRM mediante el atributo messageDomainProperty.
Si el usuario ha escrito un nodo de entrada definido por el usuario que siempre empieza con datos de una estructura conocida, puede asegurarse de que algún analizador gestione el inicio de los datos. Por ejemplo, el nodo MQInputNode sólo lee datos de las colas de WebSphere MQ, así que estos datos tienen siempre un MQMD al principio, y el nodo MQInputNode establece firstParserClassName en MQHMD. Si el nodo definido por el usuario gestiona siempre datos que empiezan con una estructura que puede analizarla un analizador concreto, como por ejemplo "MYPARSER", el usuario debe establecer firstParserClassName en MYPARSER como se indica a continuación:
  1. Declare las funciones de implementación:
    CciFactory LilFactoryExportPrefix * LilFactoryExportSuffix bipGetMessageflowNodeFactory()
    {
      ....
      CciFactory*      factoryObject;
      ....
      factoryObject = cniCreateNodeFactory(0, (unsigned short *)constPluginNodeFactory);
      ...
      vftable.iFpCreateNodeContext = _createNodeContext;
      vftable.iFpSetAttribute      = _setAttribute;
      vftable.iFpGetAttribute      = _getAttribute;
      ...  
      cniDefineNodeClass(&rc, factoryObject, (CciChar*)constSwitchNode, &vftable);
      ...
      return(factoryObject);
    }
  2. Configure el atributo en la función de implementación cniCreateNodeContext:
    CciContext* _createNodeContext(
      CciFactory* factoryObject,
      CciChar*    nodeName,
      CciNode*    nodeObject
    ){
      NODE_CONTEXT_ST* p;
      ...
    
        /* Asignar un puntero al contexto local                      */
        p = (NODE_CONTEXT_ST *)malloc(sizeof(NODE_CONTEXT_ST));
        /* Crear atributos y establecer valores por omisión          */
        {
          const CciChar* ucsAttrName  = CciString("firstParserClassName", BIP_DEF_COMP_CCSID) ;
          const CciChar* ucsAttrValue = CciString("MYPARSER", BIP_DEF_COMP_CCSID) ;
          insAttrTblEntry(p, (CciChar*)ucsAttrName, CNI_TYPE_INTEGER);
          /*ver BipSampPluginNode.c de ejemplo para la               */
          /*implementación de insAttrTblEntry                        */
    
          _setAttribute(p, (CciChar*)ucsAttrName, (CciChar*)ucsAttrValue);
          free((void *)ucsAttrName) ;
          free((void *)ucsAttrValue) ;
        }

Supresión de una instancia del nodo

Los nodos se destruyen cuando un flujo de mensajes vuelve a difundirse o cuando el proceso de grupo de ejecución se detiene (mediante el mandato mqsistop). Al destruirse, el nodo debe liberar la memoria utilizada, así como los recursos retenidos. Para destruirlo, debe utilizarse la función cniDeleteNodeContext. Por ejemplo:

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

  return;
}

Conceptos relacionados
Planificación de extensiones definidas por el usuario
Extensiones definidas por el usuario en el entorno de ejecución
Diseño de extensiones definidas por el usuario
Nodos de entrada definidos por el usuario

Tareas relacionadas
Desarrollo de extensiones definidas por el usuario
Implementación de los ejemplos proporcionados
Compilación de una extensión definida por el usuario en C

Referencia relacionada
Nodo MQInput
API de nodo C definido por el usuario