Pool de conexões Siebel

Recentemente estive em um projeto e tive que desenvolver uma solução bem bacana com integração entre Oracle Webcenter e Siebel. O Siebel trabalha com tokens de segurança nas chamadas de serviços, o problema é que não há implementação de WS-Security que faça a autenticação e tokens de segurança de forma satisfatória e sem problemas. Além disso há outros detalhes de desempenho que a equipe da Siebel disse que é fundamental.

A equipe Siebel queria que fosse associada a sessão Web do usuário com a sessão de conexão com o Siebel para as chamadas de serviços. O problema dessa solução é justamente fazer essa associação de maneira correta (uma não tem nada a ver com a outra) e tratar solicitações paralelas. Isso é fundamental em ambientes Web. Outro problema é que haveriam muitas sessões Siebel ociosas, pois após o usuário realizar a requisição, ele fica analisando os dados e navegando na página, antes de realizar novas requisições. A solução que eu sugeri foi a implementação de um pool de conexões  compartilhadas com o Siebel, da mesma maneira que utilizamos para Banco de Dados. Para atender o requisito solicitado pela equipe Siebel, definimos o número de conexões mínimas igual a 1 e o máximo igual ao número de usuários conectados.

Antes de entender o funcionamento do pool Siebel, vamos entender o funcionamento dos tokens no Siebel.

Requisição anônima sem sessão

Essa foi nossa primeira abordagem antes de criar o pool de conexões. No header da requisição SOAP não enviamos nenhuma informação de usuário, apenas definimos o SessionType como None. Para cada requisição ele abre uma sessão e fecha ao enviar a resposta. O problema dessa implementação é que embora seja simples, o tempo de criação de sessão sem o uso do token é bem elevado.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
 <SessionType xmlns="http://siebel.com/webservices">None</SessionType>
</soap:Header>
<soap:Body>
 <!-- ... -->
</soap:Body>
</soap:Envelope>

Autorização Siebel, sem sessão

Semelhante à anterior, inclusive com os mesmo problemas, mas passando o usuário e senha para autorização.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
 <UsernameToken xmlns="http://siebel.com/webservices">usuario</UsernameToken>
 <PasswordText xmlns="http://siebel.com/webservices">senha</PasswordText>
 <SessionType xmlns="http://siebel.com/webservices">None</SessionType>
</soap:Header>
<soap:Body>
 <!-- ... -->
</soap:Body>
</soap:Envelope>

Autorização Siebel com sessão stateless

Essa foi a solução adotada para o pool de conexões com o Siebel que explicarei mais adiante. O funcionamento é simples de entender, mas um pouco trabalhoso de se implementar já que é sempre necessário guardar o último token de cada conexão. A primeira requisição deve realizar a autorização passando o usuário, senha e o tipo de sessão Stateless, com isso uma sessão é aberta para esse usuário. Ao obter uma resposta da requisição, o Siebel retornará o token de segurança que deverá ser utilizado na próxima requisição, que retornará outro token de segurança que deverá ser utilizado na próxima requisição e assim por diante. Para finalizar a sessão Siebel é só passar o usuário e senha com o tipo de sessão None.

Requisição inicial

Para efetuar o log in e criar a sessão é necessário passar o seguinte header:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
 <UsernameToken xmlns="http://siebel.com/webservices">usuario</UsernameToken>
 <PasswordText xmlns="http://siebel.com/webservices">senha</PasswordText>
 <SessionType xmlns="http://siebel.com/webservices">Stateless</SessionType>
</soap:Header>
<soap:Body>
 <!-- ... -->
</soap:Body>
</soap:Envelope>

Dessa forma ele deverá retornar uma resposta parecida com essa:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
 <siebel-header:SessionToken xmlns:siebel-header="http://siebel.com/webservices">TokenCriptografadoComValorEstranhoDoResponse
</siebel-header:SessionToken>
</soap:Header>
<soap:Body>
 <!-- ... -->
</soap:Body>
</soap:Envelope>

Próximas Requisições

Para as próximas requisições e necessário recuperar o token da resposta e colocar no header da requisição da seguinte forma:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<SessionType xmlns="http://siebel.com/webservices">Stateless</SessionType>
<SessionToken xmlns="http://siebel.com/webservices">TokenCriptografadoComValorEstranhoDoResponse</SessionToken>
</soap:Header>
<soap:Body>
<!-- ... -->
</soap:Body>
</soap:Envelope>

E assim por diante. Existe a opção do tipo de sessão ser Statefull também para manter o estado da sessão. O funcionamento é o mesmo, só muda o SessionType para Statefull.

Pool de conexões

Como assinei contrato de confidencialidade não poderei disponibilizar o código fonte utilizado no projeto, mas explicarei os caminhos necessários para tudo funcionar adequadamente.

Para a inclusão do header nas requisições do Siebel, utilizei um SOAPHandler. O SOAPHandler funciona como uma espécie de "filtro" para todas as requisições que utilize ele. Dessa forma eu posso modificar o header, alterar a requisição e obter ou mesmo alterar os dados da resposta. Para realizar isso, é só criar uma classe que implemente a interface SOAPHandler como no exemplo e associar com os clientes de cada serviço (JAX-WS 2.0).

public class SiebelHandler implements SOAPHandler<SOAPMessageContext> {
    //...
}

Dos métodos que a interface nos obriga a implementar utilizaremos dois: handleMessage e handleFault. Que é onde iremos modificar os headers e obter os tokens.

    public boolean handleMessage(SOAPMessageContext smc) {
        Boolean outboundProperty =
            (Boolean)smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if (outboundProperty.booleanValue()) {

            SiebelTokenHelper tokenHelper =
                SiebelSimpleTokenManager.getInstance().getNextToken();
            tokenUsed(tokenHelper);

            SOAPMessage message = smc.getMessage();

            try {
                SOAPEnvelope envelope =
                    smc.getMessage().getSOAPPart().getEnvelope();

                SOAPHeader header = envelope.getHeader();
                if (header == null) {
                    header = envelope.addHeader();
                }
                // Token
                if (tokenHelper.getTokenValue() == null) {
                    SOAPElement userNameToken =
                        header.addChildElement("UsernameToken", "",
                                               "http://siebel.com/webservices");
                    userNameToken.addTextNode(tokenHelper.getUser());

                    SOAPElement password =
                        header.addChildElement("PasswordText", "",
                                               "http://siebel.com/webservices");
                    password.addTextNode(tokenHelper.getPassword());

                } else {

                    SOAPElement sessionToken =
                        header.addChildElement("SessionToken", "",
                                               "http://siebel.com/webservices");
                    sessionToken.addTextNode(tokenHelper.getTokenValue());
                }

                SOAPElement sessionType =
                    header.addChildElement("SessionType", "",
                                           "http://siebel.com/webservices");

                if (isLogout(envelope)) {
                    sessionType.addTextNode("None");
                    SiebelSimpleTokenManager.getInstance().removeFromPool(tokenHelper);
                    tokenHelper = null;
                } else {
                    sessionType.addTextNode("Stateless");
                }

            } catch (Exception e) {
                logger.log(Level.SEVERE, "Erro ao invocar servico", e);
            }

        } else {
            SiebelTokenHelper tokenHelper = getUsedToken();
            try {

                SOAPMessage message = smc.getMessage();
                if (tokenHelper != null) {
                    Node tokenCHild = message.getSOAPHeader().getFirstChild();
                    if (tokenCHild != null) {
                        tokenHelper.setTokenValue(tokenCHild.getTextContent());
                    }
                }

            } catch (Exception ex) {
                ex.printStackTrace();
                tokenHelper.setTokenValue(null);
            } finally {
                if (tokenHelper != null) {
                    SiebelSimpleTokenManager.getInstance().pushElemIntoPool(tokenHelper);
                }
            }
        }

        return outboundProperty;

    }

A primeira condição na linha 05 verifica se é requisição ou reposta. Se for requisição o valor será verdadeiro. Na linha 07 e 08 utilizo uma implementação de fila (utilizando LinkedList) para recuperar o elemento mais antigo (menos recente usado), removo ele da fila para que outras conexões Siebel possam ser utilizadas, ele será novamente inserido no final da fila (linha 74), após ter o valor do token atualizado (linha 65) na resposta (linha 57). Na linha 22 verifico se existe token anterior ou se é uma conexão nova. Se for uma conexão nova, atribuo o valor do usuário (linha 26), da senha (linha 31). Caso contrário, utilizo o token (linha 38) atributo anteriomente na resposta (linha 65). Se for uma requisição de logout (linha 45), atribuo o SessionType como None, caso contrário Stateless.

O coração da implementação é esse método, mas ainda não estamos tratando exceção e reestabelecendo a conexão caso a sessão expire. Para reestabelecer a conexão, basta colocar no catch do cliente uma nova tentativa se o erro tiver o código 10944642. No nosso handleFault basta remover da fila para não ser mais utilizado, caso contrário, se for outro tipo de erro, o token é retornado no fault e devemos atribuir para ser utilizado na próxima requisição. Quando a sessão expira o Siebel retorna a seguinte mensagem:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <SOAP-ENV:Body>
 <SOAP-ENV:Fault>
 <faultcode>SOAP-ENV:Client</faultcode>
 <faultstring>Error Code: 10944642 Error Message: Error: Inbound SOAP Message - Session Token is missing or invalid or has expired</faultstring>
 <detail>
 <siebelf:errorstack xmlns:siebelf="http://www.siebel.com/ws/fault">
 <siebelf:error>
 <siebelf:errorsymbol/>
 <siebelf:errormsg>Error: Inbound SOAP Message - Session Token is missing or invalid or has expired</siebelf:errormsg>
 </siebelf:error>
 </siebelf:errorstack>
 </detail>
 </SOAP-ENV:Fault>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Por isso devemos tratar quando obtermos o código: 10944642, pois não é um erro de serviço, é somente sessão expirada. O controle de criação de novas conexões foi feito em uma classe que possui a fila de conexões e tokens e o controle do número máximo foi feito utilizando um Managed Bean para contabilização de login e logout e um Session Listener, para caso o usuário esqueça de clicar no botão Sair.

Para testar a Oracle disponibiliza um VM do Siebel no e-delivery. Com essa implementação, tivemos um ganho de performance absurdo, paralelizamos as requisições e otimizamos o uso de sessões e compartilhamento de tokens no Siebel.

Sobre: Thiago Galbiatti Vespa

Thiago Galbiatti Vespa é mestre em Ciências da Computação e Matemática Computacional pela USP e bacharel em Ciências da Computação pela UNESP. Coordenador de projetos do JavaNoroeste, membro do JCP (Java Community Process), consultor Oracle, arquiteto de software de empresas de médio e grande porte, palestrante de vários eventos e colaborador de projetos open source. Possui as certificações: Oracle Certified Master, Java EE 5 Enterprise Architect – Step 1, 2 and 3; Oracle WebCenter Portal 11g Certified Implementation Specialist; Oracle Service Oriented Architecture Infrastructure Implementation Certified Expert; Oracle Certified Professional, Java EE 5 Web Services Developer; Oracle Certified Expert, NetBeans Integrated Development Environment 6.1 Programmer; Oracle Certified Professional, Java Programmer; Oracle Certified Associate, Java SE 5/SE 6