sábado, 26 de julho de 2008

JBoss JNDI, como utilizar? Erros comuns, principalmente com a utilização do Maven.

Olá pessoal, sou novo aqui na comunidade do blog e quero compartilhar informações sobre problemas que tive com Java e de programação em geral, bem como descobertas interessantes e troca de experiência. Gosto de pesquisar sobre computação, mas não apenas me focar em um assunto, gosto desde computação gráfica à sistemas distribuídos. Bom, chega de lorota... vou começar a falar um pouco sobre o que é JNDI, depois falar sobre como configurar uma aplicação cliente e servidora para acessar esse recurso através de um EJB. Falarei também sobre o Maven e possíveis problemas que venham a ocorrer.

JNDI é um serviço que provê uma interface comum para uma grande variedade de "serviços de nomes", tais como: DNS, RMI registry, sistemas de arquivos, etc. a API do JNDI é dividida em uma API de cliente na qual acessa os serviços de nomes e uma interface provedora de serviços que permite o usuário a criar implementações para serviços de nomes JNDI. Abaixo se encontra um exemplo de código fonte de utilização do JNDI, que faz acesso a um recurso publicado no servidor JBoss 4.x

FacadeHome facadeHome = null;
Facade facade = null;
try {
Context context = new InitialContext();
Object objRef = context.lookup("java:comp/env/pkg.interfaces.FacadeHome");
facadeHome = (FacadeHome) PortableRemoteObject.narrow(objRef, FacadeHome.class);
facade = facadeHome.create();
} catch (RemoteException e) {
e.printStackTrace();
} catch (CreateException e) {
e.printStackTrace();
} catch (NamingException e) {
e.printStackTrace();
}

Temos o handle da interface home (FacadeHome) e a interface remota (Facade) do EJB 2. O contexto (Context) é uma interface primária que interage com um serviço de nome do JNDI. A classe InitialContext implementa a interface Context e provê um ponto de partida para interagir com um serviço de nome. Quando você instancia um InitialContext, este é inicializado com propriedades do ambiente. Logo após é procurado (lookup) o ambiente em que o nome está publicado, neste caso com o nome java:comp/env/pkg.interfaces.FacadeHome. A classe PortableRemoteObject retorna através de um handle e invocação do método narrow, o objeto publicado.

Para que o serviço funcione corretamente com o EJB 2, precisamos criar alguns arquivos XML, tanto na aplicação servidora, como pra aplicação cliente. É comum aos principiantes com essa tecnologia acontecer a exceção NameNotFoundException pela ausência desses arquivos de mapeamento. Os elementos que precisam ser mapeados são usualmente chamados de Enterprise Naming Context (ENC) elements.

No meu caso, tenho um EJB que precisa ser acessado por uma camada WEB utilizando o framework VRaptor. Na aplicação servidora é necessário ter os seguintes arquivos XML: ejb-jar.xml (descriptor) e jboss.xml (deployment descriptor). Na aplicação cliente, no meu caso, o web.xml fará o papel de descriptor e o jboss-web.xml será o deployment descriptor.

O ejb-jar.xml descreve uma visão lógica de qual ambiente o EJB precisa operar. Você pode encontrar exemplos deste arquivo através do Google. Já o jboss.xml provê o mapeamento dos nomes JNDI que devem ser jogados no deploy. Dessa maneira uma aplicação cliente poderá procurar pelo nome publicado.

Para as aplicações cliente pode-se fazer analogia com esta lógica, ocorrendo pequenas mudanças nas tags do XML. Com estas configurações setadas, já é possível acessar um recurso EJB através do JNDI.

Maven é uma ferramenta gerenciadora de projetos. Ela se baseia no conceito de Project Object Model (POM). Existem várias configurações que o Maven suporta. Para maiores informações acesse (http://maven.apache.org/). No meu caso, eu utilizo o Maven para buscar por bibliotecas padrões de projetos do repositório local (ou da internet), juntamente com outros gerenciamentos.

Essa semana ao tentar acessar um recurso JNDI me ocorreu um erro bem incomum. Abaixo pode-se encontrar o erro gerado:

08:37:16,472 ERROR [STDERR] javax.naming.NamingException: Could not dereference object [Root exception is javax.naming.CommunicationException [Root exception is java.io.InvalidClassException: org.jboss.util.id.GUID; local class incompatible: stream classdesc serialVersionUID = 3289509836244263718, local class serialVersionUID = 6926421946503004889]]
08:37:16,472 ERROR [STDERR] at org.jnp.interfaces.NamingContext.resolveLink(NamingContext.java:1067)
08:37:16,472 ERROR [STDERR] at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:700)
08:37:16,472 ERROR [STDERR] at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:716)
08:37:16,472 ERROR [STDERR] at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:587)
08:37:16,472 ERROR [STDERR] at javax.naming.InitialContext.lookup(InitialContext.java:351)
…more

Todas as configurações estavam corretas. Todos as dependências estavam sendo baixadas corretamente do repositório pelo Maven, os arquivos de deployment e deployment descriptor foram setados corretamente. Onde eu trabalho, todos os projetos criados herdam de um “default project” onde todos os jars e configurações básicas são definidas no POM desse projeto. Acontece que a pouco tempo foi mudada a versão do JBoss e o defaultproject referenciava uma lib para a aplicação cliente chamada "jboss-common-3.2.3.jar". Essa lib possuía uma versão diferente do org.jboss.util.id.GUID relativo ao arquivo que se encontrava no servidor JBoss. De alguma forma o JBoss valida a requisição do serviço pelo versionamento dessa classe. O que eu fiz foi atualizar a referência dessa lib no meu POM e tudo funcionou corretamente. É isso! Qualquer dúvida, postem ai =)

[]´s