terça-feira, 22 de janeiro de 2013

Criando seu primeiro serviço Java-First com JAXWS e Maven

Neste artigo, criaremos nosso primeiro serviço utilizando a API JaxWS. Para implementar os exercícios mencionados aqui, é necessário fazer algumas configurações no seu ambiente de desenvolvimento (seu ambiente necessitará de acesso a web).

A primeira modificação necessária, é instalar o plugin para adicionar suporte a Maven no eclipse (sim, utilizaremos o eclipse como IDE). Para efetuar esta instalação, abra o eclipse market e instale o plugin M2E. Outro ponto importante é atualizar sua biblioteca Jaxb (se estiver utilizando o JDK 1.6). Para executar esta atividade, copie a biblioteca do jaxb 2.2.0 ou superior para a pasta endorsed da JRE em execução (no meu caso, copiei para <i>/usr/lib/jvm/jdk1.6.0_37/jre/lib/endorsed</i> (caso a pasta endorsed não exista, você deverá criá-la).

Lembre-se também que será necessário ter o servlet container Tomcat devidamente configurado e executando em sua máquina (não coberto pelo artigo).

Crie um projeto do tipo Maven (File -> New -> Project -> Maven -> Maven Project), e selecione a opção "Create a simple project (skip archetype selection)" e preencha as informações básicas do projeto (seção Artifact) de acordo com os dados disponibilizados abaixo:

Group Id: br.com.ws.exemplo
Artifact Id: helloplanet-jaxws
Version: 1.0.0
Packaging: war


O projeto é criado e automaticamente é adicionado um arquivo chamado pom.xml. Este arquivo configura o processo de compilação e construção do pacote final de software. Precisamos alterá-lo para compilar o projeto de acordo com as nossas necessidades (gerar o serviço e a estrutura de um projeto war). Adicione um source folder no projeto (Clicar com o botão direito sobre o projeto -> New -> Source Folder), seguindo a estrutura main/src e nomeie como "webresources". Altere seu pom.xml de acordo com a estrutura abaixo:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>br.com.ws.exemplo</groupId>
  <artifactId>helloplanet-jaxws</artifactId>
  <version>1.0.0</version>
  <packaging>war</packaging>

  <dependencies>
      <dependency>
          <groupId>com.sun.xml.ws</groupId>
          <artifactId>jaxws-rt</artifactId>
          <version>${jaxb.version}</version>
          <scope>compile</scope>
      </dependency>
  </dependencies>

  <properties>
      <jaxb.version>2.2.7</jaxb.version>
  </properties>

  <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-war-plugin</artifactId>
            <version>2.1.1</version>
            <configuration>
                <webResources>
                    <resource>
                        <directory>${basedir}/src/main/webresources</directory>
                    </resource>
                </webResources>
                <warName>helloplanet-jaxws</warName>
            </configuration>
          </plugin>
          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxws-maven-plugin</artifactId>
            <version>1.11</version>
            <executions>
              <execution>
                <goals>
                  <goal>wsgen</goal>
                </goals>
                <configuration>
                  <sei>br.com.exemplo.ws.impl.HelloPlanetImpl</sei>
                  <genWsdl>true</genWsdl>
                  <keep>true</keep>
                </configuration>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugin</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.5.1</version>
            <configuration>
              <source>1.6</source>
              <target>1.6</target>
            </configuration>
          </plugin>
      </plugins>
  </build> 
</project>


É importante explicar melhor algumas partes do projeto, como a seguinte:

          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-war-plugin</artifactId>
            <version>2.1.1</version>
            <configuration>
                <webResources>
                <!-- configura a pasta webresources como a pasta de recursos do projeto web -->
                    <resource>
                        <directory>${basedir}/src/main/webresources</directory>
                    </resource>
                </webResources>
            <!-- define o nome do web archive (war) que será gerado -->
                <warName>helloplanet-jaxws</warName>
            </configuration>
          </plugin>


O trecho abaixo também merece uma atenção especial, pois configura a execução do plugin de geração do serviço:

          <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxws-maven-plugin</artifactId>
            <version>1.11</version>
            <executions>
              <execution>
                <goals>
                  <goal>wsgen</goal>
                </goals>
                <configuration>
                  <sei>br.com.exemplo.ws.impl.HelloPlanetImpl</sei>
                  <genWsdl>true</genWsdl>
                  <keep>true</keep>
                </configuration>
              </execution>
            </executions>
          </plugin>


Agora, vamos criar a pasta WEB-INF e colocar nosso arquivo web.xml dentro. Esta estrutura é necessária para aplicações web em java. Clique com o botão direito sobre a pasta webresources e adicione uma pasta denominada WEB-INF. Adicione um novo arquivo xml nesta pasta e nomeie este arquivo como web.xml. O conteúdo do web.xml deve ser o seguinte:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <display-name>HelloPlanetJAXWS Application</display-name>
    <description>
        This is a simple web application with a source code organization
        based on the Java web-apps standard.
    </description>
   
    <listener>
      <listener-class>
        com.sun.xml.ws.transport.http.servlet.WSServletContextListener
      </listener-class>
    </listener>
    <servlet>
      <servlet-name>HelloPlanetJAXWS</servlet-name>
      <servlet-class>
        com.sun.xml.ws.transport.http.servlet.WSServlet
      </servlet-class>
      <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
      <servlet-name>HelloPlanetJAXWS</servlet-name>
      <url-pattern>/hello</url-pattern>
    </servlet-mapping>
    <session-config>
      <session-timeout>120</session-timeout>
    </session-config>
</web-app>


Perceba que neste arquivo configuramos o servlet do JaxWS para para carregar nosso serviço. Agora que temos o webxml devidamente configurado, devemos configurar nosso projeto. Dentro desta mesma pasta (WEB-INF), adicione o arquivo sun-jaxws.xml para configuração do serviço. Este arquivo deve conter o seguinte conteúdo:

<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
  <endpoint name="HelloPlanetJAXWSEndPoint" implementation="br.com.exemplo.ws.impl.HelloPlanetImpl" url-pattern="/hello"/>
</endpoints>


Repare que a url do segundo arquivo (sun-jaxws.xml) é a mesma utilizada no primeiro (web.xml). Agora que já configuramos todo o projeto, vamos ao código. Primeiro vamos criar a interface do serviço. Para isso, clique com o botão direito sobre a pasta "src/main/java" e selecione New -> Interface.

No pacote, adicione br.com.exemplo.ws e dê o nome HelloPlanet. Insira o seguinte código para a interface criada:

package br.com.exemplo.ws;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlElement;

import br.com.exemplo.ws.domain.Planet;

@WebService(name="HelloPlanet", targetNamespace="http://www.helloplanet.com.br/jaxws")
public interface HelloPlanet {
    @WebMethod(operationName="SayHello", action="http://www.helloplanet.com.br/jaxws/sayHello")
    @WebResult(name="Greeting", targetNamespace="http://www.helloplanet.com.br/jaxws/sayhello/result")
    public String sayHello(@WebParam(name="PlanetName", targetNamespace="http://www.helloplanet.com.br/jaxws/domain/planet") @XmlElement(required=true, nillable=false) Planet planet);
}


Repare que a nossa interface renomeia o método, parâmetro e resultado retornado. Outro ponto importante é a anotação @XmlElement que efetuará a validação do parâmetro para o serviço (necessário ter sobrescrito a jaxb api conforme descrito no começo do artigo). Agora vamos criar o código da nossa classe. Para isso, clique com o botão direito sobre a pasta "src/main/java" e selecione New -> Class, configure o package como "br.com.exemplo.ws.impl", nomeie a classe como HelloPlanetImpl e adicione o seguinte código:

package br.com.exemplo.ws.impl;

import javax.jws.WebService;

import com.sun.xml.ws.developer.SchemaValidation;

import br.com.exemplo.ws.HelloPlanet;
import br.com.exemplo.ws.domain.Planet;

@SchemaValidation
@WebService(endpointInterface="br.com.exemplo.ws.HelloPlanet", portName="HelloPlanetPort",  serviceName="HelloPlanet")
public class HelloPlanetImpl implements HelloPlanet {

    public String sayHello(Planet planet) {
        return "Hello " + planet.getName() + "! Your id is: " + planet.getId() + " and your distance is: " + planet.getDistance();
    }

}


Neste ponto, você ainda deve estar vendo um erro no seu projeto. A classe Planet, que é recebida como parâmetro pelo método (operação do serviço) ainda não existe, então vamos criá-la. Para isso, clique com o botão direito sobre a pasta "src/main/java" e selecione New -> Class, configure o package como "br.com.exemplo.ws.domain", nomeie a classe como Planet e adicione o seguinte código:

package br.com.exemplo.ws.domain;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="Planet", namespace="http://www.helloplanet.com.br/jaxws/domain/planet")
@XmlAccessorType(XmlAccessType.FIELD)
public class Planet {

    @XmlElement(name="ID", required=true, nillable=true)
    private Integer id;
    @XmlElement(name="Name", required=false, nillable=false)
    private String name;
    @XmlElement(name="Distance", required=true, nillable=false)
    private Double distance;
   
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Double getDistance() {
        return distance;
    }
    public void setDistance(Double distance) {
        this.distance = distance;
    }
   
}


Pronto! Agora vamos compilar o projeto. Para isso, clique com o botão direto sobre o arquivo pom.xml e selecione "Run as -> Maven Install". Aguarde o maven baixar as dependências e compilar o projeto. Ao final do processo de compilação você deverá ver uma mensagem semelhante a esta:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.439s
[INFO] Finished at: Wed Dec 12 20:10:13 BRST 2012
[INFO] Final Memory: 10M/25M
[INFO] ------------------------------------------------------------------------


Verifique a pasta target do seu projeto, e dentro, deverá existir o arquivo helloplanet-jaxws.war. Este arquivo será jogado dentro da pasta webapps do tomcat. Após copiar o arquivo, acesse a URL:

http://localhost:8080/helloplanet-jaxws/hello?wsdl

E você verá o wsdl pertinente ao seu serviço. Parabéns, você criou seu primeiro serviço utilizando JaxWS e ele está disponível em:

http://localhost:8080/helloplanet-jaxws/hello

Caso tenha percebido, o @XmlElement não gerou a validação corretamente para o serviço (xml retirado de exemplo de consumo utilizando SoapUI).

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:jax="http://www.helloplanet.com.br/jaxws" xmlns:plan="http://www.helloplanet.com.br/jaxws/domain/planet">
   <soapenv:Header/>
   <soapenv:Body>
      <jax:SayHello>
         <!--Optional:-->
         <plan:PlanetName>
            <ID>?</ID>
            <!--Optional:-->
            <Name>?</Name>
            <Distance>?</Distance>
         </plan:PlanetName>
      </jax:SayHello>
   </soapenv:Body>
</soapenv:Envelope>


Repare que o parâmetro PlanetName está demarcado como opcional (trecho retirado do contrato gerado) enquanto que a validação foi respeitada para o parâmetro planetId:

<xs:element ref="ns1:PlanetName" minOccurs="0"/>

Este é um bug do JaxWS (a validação através de @XmlElement é respeitada para tipos primitivos da linguagem mas não para objetos) e a para sanar o problema, podemos utilizar CXF para a construção do nosso serviço, como veremos no próximo post.