O Apache CXF é um framework de integração que dá suporte à criação de serviços no padrão Web Service. Através dele podemos criar e consumir serviços, utilizando código Java e configuração do framework Spring. Não é objetivo desse artigo explicar extensamente o que é um Web Service, ou como é organizado um WSDL e nem mesmo o que é SOA. Abordaremos apenas como criar um projeto simples com o apache Maven, como utilizar a geração de código Java a partir de um arquivo WSDL pré-existente e ainda como publicar uma implementação do serviço.
Quando trabalhamos com Apache CXF, temos claramente duas linhas principais de desenvolvimento. A primeira é gerar o código Java anotado e compatível com o JAX-WS a partir de um descritor WSDL (que veremos a seguir). A segunda forma é criar o código Java manualmente e em seguida utilizá-lo para gerar o arquivo WSDL. O resultado final é o mesmo: um serviço no padrão Web Service.
O WSDL que utilizaremos é bem simples e oferece apenas uma operação que retorna obrigatoriamente uma resposta de sucesso ou um erro. Operações que trabalham requisição/resposta de forma síncrona são ditas que seguem o MEP (message exchange pattern) Request/Reply. Erros são comumente chamados de Fault.
<?xml version="1.0" encoding="UTF-8" ?>
<wsdl:definitions name="HelloWorldService"
targetNamespace="http://helloworldservice.devmedia.com.br/wsdl/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://helloworldservice.devmedia.com.br/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:do="http://helloworldservice.devmedia.com.br/schema/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:types>
<xs:schema targetNamespace="http://helloworldservice.devmedia.com.br/schema/"
attributeFormDefault="unqualified" elementFormDefault="unqualified"
xmlns:tns="http://helloworldservice.devmedia.com.br/schema/"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Nome" type="tns:Nome"/>
<xs:element name="Saudacao" type="tns:Saudacao" />
<xs:element name="Erro" type="tns:Erro"/>
<xs:complexType name="Nome">
<xs:sequence>
<xs:element name="conteudo" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Saudacao">
<xs:sequence>
<xs:element name="conteudo" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Erro">
<xs:sequence>
<xs:element name="conteudo" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="DigaOlaRequisicao">
<wsdl:part name="parametro" type="do:Nome" />
</wsdl:message>
<wsdl:message name="DigaOlaResposta">
<wsdl:part name="saudacao" type="do:Saudacao" />
</wsdl:message>
<wsdl:message name="DigaOlaException">
<wsdl:part name="DigaOlaException" type="do:Erro" />
</wsdl:message>
<wsdl:portType name="DigaOla_PortType">
<wsdl:documentation>
A operacao digaOla recebe um nome por parametro e devolve
um cumprimento ou erro em situacoes especificas
</wsdl:documentation>
<wsdl:operation name="digaOla">
<wsdl:input name="DigaOlaRequisicao" message="tns:DigaOlaRequisicao" />
<wsdl:output name="DigaOlaResposta" message="tns:DigaOlaResposta" />
<wsdl:fault name="DigaOlaException" message="tns:DigaOlaException" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="DigaOla_SoapBinding" type="tns:DigaOla_PortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="digaOla">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="DigaOlaRequisicao">
<soap:body use="literal" />
</wsdl:input>
<wsdl:output name="DigaOlaResposta">
<soap:body use="literal" />
</wsdl:output>
<wsdl:fault name="DigaOlaException">
<soap:fault name="DigaOlaException" use="literal" />
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HelloWorldService">
<wsdl:port name="HelloWorldService_Port" binding="tns:DigaOla_SoapBinding">
<soap:address location="//www.devmedia.com.br/HelloWorldService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Na Figura 1 conseguimos ver a estrutura do nosso WSDL. Todo descritor WSDL é composto de duas partes principais: uma abstrata que relaciona os tipos suportados (types), as mensagens (message) e as operações (portType e operation) e a outra concreta, que amarra as definições abstratas em detalhes de protocolo, transporte, endereços físicos e codificação (binding, service). Para mais detalhes sobre a estrutura de um WSDL, visite o site: Web Services Description Language.
Uma vez que já temos o nosso descritor de serviço, podemos criar nosso projeto do maven (pom.xml) da seguinte forma:
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.cxf.samples</groupId>
<artifactId>wsdl_first_cxf</artifactId>
<packaging>jar</packaging>
<name>WSDL first usando apache cxf</name>
<description>WSDL first usando apache cxf</description>
<version>2.7.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>${project.version}</version>
<executions>
<execution>
<id>generate-sources</id>
<phase>generate-sources</phase>
<configuration>
<wsdlOptions>
<wsdlOption>
<wsdl>src/main/resources/
HelloWorldService.wsdl</wsdl>
<extraargs>
<extraarg>-all</extraarg>
</extraargs>
</wsdlOption>
</wsdlOptions>
</configuration>
<goals>
<goal>wsdl2java</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
</dependencies>
</project>
Nesse pom.xml a parte mais interessante está na seção de plugins. Vemos uma ferramenta bem útil do CXF chamada wsdl2java. Esse plugin transformará o WSDL em código Java anotado utilizando o padrão JAX-WS. O JAX-WS é um padrão de mercado suportado por vários fornecedores e traz facilidade na criação de serviços, apenas adicionado anotações do Java a classes Pojo. Um fato importante é que se utilizamos JAX-WS, não estamos necessariamente amarrados ao CXF. O JAX-WS é nativamente suportado pelo Java 6 em diante.
mvn clean package eclipse:eclipse
Esse comando faz com que o código Java seja gerado e ao mesmo tempo cria o projeto compatível com o IDE Eclipse. Podemos agora abrir o projeto e finalmente fazer o nosso serviço funcionar. Lembre-se: se você não conhece o maven, visite Apache.org.
A opção “-all” criará para nós exemplos de servidor e cliente. O servidor é simplesmente a classe Java que dá vida ao Web Service. Essa classe implementará uma interface Java que define o comportamento descrito pelo WSDL e será publicada em um endereço físico acessível pelo cliente. O cliente é qualquer sistema que queira invocar operações do serviço inclusive outros serviços. A seguir, uma implementação simples da operação digaOla.
package br.com.devmedia.helloworldservice;
import br.com.devmedia.helloworldservice.schema.Erro;
import br.com.devmedia.helloworldservice.schema.Nome;
import br.com.devmedia.helloworldservice.schema.Saudacao;
import br.com.devmedia.helloworldservice.wsdl.DigaOlaException;
import br.com.devmedia.helloworldservice.wsdl.DigaOlaPortType;
@javax.jws.WebService(
serviceName = "HelloWorldService",
portName = "HelloWorldService_Port",
targetNamespace = "http://helloworldservice.devmedia.com.br/wsdl/",
endpointInterface = "br.com.devmedia.helloworldservice.wsdl.DigaOlaPortType")
public class SimpleHelloWorldService implements DigaOlaPortType {
@Override
public Saudacao digaOla(Nome parametro) throws DigaOlaException {
Saudacao s = new Saudacao();
if ("ze".equals(parametro.getConteudo())) {
s.setConteudo("Bom dia");
} else if ("maria".equals(parametro.getConteudo())){
s.setConteudo("Ola");
} else {
Erro erro = new Erro();
erro.setConteudo("ERR_01");
throw new DigaOlaException("Nao quero conversar",erro);
}
return s;
}
}
Alguns pontos importantes dessa implementação devemos ressaltar:
- A classe deve ser anotada para ser interpretada pelo framework CXF. O CXF usará as informações da anotação WebService para conseguir interpretar as chamadas ao serviço e invocar esse código.
- A classe implementa a interface DigaOlaPortType. Essa interface foi gerada pela ferramenta wsd2java no diretório “target/generated-sources/cxf”.
- Os tipos utilizados nessa operação (Nome, Saudacao, Erro) são aqueles que estão descritos no WSDL.
- Quando queremos lançar um erro, podemos utilizar a exception DigaOlaException (também gerada). Isso se traduzirá naturalmente em um Message Fault.
Finalmente, vamos criar um teste unitário com um cliente simples para o nosso serviço.
package br.com.devmedia.helloworldservice; import java.net.URL; import javax.xml.ws.Endpoint; import junit.framework.Assert; import org.junit.Test; import br.com.devmedia.helloworldservice.schema.Nome; import br.com.devmedia.helloworldservice.schema.Saudacao; import br.com.devmedia.helloworldservice.wsdl.DigaOlaException; import br.com.devmedia.helloworldservice.wsdl.DigaOlaPortType; import br.com.devmedia.helloworldservice.wsdl.HelloWorldService; public class HelloWorldServiceTest { @Test public void requestResponseTest() throws Exception { Object implementor = new SimpleHelloWorldService(); String address = "http://localhost:9292/HelloWorldService"; Endpoint.publish(address, implementor); HelloWorldService ss = new HelloWorldService(new URL("http://localhost:9292/HelloWorldService")); DigaOlaPortType port = ss.getHelloWorldServicePort(); System.out.println("Invoking digaOla..."); Nome _digaOla_parametro = new Nome(); try { _digaOla_parametro.setConteudo("ze"); Saudacao ret = port.digaOla(_digaOla_parametro); Assert.assertEquals("Bom dia", ret.getConteudo()); } catch (DigaOlaException e) { System.out.println("Expected exception: DigaOlaException has occurred."); System.out.println(e.toString()); } try { _digaOla_parametro.setConteudo("maria"); Saudacao ret = port.digaOla(_digaOla_parametro); Assert.assertEquals("Ola", ret.getConteudo()); } catch (DigaOlaException e) { System.out.println("Expected exception: DigaOlaException has occurred."); System.out.println(e.toString()); } try { _digaOla_parametro.setConteudo("anonimo"); Saudacao ret = port.digaOla(_digaOla_parametro); Assert.assertTrue(false); } catch (DigaOlaException e) { Assert.assertEquals("ERR_01",e.getFaultInfo().getConteudo()); System.out.println("Expected exception: DigaOlaException has occurred."); System.out.println(e.toString()); } } }
Reparem que para publicar o serviço, utilizamos apenas a classe utilitária javax.xml.ws.Endpoint passando um endereço físico e uma instância da classe SimpleHelloWorldService. Além disso, o cliente (também gerado) é a classe br.com.devmedia.helloworldservice.wsdl.HelloWorldService e precisamos apenas passar o endereço do endpoint (servidor) para invocarmos a operação digaOla.
É isso pessoal. Finalizamos a criação e chamada do nosso serviço utilizando o Apache CXF. Existem outras funcionalidades fantásticas que exploraremos no futuro mas já deu pra ter uma noção da facilidade e produtividade oferecida por esse framework. Até a próxima.