quarta-feira, 25 de novembro de 2015



Lookup é usado para fazer chamadas a recursos como banco de dados, EJBs, filas etc. Mas acredito que existem algumas dúvidas de como fazer isto, sendo que existem duas formas e diversas configurações.

Tentarei explicar meu entendimento do assunto através de três pontos:
1 – Global JNDI Namespace e lookup direto
(Quando você implanta um recurso é necessário fornecer um nome JNDI o qual fica registrado no Global JNDI namespace do servidor de aplicação)
2 – Lookup portável
(O lookup direto apresenta alguns problemas que podem ser resolvidos com a forma java:comp/env)
3 – Onde estou e para onde vou
(conforme a origem do lookup sua configuração pode mudar, em outras palavras, um lookup partindo de um WAR pode ser diferente de um JAR) 

Considerações:
Como o EJB 3 deixa transparente muito dos conceitos apresentados aqui (e por existir muitas aplicações com versões mais antigas de EJB), aqui será abordado o EJB 2.1 o que pode ser importante para quem apenas usalookup com anotações do EJB 3 e não conhece como seria sem elas.
Download do código no final do post. 

1 – Global JNDI Namespace e lookup direto
Alguns servidores de aplicações (como Websphere e Weblogic) possuem wizards para registrar recursos. Neste post será utilizado o JBoss onde é necessário descritores (XML) ao invés dos wizards.
São estes registros que permitem que os recursos fiquem disponíveis para lookups:

 










1.1 Registrando um acesso ao MySQL
Neste caso precisamos criar o descritor listado abaixo (mysql-ds.xml) e colocá-lo na pasta server/deploy do JBoss:


<datasources>
  <local-tx-datasource>
        <jndi-name>jdbc/MySQLDB</jndi-name>
        <use-java-context>false</use-java-context>
        <connection-url>
jdbc:mysql://myserver:3306/database</connection-url>
        <driver-class>com.mysql.jdbc.Driver</driver-class>
        <user-name>user</user-name>
        <password>pass</password>
        <exception-sorter-class-name>
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
</exception-sorter-class-name>
        <metadata>
              <type-mapping>mySQL</type-mapping>
        </metadata>
  </local-tx-datasource>
</datasources>


Desta maneira, o nome JNDI jdbc/MySQLDB estará disponível para qualquer aplicação. Mais adiante será mostrado como é possível consultar os nomes JNDI disponíveis no namespace do servidor.

1.2 Registrando um Session Bean
No momento de implantar um projeto EJB (jar) com seu respectivo descritor (ejb-jar.xml) também é necessário informar um nome JNDI (para cada EJB). E da mesma forma que existe um descritor de banco de dados, existe um descritor (do lado servidor) para os recursos da aplicação EJB (jboss.xml). Este descritor também é conhecido comodeployment plan*.
Obs: No websphere existe um passo no momento do deploy onde é necessário informar estes dados.


Desta maneira, o nome JNDI br/com/billcode/CustomerServiceLocal estará disponível para as aplicações.

Nota:
Para consultar os recursos disponíveis, todo servidor de aplicação possui uma árvore JNDI que pode ser consultada. No caso do JBoss, é possível acessar o jmx-console e selecionar o item JNDIView o qual irá listar os dois recursos implantados: 




Abaixo é exibido como fazer as chamadas a estes dois recursos (partindo da aplicação web – WAR):
- Lookup 1: Chamando a base de dados
- Lookup 2: Chamando o EJB local da aplicação EJB



Para conseguir executar estas chamadas, existem duas formas:
- Chamada utilizando lookup direto (item 1.3) mais o nome jndi disponível no Global JNDI Namespace
- Chamada utilizando um lookup sobre um nome lógico (item 2)

1.3 Lookup direto
É possível de qualquer parte do código iniciar o contexto e fazer uma chamada JNDI. Não é necessário adicionar nenhum comando ou parâmetro nos descritores (web.xml e jboss-web.xml). Abaixo é exibida uma Servlet com as duas chamadas. Mais adiante será explicado alguns dos problemas desta abordagem.



Em resumo, é possível fazer um context.lookup diretamente sobre um nome JNDI disponível no global JNDI namespace do servidor de aplicação.

Mas quais são os problemas deste lookup direto?
Embora seja simples, não é uma boa prática fazer o lookup direto sendo que existem alguns problemas:
- Em ambientes corporativos, geralmente existem servidores exclusivos para banco de dados, onde já pode existir o nome que você fez referência ao seu código (jdbc/MySQLDB)
- Será necessário alterar o código do lookup quando existe a necessidade de mais ambientes, por exemplo jdbc/MySQL_DESENV, jdbc/MySQLDB_TEST ou jdbc/MySQL_PROD
- Pode existir uma equipe de DBA onde eles são responsáveis pela criação da base, bem como definir os nomes dos databases.
- No caso dos EJBs, alguns servidores de aplicação tem formas diferentes na árvore JNDI. Por exemplo, o ejb do item 1.2 no Websphere seria disponibilizado comocell/clusters/cluster1/br/com/billcode/CustomerServiceLocal, em outras palavras, fazer lookup diretonão é portável
- No caso dos EJBs e filas também existe o problema de ser necessário alterar o código fonte para conseguir fazer o deploy da mesma aplicação mais de uma vez.


2 – Lookup portável

Visto os problemas do lookup direto (item 1.3), a solução é definir nomes lógicos para os recursos da sua aplicação os quais devem ser chamados utilizando java:comp/env/nome_lógico.
Existe um custo de desenvolvimento maior inicialmente, sendo que existe a necessidade de descrever a dependência e fazer o mapeamento do nome lógico ao nome físico (JNDI). Mas esta maneira minimiza todos os problemas citados anteriormente, sendo que apenas com alteração no descritor do servidor (deployment plan) será possível referenciar outro nome JNDI. 


Abaixo é listado o código e os descritores que exemplificam o lookup portável. 
Note que alterando apenas o deployment plan do servidor (jboss-web.xml) será possível referenciar outra base de dados. Ou ainda em uma migração para outro servidor de aplicação, apenas este arquivo seria substituído pelo seu correspondente (ex: sun-web.xml para o glassfish)





3 – Onde estou e para onde vou

Sabendo que são necessários os três passos abaixo para fazer um lookup:
Chamada via java:comp/env/nome_lógico
Declaração no descritor da aplicação (web.xml ou ejb-jar.xml)
Mapeamento no deployment plan do servidor (jboss-web.xml ou jboss.xml)

O último ponto e o item que algumas vezes passa despercebido, até mesmo na hora de questionar sobre por que esta ocorrendo um determinado erro no momento do lookup, é entender a origem da chamada e onde se encontra o recurso desejado.
Em outras palavras, como existem diversas possibilidades, é necessário saber qual delas é o seu cenário antes de sair alterando seus descritores e/ou código:


Item 1 e 2) Acessando BASE via WAR / EJB

Descritor (web.xml ou ejb-jar.xml) 
<resource-ref>
   <res-ref-name>jdbc/customerDB</res-ref-name>
   <res-type>javax.sql.DataSource</res-type>
   <res-auth>Container</res-auth>
</resource-ref>

Deployment plan - mapeamento (jboss-web.xml ou jboss.xml)
<resource-ref>
  <res-ref-name>jdbc/customerDB</res-ref-name>
  <jndi-name>jdbc/MySQLDB</jndi-name>
</resource-ref> 

Item 3 e 4) Acessando EJB-Local via WAR / Outro EJB  
Descritor (web.xml ou ejb-jar.xml) 
<ejb-local-ref>
  <ejb-ref-name>ejb/CalculaFreteService</ejb-ref-name>   
  <ejb-ref-type>Session</ejb-ref-type>   
  <local-home>br.com.billcode.service.client.CalculaFreteServiceLocalHome</local-home>
  <local>br.com.billcode.service.client.CalculaFreteServiceLocal</local>

</ejb-local-ref>   


Deployment plan - Mapeamento
WAR (jboss-web.xml)EJB (jboss.xml)
<ejb-local-ref>   
  <ejb-ref-name>ejb/CalculaFreteService</ejb-ref-name>   
  <local-jndi-name>local/CalculaFreteService</local-jndi-name>   
</ejb-local-ref>
<resource-managers>
  <resource-manager>
    <res-name>CepRepositoryLocal</res-name>
    <res-jndi-name>java:ejb/CepRepositoryLocal</res-jndi-name>
  </resource-manager>
</resource-managers> 


Item 5 e 6) Acessando EJB-Remoto via WAR / outro EJB 
Descritor (web.xml ou ejb-jar.xml)
<ejb-ref>
  <ejb-ref-name>ejb/BuscaCep</ejb-ref-name>
  <ejb-ref-type>Session</ejb-ref-type>
  <home>br.com.billcode.service.client.BuscaCepHome</home>
  <remote>br.com.billcode.service.client.BuscaCep</remote>
</ejb-ref>

Deployment plan - Mapeamento (jboss-web.xml ou jboss.xml)
<ejb-ref>
  <ejb-ref-name>ejb/BuscaCep</ejb-ref-name>
  <jndi-name>jnp://server:1099/br/com/billcode/service/BuscaCep</jndi-name>
</ejb-ref>


Item 7) Acessando EJB-Remoto via Aplicação cliente (fora do servidor)

É necessario informar os dados do servidor ao iniciar o contexto:

Hashtable environment = new Hashtable();
environment.put(Context.INITIAL_CONTEXT_FACTORY, 
    "org.jnp.interfaces.NamingContextFactory");
environment.put(Context.URL_PKG_PREFIXES, 
    "org.jboss.naming"); 
environment.put(Context.PROVIDER_URL, 
    "jnp://server:1099");

InitialContext context = new InitialContext(environment);

Sobre o contexto é possível chamar o JNDI: 

Object obj = context.lookup("br/com/billcode/service/BuscaCep");





Download dos exemplos:
Ambiente: jdk 1.6.24, jboss 6.0, eclipse 3.6
1 - Lookup direto
2 - Lookup portável (bom uso das chamadas)

Considerações:
- O código exemplo tem como objetivo mostrar chamadas JNDI, não é o foco deste post, mas para melhorar seu código que faz chamadas a recursos, adicione o padrão de projeto ServiceLocator ou utilize injeção de dependência.

* Deployment plan: 
É o descritor do servidor de aplicação, geralmente existem 2, um para aplicações web e outro para aplicações enterprise. Exemplos: 
JBoss – jboss-web.xml e jboss.xml 
Weblogic – weblogic-web.xml e weblogic.xml 
Glassfish – sun-web.xml e sun-ejb-jar.xml