Criando um Nó de Processamento de Mensagem em C

Antes de começar

Uma biblioteca de implementação carregável, ou uma LIL, é o módulo de implementação para um nó (ou analisador) em C.Uma LIL é implementada como uma DLL (Dynamic Link Library). Ela não tem a extensão de arquivo .dll, mas .lil.

As funções de implementação que precisam ser escritas pelo desenvolvedor estão relacionadas em Funções de Implementação de Nó em Linguagem C. As funções utilitárias fornecidas pelo para auxiliar esse processo estão relacionadas em Funções Utilitárias de Nó em Linguagem C.

oferece o código fonte de dois nós definidos pelo usuário chamados SwitchNode e TransformNode. Você pode utilizar esses nós em seus estados atuais ou poderá modificá-los.

Conceitualmente, um nó de processamento de mensagem é utilizado para processar uma mensagem de alguma forma, e um nó de saída é utilizado para enviar uma mensagem para a saída como um fluxo de bits. Entretanto, quando se codifica um nó de processamento de mensagem ou um nó de saída, eles são essencialmente a mesma coisa. É possível executar processamento de mensagem dentro de um nó de saída, e da mesma forma é possível enviar uma mensagem para a saída para um fluxo de bits utilizando um nó de processamento de mensagem. Para simplificar, este tópico refere-se principalmente ao nó como um nó de processamento de mensagem, no entanto, ele discute a funcionalidade dos dois tipos de nó.

Acessando os Dados da Mensagem

Em muitos casos, o nó definido pelo usuário necessita acessar o conteúdo da mensagem recebida em seu terminal de entrada. A mensagem é representada como uma árvore de elementos de sintaxe. Grupos de funções utilitárias são fornecidos para gerenciamento de mensagens, acesso a buffer de mensagem, navegação de elementos de sintaxe e acesso a elementos de sintaxe. (Consulte Funções Utilitárias de Nó em Linguagem C para obter detalhes sobre as funções de utilitário).

Os tipos de consulta que provavelmente você deseje executar incluem:
  • Obter o elemento raiz do objeto de mensagem requerido
  • Navegar ou consultar a árvore perguntando por elementos filhos ou irmãos pelo nome
  • Obter o tipo do elemento
  • Obter o valor do elemento

Por exemplo, para consultar o nome e o tipo do primeiro filho do corpo:

void cniEvaluate( ...
){                                    
  ...
/* Navegar até o elemento de destino */
  rootElement = cniRootElement(&rc, message);
  bodyElement = cniLastChild(&rc, rootElement);
  bodyFirstChild = cniFirstChild(&rc, bodyElement);

/* Consultar o nome e o valor do elemento de destino */
  cniElementName(&rc, bodyFirstChild, (CciChar*)&elementname, sizeof(elementName));
  bytes = cniElementCharacterValue(
		&rc, bodyfirstChild, (CciChar*)&eValue, sizeof(eValue));
  ...    
}

Transformando um Objeto Mensagem

A mensagem de entrada recebida é somente leitura, portanto antes que uma mensagem possa ser transformada é preciso gravá-la para uma nova mensagem de saída utilizando a função cniCreateMessage. É possível copiar elementos da mensagem de entrada ou criar novos elementos e anexá-los á mensagem. Os novos elementos em geral estão no domínio de um analisador.

Por exemplo:
  1. Para gravar a mensagem de entrada para uma nova mensagem:
    {
      ...
      context = cniGetMessageContext(&rc, message));
      outMsg = cniCreateMessage(&rc, context));
      ...
    }
  2. Para modificar o valor de um elemento de destino:
      cniSetElementIntegerValue(&rc, targetElement, L"newValue", 8); 
  3. Depois de finalizar e propagar a mensagem é preciso excluir a mensagem de saída utilizando a função cniDeleteMessage:
     cniDeleteMessage(&rc, outMsg);

Acessando ESQL

Os nós podem chamar expressões ESQL utilizando sintaxe ESQL de nó Compute. É possível criar e modificar os componentes da mensagem utilizando as expressões ESQL e referir-se a elementos da mensagem de entrada e aos dados de um banco de dados externo utilizando as funções cniSqlCreateStatement, cniSqlSelect, cniSqlDeleteStatement e cniSqlExecute.

Por exemplo, para ocupar o elemento Result do conteúdo de uma coluna em uma tabela de banco de dados:

{
  ...
  sqlExpr = cniSqlCreateStatement(&rc,
   (NODE_CONTEXT_ST *)context->nodeObject,
   L"DB", CCI_SQL_TRANSACTION_AUTO,
   L"SET OutputRoot.XML.Result[] = (SELECT T.C1 AS Col1 FROM Database.TABLE AS T;");
  ...
  cniSqlSelect(&rc, sqlExpr, destinationList, exceptionList, message, outMsg);
  cniSqlDeleteStatement(&rc, sqlExpr);
  ...                                                               
}

Para obter informações adicionais sobre ESQL, consulte ESQL.

Propagando uma Mensagem

Antes de propagar uma mensagem, você precisa decidir que dados do fluxo de mensagens deseja propagar, e que terminal deve receber os dados.
  1. Se mensagem foi alterada, ela deve ser finalizada antes de ser propagada, utilizando a função cniFinalize. Por exemplo:
          cniFinalize(&rc, outMsg, CCI_FINALIZE_NONE);
  2. O terminalObject é derivado de uma lista que o próprio nó definido pelo usuário mantém. Para propagar a mensagem para o terminal de saída, utilize a função cniPropagate:
      if (terminalObject) {
        if (cniIsTerminalAttached(&rc, terminalObject)) {
          if (rc == CCI_SUCCESS) {
            cniPropagate(&rc, terminalObject, destinationList, exceptionList, outMsg);
          }
        }
  3. Se tiver criado uma nova mensagem de saída utilizando cniCreateMessage, depois de propagar a mensagem, você deve excluir a mensagem de saída utilizando a função cniDeleteMessage:
     cniDeleteMessage(&rc, outMsg);

Gravando em um Dispositivo de Saída

Uma mensagem transformada precisa ser serializada para um fluxo de bits. O fluxo de bits pode então ser acessado e gravado para um dispositivo de saída. A mensagem é gravada para um fluxo de bits utilizando a função cniWriteBuffer. Por exemplo:
{
  ...
  cniWriteBuffer(&rc, message);
  writeToDevice(cniBufferPointer(&rc, message), cniBufferSize(&rc, message));
  ...                                                               
}
Uma mensagem somente pode ser serializada uma vez.
Nota: É preciso utilizar o nó MQOutput fornecido ao gravar para filas do , porque o intermediário mantém internamente uma conexão ao e o identificador de filas aberto em uma base encadeamento a encadeamento, e estes são armazenados em cache para otimizar o desempenho. Além disso, o intermediário manipula cenários de recuperação quando determinados eventos ocorrem e isso seria afetado de forma adversa se chamadas fossem utilizadas em um nó de saída definido pelo usuário.

Declarando o Nó ao Intermediário

O procedimento a seguir mostra como declarar o nó ao intermediário:

  1. A função de inicialização, bipGetMessageflowNodeFactory, é chamada pelo intermediário depois que a LIL tiver sido carregada e inicializada pelo sistema operacional. Ela é chamada a partir do encadeamento de configuração do intermediário. O intermediário chama essa função para compreender o que a LIL é capaz de fazer e como o intermediário deve chamar a LIL. Por exemplo:
    CciFactory LilFactoryExportPrefix * LilFactoryExportSuffix
    bipGetMessageflowNodeFactory()
  2. A função bipGetMessageflowNodeFactory chama a função utilitária cniCreateNodeFactory. Essa função transmite de volta um nome de fábrica (ou nome de grupo) para todos os nós que a LIL suporta. Por exemplo:
    {
    	CciFactory* factoryObject;
    	int rc = 0;
    	CciChar factoryName[] = L"SwitchNodeFactory";
    	CCI_EXCEPTION_ST exception_st;
    
    	/* Criar a Fábrica de Nó para este nó */
    	factoryObject = cniCreateNodeFactory(0, factoryName);
    	if (factoryObject == CCI_NULL_ADDR) {
    		if (rc == CCI_EXCEPTION) {
    			/* Obter detalhes da exceção */
    			cciGetLastExceptionData(&rc, &exception_st);
    
    			/* Qualquer tratamento de erro local pode ir aqui */
    
    			/* Relançar a exceção */
    			cciRethrowLastException(&rc);
    		}
    
    			/* Qualquer tratamento de erro local adicional pode ir aqui */
    	}
    	else {
    		/* Definir os nós suportados por esta fábrica */
    		defineSwitchNode(factoryObject);
    	}
    
    	/* Retornar o endereço deste objeto de fábrica para o intermediário */
    	return(factoryObject);
    }

Definindo o Nó Como um Nó de Processamento de Mensagem

A LIL deve em seguida chamar a função utilitária cniDefineNodeClass para transmitir o nome de cada nó e uma tabela de funções virtuais dos endereços das funções de implementação. Por exemplo, para definir um único nó chamado SwitchNode e sua tabela de funções:
void defineSwitchNode(void* factoryObject){
	static CNI_VFT vftable = {CNI_VFT_DEFAULT};

  /* Configurar tabela de funções com ponteiros para funções de
implementação do nó */
	vftable.iFpCreateNodeContext = _createNodeContext;
	vftable.iFpDeleteNodeContext = _deleteNodeContext;
	vftable.iFpGetAttributeName = _getAttributeName;
	vftable.iFpSetAttribute = _setAttribute;
	vftable.iFpGetAttribute = _getAttribute;
	vftable.iFpEvaluate = _evaluate;

	cniDefineNodeClass(0, factoryObject, L"SwitchNode", &vftable);

	return;
}
Ela é chamada a partir do encadeamento de configuração.

Um nó definido pelo usuário se identifica como fornecendo a capacidade de um nó de processamento de mensagem ou de saída implementando a função cniEvaluate. Os nós definidos pelo usuário precisam implementar uma função de implementação cniEvaluate ou a cniRun, ou ambas, caso contrário, o intermediário não carrega o nó definido pelo usuário e a função de utilitário cniDefineNodeClass falha, retornando CCI_MISSING_IMPL_FUNCTION.

Quando um fluxo de mensagens contendo um nó de processamento de mensagem definido pelo usuário é implementado com êxito, a função cniEvaluate do nó é chamada para cada mensagem transmitida através do fluxo de mensagens.

Os dados do fluxo de mensagens são recebidos através do terminal de entrada do nó, ou seja, a mensagem, o ambiente global, o ambiente local e a lista de exceções.

Por exemplo:
void cniEvaluate(
  CciContext* context,
  CciMessage* destinationList,
  CciMessage* exceptionList,
  CciMessage* message
){                                    
  ...
}

Criando uma Instância do Nó

O procedimento a seguir mostra como instanciar o nó:

  1. Quando o intermediário tiver recebido a tabela de ponteiros de função, ele chamará a função cniCreateNodeContext para cada instanciação do nó definido pelo usuário. Se houver três fluxos de mensagens que estejam utilizando o nó definido pelo usuário, a função cniCreateNodeContext será chamada para cada um deles. Essa função deve alocar memória para essa instanciação do nó definido pelo usuário para conter os valores dos atributos configurados. Por exemplo:
    1. A função de usuário cniCreateNodeContext é chamada:
      CciContext* _Switch_createNodeContext(
        CciFactory* factoryObject,
        CciChar*    nodeName,
        CciNode*    nodeObject
      ){
        static char* functionName = (char *)"_Switch_createNodeContext()";
        NODE_CONTEXT_ST* p;
        CciChar          buffer[256];
      
      
    2. Alocar um ponteiro para o contexto local e limpar a área do contexto:
        p = (NODE_CONTEXT_ST *)malloc(sizeof(NODE_CONTEXT_ST));
      
        if (p) {
           memset(p, 0, sizeof(NODE_CONTEXT_ST));
    3. Salvar o ponteiro do objeto do nó no contexto:
         p->nodeObject = nodeObject;
    4. Salvar o nome do nó:
       CciCharNCpy((CciChar*)&p->nodeName, nodeName, MAX_NODE_NAME_LEN);
  2. O intermediário chama as funções utilitárias apropriadas para localizar sobre os terminais de entrada e de saída do nó. Um nó tem um número de terminais de entrada e de terminais de saída associados a ele. Na função de usuário cniCreateNodeContext, as chamadas devem ser feitas a cniCreateInputTerminal e cniCreateOutputTerminal para definir os terminais do nó do usuário. Essas funções devem ser chamadas dentro da função de implementação cniCreateNodeContext. Por exemplo, para definir um nó com um terminal de entrada e dois terminais de saída:
        {
          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) ;
        }

Definindo Atributos

Os atributos são definidos sempre que se inicia o intermediário, ou quando se reimplementa um fluxo de mensagens com novos valores. Os atributos são definidos pelo intermediário, chamando o código do usuário a partir do encadeamento de configuração. O código do usuário precisa armazenar esses atributos em sua área de contexto do nó, para utilização no processamento de mensagens posterior.

Em seguida à criação de terminais de entrada e de saída, o intermediário chama a função cniSetAttribute para transmitir os valores dos atributos configurados para esta instanciação do nó definido pelo usuário. Por exemplo:
    {
      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) ;
    }

Implementando a Funcionalidade do Nó

Quando o intermediário recupera uma mensagem da fila e essa mensagem chega no terminal de entrada do nó de processamento de mensagem ou de saída definido pelo usuário, o intermediário chama a função de implementação cniEvaluate. Essa função é chamada a partir do encadeamento de processamento de mensagens e ela deve decidir o que fazer com a mensagem. Essa função pode ser chamada em vários encadeamentos, principalmente, se instâncias adicionais forem utilizadas.

Excluindo uma Instância do Nó de Processamento de Mensagem

Para excluir uma instância de um nó, é utilizada a função cniDeleteNodeContext. Por exemplo:

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

    return;
}

A função cniDeleteNodeContext é fornecida pelo usuário e é chamada pelo intermediário quando um fluxo de mensagens é excluído.

Conceitos relacionados
ESQL
Planejando Extensões Definidas pelo Usuário
Extensões Definidas pelo Usuário no Ambiente de Tempo de Execução
Projetando Extensões Definidas pelo Usuário
Nós Input definidos pelo usuário

Tarefas relacionadas
Desenvolvendo Extensões Definidas pelo Usuário
Implementando as Amostras Fornecidas
Compilando uma Extensão Definida pelo Usuário em C

Referências relacionadas
API de Nó C Definido pelo Usuário