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.