Nesse artigo vamos explorar o framework Apache CXF e como criar e publicar um WebService simples de forma prática e rápida utilizando o Spring framework para aumentar nossa produtividade.
O Apache CXF é um framework de integração que dá suporte à criação de serviços no padrão WebService. Através dele podemos criar e consumir serviços, utilizando código Java e configuração do framework Spring. Nesse artigo vamos utilizar um WSDL pronto para demonstrar como trabalhar com o Apache CXF junto com o Spring Framework.
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.
Listagem 1: WSDL do serviço de exemplo
<?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>
Figura 1: Estrutura do WSDL
Agora, vamos ao trecho do nosso projeto maven. O projeto completo e outros arquivos você poderá consultar no repositório github para esse artigo.
Listagem 2: Dependências especiais do nosso projeto
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> < version>${spring.version}< /version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> < version>${spring.version}< /version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> < version>${spring.version}< /version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> < version>${spring.version}< /version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-api</artifactId> < version>${project.version}< /version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-core</artifactId> < version>${project.version}< /version> < /dependency> < dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> < version>${project.version}< /version> < /dependency> < dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> < version>${project.version}< /version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> < version>${project.version}< /version> < /dependency>
Vemos várias dependências de Spring e CXF, sendo que as do CXF chamam bastante atenção porque têm palavras tais como transports e frontend. Isso não é por acaso. O diagrama de arquitetura do CXF (Figura 1) demonstra a separação das responsabilidades em módulos distintos, permitindo um ajuste fino do framework à real necessidade do projeto.
Um front-end nada mais é do que um modelo de programação. Usamos JAX-WS ou JAX-RS, mas poderíamos trabalhar com “Simple” ou JavaScript ou ainda algo totalmente novo que nós podemos inventar. Assim como o front-end, o Apache CXF suporta transportes diferentes. Nesse caso estamos expondo nosso WebService através de um servidor HTTP, por isso utilizamos os transportes selecionados, mas poderíamos utilizar JMS, SMTP, Local, Servlet e outros tantos mais. Uma observação importante: no WSDL definimos um transporte através do elemento “binding” em seu atributo “transport”. Em nosso exemplo utilizamos “http://schemas.xmlsoap.org/soap/http” que só funcionará se o CXF reconhecer esse namespace através de seus componentes.
Figura 2: Visão de módulos arquiteturais do Apache CXF
Vamos agora dar uma olhada no código fonte do projeto.
Listagem 3: Comando maven para gerar projeto para IDE Eclipse
mvn clean package eclipse:eclipse
O arquivo principal é o que define o contexto do Spring. Nele podemos ver dois exemplos interessantes: jaxws:endpoint e jaxws:client. Eles configuram um servidor para hospedar nosso WebServive e um cliente para invocar operações nesse serviço.
Listagem 4: Implementação de exemplo
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd" > <jaxws:endpoint id="server" address="http://localhost:9292/HelloWorldService" implementor="br.com.devmedia.helloworldservice.SimpleHelloWorldService" wsdlLocation="HelloWorldService.wsdl"> <jaxws:features> <bean class="org.apache.cxf.feature.LoggingFeature" /> </jaxws:features> <jaxws:properties> <entry key="schema-validation-enabled" value="true" /> </jaxws:properties> </jaxws:endpoint> <jaxws:client address="http://localhost:9292/HelloWorldService" id="client" xmlns:s="http://helloworldservice.devmedia.com.br/wsdl/" serviceName="s:HelloWorldService" endpointName="s:HelloWorldService_Port" wsdlLocation="HelloWorldService.wsdl" serviceClass="br.com.devmedia.helloworldservice.wsdl.DigaOlaPortType" > </jaxws:client> </beans>
Na definição de endpoint, passamos parâmetros “address” indicando em que endereço físico nosso serviço estará disponível, implementor contendo a classe que implementa nosso o WebService e onde está localizado o WSDL. Como o arquivo WSDL está na pasta src/main/resources, apenas colocamos o nome do arquivo. Se ele estivesse em src/main/resources/wsdl, a configuração seria “wsdl/HelloWorldService.wsdl” e assim por diante. A “feature” liga permite que sejam mostradas no log as mensagens SOAP de entrada e saída durante a execução do serviço.
Logo abaixo da definição do servidor vemos a definição do cliente. Ela é um pouco mais “complicada”, pois demanda que conheçamos o “serviceName” e o endpointName além do namespace do WebService. Esses valores estão no próprio WSDL como mostrado na Figura 3.
Figura 3: Configuração do cliente
Claramente o namespace é aquele definido no início no WSDL (http://helloworldservice.devmedia.com.br/wsdl/). O serviceName tem seu valor retirado do atributo “name” do elemento wsdl:service enquanto que o endpointName é o atributo “name” do elemento wsdl:port. Fácil não é? Um programador pode gastar horas quebrando a cabeça até entender como configurar o cliente. Finalmente vamos olhar o código-fonte do teste unitário.
Listagem 5: Teste unitário para chamada do serviço
package br.com.devmedia.helloworldservice; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 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; @ContextConfiguration @RunWith(SpringJUnit4ClassRunner.class) public class HelloWorldServiceTest { @Autowired DigaOlaPortType port; @Test public void requestResponseTest() throws Exception { 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()); } } }
Como estamos trabalhando com Spring, nada mais natural que usar o suporte do framework Spring a testes. Para isso, utilizamos duas anotações: ContextConfiguration e RunWith. A primeira indica que um contexto do Spring será criado quando o teste for executado. O arquivo XML deverá por padrão ter o nome < Nome da classe de Teste>-context.xml ou seja HelloWorldServiceTest-context.xml.
RunWith é uma anotação que permite ao framework Junit delegar a execução de Teste a uma classe executora específica que nesse caso é org.springframework.test.context.junit4.SpringJUnit4ClassRunner. O interessante do SpringJunit4ClassRunner é que através dela podemos trabalhar com outras funcionalidades de testes do Spring tais como injeção de dependências (@Autowired).
No teste, a nossa única preocupação foi implementar os cenários de teste para validar o serviço. Toda inicialização e execução do WebService está sob responsabilidade do Spring. Se reparamos no console, veremos também que todas as mensagens SOAP trocadas entre cliente e servidor estão sendo mostradas por que a feature de Logging está configurada.
É isso pessoal. Finalizamos a criação e chamada do nosso serviço utilizando o Apache CXF e Spring. Para ver o projeto completo, basta acessar o repositório do GITHUB https://github.com/leogsilva/ApacheCxfSpringHelloWorld.git. Até a próxima.