Artigo WebMobile 26 - Utilizando Web Services com EJB 3 e JBoss
O artigo tem o objetivo de apresentar a estrutura de um WebService utilizando os recursos do EJB3 no Servidor de Aplicações JBoss. Faz parte também dos objetivos deste artigo demonstrar o funcionamento do protocolo SOAP e apresentar os conceitos necessários para criação de um Web Service.
O artigo tem o objetivo de apresentar a estrutura de um WebService
utilizando os recursos do EJB3 no Servidor de Aplicações JBoss. Faz parte
também dos objetivos deste artigo demonstrar o funcionamento do protocolo SOAP
e apresentar os conceitos necessários para criação de um Web Service. Para que serve: O artigo serve para apresentar e demonstrar a utilização dos recursos
do EJB3 para criação de Web Services utilizando o Servidor de Aplicações JBoss. Em que situação o tema é útil: O artigo é útil em sistemas em que haja necessidade de trocar informações
utilizando a Web como meio de prover a interoperabilidade de sistemas não
necessariamente desenvolvidos utilizando a linguagem de programação Java.
O uso de Web Services está se tornando cada vez mais comum tendo em vista uma maior necessidade de criarmos ambientes de software integrados onde várias plataformas diferentes precisam se comunicar.
Esta necessidade de integração acaba resgatando alguns conceitos que teoricamente são oriundos de projeto de software estruturado, tais como acoplamento e coesão. Sistemas isolados que no início atendiam um determinado grupo de usuários passam a ter a responsabilidade de buscar e/ou fornecer informações para outros sistemas, transformando este sistema, até então isolado, como um módulo de outro sistema. Daí os conceitos de acoplamento e coesão.
Os conceitos de acoplamento e coesão acabam voltando à tona de uma forma mais moderna, dando espaço a outros conceitos como SOA (Service Oriented Architeture, ou ainda, Arquitetura Orientada a Serviços), onde estes sistemas isolados numa visão mais retrógrada seriam os módulos, e numa visão mais moderna os Serviços.
O uso de Web Services é a forma mais objetiva de implementar projetos de software utilizando SOA. Web Services são recursos fracamente acoplados responsáveis por encapsular a lógica de negócio e disponibilizá-los de uma forma denominada “sem estado” (Stateless). Estas são algumas das características que projetos de software utilizando SOA devem possuir, por isso os Web services são recursos tão utilizados com projeto envolvendo a metodologia Orientada a Serviços.
A plataforma Java auxilia muito neste processo, oferecendo uma grande gama de ferramentas para proporcionar flexibilidade, portabilidade e escalabilidade no desenvolvimento de software.
Entre estas ferramentas disponíveis pelo Java, o Enterprise Java Bean, ou ainda EJB, são recursos que oferecem ao projeto estas características necessárias para o software que irá participar de um processo de integração. O EJB atualmente na sua versão três traz uma série de recursos, como as anotações, que agilizam o processo de desenvolvimento.
Neste artigo, abordaremos a construção de Web Services utilizando recursos do EJB3. Para isso, foram criados dois estudos de caso para demonstrar o uso das anotações. O primeiro estudo de caso é um exemplo básico de Web Service que disponibiliza o acesso a alguns métodos, onde o principal deles retorna uma String contendo um nome. O segundo estudo de caso é um pouco mais complexo, onde é feito o uso de mais algumas anotações que não foram utilizadas no primeiro estudo de caso, e também é utilizado o banco de dados PostgreSQL, com gerenciamento de acesso às conexões de banco realizadas pelo JBoss através de um DataSource. Além disso, no decorrer deste artigo alguns conceitos importantes serão definidos para o completo entendimento de projetos de software utilizando Web services.
Web Services – Conceitos Básicos
A necessidade de integração de sistemas, muitas vezes, surge com o ganho de complexidade nas regras de negócio, conhecido como Core Business, de empresas motivadas pelo seu crescimento. Os sistemas já implantados podem não mais atenderem aos requisitos da nova estrutura, e para isso os softwares já implantados que implementam as regras de negócio precisam ser remodelados.
Como citado anteriormente, este processo de integração de sistemas acaba resgatando conceitos como acoplamento e coesão, bastante comuns em projeto de software estruturado.
Acoplamento pode ser medido com a seguinte pergunta: Quão dependentes são estes sistemas? Quanto maior for à relação de dependência entre sistemas integrados, podemos dizer que estes são fortemente acoplados. Caso contrário, são ditos fracamente acoplados. O conceito de acoplamento está bastante relacionado ao que se refere à integração entre sistemas.
A coesão é um conceito mais ligado ao próprio software, que também pode ser medido pela pergunta: Quão dependentes entre si são os módulos do sistema? O conceito de coesão está mais ligado a um ponto de vista intrínseco. Diz-se que um software tem baixa coesão quando seus módulos possuem pouca dependência entre si, e alta coesão quando o inverso.
Dentro desta nova visão de arquitetura de software, que transforma softwares em serviços, dando espaço a construção de Software Orientados a Serviço (SOA) surge o conceito de Orquestração.
Orquestrar serviços pode ser definido dentro dos conceitos do SOA como “definir os procedimentos a serem realizados dentro das regras de negócio da Empresa”. A Orquestração deve garantir as pré e pós-condições de cada serviço.
Nesse cenário, é possível definir um Web Service como um software que disponibiliza um ou mais serviços de uma forma fracamente acoplada. Uma das suas principais vantagens é permitir que os clientes que o acessam não necessariamente sejam desenvolvidos na mesma tecnologia em que o Web service foi implementado. Para isso, é altamente recomendado que as informações retornadas por um Web service sejam de tipos primitivos como: String, Int, Float, Double, etc. Apesar de ser recomendado este procedimento na criação de Web services, isto não é uma regra. É possível retornar tipos complexos como objetos.
Esta flexibilidade é possível através da troca de informações entre o Web service e seus clientes utilizando uma linguagem “universal” neste processo de comunicação – a troca de documento XML. Este documento XML disponibilizado do lado do Web Service tem a responsabilidade de oferecer ao cliente os seus dados de uma forma que este entenda e processe as informações recebidas. Este documento é conhecido como WSDL (Web service Description Language). O WSDL recebe este nome pela sua estrutura e forma como foi concebido para ser o padrão de comunicação.
O WSDL é um documento reconhecido pelos padrões W3C. O papel fundamental deste documento é definir os end points do web service criado. Um end point pode ser definido como a interface exposta pelo web service para que as aplicações clientes solicitem as informações necessárias a serem obtidas. A solicitação efetuada ao web service é feita através de uma chamada HTTP. A união do documento WSDL usando como meio de comunicação o protocolo HTTP deu origem ao protocolo SOAP (Simple Object Access Protocol). O SOAP atualmente é um protocolo reconhecido pelo W3C para troca de mensagens em formato XML. O SOAP se encontra atualmente na versão 1.2.
Para tornar viável a comunicação entre o cliente e o web service, houve a necessidade de criar uma API do lado do cliente que facilitasse a troca e processamento de informações pelo protocolo SOAP. Inicialmente esta API era chamada de JAX – RPC (Java API for XML – Remote Procedure Call). Nos dias atuais, esta API evoluiu para o JAX – WS (Java API for XML – Web Service). A evolução desta API também foi um grande facilitador para implementações de softwares clientes de web service, tornando o processo de desenvolvimento mais ágil e prático.
Existem algumas formas do cliente solicitar informações à estrutura de um web service, onde as mais conhecidas são chamadas RPC (Remote Procedure Call) ou o envio de informações através de documentos – forma conhecida como Document Centric. A forma de requisição a ser efetuada aos web services de nossos estudos de casos será as requisições RPCs, que é a mais utilizada atualmente. Uma requisição RPC à estrutura de um web service é uma chamada síncrona, pois a cada requisição feita a um web service é aguardada uma resposta.
A API JAX-WS já implementa funcionalidades para facilitar e agilizar o tratamento do documento WSDL retornado pelo web service. Entre as formas existentes, a utilizada em nossos estudos de casos é o Dynamic Proxy. O Dynamic Proxy utiliza classes criadas dinamicamente para o acesso as informações de um web service. Estas classes dinâmicas são baseadas no conteúdo do documento WSDL gerado, e é responsabilidade da API JAX-WS em criar este “Proxy” do web service para obter as informações desejadas.
Para ficar mais claro, o RPC é a forma pela qual o cliente se comunica com o web service, ou seja, a chamada a estrutura em si. O Dynamic Proxy é a forma pela qual a API JAX-WS obtém e/ou trata as informações retornadas pela chamada RPC, tornando transparente o tratamento das informações contidas no documento WSDL. Imagine se este trabalho fosse responsabilidade do software cliente do web service? Ficaria muito trabalhosa o tratamento das informações.
No decorrer do artigo, esses conceitos serão discutidos através dos códigos-fonte de nossos estudos de caso para ficar mais claro as diferenças.
Conhecendo EJB 3 e JBoss
O Enterprise Java Beans (EJB) é umas das ferramentas que compõem o carro-chefe da tecnologia Java EE. O Java, por sua natureza, prima pela escalabilidade, robustez, portabilidade e manutenibilidade das aplicações desenvolvidas sobre a sua tecnologia. Os objetivos de construir uma aplicação disponibilizando algumas de suas informações em forma de serviço estão diretamente ligados aos da tecnologia Java EE.
A evolução do EJB para versão três levou em consideração esta nova demanda do mundo da Engenharia de Software. Através do documento JSR-181- “Web Services Metadata for the JavaTM Platform”, descrita anteriormente, são definidos alguns objetivos importantes, a saber:
• Definir a sintaxe das anotações Java para desenvolvimento de aplicação Web Services;
• Prover a simplificação do modelo de deployment de aplicações Java Web Services, facilitando e otimizando o processo de desenvolvimento;
• Disponibilizar uma sintaxe que possibilite a manipulação de suas ferramentas;
• Definir um padrão para desenvolvimento e deployment de Web services sem exigir conhecimento da implementação da API e de seus descritores.
As anotações descritas neste documento são:
• @Webservice
• @WebMethod
• @OneWay
• @WebParam
• @WebResult
• @Handler Chain
• @SOAPBinding
As anotações em negrito serão citadas e explicadas nos estudos de casos apresentados mais a frente.
Num passado não tão distante, o processo de criação de web services era bem trabalhoso. Anteriormente, era necessário criar o Deploymet Descriptor (arquivo responsável por definir os procedimentos de Deployment, chamado webservice.xml, que comparado a uma aplicação web é equivalente ao web.xml). Este arquivo era criado utilizando ferramentas disponibilizadas juntamente com o servidor de aplicações. Este mesmo processo também era uma realidade para criação do arquivo WSDL. Atualmente, o processo de criação destes arquivos passou a ser responsabilidade do servidor de aplicações.
Com o lançamento da JSR-181, os web services podem fazer uso da tecnologia Java EE utilizando as ferramentas do EJB3 juntamente com suas anotações definidas na JSR – conceito este fortemente inserido na sua versão 3.
Um web service desenvolvido com EJB3 pode ser disponibilizado no servidor de aplicações como um pacote JAR (disponibilizando apenas o “EJB Web Service” – Web Services que fazem uso das anotações do EJB3 para este fim) ou EAR (disponibilizando todo o pacote da aplicação, inclusive com os EJBs) como qualquer outro componente EJB.
O JBossAS na sua versão 4.2.3 foi o servidor de aplicações escolhido para demonstrar o uso de web services com EJB3. O JBossAS é uma produto completo que oferece todos os recursos para disponibilizar aplicações Java fazendo uso de Web Services com EJB. Dentro do JBossAS é encontrado o JBossWS (JBoss Web Service). Este é responsável pelo deployment e gerenciamento dos web services.
A interface de administração dos web services em produção pode ser acessada através do endereço http://IP_JBOSS:8080/jbossws. O mais importante nesta interface é ter acesso ao link View a list of deployed services. Através deste, é possível acessar informações como: contexto do web service, nome do End Point, End point Address, quantidade de requisições atendidas pelo web service etc. Na Figura 1 é possível visualizar a interface disponível para o contexto.
Figura 1. Contexto JBossWS
Através do End point address é possível visualizar o arquivo WSDL gerado pelo servidor de aplicações no deployment do web service. Na Figura 2 é possível visualizar alguns exemplos de web services já em produção.
Figura 2. Web Services em produção
Com a utilização destes novos recursos, o desenvolvedor pode ficar focado apenas na modelagem e desenvolvimento dos web services, sem precisar se preocupar com a criação de arquivos específicos, como o WSDL. O arquivo WSDL é criado dinamicamente pelo JBoss no ato do deployment do componente. É importante citar que este arquivo é criado uma única vez, e para que este seja modificado é necessário efetuar um novo deployment do pacote do web service.
A seguir, passaremos para o primeiro estudo de caso demonstrando o uso do das anotações e dos recursos de EJB.
Estudo de Caso 1 – Apresentando o uso da anotações da JSR-181
Este estudo de caso visa apresentar as principais anotações na construção de um web service e onde estas devem ser utilizadas no projeto. As anotações definidas neste primeiro estudo de casos são: @Webservice, @WebMethod, @OneWay, @SOAPBinding. Ainda no primeiro estudo de caso é utilizada a anotação @Stateless. Esta anotação não faz parte das anotações descritas na JSR 181, mas é de suma importância para a criação de Web Services.
O exemplo basicamente retorna o nome de uma pessoa qualquer que o web service disponibilizado irá responder a solicitação via protocolo SOAP. Lembrando que o protocolo SOAP utiliza uma chamada HTTP para troca de informações via documento XML.
Para construção deste primeiro estudo de caso, foi utilizada a IDE Eclipse com um “Java Project” comum utilizando JVM 1.5. É necessária a utilização de algumas bibliotecas disponibilizadas pelo JBoss para que durante a construção da aplicação cliente, esta reconheça as anotações necessárias a serem utilizadas para construção do web service. Vide a seção “Dicas importantes” para visualizar as bibliotecas do JBoss a serem utilizadas.
Através da Figura 3 podemos visualizar como o projeto foi estruturado, onde basicamente foi criado uma interface para representar o EndPoint (EndPoint.java) e uma classe que representa o web service em si (WebService1.java).
Figura 3. Estrutura do projeto – Estudo de caso 1
O cliente para acessar a estrutura do web service deve efetuar uma chamada HTTP ao endereço do Endpoint. Para nosso estudo de caso, este endereço é http://claimant:8080/webmobile1/WebService1?wsdl.
A resposta a esta chamada recebida pelo cliente é um documento WSDL criado dinamicamente, onde o cliente através de implementação da API JAX-WS trata o documento recebido como resposta. Internamente no JBoss o pacote webmobile1.jar, que representa o projeto do primeiro estudo de caso, faz a troca de informações entre o EndPoint e o Web Service para processar a requisição. Através da Figura 4 podemos visualizar este cenário.
Figura 4. Comunicação com Web Service
Vejamos na Listagem 1 a estrutura da interface br.claimant.endpoint.EndPoint.java.
Listagem 1. O Endpoint
package br.com.claimant.endpoint;
import java.rmi.Remote;
import javax.jws.Oneway;
import javax.jws.WebMethod;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
@WebService(endpointInterface = "br.com.claimant.endpoint.EndPoint")
public interface EndPoint extends Remote {
@WebMethod(operationName = "getNome")
public String getNome();
@Oneway
@WebMethod(operationName = "ativaNada")
public void ativaNada();
}
Durante a criação da interface descrita na Listagem 1, foi utilizada a anotação @WebService. Com o uso desta anotação é que será possível disponibilizar uma interface Java como web service para ser acessado pela aplicação Cliente. Obrigatoriamente, a interface que representa o Endpoint deve implementar a interface Remote do pacote javax.rmi. Sem esta implementação não será possível acessar o seu conteúdo remotamente. É através da propriedade endpointInterface que o Servidor de Aplicações saberá em qual pacote irá buscar a interface que assume o papel de Endpoint.
Na interface br.com.claimant.endpoint.EndPoint foram declarados dois métodos: getNome() e ativaNada(). Com relação ao método getNome(), foi utilizada apenas a anotação @WebMethod. Esta anotação expõe o método para ser acessível à aplicação cliente do Web Service criado. Por default, todos os métodos de uma interface declarada como Endpoint serão acessíveis pelo web service. Este processo pode ser evitado utilizando a anotação @WebMethod(exclude=true). A anotação @WebMethod ainda faz uso da propriedade operationName, que define o nome do método (ou ainda a operação, do ponto de vista de um web service) que será encontrado no arquivo WSDL que será gerado dinamicamente pelo JBoss no ato do Deployment do “EJB Web Services”.
Já com relação ao método ativaNada(), além da anotação @WebMethod foi utilizada a anotação @Oneway. A anotação @Oneway é utilizada para métodos que apenas enviam uma solicitação ao web service e não aguardam retorno de informações. Perceba que o retorno do método que fazem uso desta anotação é declarado como void.
Para completar a construção de nosso primeiro web service, é necessário criar a classe do web service que irá implementar o Endpoint. A Listagem 2 apresenta seu código.
Listagem 2. O Web Service
package br.com.claimant.webservice;
import javax.ejb.Stateless;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.Style;
import javax.xml.ws.BindingType;
import br.com.claimant.endpoint.*;
@Stateless
@WebService(targetNamespace = "http://claimant:8080/webmobile1",
name = "ENDPOINT",
serviceName = "EndPointService")
@SOAPBinding(style = Style.RPC)
public class WebService1 implements EndPoint {
public String getNome() {
return "Augusto Marinho";
}
public void ativaNada() {
// TODO Auto-generated method stub
}
}
A classe br.com.claimant.webservice.WebService1 obrigatoriamente deve implementar a interface declarada como Endpoint do web service. A classe que representa um web service fazendo uso de EJB3 deve ser um Bean de Sessão sem estado (stateless), lembram do conceito citado anteriormente?
Para indicar esta característica para o servidor de aplicações, é necessário fazer uso da anotação @Stateless. Esta anotação não faz parte da JSR 181, mas é importante para informar ao JBoss que este é um EJB, e deve ser tratado como tal; este deve ser um recurso de software “sem estado” e disponível a todos que queiram utilizar e que possam efetuar uma chamada a sua estrutura via HTTP.
Da mesma forma que a interface que representa o Endpoint utiliza a anotação @WebService, a classe que implementa está interface também faz uso desta com as seguintes diferenças.
• É na classe que deve ser definido o targetNamespace de um Web Service. Este pode ser comparado a um contexto de um Servlet.
• É na classe que deve ser definida a propriedade name e serviceName de um Web Service.
• Se estas recomendações não forem seguidas, com certeza o JBoss não irá efetuar o deployment do web service corretamente (estes passos são muitos importantes).
Na classe que representa o web service, ainda é utilizada a anotação @SOAPBinding. É através desta anotação que a aplicação cliente saberá se comunicar com o web service via protocolo SOAP. Lembrando que o protocolo SOAP nada mais é do que uma chamada HTTP com um retorno em formato XML. Através da Figura 4 é apresentado um esboço da comunicação.
Pronto! Com estes passos já é possível empacotar nosso “EJB Web Service” e efetuar o deployment no JBoss. Para empacotar o web service no formato JAR foi utilizado um script Ant para facilitar nosso trabalho. Como na estrutura computacional em que foi desenvolvido o estudo a máquina onde o JBoss estava em execução era diferente da máquina de desenvolvimento, é possível perceber que na execução do script Ant faz uma requisição SCP à máquina Server do JBoss para efetuar o deployment do pacote. Caso na sua estrutura não haja necessidade de efetuar este procedimento, vide a seção “Dicas importantes” para consultar as modificações. A Listagem 3 apresenta o script de build utilizado para empacotamento e deployment do pacote.
Listagem 3. Arquivo build.xml
<?xml version="1.0" encoding="UTF-8" ?>
<project name="WebMobile1" default="transfere" basedir=".">
<!-- Criando as propriedades para empacotamento
-->
<property name="src" value="../src" />
<property name="classes" value="../bin" />
<property name="dest" value="../dest" />
<property name="build" value="../build" />
<target name="compile">
<javac srcdir="$" destdir="$" />
</target>
<target name="criajar" depends="compile">
<jar destfile="$\webmobile1.jar" basedir="$">
<manifest>
<attribute name="Main-Class" value="" />
</manifest>
</jar>
</target>
<target name="transfere" depends="criajar">
<scp file="$\webmobile1.jar" todir="jboss@claimant:/usr/local/programas/jboss/server/default/deploy" password="jboss" />
</target>
</project>
Este script encontra-se dentro do diretório build-ant, como pode ser visto através da Figura 3. Foram criadas quatro propriedades (tag property) que representam a estrutura de diretório do projeto, onde foram definidos os paths do fontes do projeto, caminho para qual será enviado o arquivo após compilado e empacotado (como arquivo jar) e onde será armazenadas as classes após o processo de compilação.
Após definidas as propriedades, o projeto possui três targets a saber: transfere, criajar, compile. Estes targets possuem dependências entre si, por isso que na segunda linha deste arquivo é informado o target default “transfere”, pois é executado de forma decrescente. Perceba que o target transfere possui uma dependência com o target “criajar”, que por sua vez possui uma dependência com o target “compile”.
Como todos estão “amarrados” em sua execução, com exceção do target “compile”, primeiro será executado o target “compile” e após será executados na ordem o target “criajar” e por último o “transfere”, que já irá fazer a chamada SCP à máquina do JBoss no diretório em que deve ser disponibilizado o pacote para efetuar o Hot Deployment (o JBoss por natureza efetuar o deployment automaticamente de qualquer pacote disponibilizado no diretório %JBOSS_HOME%/Server/default/deploy).
A Listagem 4 apresenta um pequeno trecho do arquivo Server.log contendo informações no ato do Deployment do pacote webmobile1.
Listagem 4. Trecho do server.log
23:29:48,136 INFO [EJBContainer] STOPPED EJB: br.com.claimant.webservice.WebService1 ejbName: WebService1
23:29:48,138 WARN [JmxKernelAbstraction] jboss.j2ee:jar=webmobile1.jar,name=WebService1,service=EJB3 is not registered
23:29:48,141 INFO [TomcatDeployer] undeploy, ctxPath=/webmobile1, warUrl=.../tmp/deploy/webmobile1.jar2199891621736841012.war/
23:29:48,173 INFO [DefaultEndpointRegistry] remove: jboss.ws:context=webmobile1,endpoint=WebService1
23:29:48,263 INFO [JmxKernelAbstraction] creating wrapper delegate for: org.jboss.ejb3.stateless.StatelessContainer
23:29:48,264 INFO [JmxKernelAbstraction] installing MBean: jboss.j2ee:jar=webmobile1.jar,name=WebService1,service=EJB3 with dependencies:
23:29:48,317 INFO [EJBContainer] STARTED EJB: br.com.claimant.webservice.WebService1 ejbName: WebService1
23:29:48,382 INFO [EJB3Deployer] Deployed: file:/usr/local/programas/jboss/server/default/deploy/webmobile1.jar
23:29:48,385 INFO [DefaultEndpointRegistry] register: jboss.ws:context=webmobile1,endpoint=WebService1
23:29:48,786 INFO [WSDLFilePublisher] WSDL published to: file:/usr/local/programas/jboss/server/default/data/wsdl/webmobile1.jar/EndPointService4990958546927506202.wsdl
23:29:48,868 INFO [TomcatDeployer] deploy, ctxPath=/webmobile1, warUrl=.../tmp/deploy/webmobile1.jar4346819442342762836.war/
Perceba que nas linhas em negrito é apresentada a criação do arquivo WSDL dinamicamente pelo JBoss. É importante lembrar que este arquivo é criado apenas uma única vez. Para o arquivo WSDL ser modificado, deverá ser feito um novo deployment do pacote.
Para finalizar este primeiro estudo de classes, será apresentado um pequeno exemplo de aplicação cliente standalone para demonstrar o funcionamento. Vide na Listagem 5 o código-fonte.
Listagem 5. Consumindo o Web Service
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import br.com.claimant.endpoint.*;
public class Principal {
/**
* @param args
* @throws MalformedURLException
* @throws Exception
*/
public static void main(String[] args) throws MalformedURLException, Exception {
System.out.println("... CONECTANDO AO WEBSERVICE ...");
URL url = new URL("http://claimant:8080/webmobile1/WebService1?wsdl");
QName qname = new QName("http://claimant:8080/webmobile1","EndPointService");
System.out.println("Criando um serviço com " + url + "-" + qname);
ServiceFactory factory = ServiceFactory.newInstance();
Service remote = factory.createService(url,qname);
EndPoint proxy = (EndPoint) remote.getPort(br.com.claimant.endpoint.EndPoint.class);
System.out.println("Retorno WebServices: " + proxy.getNome());
}
}
Através desta pequena aplicação cliente é possível invocar métodos de web services e possuir acesso às informações retornadas via protocolo SOAP. Para esclarecermos seu funcionamento, os seguintes comentários são importantes.
O objeto URL instanciado deve fazer sempre referência ao path completo do arquivo WDSL a ser invocado. O objeto QName instanciado representa um nome qualificado para especificação XML definida. Um objeto da classe QName contém informações referentes ao NameSpaceURI, partes locais e prefixos de um documento XML. Para este caso em específico, faz referência ao contexto do web service e o nome de serviço a ser invocado (Service name).
Além disso, o objeto service instanciado da classe javax.xml.rpc.Service representa o serviço que será invocado do web service. Quando é apresentado no código da Listagem 5 o trecho remote.getPort(br.com.claimant.endpoint.EndPoint.class) está sendo realizado uma chamada RPC para que seja retornada uma instância do Endpoint do web service (qualquer semelhança com um cliente de EJB não é mera coincidência). A chamada RPC fica caracterizada no trecho de código a seguir:
Service remote = factory.createService(url,qname);
Perceba que é necessário passar como parâmetro a instância dos objetos URL e QName, que basicamente representam respectivamente o endereço do arquivo WSDL e o serviço a ser acessado. E neste trecho de código que podemos caracterizar o uso do Dynamic Proxy, pois uma instância do web service é obtida dinamicamente, ou ainda, em tempo de execução. Após termos obtido uma cópia da instância do Endpoint, o Proxy, já é possível invocar os métodos do web service. Perceba que no trecho de código remote.getPort(br.com.claimant.endpoint.EndPoint.class); é feita uma chamada à interface que representa o EndPoint. O método getPort() do objeto remote (que representa uma instância da classe Service do pacote javax.xml.rpc.Service) retorna um Object, por isso é necessário efetuar o Type Casting do Retorna para um objeto da interface Endpoint do pacote br.com.claimant.endpoint.EndPoint. Na última linha de código do nosso exemplo de aplicação cliente já é possível efetuar uma chamada a estrutura do web service através do Proxy obtido, conforme trecho abaixo:
System.out.println("Retorno WebServices: " + proxy.getNome());
A chamada ao método getNome() do Web Service é apresentada na Figura 5.
Figura 5. Resultado da chamada ao método getNome
Através da Figura 5 foi possível visualizar o resultado retornado pelo web service através de uma chamada feita ao método getNome(). Como neste nosso primeiro exemplo foi definido no método getNome um retorno fixo com o nome “Augusto Marinho”, toda e qualquer chamada a esta estrutura retornará o mesmo resultado.
No Estudo de Caso 2 será apresentado mais dinamismo no funcionamento do web service, aproveitando para apresentar os recursos oferecidos pelo JBoss a uma aplicação Java EE. Para este segundo exemplo, será utilizado o PostgreSQL.
Estudo de Caso 2 – Autenticando a requisição
O nosso segundo estudo de caso visa implementar alguns critérios de segurança na construção de web services.
Como citado anteriormente, um web service implementado com recursos do EJB3 é um Bean de Sessão sem estado. Mediante esta característica, qualquer um que conheça a construção de uma aplicação cliente poderá ter acesso às informações de negócio disponibilizadas por um web service sem procedimentos de autenticação. É evidente que este acesso poderá ser impedido pela estrutura de redes e outras soluções, mas em teoria, qualquer pessoa poderia desenvolver um software cliente para ter acesso a um web service exposto.
Além desta requisição não ser autêntica, ainda cabe a questão: como identificar o cliente e manter o “estado” desta conexão? Esta pergunta é cabível já que os web services utilizando Bean de Sessão sem estado são invocados por seus clientes através de uma requisição HTTP.
O protocolo HTTP por natureza é orientado a conexão e não mantém informações do solicitante após a resposta enviada. Ainda existe outra característica inibidora de persistência de informações do solicitante, já que a classe que representa o web service utiliza a anotação @Stateless, onde caracteriza uma ação “sem estado” ao Bean de Sessão. O servidor de aplicações após criar uma instância deste EJB e devolver a resposta solicitada, já não mantém mais qualquer informação com relação à ação executada, por isso o nome “sem estado”.
A solução para este cenário é criar um mecanismo que permita autenticar a requisição do cliente e manter o “estado” desta conexão por um intervalo de tempo para que o cliente não precise se autenticar a cada requisição, tendo em vista que num ambiente de produção real este processo poderia causar sérios impactos de desempenho no ambiente.
Para viabilizar esta estratégia, continuaremos utilizando os web services como Beans de Sessão sem estado (Stateless), e passaremos a fazer uso do SessionID criado pelo web service no JBoss. O SessionID será criado e manipulado por uma instância da classe HttpSession, muito utilizada com Servlets e páginas JSPs para manter o “estado” da conexão HTTP.
Através destes recursos apresentados, o segundo estudo de caso cria um web service autenticado, onde a aplicação cliente somente terá que se autenticar uma única vez para efetuar chamadas ao métodos de um web service, até que está sessão seja encerrada pelo cliente. Para viabilizar esta arquitetura, o padrão de projeto web service broker (Figura 6) é bastante indicado para o modelo do serviço a ser disponibilizados pelos “EJBs Web Services”.
Figura 6. Padrão de Projeto Web Service broker
Na Figura 6 onde é definido um EndPointProcessor não necessariamente devemos fazer uso de um servlet para efetuar uma chamada ao web service. Na verdade, o bloco que identifica o EndPointProcessor deve ser uma “interface de acesso”, e para isto no nosso estudo de caso usamos uma interface Java para representar o EndPointProcessor.
Existem várias formas de se construir um web service e efetuar requisições à sua estrutura. O uso de Servlet nesta arquitetura poderá ser um forte aliado na utilização da classe HttpSession. No nosso caso, em específico, utilizamos um Singleton denominado de Sessao. A classe Sessao possui um ArrayList de String responsável por armazenar os SessionIDs criados pela instância da classe HttpSession. Estes SessionIDs nada mais são do que uma String que representa um Hash identificando unicamente esta requisição oriunda da máquina a qual o solicitou.
Para relembrar os conceitos de padrões de projeto, um Singleton é uma classe Java que possui apenas uma única instância para JVM responsável pela execução da aplicação Java.
O Cliente antes de tentar solicitar um método de negócio deve primeiramente solicitar uma autenticação, informando usuário e senha, previamente cadastrados, ao método do web service. Caso o processo de autenticação seja bem sucedido, o web service irá retornar o SessionID criado para o solicitante, e este valor será armazenado dentro do ArrayList do Singleton Sessao.
A cada requisição a métodos de negócio, o cliente deverá informar este SessionID como uma espécie de Token, para validar seu acesso. Mas a frente será apresentando o código-fonte da solução. Do ponto de vista da arquitetura utilizada, é possível ilustrar este cenário através da Figura 7.
Figura 7. Arquitetura do Estudo de Caso 2
Na Figura 7 é possível perceber que o Core Business das informações encontra-se dentro do pacote do web service. Este procedimento não é obrigatório, apenas foi a maneira adotada para simplificar o projeto e manter o foco no assunto de “estado da requisição”. Em um projeto real este procedimento não deve ser adotado por motivos de escalabilidade, mas sigamos com o nosso estudo de caso.
O Core Business apresentado na Figura 7 representa os métodos de negócio, que no projeto do estudo de caso 2 podem ser encontrados dentro package br.com.claimant.business. Os métodos de negócio, especificamente em nosso estudo de caso 2, têm a responsabilidade de efetuar a conexão com o banco de dados e obter as informações solicitadas através da chamada feita do web service a esta estrutura de negócio.
A camada de negócio criada para solicitar as informações à base de dados fará uso de um Datasource disponibilizado no JBoss para conexão com o banco de dados. Para maiores detalhe, vide a seção “Dicas importantes” para demonstrar o deployment de um DataSource. O uso de Datasource no processo de conexão de uma aplicação web que utilize banco de dados é uma das melhores formas de otimizar os recursos computacionais, já que boa parte deste trabalho de gerenciamento é divido com o JBoss.
O projeto de desenvolvimento do nosso segundo estudo de caso foi estruturado da seguinte maneira:
• Packages:
o br.com.claimant.business: As classes contidas neste package representam as classes de negócio, compondo o Box Core Business apresentado na Figura 7.
ü Clientes: Classe criada para representar o acesso à camada de negócio da aplicação. Esta é responsável por acessar a base de dados PostgreSQL e retorna os clientes ativos, num fictício cadastro de clientes.
ü Retornos: Interface utilizada para identificar os erros já mapeados no processo de desenvolvimento, e termos os retornos unificados apresentados pela aplicação. É apenas uma forma mais organizada de tratar as informações!
ü Sessao: Singleton criado para ser o mantenedor do “estado” da requisição dos clientes deste web service. Basicamente, este Singleton adiciona e remove SessionIDs da sua estrutura, estrutura está que utiliza um ArrayList para gerenciar estas informações.
o br.com.claimant.dao
ü ConexaoSGBD: Interface criada para definir os dados referentes ao DataSource disponibilizado no JBoss para acesso ao banco de dados PostgreSQL. A motivação para criação desta interface é idêntica a da interface Retornos. É apenas uma forma de organizar as informações!
ü Queries: Interface criada para armazenar as queries que serão realizadas na base de dados. Idem à ConexaoSGBD e Retornos.
o br.com.claimant.dao.impl
ü ConexaoSGBDImpl: Classe criada para implementar a interface br.com.claimant.dao.ConexaoSGBD. É nesta classe que será obtida uma conexão instanciada pelo DataSource do JBoss. É com o uso desta conexão que será possível efetuar as consultas a base de dados.
o br.com.claimant.endpoint
ü JAXWSIF: Interface que faz uso da anotação @webservice e implementa a interface Remote do pacote javax.rmi. Esta interface representa o EndPoint do web service.
o br.com.claimant.webservice
ü JAXWS: Classe que representa o web service de fato. Também faz uso da anotação @webservice.
Conforme citado a pouco, foi utilizado o padrão web service broker, que através da Figura 6 apresenta as camadas a serem implementadas pelo web service. Ainda com relação à Figura 6, é definido a sequência de construção: Cliente, EndPointProcessor, Web Service, Business Service. Substituindo esta seqüência para estrutura do nosso segundo estudo de caso, teremos os seguintes passos a serem implementados:
1. Efetuar uma chamada RPC ao endereço do arquivo WSDL e obter um Proxy do Web Service.
2. Através do Proxy invocar o método criaSessao(String usuário, String senha), passando como parâmetros o usuário e senha pré-cadastrados no sistema. A chamada a este método, caso a autenticação tenha sido bem sucedida, irá retornar o SessionID do usuário e armazená-lo no ArrayList no Singleton Sessao do pacote br.com.claimant.business.
3. O passo a seguir é invocar o método do web service, através do Proxy obtido, para ter acesso as informações de negócio. Neste estudo de caso o método que fará este papel é o getClientesAtivos(String SESSIONID). Perceba que este método espera como parâmetro o SessionID retornado pelo web service no momento da autenticação.
4. Ao efetuar a chamada ao método getClientesAtivos(String SESSIONID), internamente o parâmetro será validado através de consulta ao Singleton Sessao. Caso este SessionID exista será chamado o método de negócio.
5. O método de negócio recebe o mesmo nome do método do Web Service, com uma sutil diferença. O método getClientesAtivos() da classe Clientes do pacote br.com.claimant.business não espera nenhum parâmetro. Esta sutil diferença é utilizada, pois a camada de negócio, representada pela classe Clientes do pacote br.com.claimant.business, não deve ficar responsável por efetuar procedimentos de validação; este procedimento deve ficar na camada que acessa a estrutura de negócio. Em nosso estudo de caso, a camada anterior é representada pela classe JAXWS do pacote br.com.claimant.webservice.
6. Após o web service ter invocado o método de negócio, este será responsável em efetuar a consulta ao banco de dados e retornar as informações necessárias ao método ao qual o chamou, no caso, o método getClientesAtivos(String SESSIONID).
7. Terminadas as operações, o cliente poderá invocar uma chamada ao método encerraSessao(String SESSIOID) para que seja removido do Sinlgeton Sessao o hash referente a sessão deste cliente.
• Base de dados
A Listagem 6 apresenta o script responsável por criar a estrutura de tabelas utilizadas no nosso estudo de caso.
Listagem 6. Estrutura de Tabelas
-- Table: clientes
CREATE TABLE clientes
(
id_cliente bigserial NOT NULL,
ativo boolean,
nome_cliente character varying(50) NOT NULL,
email character varying(60),
CONSTRAINT clientes_pkey PRIMARY KEY (id_cliente)
)
WITH (OIDS=FALSE);
ALTER TABLE clientes OWNER TO postgre;
-- Table: usuarios
CREATE TABLE usuarios
(
id_usuario bigserial NOT NULL,
"login" character varying(50) NOT NULL,
senha character varying(10) NOT NULL,
CONSTRAINT usuarios_pkey PRIMARY KEY (id_usuario)
)
WITH (OIDS=FALSE);
ALTER TABLE usuarios OWNER TO postgre;
Foram criadas apenas duas tabelas para o nosso segundo estudo de caso. A tabela clientes é responsável por cadastrar todos os clientes fictícios de uma determinada empresa. A tabela usuarios é responsável por armazenar os usuários que terão acesso a estrutura do web service disponibilizado.
No decorrer do artigo poderá ser percebido que o objetivo do web service é consultar a tabela clientes e obter todos os clientes que possuam o cadastro ativo.
Obtendo a sessão com HttpServletRequest
A Listagem 7 apresenta parte do código da classe responsável pela implementação do EndPoint (br.com.claimant.webservice.JAXWS). A classe JAXWS é o próprio web service. O projeto completo poderá ser obtido no portal da Revista WebMobile.
Listagem 7. Método criaSessao da classe br.com.claimant.webservice.JAXWS
1. package br.com.claimant.webservice;
// Área de imports
2. @Stateless
@WebService(targetNamespace = "http:// am-engenharia:8080/ jaxws", name = "JAXWS")
@SOAPBinding(style = Style.RPC)
public class JAXWS implements JAXWSIF {
3. @Resource private WebServiceContext ctx;
4. private HttpSession sessao;
/* Método responsável pela criação de Sessão do Usuário */
5. public String criaSessao(String usuario, String senha) throws Exception {
6. ConexaoSGBDImpl conexao = new ConexaoSGBDImpl();
7. PreparedStatement pt = null;
8. ResultSet rs = null;
9. pt = conexao.getConnection().prepareStatement(Querys.QUERY_AUTENTICA, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
10. pt.setString(1, usuario);
11. pt.setString(2, senha);
12. rs = pt.executeQuery();
13. if (rs.next()) {
14. MessageContext msgCtx = ctx.getMessageContext();
15. HttpServletRequest request = (HttpServletRequest)msgCtx.get(MessageContext.SERVLET_REQUEST);
16. sessao = request.getSession();
//Atribui um número aleatório de 0 a 999
17. sessao.setAttribute("ID", Math.random()*1000);
18. rs.close();
19. pt.close();
20. conexao.fecharconexao();
21. Sessao.setSessoes(sessao.getId());
22. return sessao.getId();
}
else {
23. rs.close();
24. pt.close();
25. conexao.fecharconexao();
26. return Retornos.ERRO_CRIACAO_SESSAO;
27. }
28. }
29. }
Nesta classe é declarado o atributo WebServiceContext utilizando a anotação @Resource, como pode ser visto na linha 4 da Listagem 7.
O uso de uma instância do WebServiceContext para o cenário do estudo de caso 2 é necessário, pois é através deste objeto que será possível a utilização do EndPoint (o próprio Web Service) do lado do servidor. Para que isto seja possível, é necessário fazer uso da anotação @Resource, pois é através desta anotação que é feita uma injeção de dependência da implementação do EndPoint a instância do classe WebServiceContext.
Posteriormente, no momento da criação da sessão no método criaSessao(String usuario, String senha), é possível ter acesso a uma instância do Objeto HttpServletRequest utilizando a injeção de dependência realizada através da anotação @Resource no objeto ctx da classe WebServiceContex (linhas 14 e 15).
É através do objeto ctx que é possível efetuar a instância de um objeto da classe HttpSession. Este artifício se faz necessário, pois o acesso da aplicação cliente a estrutura do Web Service é feita via SOAP, lembram? O SOAP é uma chamada HTTP com retorno em XML, não existe acesso as instâncias de HttpServletRequest e HttpSession.
Após ter acesso aos objetos HttpServletRequest, já é possível efetuar uma chamada ao método getSession(), que basicamente tem a função de retornar uma instância da classe HttpSession (linha 16).
Após ter obtido a instância da classe HttpSession, é criada a sessão passando como parâmetro o atributo ID juntamente com um número aleatório de 0 a 999. Na seqüência, o Hash da Session é obtido através do método getId(), como está descrito nas linhas 17 a 21.
Toda está lógica foi criada para atender a demanda do método public String criaSessao(String usuario, String senha). É através deste método que a aplicação cliente irá validar seu acesso a estrutura de negócio e armazenar o SessionID criado para passar como parâmetro aos próximos acessos a métodos do web service nas próximas requisições.
Este SessionID será válido até que o software cliente invoque método encerraSessao().
Acessando a estrutura de negócio
O método do web service JAXWS que faz o papel de “ponte” para acessar a camada de negócio (Core Business) é o método getClientesAtivos(String SESSIONID), descrito na Listagem 8.
Listagem 8. Método getClientesAtivos da classe br.com.claimant.webservice.JAXWS
1. public String getClientesAtivos(String SESSIONID) throws Exception {
2.
3. if(getSessionID(SESSIONID).equals(SESSIONID)) {
5. Clientes clientes = new Clientes();
6. return clientes.getClientesAtivos();
7. }
8. else {
9. return Retornos.ERRO_SESSAO_NAO_EXISTE;
10. }
11. }
/* Método responsável por retornar o SessionID criado */
12. @SuppressWarnings({ "unchecked" })
13. public String getSessionID(String SESSAOID) {
14. ArrayList temp = new ArrayList();
15. String sessaotemp = "";
16. temp = Sessao.getSessoes();
17. for(int i = 0; i < temp.size(); i ++) {
18. if(((String) temp.get(i)).equals(SESSAOID)) {
19. sessaotemp = (String) temp.get(i);
20. break;
21. }
24. }
25. return sessaotemp;
26. }
Perceba que este método exige como parâmetro o SESSIONID do cliente, que foi criado na primeira chamada ao método criaSessao(String usuario, String senha, descrito na Listagem 7.
Este método valida o SESSIONID passado como parâmetro utilizando o método getSessionID(String SESSIONID) descrito nas linhas 12 a 24 da Listagem 8. É através deste método que é percorrido o ArrayList do Singleton Sessao do pacote br.com.claimant.business para validar o SESSIONID.
Caso este método não retorne true para a crítica feita na linha 3, será retornado uma mensagem de erro mapeada na interface Retornos do pacote br.com.claimant.business (linhas 8 a 10). Em caso positivo, o método getClientesAtivos(String SESSIONID) do Web Service irá chamar o método getClientesAtivos() da classe Clientes do pacote br.com.claimant.business (linhas 3 a 7).
A diferença entre os métodos é a exigência do parâmetro. O método getClientesAtivos() da classe Cliente é acesso pelo método getClientesAtivos() do web service, conforme é descrito no padrão de projeto web service broker, vide Figura 6.
Encerrando a Sessão
Após a aplicação cliente ter obtido as informações necessárias, é importante que seja feito o encerramento da sua sessão. Para isso, o software cliente deve invocar o método encerraSessao(String SESSIONID) do web service, passando como parâmetro o SESSIONID retornado durante a solicitação de autenticação (Listagem 9).
Listagem 9. Método encerraSessao da classe br.com.claimant.webservice.JAXWS
1. @SuppressWarnings({ "unchecked", "static-access" })
2. public String encerraSessao(String SESSAOID) {
3. boolean retorno = false;
4. ArrayList temp = new ArrayList();
5. temp = Sessao.getSessoes();
6. for(int i = 0; i < temp.size(); i ++) {
7. if(((String) temp.get(i)).equals(SESSAOID)) {
8. temp.remove(i);
9. break;
10. }
11. }
12. retorno = true;
13. return String.valueOf(retorno);
14. }
Basicamente, o método encerraSessao(String SESSIONID) percorre o ArrayList do Singleton Sessao procurando pelo SESSIONID. Caso este seja encontrado, o mesmo será removido (linha 8).
Entendo o método de negócio getClientesAtivos()
Agora conheceremos um pouco sobre a classe de Clientes (br.com.claimant.business.Clientes), descrita na Listagem 10.
Listagem 10. Explicando a classe br.com.claimant.business.Clientes
1. package br.com.claimant.business;
2.
3. import java.sql.PreparedStatement;
4. import java.sql.ResultSet;
5. import br.com.claimant.dao.Querys;
6. import br.com.claimant.dao.impl.ConexaoSGBDImpl;
7.
8. public class Clientes {
9.
10. /* Método responsável em obter os clientes ativos */
11. public String getClientesAtivos() throws Exception {
12.
13. ConexaoSGBDImpl conexao = new ConexaoSGBDImpl();
14. PreparedStatement pt = null;
15. ResultSet rs = null;
16.
17. pt = conexao.getConnection().prepareStatement(Querys.QUERY_CLIENTES_ATIVOS, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
18. rs = pt.executeQuery();
19.
20. if (rs.next()) {
21. rs.last();
22. int qtd_linhas = rs.getRow();
23. rs.first();
24. StringBuffer resultado_rs = new StringBuffer();
25.
26. for(int i = 0; i < qtd_linhas; i++) {
27. resultado_rs.append("[");
28. resultado_rs.append(rs.getString(1));
29. resultado_rs.append(",");
30. resultado_rs.append(rs.getString(2));
31. resultado_rs.append("];");
32. rs.next();
33. }
34.
35. rs.close();
36. pt.close();
37. conexao.fecharconexao();
38.
39. return resultado_rs.toString();
40. }
41. else {
42. rs.close();
43. pt.close();
44. conexao.fecharconexao();
45. return Retornos.SEM_CLIENTES_ATIVO;
46. }
47. }
48. }
O método getClientesAtivos() da classe Clientes (Listagem 10) é acessado pelo método getClientesAtivos() da classe JAXWS do pacote br.com.claimant.webservice (Listagem 8), que representa o web service em si. Ao ser invocado o método da classe de negócio, esta fica responsável por acessar a base de dados utilizando o DataSource e obter as informações necessárias.
Este método de negócio traz a lista de clientes ativos na base de dados e monta as informações em uma grande String. Esta String é montada da seguinte forma:
[NOME_CLIENTE, E_MAIL],[NOME_CLIENTE,EMAIL] ....
Vide exemplo a seguir:
[Augusto Marinho, augusto@claimant.com.br],[Fulano, fulano@fulano.net] ....
Esta estrutura “chave-valor”, que na verdade é semelhante a uma tupla, foi o artifício utilizado para seguir a recomendação referente ao tipo de dados retornado pelo web service. Resgatando os conceitos já apresentados, é aconselhável que ele retorne tipos de dados primitivos para que possam ser acessados por qualquer outra linguagem de programação.
Ao retornar as informações seguindo esta estrutura, podemos garantir que qualquer aplicação poderá ter acesso às informações retornadas pelo web service. A única desvantagem nesta estrutura é que fica sob responsabilidade da aplicação cliente efetuar o parse desta String para processar as informações de forma correta.
Após este método ter montado o dado, este será retornado ao método ao qual o invocou. No nosso estudo de casos, estas informações serão retornadas ao método getClientesAtivos() do web service JAXWS, e este por sua vez retorna as informações ao Proxy do Web Service obtido pela aplicação cliente.
Após apresentar estes conceitos do nosso segundo estudo de caso, é importante apresentar como este web service deverá ser consumido pela aplicação cliente, assim como feito no estudo de caso 1.
Para testar o funcionamento deste projeto, foi implementando uma pequena aplicação cliente. Esta aplicação está descrita na Listagem 11 (JAXWSClient).
A aplicação cliente disponibiliza um pequeno menu de acesso a estrutura de métodos do web service. Para agilizar o processo de desenvolvimento, a classe JAXWSClient utiliza a classe Keyboard.java para facilitar a captura de informações do teclado. Esta classe pode ser obtida em http://duke.csc.villanova.edu/jss/downloads/Keyboard.java.
Diferente da aplicação cliente do estudo de caso 1, o software cliente do segundo estudo de caso define a classe JAXWSClient herdando as características da classe javax.xml.ws.Service (declaração da classe JAXWSClient), como pode ser visto na Listagem 11.
Listagem 11. Código da classe JAXWSClient
1. public class JAXWSClient extends javax.xml.ws.Service {
//Construtor Default da Super-Classe
2. protected JAXWSClient(URL wsdlDocumentLocation, QName serviceName){
3. super(wsdlDocumentLocation, serviceName);
// TODO Auto-generated constructor stub
4. }
// Construtor criado para facilitar o trabalho
5. public JAXWSClient() throws Exception {
6. super(new URL("http://am-engenharia:8080/jaxws/JAXWS?wsdl"),
7. new QName("http://am-engenharia:8080/jaxws", "JAXWSService"));
8. }
9. public JAXWSIF getJAXRPCPort() {
10.
11. return (JAXWSIF) super.getPort(new QName("http://am-engenharia:8080/jaxws", "JAXWSPort"), JAXWSIF.class);
12. }
.
.
.
13. }
Esta implementação foi adotada para demonstrar que existem formas mais elegantes no processo de construção de web service e que podem tornar o trabalho mais rápido e adequado aos conceitos de orientação a objetos
A classe Service do pacote javax.xml.ws possui o construtor protected JAXWSClient(URL wsdlDocumentLocation, QName serviceName), vide linhas 2 a 4. Ele recebe como parâmetros um objeto do tipo URL (para representar o endereço remoto do documento WSDL) e um objeto do tipo QName (que representa o endereço do contexto disponibilizado para acesso ao Web Service).
Este construtor é invocado pelo Construtor Default JAXWSClient(), vide linhas 5 a 8. É importante citar que para este exemplo de aplicação cliente, apenas foi disponibilizado um web service a ser consumido. Por isso os parâmetros foram passados de forma estática. Em projetos de softwares com dimensões maiores, este trecho deve ser modificado para oferecer o dinamismo esperado.
Através do método Main foram disponibilizadas na aplicação cliente quatro funcionalidades através de um pequeno menu. As funcionalidades são: Criar sessão, Capturar clientes, Encerrar sessão e Sair. Sendo assim, ao executar a classe JAXWSClient, que representa o nosso software cliente, será disponibilizado um pequeno menu com as opções citadas a pouco, conforme a Figura 8.
Figura 8. Menu da aplicação cliente – Estudo de caso 2
Ao ser apresentado o menu, a aplicação cliente já estará pronta para interação do usuário aguardando que seja escolhida uma das opções.
Antes de ser feita qualquer solicitação a métodos de negócio ou até mesmo encerrar uma “possível” sessão, deve ter sido solicitado ao web service a criação de uma nova sessão para o cliente. Para isso é necessário selecionar a opção 1 do menu disponibilizado para criação de sessão, onde então será executado o trecho responsável pela criação de sessão (Listagem 7).
Caso a sessão tenha sido criada com sucesso, uma String representando o SessionID criado será retornada pelo web service. Em caso de falha, o web service retornará a String "#002#", que significa erro na criação de Sessão – vide interface br.com.claimant.business.Retornos para consultar os retornos mapeados pela aplicação.
Após ter obtido a sessão, já é possível acessar a opção 2 do menu para obter as informações de negócio, ou seja, a relação de clientes ativos na empresas (Listagem 8). A Figura 9 visualiza um exemplo de retorno fornecido pela aplicação clientes.
Figura 9. Consumindo os dados do Web Service– Estudo de caso 2
As informações retornadas pelo web service deverão ser tratadas pela aplicação cliente. Neste segundo estudo de caso não será feito parse da String retornada pelo web service, já que perderíamos o foco do tema, tendo em vista que este é um procedimento bem simples de ser feito.
Para finalizar o nosso software cliente, após termos obtidos os dados referentes às regras de negócio da empresa (Core Business), já é possível encerrar a sessão criada (Listagem 9). Para isso, deve ser escolhida a opção 3 do menu, cujo resultado será apresentado na Figura 10, para o encerramento da sessão atual utilizada.
Figura 10. Encerrando sessão do Web Service– Estudo de caso 2
A seguir serão apresentados maiores detalhes da solução, assim como a regra de negócio utilizada e os resultados da análise do estudo de caso 2.
Apresentando os resultados
Para implementar os critérios de segurança do nosso segundo estudo de caso, fizemos uso de uma instância da classe HttpSession. Neste exemplo, em específico, o objeto da classe HttpSession é apenas utilizado para criação de um novo SessionID no JBoss, pois a persistência desta informação será feita no Singleton Sessao.
Na descrição deste estudo de caso não será discutido a fundo os critérios de segurança que deveriam ter sido implementados para que realmente este projeto possa ser utilizado como um mecanismo de segurança. O foco neste momento é apresentar os recursos de web services com EJB3 e destacar a idéia de que os critérios de segurança devem fazer parte de nosso dia a dia no processo de construção de software.
Existem várias maneiras de implementar um projeto desta natureza. Com isso, deixaremos que a melhoria desta etapa conte com a sua criatividade na arquitetura de sistemas, ok? Voltemos ao nosso raciocínio.
Toda e qualquer chamada a um web service deve se referir a um método exposto por este. Com base nestas informações, através de nosso web service JAXWS é possível termos acesso aos métodos definidos na sua interface remota JAXWSIF:
• getSessionID();
• criaSessao(String usuario, String senha);
• getClientesAtivos(String SESSIONID);
• encerraSessao(String SESSIONID);
Dentro do conceito de web services, estas são as operações definidas e disponibilizadas para acesso aos clientes.
Como já citado anteriormente, somente será possível ter acesso a um método de negócio do web service se o cliente, por sua vez, já tiver solicitado uma autenticação ao web service e este ter devolvido ao solicitante um SessionID. Para que o cliente tenha este SessionID retornado pelo web service, primeiramente, deverá ser feita uma chamada ao método criaSessao, onde o cliente deverá informar seu usuário e senha para validação. Após esta validação ter sido bem sucedida, neste momento o software cliente deverá armazenar de alguma forma o SessionID retornado (lembrando que este SessionID foi criado internamente fazendo uso de uma instância da classe HttpSession).
De posse deste Token, o SessionID, o software cliente já poderá acessar a estrutura de negócio disponibilizada pelo web service passando sempre como parâmetro o Token para se identificar.
Perceba no código-fonte do web service JAXWS que o cliente para acessar a estrutura de negócio disponibilizada passará a String contento o SessionID recebido no ato da autenticação, mas este SessionID é passado ao método getClientesAtivos do web service, e não da camada de negócio. Em nosso caso, ao método getClientesAtivos() da classe br.com.claimant.business.Cliente.
Lembram quando foi citado anteriormente que em um ambiente de produção real a camada de negócio não deverá ser disponibilizada junto com o web service? A camada de negócio em um ambiente de dados distribuídos deve ser acessível a todas as aplicações, e quem deve ser responsável pela validação, conseqüentemente permitindo o acesso, deve ser uma camada de software anterior.
Em nosso cenário, este processo deve estar na camada responsável pelo Web Service Broker, vide Figura 6. O Web Service Broker é representado pelo web service JAXWS. Perceba que o método getClientesAtivos do web service JAXWS é que requer o parâmetro “String SESSIONID” e não o método getClientesAtivos da classe br.com.claimant.business.Cliente.
O encerramento da sessão deve ser comandada pelo cliente do web service. Para isto, é necessário efetuar uma chamada ao método encerraSessao(String SESSIONID).
Ao efetuar esta chamada, o método encerraSessao(String SESSIONID) “varre” o ArrayList procurando pelo SESSIONID passado como parâmetro. Ao encontrar a informações, o método remove do ArrayList na posição onde foi encontrada o SESSIONID.
Após este procedimento, este “Token” já não estará mais disponível para ser validado pelo web service. Ao tentar efetuar uma chamada a métodos de negócio do web service, será apresentado os devidos tratamentos mapeados na interface br.com.claimant.business.Retornos.
É importante reforçar que este estudo de caso direciona o desenvolvedor a melhorar esta lógica de segurança, senão perderíamos nosso foco! Mas como dica, aproveitando ainda esta solução apresentada, poderia substituir o ArrayList por um HashMap, onde nesta nova estrutura de dados poderia ser armazenado o endereço IP do solicitante e o SessionID criado para este IP.
Com o uso do HashMap, o web service poderia garantir que o cliente solicitante possui um único endereço IP e um único SessionID. Na estrutura atual, o cliente de nosso web service poderá ter “n” SessionIDs, e isso com certeza caracteriza uma falha de segurança!
Está é apenas mais uma forma de tornar o exemplo mais seguro, mas acreditamos que cada leitor deste artigo terá uma solução utilizando estes recursos. Sintam-se a vontade em melhorar o processo!
Dicas importantes
No decorrer deste artigo foram apresentandos muitos conceitos e aplicações práticas destes. É importante fazer alguns parênteses para garantir que a partir deste momento o leitor esteja apto a desenvolver web services com EJB3 e JBoss.
• Inicializando o JBoss
O JBoss, basicamente, pode ser iniciado utilizando três opções: minimum, default, all. Para nosso exemplo, o JBoss deverá ser iniciado como default. A opção default oferece todos os recursos necessários para o nosso exemplo e quase todos os recursos disponíveis pelo JBoss.
O JBoss inicia com o modo default sem precisar passar nada como parâmetro para o script de inicialização, mas por medidas de segurança, para garantir a execução do modo default pelo JBoss o script run.sh deve ser iniciado com a opção –c default.
É importante ainda citar que o JBoss por default é inicializado apenas para atender requisições locais, para que ele possa atender as requisições oriundas da rede ao qual pertence deve ser inicializado com a opção –b [endereço_ip]. Segue o exemplo de utilização do comando: ./run.sh –c default –b 192.168.0.1.
• Adaptando script de build
Caso não haja necessidade de transferir o pacote .jar gerado para Hot Deploy no JBoss, segue o arquivo de build a ser utilizado com o Ant para geração.
<?xml version="1.0" encoding="UTF-8" ?>
<project name="WebMobile1" default=" criajar" basedir=".">
<!-- Criando as propriedades para empacotamento
-->
<property name="src" value="../src" />
<property name="classes" value="../bin" />
<property name="dest" value="../dest" />
<property name="build" value="../build" />
<target name="compile">
<javac srcdir="$" destdir="$" />
</target>
<target name="criajar" depends="compile">
<jar destfile="$\webmobile1.jar" basedir="$">
<manifest>
<attribute name="Main-Class" value="" />
</manifest>
</jar>
</project>
Caso haja necessidade de transferir os arquivos via SCP, é necessário baixar o framework JSCP e disponibilizá-lo no Classpath das bibliotecas do Eclipse. O Framework JSCP pode ser obtido através do site http://www.jcraft.com/jsch/.
• Libs necessárias para os estudos de caso
Para que os nossos estudos de caso possam ser compilados corretamente, algumas libs do JBoss devem ser inseridas no Classpath de nossos projetos. As libs utilizadas podem ser encontradas nos seguintes diretórios do JBoss:
o %JBOSS_HOME%/lib
o %JBOSS_HOME%/lib/endorsed
o %JBOSS_HOME%/server/default/lib
Estas libs poderiam ser utilizadas para desenvolver qualquer projeto que utilize o JBoss como Servidor de Aplicações, fazendo uso de EJB3, JPA etc.
• Efetuando Deploy de um DataSource
Para efetuar o Deployment de um DataSource no JBoss, basta disponibilizar o um arquivo XML com o nome postgres-ds.xml com o conteúdo da Listagem 12.
Listagem 12. Arquivo XML
<?xml version="1.0" encoding="UTF-8" ?>
<!-- ========================================================= -->
<!-- JBoss Server Configuration -->
<!-- $Id: postgres-ds.xml 63175 2007-05-21 16:26:06Z rrajesh $ -->
<!-- Datasource config for Postgres -->
<!-- ========================================================= -->
<datasources>
<local-tx-datasource>
<jndi-name>PostgresDS</jndi-name>
<connection-url>
jdbc:postgresql://192.168.0.1:5432/webmobile
</connection-url>
<driver-class>org.postgresql.Driver</driver-class>
<user-name>postgre</user-name>
<password>12345678</password>
<use-java-context>false</use-java-context>
<metadata>
<type-mapping>PostgreSQL 8.0</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
O nome do arquivo que representa o DataSource pode ter qualquer nome, mas para que o JBoss possa entender que este arquivo representa um Deployment de DataSource o nome do arquivo obrigatoriamente deve terminar com “-ds.xml”.
Conclusões
A tecnologia Java EE vem crescendo a cada dia, onde novos recursos são apresentados a cada nova versão da ferramenta. É cada vez mais comum as empresas buscarem a integração de seus sistemas, onde o mais importante para um gestor é a informação concisa, independente de qual sistema esta seja oriunda.
A arquitetura Orientada a Serviços vem trazendo uma série de conceitos que permitem estruturar um ambiente de software de forma integrada. Através desta centralização é possível criar uma “base de conhecimento”.
Os web services são parte fundamentais de um projeto de software utilizando SOA. Estes conceitos visam produtividade e redução do impacto de um possível retrabalho nas aplicações já em produção, visam também um aumento na produtividade, conseqüentemente otimizando o tempo de desenvolvimento. Mediante estas características, a tecnologia J2EE não poderia deixar de oferecer ferramentas para estarem em conformidade com as novas demandas da Engenharia de Software, já que seu principal foco é a robustez, escalabilidade e portabilidade, conceitos estes que estão totalmente alinhados com os conceitos de SOA.
Num passado não tão distante, criar web services era um trabalho árduo, onde o desenvolvedor além de ter que conhecer as regras de negócio e a tecnologia de desenvolvimento, ainda era praticamente obrigado a dominar a formação do arquivo WSDL, que como vimos no artigo nada mais é que um documento XML baseado em XML Schema.
A evolução do EJB para sua versão três trouxe fortemente o uso das anotações, que como vimos no artigo, facilita muito o processo de desenvolvimento e torna transparente para o desenvolvedor muitas etapas do processo de criação e desenvolvimento de um web service.
Os conceitos apresentados aqui servem para estimular a pesquisa e a criatividade em fazer uso destes recursos e oferecer as suas aplicações os principais conceitos adotados pelo mercado corporativo. Aproveitem!
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo