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 e Apache Camel para aumentar nossa produtividade.
O Apache CXF é um framework de integração que dá suporte a criação de serviços no padrão WebService. O Apache Camel é um framework que implementa patterns de integração. Juntos, eles são uma combinação poderosa para publicação e consumo de serviços. É o que veremos nesse artigo, além de usar a simplicidade do framework Spring para configuração e execução dos testes.
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. A definição completa do arquivo WSDL pode ser encontrada no em HelloWorldService.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.
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-cxf</artifactId>
<version>${camel.version}</version>
</dependency>
Esse artefato é o único necessário para acrescentarmos o suporte ao Apache Camel e sua integração com o CXF. O maven se encarregará de adicionar ao nosso projeto as demais dependências do Camel e CXF compatíveis.
Como estamos utilizando o framework Spring para configurar a criação dos componentes utilizados no nosso projeto, teremos um arquivo XML que configura o contexto descrito na Listagem 2.
<?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:cxf="http://camel.apache.org/schema/cxf"
xmlns="http://www.springframework.org/schema/beans"
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="
http://camel.apache.org/schema/spring http://camel.apache.org/schema/
spring/camel-spring.xsd
http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/
camel-cxf.xsd
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" >
<bean id="routeBuilder" class="br.com.devmedia.helloworldservice.HelloWorld
ContextBuilder"/>
<bean id="pojoHelloWorldService" class="br.com.devmedia.helloworldservice.
PojoHelloWorldService" />
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<camel:routeBuilder ref="routeBuilder"/>
</camelContext>
<cxf:cxfEndpoint id="soapMessageEndpoint"
serviceClass="br.com.devmedia.helloworldservice.wsdl.DigaOlaPortType"
xmlns:s="http://helloworldservice.devmedia.com.br/wsdl/"
address="http://localhost:9292/HelloWorldService"
wsdlURL="HelloWorldService.wsdl"
endpointName="s:HelloWorldService_Port"
serviceName="s:HelloWorldService"
>
</cxf:cxfEndpoint>
</beans>
A primeira configuração que nos chama a atenção é justamente o componente “camelContext”. O contexto do Camel é o ponto principal de toda aplicação. Esse componente é o responsável por gerenciar o ciclo de vida das nossas integrações. Essas integrações podem ser de qualquer tipo: jms, ftp, webservice, rest, local, beans do próprio spring, banco de dados entre muitas outras. Integrações são suportadas por diversos componentes do próprio Camel. Para criarmos rotas, dependemos principalmente de um configurador. Isso no Camel é possível através de um RouteBuilder cuja implementação é referenciada pelo nosso camelContext.
O Apache Camel tem nele um conceito muito forte chamado Rota. Rota é a capacidade de consumir um conteúdo de um ponto de entrada e produzi-lo em um ponto de saída. Se queremos por exemplo, ler arquivos de um diretório e enviá-los a uma fila JMS criaremos uma rota que “consuma” arquivos e “produza” o seu conteúdo para uma fila JMS. Tanto o arquivo quando a fila serão pontos de entrada e saída (endpoints) respectivamente. Veremos mais adiante como isso fica em Java.
Outra Bean do XML do Spring que chama a atenção é o cxfEndpoint. Através dele podemos configurar muito facilmente um ponto de entrada ou saída para nossas rotas do Camel. Isso é interessante: ele serve como servidor e cliente ao mesmo tempo, mas é interpretado diferentemente de acordo com o seu uso. Se usamos ele como ponto de entrada, então estaremos publicando um serviço WebService no endereço físico http://localhost:9292/HelloWorldService. Se utilizamos ele como ponto de saída, então temos um cliente para envio de mensagens para esse endereço. Vamos olhar as rotas que demonstram o que acabei de explicar.
package br.com.devmedia.helloworldservice;
import org.apache.camel.builder.RouteBuilder;
public class HelloWorldContextBuilder extends RouteBuilder{
@Override
public void configure() throws Exception {
from("direct:digaOla").to("cxf:bean:soapMessageEndpoint?dataFormat=POJO");
from("cxf:bean:soapMessageEndpoint?dataFormat=POJO").to("bean:pojoHelloWorldService");
}
}
Vemos apenas 2 rotas. Uma consome de “direct:digaOla” e produz para “cxf:bean:soapMessageEndpoint”. A segunda rota consome de cxf:bean:soapMessageEndpoint e produz para “bean:pojoHelloWorldService”. No nosso exemplo, “soapMessageEndpoint” é o nome do bean configurado com a tag “cxf:cxfEndpoint” no arquivo XML do Spring enquanto que “pojoHelloWorldService” é o nome do bean que implementa a lógica de negócio do WebService.
Essas 2 rotas são respectivamente cliente e servidor para o WebService. Se você gostou, então veja como ficou a implementação do serviço na Listagem 4.
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;
public class PojoHelloWorldService {
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;
}
}
Simples e prático. Melhor ainda: na nossa implementação de serviço não fazemos referência a qualquer anotações seja do Camel, CXF, JAX-WS etc. Também não precisamos implementar qualquer interface de qualquer framework. Apenas com um teste unitário conseguimos verificar a implementação do negócio e nem precisamos de servidor de aplicação, servidor HTTP, nada. Isso só é possível por alguns detalhes: o primeiro é que o Camel já faz todo o trabalho de conversão entre mensagens SOAP e os objetos mapeados pelo JAX-WS. Ele também consegue “adivinhar” que se o nosso método tem um parâmetro do tipo “Nome”, ele deve injetar objetos desse tipo para nós. Mesmo se lançarmos uma exceção, ele conseguirá transformá-la em um SOAP Fault. Perceba o parâmetro dataFormat=POJO. Isso informa ao Camel que usaremos classes mapeadas do JAX-WS e é o que ele também usará para fazer as conversões necessárias. O Camel suporta outros tipos de formato de dados que você pode conhecer em http://camel.apache.org/cxf.html.
Podemos utilizar a própria API do Camel para enviar mensagens para o WebService. Para isso, instanciamos um utilitário (ProducerTemplate) capaz de interagir com o contexto do Camel. Depois criamos objetos e o passamos ao endpoint “direct:digaOla”. Toda a execução fica à cargo do Apache Camel. Veja o exemplo na Listagem 5.
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class HelloWorldServiceTest {
@Autowired
CamelContext context;
@DirtiesContext
@Test
public void requestResponseTest() throws Exception {
ProducerTemplate template = context.createProducerTemplate();
Nome _digaOla_parametro = new Nome();
MessageContentsList ret = null;
_digaOla_parametro.setConteudo("ze");
ret = (MessageContentsList) template.sendBody("direct:digaOla",
ExchangePattern.InOut, _digaOla_parametro);
Saudacao saudacao = (Saudacao) ret.get(0);
Assert.assertEquals("Bom dia", saudacao.getConteudo());
}
}
}
É isso, pessoal. Finalizamos a criação e chamada do nosso serviço utilizando o Apache CXF, Apache Camel e Spring. Para ver o projeto completo, basta acessar o repositório do GITHUB.