Sunteți pe pagina 1din 288

Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.

com
Caelum Sumário

Sumário

1 Como aprender SOA e Integração 1


1.1 Falando em Java 1
1.2 O curso 2
1.3 Sobre os exercícios 2
1.4 Sobre os cursos 2
1.5 Indicação de bibliografia extra 2
1.6 Tirando dúvidas 3

2 Arquitetura de sistemas distribuídos 4


2.1 A livraria 4
2.2 Exercícios: Preparando o Ambiente 4
2.3 Serializando e Persistindo Objetos 10
2.4 Implementando uma classe Serializable 12
2.5 Compatibilidade (serialVersionUID) 13
2.6 Serialização em Cascata 15
2.7 Atributos Transientes 15
2.8 Exercícios: Serialização Java 17
2.9 Invocação remota de método 19
2.10 Java RMI - Remote Method Invocation 21
2.11 Colocando o objeto no Servidor 25
2.12 Quem são os servidores? 27
2.13 O Cliente 28
2.14 E os stubs? 29
2.15 Rodando a aplicação 29
2.16 Exercícios: Sistema de Estoque com RMI 30
2.17 Erros comuns no desenvolvimento 32
2.18 Exercícios: Integração da livraria com o estoque 32

3 Web Services SOAP com JAX-WS 34


3.1 Integração hetereogênea 34

Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com


Sumário Caelum

3.2 Estilos de integração 34


3.3 SOA vs Integração 36
3.4 Web Services 36
3.5 Exercícios: Instalação do JBoss AS 8 - Wildfly 37
3.6 Exercício - Configuração do usuário administrador do Wildfly 39
3.7 Configurando o JBoss Wildfly no Eclipse em casa 41
3.8 SOAP - Simple Object Access Protocol 43
3.9 Exercício Opcional: SAAJ 45
3.10 WSDL: o contrato do seu Web Service 46
3.11 Web Services com JAX-WS 52
3.12 Exercícios: Disponibilizando Web Service com JAX-WS 54
3.13 Testando o Web Service com SoapUI 57
3.14 Exercícios: Consumindo o Web Service 59
3.15 Granularidade do Serviço 61
3.16 SOA Fallacies 61
3.17 Exercícios - Granularidade do serviço 62
3.18 Mais recursos do JAX-WS 64
3.19 Exercícios: Personalizando o Web Service 65
3.20 A importância do versionamento 66
3.21 Segurança no Web Service 68
3.22 Entendendo e habilitando HTTPS 69
3.23 Gerando o certificado 71
3.24 Habilitando HTTPS no servidor 72
3.25 Introdução aos padrões WS-* 73
3.26 Conhecendo o WS-Security 74
3.27 Exercícios: Autorização e Versionamento 74
3.28 Criação do cliente com JAX-WS 76
3.29 Exercícios: Usando o Web Service na livraria 77
3.30 Abordagem: Contract first - Web Service Java a partir de um WSDL 79
3.31 Contract first vs Contract last 80
3.32 Um pouco do modelo canônico 81
3.33 Exercícios: Consumindo o Web Service dos Correios 82

4 Web Services Restful com JAX-RS 84


4.1 Uma alternativa - o estilo Arquitetural REST 84
4.2 Orientado ao recurso 85
4.3 Diferentes representações 85
4.4 Métodos HTTP 86

Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com


Caelum Sumário

4.5 Hipermídia 86
4.6 Vantagens Restful 87
4.7 Web-Services REST com JAX-RS 87
4.8 Botando pra rodar 88
4.9 Usando Resources com JAX-RS 89
4.10 Outros tipos de retorno 90
4.11 Buscando um recurso específico 91
4.12 Usando o JAX-RS com EJB3 92
4.13 Exercícios: JAX-RS com Resteasy 92
4.14 Inserindo dados 95
4.15 Exercícios: Criação de resources de maneira Restful 95
4.16 Coreografia de serviços utilizando HATEOAS 97
4.17 Exercícios: Aplicando HATEOAS 98
4.18 Hypermidia com Spring Hateoas 99
4.19 Controlando o XML gerado pelo JAX-RS 101
4.20 Exercício Anotações do JAX-B 103
4.21 JAX-RS 2.0 Client API 104
4.22 Versionamento de serviços REST 104
4.23 Exercícios: Cliente Restful com JAX-RS 2.0 105
4.24 Cliente JAX-RS com XML 106
4.25 Exercício Opcional - Cliente JAX-RS com XML 107
4.26 Um pouco sobre Microservices 108

5 Mensageria com Java Message Service e HornetQ 110


5.1 Assíncrono vs Síncrono 110
5.2 Middleware Orientado à Mensagens (MOM) 111
5.3 Modelos de entregas: Email ou Lista de Discussão? 113
5.4 Criação de Queues e Topics no JMS 115
5.5 Exercícios: Configuração do HornetQ e a primeira fila 115
5.6 JMS 2.0 116
5.7 ConnectionFactory 117
5.8 JMSContext 117
5.9 Enviando uma mensagem 117
5.10 Consumindo mensagens enviadas para a fila 118
5.11 Exercícios: Consumindo mensagens da fila 119
5.12 Para saber mais - JMS 1.0 122
5.13 Para saber mais - Componentes do JMS 1.0 123
5.14 Assinaturas duráveis 123

Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com


Sumário Caelum

5.15 Exercícios: Durable Subscriber 124


5.16 Roteamento baseado no conteúdo 128
5.17 Exercícios: Roteamento com Selectores 129
5.18 Persistindo mensagens 130
5.19 Recebendo mensagens no Servidor de Aplicação 130
5.20 Exercícios opcionais: Message Driven Bean 132
5.21 Integrando o Spring com o JMS 133
5.22 Exercícios: Envio de mensagens na livraria 134
5.23 Para saber mais Shared Consumer do JMS 2 136
5.24 ActiveMQ, RabbitMQ, Apache Apollo e outros 137
5.25 Mensageria na Cloud 137
5.26 Mensagens independentes de plataforma: AMQP, Stomp e REST 138

6 Criação do modelo canonical 142


6.1 Mensagens - XML 142
6.2 Modelo Canonical e o seu Contexto 144
6.3 Introdução ao JAXB 144
6.4 Serializando dados compostos 145
6.5 Usando ObjectFactory 146
6.6 Exercícios: Serialização para XML com JAX-B 146
6.7 Definição de Tipos - XML Schema Definition (XSD) 148
6.8 Identificação pelo Namespace 150
6.9 Exercícios: Schema e Namespace 152
6.10 Exercícios opcionais: Validação com XSD 153
6.11 Serializando JSON: MOXy, Jackson e Jettison 155
6.12 Exercícios opcionais: Serializando JSON com Jackson 156
6.13 Validando JSON com JSON Schema 157
6.14 Exercícios: Enviando mensagens XML pela loja 158

7 Aplicando Enterprise Integration Pattern 160


7.1 Frameworks de Integração 160
7.2 Apache Camel 160
7.3 Os primeiros Components e Endpoints 161
7.4 Camel DSL 161
7.5 Exercícios: Roteamento com Apache Camel 163
7.6 Aplicando os primeiros padrões de integração 164
7.7 Tratamento de exceções 166
7.8 Validação de XSD com Camel 168
7.9 Exercícios: Validação de Mensagens, Redelivery e Dead Letter Channel 169

Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com


Caelum Sumário

7.10 Marshall e Unmarshal de dados 170


7.11 Polling em serviços HTTP 171
7.12 Message Splitter 172
7.13 Exercício: Realizando Polling em um serviço 173
7.14 Polling no banco de dados 175
7.15 Camel JDBC 176
7.16 Exercício: Inserindo os livros no banco de dados 177
7.17 Integração do Camel com Spring 179
7.18 Enviando e consumindo mensagens JMS 180
7.19 Exercícios: Consumindo mensagens JMS com Apache Camel 182
7.20 Pipes e Filters 183
7.21 Exercícios opcionais: Filtro e divisão de conteúdo 184
7.22 Integrar serviços SOAP e REST 185
7.23 Orquestração de tarefas e serviços 188
7.24 Agregação de mensagens 189
7.25 Exercícios opcionais: Orquestração e Template 190
7.26 Framework de integração ou Enterprise Service Bus? 191

8 Enterprise Service Bus 193


8.1 O que é um ESB? 193
8.2 Quando usar um ESB? 193
8.3 Exercícios: Instalação do AnypointStudio 194
8.4 Fluxo com Mule ESB 194
8.5 Exercícios: Criação do primeiro fluxo 195
8.6 Roteamento pelo Namespace 202
8.7 Exercícios: Roteando entre versões do serviço 203
8.8 Transformação da mensagem pelo XSLT 210
8.9 Exercícios opcional: Aplicando XSLT para adicionar novos headers SOAP 211
8.10 Orquestração de serviços 211
8.11 Exercícios: Desenhando o fluxo de pedidos 212
8.12 Exercícios: Filter, Split e Transformação de dados 220
8.13 Exercícios: Multicast com Scatter-Gather Pattern 225
8.14 Exercícios opcionais: Chamando serviços 228
8.15 Tratamento de Erro 231
8.16 Exercícios opcionais: Exception Strategy 231

9 Apêndice: OAuth 2.0 238


9.1 Participantes do fluxo do OAuth 238
9.2 Access Token 256

Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com


Sumário Caelum

9.3 Exercício 240


9.4 Obtenção do Access Token 241
9.5 Resource Owner Password Credential Grant 241
9.6 Exercícios 260
9.7 Obtendo um access token na aplicação cliente 245
9.8 Implementando e consumindo um serviço protegido 245
9.9 Exercícios: Consumindo um servidor protegido 246
9.10 Authorization Code Grant 248
9.11 Implementando o Authorization Code Grant com o Apache Oltu 250
9.12 Exercício Authorization Code Grant 253
9.13 Considerações sobre a implementação 255
9.14 Credencias do usuário e do client application 255
9.15 Authorization Code 256
9.16 Access Token 256
9.17 Cross Site Request Forgery 257
9.18 Filtros do JAX-RS 258
9.19 Exercícios 260
9.20 Para saber mais: Provedores OAuth 2.0 261
9.21 Para saber mais: Outros clientes OAuth 2.0 261
9.22 Exercício opcional: OAuth2 com Github 263

10 Apêndice: Swagger 266


10.1 Ferramentas para modelagem e documentação de APIs REST 266
10.2 Mas o que exatamente é o Swagger? 266
10.3 Modelando a API da Payfast 267
10.4 Defininindo o modelo de dados 267
10.5 Defininindo os recursos da API 268
10.6 Definindo os parâmetros de request 269
10.7 Definindo os tipos de response 269
10.8 Exercício: Modelando uma API com Swagger 270
10.9 Documentando uma API já existente 272
10.10 Obtendo a documentação gerada pelo Swagger 273
10.11 Corrigindo alguns detalhes 275
10.12 Documentação para humanos 277
10.13 Exercício: Documentando a API do Payfast com Swagger 278

Versão: 20.6.10

Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com


CAPÍTULO 1

COMO APRENDER SOA E INTEGRAÇÃO

"Homens sábios fazem provérbios, tolos os repetem" -- Samuel Palmer

Como o material está organizado e dicas de como estudar em casa.

1.1 FALANDO EM JAVA


Esta é a apostila da Caelum que tem como intuito ensinar Java de uma maneira elegante, mostrando
apenas o que é necessário no momento correto e poupando o leitor de assuntos que não costumam ser
de seu interesse em determinadas fases do aprendizado.

O que é realmente importante?


Muitos livros, ao passar os capítulos, mencionam todos os detalhes possíveis do assunto tratado
juntamente com os seus princípios básicos. Isso acaba criando muita confusão, em especial pois o
estudante não consegue distinguir exatamente o que é importante aprender e reter naquele momento
daquilo que será necessário mais tempo e, principalmente, experiência para dominar.

Com a experiência de centenas de treinamentos ministrados, a Caelum organizou o conteúdo desse


material de forma a construir um caminho lógico e didático na cabeça do aluno. Informações mais
avançadas e detalhes mais específicos serão adquiridos com o tempo, e não são necessários até um
segundo momento.

Neste curso, separamos essas informações em quadros especiais, já que são informações extras. Ou
então apenas citamos num exercício e deixamos para o leitor procurar informações se for do seu
interesse.

Algumas informações não são mostradas e podem ser adquiridas em tutoriais ou guias de referência,
são detalhes que, para um programador experiente em Java, podem ser importantes, mas não para quem
está começando.

Por fim falta mencionar sobre a prática, que deve ser tratada seriamente: todos os exercícios são
muito importantes e os desafios podem ser feitos quando o curso acabar. De qualquer maneira,
recomendamos aos alunos estudarem em casa, principalmente aqueles que fazem os cursos intensivos.

1 COMO APRENDER SOA E INTEGRAÇÃO 1


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
1.2 O CURSO
Este curso é de nível avançado e possui muito conteúdo. Isso quer dizer que conhecimentos básicos
de Java, Orientação a Objetos, Web, M-V-C são requeridos. Se você tiver problemas com alguns desses
conceitos, estude as apostilas do Curso Java e Orientação a Objetos (FJ-11) e Curso Java para
Desenvolvimento Web (FJ-21) que estão disponíveis no nosso site gratuitamente.

1.3 SOBRE OS EXERCÍCIOS


Os exercícios do curso variam entre práticos, até pesquisas na Internet, ou mesmo consultas sobre
assuntos avançados em determinados tópicos para incitar a curiosidade do aprendiz na tecnologia.

Como cada aluno tem seu tempo de aprendizado, os capítulos apresentam exercícios opcionais
(básicos e avançados) que você pode fazer durante o curso se sobrar tempo ou em casa para fixar os
conceitos e expandir o conhecimento.

1.4 SOBRE OS CURSOS


A Caelum oferece diversos cursos na área de desenvolvimento, desde cursos básicos até programação
para dispositivos móveis, Web e Front-end. Além disso temos treinamentos mais específicos em áreas
como gerência de projetos e outros.

Consulte nosso Site para obter mais informações sobre nossos treinamentos:
http://www.caelum.com.br

Se você possui alguma colaboração, como correção de erros, sugestões, novos exercícios e outros,
entre em contato conosco!

1.5 INDICAÇÃO DE BIBLIOGRAFIA EXTRA


É possível aprender muitos dos detalhes e pontos não cobertos no curso com tutoriais na internet,
em portais como o GUJ, em blogs (como o da Caelum: http://blog.caelum.com.br ) e em muitos
outros Sites especializados.

Mas se você deseja algum livro para expandir seus conhecimentos ou ter como guia de referência,
temos algumas indicações dentre várias possíveis:

Sobre SOA e Integração

SOA aplicado: Integrando com Web Services e além, Alexandre Saudate

Java Web Services: Up and Running, Martin Kalin

REST in Practice, Jim Webber

2 1.2 O CURSO
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
REST: Construa API's inteligentes de maneira simples, Alexandre Saudate

SOA: Principles of Service Design, Thomas Erl

SOA Design Patterns, Thomas Erl

Camel in Action, Claus Ibsen e Jonathan Anstey

Sobre Arquitetura e Design

Introdução à Arquitetura e Design de Software: Uma visão sobre a plataforma Java

Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions,


Gregor Hohpe

Domain Driven Design, Eric Evans

1.6 TIRANDO DÚVIDAS


Para tirar dúvidas dos exercícios, ou de Java em geral, recomendamos o fórum do site do GUJ
( http://www.guj.com.br/ ), onde sua dúvida será respondida prontamente.

Se você já participa de um grupo de usuários Java ou alguma lista de discussão, pode tirar suas
dúvidas nos dois lugares.

Fora isso, sinta-se a vontade para entrar em contato conosco e tirar todas as suas dúvidas durante o
curso.

1.6 TIRANDO DÚVIDAS 3


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 2

ARQUITETURA DE SISTEMAS
DISTRIBUÍDOS

"Ensinar é aprender duas vezes." -- Joseph Joubert

Nesse capítulo, você entenderá sobre a Serialização de objetos e como invocar métodos de um objeto
remotamente através de RMI.

2.1 A LIVRARIA
O nosso projeto inicial será uma loja virtual que vende livros impressos e e-books, através de uma
aplicação web. A equipe de desenvolvimento decidiu usar Java como plataforma. Os principais
componentes da aplicação são o Spring como container de inversão de controle (IoC), o Spring MVC
como framework MVC e o Hibernate/JPA para acesso ao banco de dados MySQL.

A aplicação possui três telas principais: a primeira uma listagem dos livros disponíveis, a segunda
um formulário com detalhes do livro e a escolha de seu formato e por último um formulário de
finalização do pedido.

O desenvolvimento foi bem sucedido e o projeto está na fase final. No entanto há ainda um desafio
pela frente: a integração com outros sistemas.

No âmbito da integração, ainda falta:

Consultar o sistema de estoque para verificar a disponibilidade dos livros adicionados no


carrinho.
Realizar o pagamento de maneira segura.
Emitir a nota fiscal do cliente através de um sistema legado.
Para livros no formato e-book, gerá-los através de outro sistema.

Todas as funcionalidades anteriores já existem ou existirão em outros sistemas, por isso precisam ser
integradas à loja. O problema é que a equipe não sabe ainda a melhor forma para realizar essas
integrações e ainda possui dúvidas sobre quais tecnologias utilizar.

2.2 EXERCÍCIOS: PREPARANDO O AMBIENTE

4 2 ARQUITETURA DE SISTEMAS DISTRIBUÍDOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Para preparar o ambiente, configuraremos o MySQL, o Apache Tomcat e importaremos o projeto
fj36-livraria. Para isso, siga os passos seguintes:

1. Crie o banco de dados no MySQL abrindo o terminal e rodando os seguintes comandos:

no terminal digite: mysql -u root


já dentro do MySQL digite: show databases;
se aparecer a base fj36 , significa que já havia um banco criado, que pode conter dados. Apague-
o com drop database fj36;
agora crie o novo banco: create database fj36;
ainda no MySQL digite: exit
2. No Desktop, abra a pasta caelum;

3. Entre na pasta 36 e selecione o arquivo do apache-tomcat-7.x;

Dê dois cliques para abrir o Archive Manager do Linux ;

Clique em Extract;

Escolha a pasta /home/soaXXXX (pasta pessoal) e clique em extract;

O resultado é uma pasta chamada apache-tomcat-7.x. Pronto, o tomcat já está instalado.

4. Abra o Eclipse pelo atalho no seu Desktop. Mude para a perspectiva de sua preferência: Java ou Java
EE. Para isso, vá no canto superior direito e clique no botão:

Depois selecione Java ou JavaEE.

5. Abra a View de Servers na perspectiva atual. Aperte Ctrl + 3 e digite Servers:

2.2 EXERCÍCIOS: PREPARANDO O AMBIENTE 5


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Clique com o botão direito dentro da aba Servers e vá em New > Server:

6. Selecione o Apache Tomcat 7.x e clique em Next:

6 2.2 EXERCÍCIOS: PREPARANDO O AMBIENTE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
7. Na próxima tela, selecione o diretório onde você descompactou o Tomcat e clique em Finish:

8.

Por padrão, o WTP gerencia todo o Tomcat para nós e não permite que configurações sejam feitas
por fora do Eclipse. Para simplificar, vamos desabilitar isso e deixar o Tomcat no modo padrão do
próprio Tomcat. Para isso, na aba Servers, dê dois cliques no servidor Tomcat que uma tela de
configuração se abrirá. Localize a seção Server Locations. Repare que a opção use workspace
metadata está marcada. Marque a opção Use Tomcat installation:

2.2 EXERCÍCIOS: PREPARANDO O AMBIENTE 7


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Na mesma tela altere a porta HTTP/1.1 do servidor para 8088. Isso será muito importante, pois
utilizaremos outro servidor mais tarde e não queremos conflito de portas.

Salve e feche essa janela.

9. Ainda no Eclipse vá em File > Import e selecione General e clique Existing projects into workspace
e Next:

8 2.2 EXERCÍCIOS: PREPARANDO O AMBIENTE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Na opção Select root directoy escolha Browse. Vá na pasta caelum/36 e selecione o diretório fj36-
livraria.

Verifique se nas Options está selecionado Copy projects into workspace.

Por fim, clique em Finish.

10. O último passo é configurar o projeto para rodar no Tomcat que configuramos. Na aba Servers,
clique com o botão direito no Tomcat e vá em Add and Remove...:

11. Basta selecionar o nosso projeto fj36-livraria e clicar em Add:

2.2 EXERCÍCIOS: PREPARANDO O AMBIENTE 9


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
12. Selecione o servidor que acabamos de adicionar e clique em Start (ícone play verde na view servers):

13. Abra o navegador e acesse a URL http://localhost:8088/fj36-livraria Deve aparecer a


página inicial do projeto. Em sua inicialização foram populados alguns dados no banco de dados.

Explore a loja, escolha alguns livros, no entanto nem todas as funcionalidades estarão prontas, pois
ainda falta implementá-las.

2.3 SERIALIZANDO E PERSISTINDO OBJETOS


Para explicar a maioria dos conceitos desse capítulo e dos próximos, utilizaremos como exemplo o
sistema de gerenciamento de uma livraria que tem uma grande quantidade de clientes e livros.

Essa livraria tem diversas lojas espalhadas por todo Brasil. Quando um cliente que esteja em alguma
dessas lojas deseja procurar por livros, uma aplicação Java, que está sendo executada em uma máquina
na própria loja, acessa outra aplicação Java que está sendo executada no servidor da livraria. Isso porque
todas as informações referentes aos livros são armazenadas e manipuladas pela aplicação servidora e as
consultas são feitas a partir das aplicações clientes executadas nas lojas. Nesse contexto, o primeiro
problema que devemos resolver é como transferir os dados referentes aos livros da aplicação servidora
para as aplicações clientes.

10 2.3 SERIALIZANDO E PERSISTINDO OBJETOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Tanto a aplicação servidora quanto as aplicações clientes serão orientadas a objetos. Portanto, os
livros de verdade serão representados dentro das aplicações como objetos. Lembrando que um objeto
representa alguma coisa real dentro de um sistema orientado a objetos.

Os objetos Java "vivem" dentro de uma Java Virtual Machine (JVM). Portanto, os objetos que
representam os livros da nossa livraria estão na JVM que está executando a aplicação servidora, pois é ela
quem é responsável por eles. Para que os objetos possam existir dentro de uma JVM, é necessário que a
classe desses objetos esteja carregada. Essa regra vale tanto na JVM servidora quanto nas JVMs clientes.

Para que os clientes possam consultar os livros, esses objetos devem viajar da JVM servidora até as
JVMs clientes. Na verdade, não é necessário que os objetos abandonem a JVM servidora, basta que os
dados desses objetos sejam copiados e enviados. Dessa forma, as JVMs clientes podem construir
objetos novos com as informações recebidas, ou seja, elas criam cópias dos objetos originais.

No Java, a JVM servidora, que tem o objeto original, copia os dados desse objeto. Essa cópia é criada
em formato binário e pode ser enviada até uma JVM cliente que monta um novo objeto com os dados da
cópia. O processo de criar uma cópia em formato binário dos dados de um objeto é chamado
serialização.

2.3 SERIALIZANDO E PERSISTINDO OBJETOS 11


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Quando os dados de um objeto estão serializados, podemos armazenar esse conteúdo binário em um
arquivo ou até mesmo em um banco de dados para uma posterior utilização. Quando isso ocorre,
dizemos que os dados desse objeto estão persistidos.

2.4 IMPLEMENTANDO UMA CLASSE SERIALIZABLE


Para definir os objetos que representarão os livros da nossa livraria, criaremos uma classe chamada
Livro .

package br.com.caelum;

public class Livro {


private String nome;
}

A princípio, os objetos dessa classe não podem ser serializados. Para que eles tenham essa qualidade
devemos informar ao Java que a classe Livro é uma classe especial, ela é Serializable (serializável).
Para isso, basta implementar uma interface:
public class Livro implements Serializable {
...
}

A INTERFACE JAVA.IO.SERIALIZABLE

Serializable é apenas uma interface de marcação para indicar quais classes têm a
capacidade de serializar os seus objetos.

12 2.4 IMPLEMENTANDO UMA CLASSE SERIALIZABLE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Isso nos permite serializar os objetos do tipo Livro . O ObjectOutputStream é o responsável por
fazer a tarefa de serialização de objetos. Com um código bem simples, é possível salvar as informações
serializadas de um objeto em um arquivo.
Livro arquiteturaJava = new Livro();
FileOutputStream saidaArquivo = new FileOutputStream("arquiteturaJava.txt");
ObjectOutputStream serializador = new ObjectOutputStream(saidaArquivo);
serializador.writeObject(arquiteturaJava);

Uma vez que os dados de um objeto do tipo Livro estão persistidos em um arquivo, podemos fazer
o processo inverso ao da serialização e construir uma cópia do objeto:
FileInputStream entradaArquivo = new FileInputStream("arquiteturaJava.txt")
ObjectInputStream ois = new ObjectInputStream(entradaArquivo);
Livro livro = (Livro) ois.readObject();

2.5 COMPATIBILIDADE (SERIALVERSIONUID)


Para que o processo de serialização e desserialização ocorra, tanto a JVM servidora quanto as JVMs
clientes devem ter a classe Livro carregada na memória. Então, alguns problemas sobre como e
quando distribuir o arquivo .class da classe Livro devem ser considerados.

Talvez, o problema mais complicado aconteça quando o arquivo .class é atualizado. A atualização
do .class em uma JVM pode deixar os .class nas outras JVMs incompatíveis. Por isso, o Java criou
uma estratégia para verificar a compatibilidade entre os .class .

Para que não ocorra erros na serialização e na desserialização, é necessário que as duas classes
envolvidas sejam compatíveis. Duas classes são compatíveis se elas possuem o mesmo número de série.
Toda classe que é Serializable recebe um número de série chamado de serialVersionUID . Isso é
feito automaticamente pelo Java.

Esse número não é aleatório! Ele é um hash (SHA) calculado em cima dos nomes dos atributos, do
nome do pacote e das assinaturas dos métodos em uma ordem bem definida pela especificação do
processo de serialização.

Se você quiser saber qual foi o serialVersionUID gerado para a nossa classe Livro você pode
utilizar a ferramenta serialver que vem no JDK:

$ serialver br.com.caelum.Livro
br.com.caelum.Livro: static final long serialVersionUID = -8832981870156777141L;

O que aconteceria se o serialVersionUID fosse diferente nas duas JVMs? Ocorre uma exception
na hora da desserialização, a java.io.InvalidClassException .

Uma alteração na classe Livro pode ocasionar a modificação do serialVersionUID , gerado


automaticamente pelo Java, o que torna os .class antigos incompatíveis. Por exemplo, se você
acrescentar um novo atributo.

2.5 COMPATIBILIDADE (SERIALVERSIONUID) 13


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
public class Livro implements Serializable {
private String nome;
private String descricao;
}

$ serialver br.com.caelum.Livro
br.com.caelum.Livro: static final long serialVersionUID = -3724354176234701135L;

Mas nem sempre, quando alteramos uma classe, queremos que o serialVersionUID seja
modificado, pois nem todas as modificações causariam problemas de verdade. Por exemplo, suponha
que você deseja reescrever o método toString() para melhorar as mensagens de log da aplicação
servidora. Essa modificação não precisa ser refletida nas JVMs clientes.

public class Livro implements Serializable {


private String nome;
private String descricao;

@Override
public String toString() {
return "livro";
}
}

Apesar dessa modificação não gerar problemas de fato, o serialVersionUID seria:


$ serialver br.com.caelum.Usuario
br.com.caelum.Livro: static final long serialVersionUID = -5398557161684836793L;

Para não depender do gerador da JDK você pode atribuir manualmente o serialVersionUID .
Basta definir um atributo especial na sua classe:
private static final long serialVersionUID = 1L;

O serialVersionUID deve ser private static final e do tipo long . E serve para determinar
se duas versões de arquivos .class são compatíveis ou não. É considerado boa prática escrevê-lo
sempre que sua classe implementar Serializable .

Como, então, devemos proceder para escolher um serialVersionUID apropriado? É muito


simples: se essa classe está nascendo neste momento, você pode se dar ao luxo de utilizar um
serialVersionUID , como por exemplo:

public class Livro implements Serializable {


private static final long serialVersionUID = 1L;
private String nome;
}

Porém, se você está definindo o serialVersionUID de uma classe já em produção e sabe que a
mudança que está fazendo é compatível com a versão anterior, você deve utilizar o serialVersionUID
que seria gerado pelo Java na primeira versão, como foi o caso aqui quando adicionamos o atributo
descricao na classe Livro . Quando você fizer uma alteração e quiser tornar os .class distribuídos
nas JVMs incompatíveis, basta definir um serialVersionUID diferente dos anteriores.

14 2.5 COMPATIBILIDADE (SERIALVERSIONUID)


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
2.6 SERIALIZAÇÃO EM CASCATA
A nossa livraria tem um número muito grande de livros. Para facilitar a pesquisa dos clientes, os
livros devem ser separados em categorias. Então, criaremos uma classe para modelar as categorias dos
livros.

Suponha que um cliente deseja consultar todos os livros de uma determinada categoria. Então, seria
interessante que a JVM servidora serializasse o objeto Categoria desejado juntamente com os livros.
Para isso, basta que tanto a classe Categoria quanto a classe Livro sejam Serializable :
public class Categoria implements Serializable {
private String nome;
private List<Livro> livros = new ArrayList<Livro>();

// outros atributos
// métodos
}

Mas, se a classe Livro não implementa Serializable , mesmo que a classe Categoria
implemente, o processo de serialização vai falhar.

2.7 ATRIBUTOS TRANSIENTES


Eventualmente, você não deseja que todos os atributos sejam serializados quando um objeto for
serializado. Para resolver esse problema você simplesmente avisa o Java que determinados atributos são
transientes, ou seja, não participarão do processo de serialização. Isso é feito com a palavra-chave
transient :

public class Pessoa implements Serializable {


private Calendar dataDeNascimento;
private transient int idade;
}

No exemplo do código acima, perceba que não é necessário serializar o atributo idade já que há
um atributo guardando a data de nascimento da pessoa e a idade pode ser calculada a partir da data de
nascimento.

O que vai acontecer é o seguinte: quando um objeto do tipo Pessoa for serializado, a idade não será
copiada. Quando esse objeto for desserializado, o Java coloca o valor default para esse atributo, que no
caso é 0 .

WARNING DA FALTA DE SERIALVERSIONUID

A partir da versão 5, o compilador do Java gera um warning se uma classe Serializable não
define o serialVersionUID .

2.6 SERIALIZAÇÃO EM CASCATA 15


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
SERIALVERSIONUID NO ECLIPSE

Você pode utilizar o quick fix do Eclipse para ver o serialVersionUID . Ele dá três opções:
utilizar o @SurpressWarnings para você assumir o risco, usar um valor default ou usar o valor
gerado. O gerador de UIDs do Eclipse é exatamente o mesmo gerador utilizado pelo Java SE para
criar os UIDs padrão! Reforçando: esse número não é um número aleatório!

OBJETO SERIALIZADO

O objeto serializado tem o serialVersionUID da classe utilizada no processo de serialização


do mesmo.

INCOMPATIBILIDADE

Quando alguém esquece de manter o mesmo serialVersionUID para duas versões


compatíveis de uma classe, podemos ter problemas em usar diferentes versões do software que são
teoricamente compatíveis. Isso muitas vezes acontece em servidores de aplicação e, se seu cliente
estiver desatualizado em relação à versão dos JARs necessários pelo servidor, poderemos ter alguns
InvalidClassExceptions que poderiam ser facilmente evitados se o serialVersionUID tivesse
sido corretamente aplicado. Claro que em algumas outras vezes as versões realmente não são
compatíveis e a exception faz sentido.

16 2.7 ATRIBUTOS TRANSIENTES


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
SE JÁ HOUVESSEM AS ANOTAÇÕES...

Implementar uma interface que não define métodos ( Serializable ) e ser forçado a escrever
um atributo sem um contrato mais burocrático é um tanto estranho em uma linguagem como o
Java. Sem dúvida, se esse mecanismo todo tivesse sido inventado já com a existência de anotações,
Serializable seria uma anotação e version um atributo dela, talvez obrigatório, criando algo
como @Serializable(version=12345L) .

2.8 EXERCÍCIOS: SERIALIZAÇÃO JAVA


1. No Eclipse, crie um novo projeto do tipo Java Project chamado fj36-estoque .

2.8 EXERCÍCIOS: SERIALIZAÇÃO JAVA 17


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
2. Crie a classe ItemEstoque no pacote br.com.caelum.estoque.rmi que é uma classe comum
como outra qualquer. Gere apenas os getter e o construtor que deverá receber os dois atributos da
classe:

public class ItemEstoque {

private String codigo;


private Integer quantidade;

// No Eclipse: Source -> Generate Constructor using Fields

// No Eclipse: Source -> Generate Getters


}

3. Crie a classe TestaEscritaDeObjetos dentro do pacote br.com.caelum.estoque.main que testa


o ObjectOutputStream , gravando um ArrayList de ItemEstoque :
public class TestaEscritaDeObjetos {

public static void main(String[] args) throws IOException {

try(ObjectOutputStream oos = new ObjectOutputStream(


new FileOutputStream("itens.bin"))){

ItemEstoque item1 = new ItemEstoque("ARQ", 2);


ItemEstoque item2 = new ItemEstoque("SOA", 3);

18 2.8 EXERCÍCIOS: SERIALIZAÇÃO JAVA


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
List<ItemEstoque> itens = new ArrayList<ItemEstoque>();
itens.add(item1);
itens.add(item2);

oos.writeObject(itens);
}
}
}

Rode o programa. O que acontece?

4. Para fazer o código funcionar, precisamos implementar a interface Serializable em


ItemEstoque , mas não inclua o atributo serialVersionUID e deixe o Eclipse reclamando com o
warning, pois vamos testá-lo mais à frente.
5. E a classe ArrayList ? Ela não precisaria implementar a interface Serializable ? Deveria. Abra o
código fonte da classe ArrayList e repare que ela já implementa a interface Serializable .
6. Rode novamente o teste de escrever objetos! Dê um refresh (F5) no seu projeto e abra o arquivo
itens.bin gerado!

7. Vamos criar a classe que testa o ObjectInputStream para ler os itens serializados. Crie uma nova
classe TestaLeituraDeObjetos no mesmo package main . Use o método main com o código
seguinte:
try(ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("itens.bin"))){
List<ItemEstoque> itens = (List<ItemEstoque>) ois.readObject();

for (ItemEstoque item : itens) {


System.out.println(item.getCodigo());
System.out.println(item.getQuantidade());
System.out.println("-----------------");
}
}

Rode o teste!

8. Peça para o Eclipse gerar o serialVersionUID (ATENÇÃO: opção generated) da sua classe
ItemEstoque . Rode novamente o TestaLeituraDeObjetos . Funciona?
9. Altere o serialVersionUID da sua classe ItemEstoque para 1L . Rode novamente a leitura. O
que acontece? Por quê?
10. (Opcional) Faça o atributo codigo ser transient e execute novamente TestaEscritaDeObjetos
seguindo de TestaLeituraDeObjetos. Qual é o resultado?

11. (Opcional) Você já viu a NotSerializableException antes? Em que momento e por que
aconteceu a exceção?

2.9 INVOCAÇÃO REMOTA DE MÉTODO


Imagine a situação na qual temos um micro principal capaz de fazer contas com uma velocidade

2.9 INVOCAÇÃO REMOTA DE MÉTODO 19


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
espantosa, e nosso software, que está instalado em muitos pontos de venda pelo país, deseja utilizar esse
computador central para realizar contas complicadas.

Outra situação comum é existir um repositório central de dados, que realiza pesquisa no nosso
estoque de livros para verificar a disponibilidade de algum título. Como acessar essa informação sem
expôr o banco de dados?

Como executar tal tarefa? Como disponibilizar esse serviço?

Poderíamos usar uma servlet e, sempre que quiséssemos saber o resultado de uma conta,
enviaríamos uma requisição para o servidor que, por sua vez, retornaria o resultado desejado.

O problema aqui é a existência de um sistema muito heterogêneo. Gostaríamos de fazer isso em Java
puramente. Sem nenhum código HTML ou protocolo HTTP no meio, por exemplo. Não queremos fazer
parsing de um arquivo em um formato específico.

Uma ideia poderia ser a de uma classe que controla o servidor remoto e executa o método que
desejarmos:
ClasseControladoraDoServidor.executa("estoque", "verifiqueQuantidade");

Isso seria muito confuso, dificultaria a passagem de parâmetros, recebimento de valores de retorno,
além de não checar a corretude da chamada em tempo de compilação. Teríamos centenas de possíveis
erros em tempo de execução.

Além disso, seríamos responsáveis por uma série de códigos de infraestrutura que trabalham com
Sockets , protocolos, exceptions e muito mais. Apesar de nesse caso não ser tão complicado, manter
um protocolo bem definido, tratar bem as exceptions no caso do servidor cair, etc, são problemas de
infraestrutura que levam bastante tempo para se resolver corretamente.

Seria mais interessante se a aplicação cliente simplesmente tivesse uma referência que pudesse ser
utilizada para invocar métodos em um objeto que está na aplicação servidora em outra JVM.

20 2.9 INVOCAÇÃO REMOTA DE MÉTODO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Queremos invocar um método remotamente.

objetoRemoto.fazAlgumaCoisa();

Dizemos que um objeto que pode ser invocado de outra JVM é um objeto remoto.

Remotabilidade é um dos aspectos que comumente aparecem numa grande aplicação e queremos
evitar ter que gastar muito tempo com isso. Que tal usar algo já maduro, bem especificado e robusto?

2.10 JAVA RMI - REMOTE METHOD INVOCATION


Suponha que a aplicação servidora da nossa livraria tem um objeto capaz de executar os serviços
desejados pelas aplicações que rodam nos pontos de venda (clientes). Queremos que esse objeto seja um
objeto remoto para poder ser invocado de outras JVMs.

Assim como qualquer objeto, o objeto que faz as buscas no estoque é construído a partir de uma
classe, digamos EstoqueService . Para não complicar, criaremos apenas uma busca pelo código do
livro. Além disso, no construtor, vamos instanciar alguns itens para ter o que buscar e os colocaremos
em um mapa.
public class EstoqueService {

private Map<String, ItemEstoque> repositorio = new HashMap<>();

public EstoqueService() {
repositorio.put("SOA", new ItemEstoque("SOA", 2));
repositorio.put("TDD", new ItemEstoque("TDD", 3));
repositorio.put("RES", new ItemEstoque("RES", 4));
repositorio.put("LOG", new ItemEstoque("LOG", 3));
repositorio.put("WEB", new ItemEstoque("WEB", 4));

2.10 JAVA RMI - REMOTE METHOD INVOCATION 21


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
repositorio.put("ARQ", new ItemEstoque("ARQ", 5));
}

public ItemEstoque verificaQuantidade(String codigoProduto) {


System.out.println("Verificando estoque do produto " + codigoProduto);
return this.repositorio.get(codigoProduto);
}

O ponto fundamental aqui é invocar remotamente um objeto desta classe. Para isso, imagine que a
aplicação do cliente que executa em cima de uma JVM tem uma referência especial que aponta para o
objeto remoto que está na aplicação servidora, ou seja, em outra JVM:

Se o objeto deve aceitar invocações remotas, então ele funciona como se fosse um "mini servidor".
Mas quem vai implementar essa capacidade de ser um "mini servidor"? A resposta é: o Java. Há uma
classe chamada UnicastRemoteObject que faz exatamente o que precisamos e vamos estendê-la:
public class EstoqueService extends UnicastRemoteObject {
// ...

public EstoqueService() {

}
// ...
}

Um detalhe técnico que conhecemos sobre o Java é que uma chamada ao super() é acrescentada
implicitamente aos construtores que não invocam diretamente outro construtor:
public EstoqueService() {
super(); // chamada ao construtor de UnicastRemoteObject
// ...
}

No nosso caso, esse é o construtor da classe UnicastRemoteObject , que lança uma checked
exception chamada RemoteException se ele não conseguir expor o nosso objeto como um "mini
servidor". Desta forma, a nossa classe nem compila, pois não definimos o que fazer com a exception.

22 2.10 JAVA RMI - REMOTE METHOD INVOCATION


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Vamos consertar isso adicionando o throws no nosso construtor para simplesmente repassar a
possível exception:
public EstoqueService() throws RemoteException {
super(); // chamada ao construtor de UnicastRemoteObject
// ...
}

Agora, os objetos da classe EstoqueService são também "mini servidores":

Ainda temos um problema: a referência do lado do cliente não tem a capacidade de fazer uma
chamada ao objeto remoto nem de receber uma resposta dele. Para resolver isso, utilizaremos uma
grande ideia que foi aplicada no Java: colocaremos um "objeto de mentira" do lado do cliente. A
referência apontará para esse objeto e, ao invocar um método através dela, o objeto de mentira invoca o
objeto remoto.

Para o cliente ter a impressão de estar conversando com o objeto de verdade, o objeto de mentira
tem que ter as mesmas assinaturas de métodos que o objeto remoto tem. Em orientação a objetos, isso é
extremamente simples, basta criar uma interface que é implementada pelas classes dos dois objetos.
Então, criaremos a interface EstoqueRmi .

public interface EstoqueRmi {


ItemEstoque getItemEstoque(String codigoProduto);
}

Para avisar ao Java que essa interface será utilizada por um objeto remoto e pelo objeto de mentira,
devemos fazer ela herdar de uma interface especial do Java chamada java.rmi.Remote . Quando isso
acontecer, a EstoqueRmi se tornará uma interface remota.

Mais um detalhe técnico do Java é que todos os métodos de uma interface remota devem dar
throws em RemoteException , pois na chamada desses métodos pode ocorrer algum problema de
conexão, por exemplo.
public interface EstoqueRmi extends Remote {
ItemEstoque getItemEstoque(String codigoProduto)
throws RemoteException;

2.10 JAVA RMI - REMOTE METHOD INVOCATION 23


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
}

A classe EstoqueService deve implementar a interface EstoqueRmi para que o objeto remoto
respeite as assinaturas dos métodos remotos.
public class EstoqueService extends UnicastRemoteObject implements EstoqueRmi {

// ...

public ItemEstoque getItemEstoque(String codigoProduto)


throws RemoteException {
return this.repositorio.get(isbn);
}
}

Nossa próxima questão: quem vai criar a classe do objeto de mentira? Temos três alternativas:

Nós mesmos implementamos essa classe.


Geramos ela automaticamente e estaticamente com um compilador especial que vem no JDK
(rmic).
Deixamos a máquina virtual criar a classe dinamicamente em tempo de execução a partir da
interface remota.

A terceira alternativa é a mais agradável, afinal é a que dá menos trabalho e que na prática funciona
muito bem. Do lado do cliente, basta que seja colocada no classpath a interface remota.

O objeto de mentira é chamado de stub!

Quando um método é invocado em um stub, ele cria uma conexão com o objeto remoto (ou usa a
conexão previamente aberta) e manda uma mensagem pedindo por essa execução. O objeto remoto,
então, realiza essa operação e devolve a resposta ao stub, o cliente nem nota a diferença dessa chamada
para uma chamada local.

Por fim, precisamos da nossa classe auxiliar, a ItemEstoque , que deverá ser Serializable como
anteriormente feito:

24 2.10 JAVA RMI - REMOTE METHOD INVOCATION


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
public class ItemEstoque implements Serializable {

private static final long serialVersionUID = 1;

private String codigo;


private Integer quantidade;

//...
}

O resultado final é mostrado na figura a seguir:

Toda essa arquitetura que discutimos é especificada pelo Java RMI.


http://docs.oracle.com/javase/6/docs/technotes/guides/rmi/

Na verdade, o conceito de RPC (Remote Procedure Call) é mais geral e não depende de uma
linguagem de programação. Aqui estamos interessados especificamente no RMI que é a implementação
OO do Java.

2.11 COLOCANDO O OBJETO NO SERVIDOR


Discutimos anteriormente como a aplicação cliente pode, através de um stub, invocar um método
remotamente, no entanto ainda não resolvemos uma questão fundamental: como a aplicação cliente
consegue acesso a um stub?

Inicialmente, poderíamos pensar em acessar diretamente a JVM do servidor, pois é lá que está o
objeto remoto. Porém, essa não seria uma estratégia muito interessante, pois o cliente teria que conhecer
os endereços dos servidores, aumentando o acoplamento entre os dois lados. E se o cliente fizesse o
acesso a dois objetos remotos em duas JVMs diferentes? Seria necessário conhecer o endereço dos dois.

Uma solução mais flexível é utilizar um "catálogo de objetos remotos". Nesse catálogo, toda aplicação
que tem objetos remotos pode fazer um registro (bind) desses objetos. Nesse registro, é definido um

2.11 COLOCANDO O OBJETO NO SERVIDOR 25


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
"apelido" para um dado objeto remoto e armazenadas, no catálogo, as informações necessárias para
acessá-lo.

Com os objetos remotos registrados no catálogo, os clientes fazem uma busca (lookup) pelo apelido
do objeto desejado. Se a busca for bem sucedida, o catálogo devolve as informações necessárias para a
própria máquina virtual da aplicação cliente construir um stub que pode se conectar com o objeto
remoto.

Então, precisamos mostrar três coisas aqui:

Como colocar no ar o catálogo de objetos remotos.


Como registrar (bind) um objeto remoto no catálogo.
Como buscar (lookup) um objeto remoto no catálogo.

Subindo o catálogo:

linha de comando:

$ rmiregistry

programaticamente:

LocateRegistry.createRegistry(1099);

Registrando um objeto remoto:

EstoqueService service = new EstoqueService();


Naming.rebind("/estoque", service);

Buscando um objeto remoto:


EstoqueRmi estoque = (EstoqueRmi)
Naming.lookup("rmi://localhost:1099/estoque");

Aqui utilizamos como nosso catálogo de objetos remotos o serviço de nomes básico do Java, que é
chamado rmiregistry. Vale lembrar que esse serviço de nomes é simples demais para tarefas maiores e
não é muito usado em produção.

26 2.11 COLOCANDO O OBJETO NO SERVIDOR


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
SERVIÇO DE NOMES REMOTO

Suponha que você deseja registrar seu objeto em um serviço localizado em outro local, usando a
string de nome como "/ip/nome/outroNome" em vez de "/estoque", você efetuará o registro em um
serviço de nomes remoto.

Depois que o cliente pegar uma referência para esse objeto remoto, ele passa a conversar
diretamente com a JVM que possui aquele objeto, sem passar pelo serviço de nomes. Em outras
palavras, as requisições para o objeto remoto não passam pelo serviço de nomes depois que a
referência remota foi adquirida.

2.12 QUEM SÃO OS SERVIDORES?


Na verdade, o servidor será a VM, que vai disponibilizar um objeto remoto. O RMI é uma tecnologia
realmente distribuída e poderíamos ter objetos sendo disponibilizados por mais de uma máquina.
Todas elas seriam consideradas "servidores", e o serviço de nomes centralizaria as informações sobre a
localização de cada objeto.

2.12 QUEM SÃO OS SERVIDORES? 27


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
A aplicação que disponibilizará um objeto remoto deve executar algo do tipo:
public class RegistraERodaService {

public static void main(String[] args) throws Exception {

LocateRegistry.createRegistry(1099);
Naming.rebind("/estoque", new EstoqueService());
System.out.println
("[INFO]: --- Estoque RMI registrado e rodando na porta 1099 ---");
}

Ao executar essa classe, a JVM continua executando mesmo depois do main acabar. Isso porque o
objeto remoto criado através da classe EstoqueService está esperando chamadas para poder trabalhar.

2.13 O CLIENTE
O lado do cliente é muito simples. Inicialmente pegamos a referência remota através da classe
Naming .

Adivinhe quem implementa essa interface no cliente? O stub! Pois a invocação vai chegar até o
servidor e será executada lá, não localmente.
import java.rmi.Naming;

public class ClienteRmi {

28 2.13 O CLIENTE
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
public static void main(String[] args) throws Exception {

EstoqueRmi estoque =
(EstoqueRmi) Naming.lookup("rmi://localhost:1099/estoque");
ItemEstoque item = estoque.getItemEstoque("SOA");
System.out.println("Quantidade disponível: " + item.getItemEstoque());
}
}

Um cliente RMI pode ser qualquer classe Java, como uma action do Spring MVC, um managed bean
do JSF, um ActionListener do Swing ou mesmo um applet.

2.14 E OS STUBS?
Os stubs que precisamos são realmente classes. Essas classes não serão escritas por nós (até poderiam
ser), pois são geradas pelo Java. Para gerar um stub do nosso EstoqueService :

$ rmic br.com.caelum.estoque.rmi

Você vai ver que a classe EstoqueService_Stub.class foi gerada.

Você deve chamar o RMI compiler para cada classe remota existente em sua aplicação. Esse stub deve
estar presente no classpath do cliente e do servidor.

ATENÇÃO

Nas novas versões do Java (5.0 ou superior) não é necessário nem mesmo gerar o stub, pois eles
são gerados utilizando-se proxies dinâmicas, no momento que o servidor recebe a requisição.

2.15 RODANDO A APLICAÇÃO


Repare que o nosso código de servidor apenas registra o objeto. Onde ele registra? No RMIRegistry.
Antes de mais nada, precisamos rodar esse registro.
$ rmiregistry

Importante: rmiregistry deve ser executado a partir do diretório onde estão os seus .class (ou
na raiz do pacote deles), ou utilizar a opção de classpath para que ele encontre as classes que você está
utilizando.

Você pode rodar o rmiregistry programaticamente via Java:

public class ColocaServicoDeNomesNoAr {


public static void main(String[] args){
LocateRegistry.createRegistry(1099);
}
}

2.14 E OS STUBS? 29
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
1099 é a porta default usada pelo rmiregistry .

Agora basta rodar o EstoqueService para depois rodar o ClienteRmi !

Repare que o servidor vai imprimir a mensagem de que o objeto remoto recebeu a requisição.
Lembre-se que o servidor é a máquina virtual que tem o objeto remoto.

ENTERPRISE INTEGRATION PATTERN - SYNCHRONOUS INTEGRATION

Develop each application as a large-scale object or component with encapsulated data. Provide an
interface to allow other applications to interact with the running application.

http://www.eaipatterns.com/EncapsulatedSynchronousIntegration.html

2.16 EXERCÍCIOS: SISTEMA DE ESTOQUE COM RMI


1. Vamos criar o serviço de estoque que fornece a quantidade de itens pelo código do produto.
Usaremos o RMI para fazer a chamado remota.

No projeto fj36-estoque crie uma interface EstoqueRmi dentro do pacote


br.com.caelum.estoque.rmi . A interface estenderá a interface Remote do RMI e o método
deve lançar uma RemoteException :

public interface EstoqueRmi extends Remote {

public ItemEstoque getItemEstoque(String codigoProduto)


throws RemoteException;
}

Crie a classe EstoqueService no mesmo pacote, implemente a interface EstoqueRmi , extenda


a classe UnicastRemoteObject e utilize o Eclipse para que ele mesmo gere os métodos da
interface que precisamos implementar.

Quando estender de UnicastRemoteObject , o quickfix pode ser utilizado para gerar o construtor
da classe filha que lança RemoteException .

Sua classe deve ficar assim:

package br.com.caelum.estoque.rmi;

public class EstoqueService extends UnicastRemoteObject


implements EstoqueRmi {

private static final long serialVersionUID = 1L;

private Map<String, ItemEstoque> repositorio = new HashMap<>();

public EstoqueService() throws RemoteException{

30 2.16 EXERCÍCIOS: SISTEMA DE ESTOQUE COM RMI


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
repositorio.put("ARQ", new ItemEstoque("ARQ", 5));
repositorio.put("SOA", new ItemEstoque("SOA", 2));
repositorio.put("TDD", new ItemEstoque("TDD", 3));
repositorio.put("RES", new ItemEstoque("RES", 4));
repositorio.put("LOG", new ItemEstoque("LOG", 3));
repositorio.put("WEB", new ItemEstoque("WEB", 4));
}

@Override
public ItemEstoque getItemEstoque(String codigoProduto)
throws RemoteException {

System.out.println("Verificando estoque do produto "


+ codigoProduto);
return this.repositorio.get(codigoProduto);
}
}

Crie a classe RegistraERodaService dentro do pacote br.com.caelum.estoque.main que sobe o


serviço de nomes e registra EstoqueService neste servidor. Para isso, basta adicionar uma
chamada ao método rebind da classe Naming :.

package br.com.caelum.estoque.main;

public class RegistraERodaService {

public static void main(String[] args) throws Exception {

LocateRegistry.createRegistry(1099);
Naming.rebind("/estoque", new EstoqueService());
System.out.println("Estoque registrado e rodando");
}

Crie a classe ClienteRmi no pacote br.com.caelum.estoque.cliente para realizar um teste:

package br.com.caelum.estoque.cliente;

public class ClienteRmi {


public static void main(String[] args) throws Exception {

EstoqueRmi estoque = (EstoqueRmi) Naming


.lookup("rmi://localhost:1099/estoque");

ItemEstoque item = estoque.getItemEstoque("ARQ");

System.out.println("Quantidade disponível: "


+ item.getQuantidade() );
}
}

2. Pelo Eclipse, execute primeiramente a classe RegistraERodaService , e depois o ClienteRmi .

3. (Conceitual) Depois dos dois objetos ItemEstoque terem sido recebidos no cliente, se
modificarmos, por exemplo, a quantidade de algum deles no cliente, essa modificação ocorrerá
também no objeto ItemEstoque correspondente no servidor?

2.16 EXERCÍCIOS: SISTEMA DE ESTOQUE COM RMI 31


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Dica: Pense no processo de serialização.

4. No cliente, poderíamos fazer casting direto do objeto retornado para EstoqueService em vez de
EstoqueRmi ?

Dica: O objeto remoto está na JVM do cliente? Se não estiver, quem está?

5. Após obter referência ao EstoqueRmi , faça o seguinte dentro do seu Cliente.

System.out.println(estoque.toString());

Que informações são essas?

2.17 ERROS COMUNS NO DESENVOLVIMENTO


Se nos exercícios você obteve a seguinte exceção:
Exception in thread "main" java.rmi.server.ExportException: Port already in use:
1099; nested exception is:
java.net.BindException: Address already in use
at sun.rmi.transport.tcp.TCPTransport.listen(TCPTransport.java:310)
at sun.rmi.transport.tcp.TCPTransport.exportObject(TCPTransport.java:218)
...

O que aconteceu aqui? A porta 1099 já está em uso, provavelmente pelo seu
RegistraERodaService anterior.

Solução: Basta parar a execução do processo anterior. Assim, a porta voltará a ficar livre e o novo
método de registro poderá acessá-la.

2.18 EXERCÍCIOS: INTEGRAÇÃO DA LIVRARIA COM O ESTOQUE


Nesse exercício vamos fazer chamadas remotas RMI da aplicação livraria para verificar a quantidade
disponível no estoque para cada livro. Sempre, quando o usuário escolhe um livro impresso, há uma
verificação no estoque:

1. No projeto fj36-livraria, crie o pacote br.com.caelum.estoque.rmi .

Copie para este pacote a interface EstoqueRmi e a classe ItemEstoque do projeto fj36-
estoque .

ATENÇÃO: Não copie a classe EstoqueService . Ela não é necessária para executar o código do
cliente.

2. Ainda no projeto fj36-livraria abra a classe CarrrinhoController.

Nela abra o método listar(). Vamos delegar a verificação de cada livro para o carrinho de compra.
Adicione:

32 2.17 ERROS COMUNS NO DESENVOLVIMENTO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
this.carrinho.verificarDisponibilidadeDosItensComRmi();

O método ainda não existe, gere-o com o Eclipse.

3. No método verificarDisponibilidadeDosItensComRmi() , dentro da classe Carrinho , vamos


fazer o lookup e uma chamada remota para cada item de compra para verificar o estoque disponível:
public void verificarDisponibilidadeDosItensComRmi() throws Exception {

EstoqueRmi estoque = (EstoqueRmi) Naming


.lookup("rmi://localhost:1099/estoque");

for (ItemCompra itemCompra : this.itensDeCompra) {

if (itemCompra.isImpresso()) {

System.out.println("Verificação da quantidade do livro: "


+ itemCompra.getTitulo());

ItemEstoque itemEstoque = estoque


.getItemEstoque(itemCompra.getCodigo());

itemCompra.setQuantidadeNoEstoque(itemEstoque
.getQuantidade());

}
}
}

Verifique se tudo está salvo e compilando.

4. Para testar a nova funcionalidade, inicialize o serviço EstoqueRmi do projeto fj36-estoque


executando a classe RegistraERodaService (se não tiver rodando ainda).

Depois, reinicie o Tomcat e acesse a livraria pelo navegador:

http://localhost:8088/fj36-livraria

Pelo site, adicione um livro no formato impresso no carrinho de compra. Verifique no console se a
chamada remota aconteceu.

2.18 EXERCÍCIOS: INTEGRAÇÃO DA LIVRARIA COM O ESTOQUE 33


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 3

WEB SERVICES SOAP COM JAX-WS

"Be conservative in what you send; be liberal in what you accept." -- Postel's Law

Nesse capítulo, você entenderá o protocolo SOAP, WSDL e a criação de Web Services com JAX-WS.
Veremos boas e más práticas no uso de Web Services e aprenderemos os prinicpais conceitos de um
Web Service.

3.1 INTEGRAÇÃO HETEREOGÊNEA


Toda empresa que vende produtos ou presta algum tipo de serviço deve emitir nota fiscal aos
clientes, para que o governo possa cobrar os impostos correspondentes. Tradicionalmente, as notas
fiscais são feitas no papel carbono em três vias, a primeira é entregue ao cliente, a segunda fica com a
empresa e a terceira é recolhida pelo contador no final do mês, para ser enviada ao governo.

Há diversas desvantagens nesse modelo: o custo operacional para manter as notas em papel, o tempo
necessário para o governo ter conhecimento da emissão, facilidade de fraude, entre outras.

Para diminuir os gastos operacionais e obter um controle maior sobre as notas fiscais, o governo
criou um sistema eletrônico para que as empresas emitam as notas ficais automaticamente. Esse sistema
ficou conhecido como Nota Fiscal Eletrônica.

A Nota Fiscal Eletrônica permite que as empresas integrem os seus sistemas ao sistema do governo
para que elas possam lançar as suas notas ficais. Assim, não existem mais tantos gastos operacionais com
a manutenção das notas em papel e o governo fica sabendo em tempo real o que cada empresa está
vendendo ou quais serviços elas estão prestando.

Para emitir notas fiscais, o sistema do governo tem que ser extremamente genérico para que o
sistema de qualquer empresa possa se integrar a ele. É importante salientar que cada empresa pode
escolher qualquer tecnologia para implementar o seu sistema. Dessa forma, o sistema da Nota Fiscal
Eletrônica tem que conseguir trabalhar com as mais diferentes tecnologias do mercado como por
exemplo: Java, Delphi, .NET, PHP, Ruby, entre outras.

3.2 ESTILOS DE INTEGRAÇÃO


Problemas de integração são muito comuns em sistemas grandes. A troca de informações faz parte

34 3 WEB SERVICES SOAP COM JAX-WS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
de maioria dos sistema, sendo raro o funcionamento de um sistema isoladamente. Há sistemas de
terceiros, como Google Maps ou PayPal, mas também há na mesma empresa aplicações separadas que se
conectam e trocam informações.

Aplicações como Google Maps ou Paypal foram criadas para serem integradas e por isso são
relativamente fáceis de usar. O problema é que precisamos integrar aplicações que não foram
desenhadas com esse propósito.

Para resolver isso existem diversas opções como CORBA, DCOM, FTP de arquivos texto, protocolos
binários por socket, entre outros. Além dos protocolos e formatos envolvidos podemos usar estilos de
integração diferentes.

As várias possibilidades de integrar aplicações podem ser resumidas em 4 estilos:

Troca de arquivos
Banco de dados compartilhado
RPC
Mensageria

ENTERPRISE INTEGRATION PATTERN - INTEGRATION STYLE

There’s more than one approach for integrating applications. Each approach addresses some of the
integration criteria better than others. The various approaches can be summed up in four main
integration styles.

http://www.eaipatterns.com/IntegrationStylesIntro.html

Cada um desses estilos possui vantagens e desvantagens. É por isso que devemos levantar uma série
de perguntas sobre integração antes de decidirmos qual caminho tomar:

Vamos trocar funcionalidades ou apenas dados?


Quais dados trocaremos?
Qual protocolo utilizaremos?
A comunicação será síncrona ou assíncrona?
Quais ferramentas/frameworks utilizaremos?
etc.

As respostas podem ajudar a deixar a integração mais simples e flexível, minimizando assim o
acoplamento entre aplicações, porém são decisões difíceis de tomar.

3.2 ESTILOS DE INTEGRAÇÃO 35


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3.3 SOA VS INTEGRAÇÃO
Quando falamos de SOA o protocolo ou formato de dados usados na integração são menos
importantes, inclusive detalhes da implementação não interessam. O foco é em arquitetura, um tema de
mais alto nível. Porém, não é o fato de integrarmos sistemas que garante que estamos aplicando SOA.

SOA é um estilo arquitetural que vê funcionalidades como serviços, isto é, uma arquitetura orientada
a serviço (Service Oriented Architecture) independente do produto ou tecnologia utilizadas.

Aplicações que precisem de determinada funcionalidade devem procurá-la primeiro num serviço
que já forneça essa funcionalidade, evitando redundância e diminuindo assim problemas de
manutenção.

Reutilização é um dos pontos chaves do SOA e a identificação de redundâncias é uma atividade


puramente arquitetural que vai muito além da estratégia de integração.

Criar esses serviços significa expor uma API rica que esconda a complexidade dessas funcionalidades
para o cliente, tudo independente da linguagem ou plataforma utilizada, algo totalmente agnóstico à
tecnologia.

As vantagens do SOA são muitas e vão muito além da reutilização. A interoperabilidade é


melhorada, pois obrigamos aplicações a trabalharem juntas, inclusive cada serviço é uma funcionalidade
independente. Essa última característica permite melhorias e inovações pontuais ao longo do tempo.

Ainda assim, há desafios: a interoperabilidade vem pelo custo do desempenho e a quantidade de


serviços exige uma boa padronização e governança. Ao longo do treinamento veremos como resolver
alguns dos problemas mais comuns na prática.

3.4 WEB SERVICES


A maneira de integração mais difundida hoje em dia está no uso de Web Services. Existem várias
maneiras de se implementar um Web Service, mas apesar de ser um termo genérico, existe algo muito
bem especificado pela W3C:

Um dos quesitos primordiais durante a elaboração dessa especificação era que precisaríamos
aproveitar toda a plataforma, arquitetura e protocolos já existentes a fim de minimizar o impacto de
integrar sistemas. Criar um novo protocolo do zero era fora de cogitação .

Por esses motivos o Web Service do W3C é baseado em HTTP e XML, duas tecnologias onipresentes
e que a maioria das linguagens sabe trabalhar muito bem.

Vamos estudar um pouco de XML durante as próximas seções para dar o embasamento do SOAP e
do WSDL, assuntos que veremos em breve.

36 3.3 SOA VS INTEGRAÇÃO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
É importante frisar que grande parte do código que veremos aqui está realmente muito por dentro
do protocolo, mas na prática devemos evitar ao máximo nos preocupar com XML. É a máquina que
deverá se preocupar com o protocolo XML, da mesma maneira que não precisamos nos preocupar com
o funcionamento do HTTP, do JRMP, etc.

3.5 EXERCÍCIOS: INSTALAÇÃO DO JBOSS AS 8 - WILDFLY


1. Entre no diretório Caelum , disponível no seu Desktop , e em seguida entre na pasta 36 . Copie a
pasta wildfly-8.x.x para a pasta pessoal ( /home/soaXXXX ).

Wildfly corresponde à versão 8 do JBoss AS.

2. No Eclipse, caso ainda não esteja aberta, abra a view Servers. Para tal, digite Ctrl+3 e em seguida
escreva: Servers .

Clique com o botão direito e vá em New -> Server . Agora expanda a opção JBoss Community e
escolha WildFly 8.x , como indica a figura a seguir:

3.5 EXERCÍCIOS: INSTALAÇÃO DO JBOSS AS 8 - WILDFLY 37


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Na próxima janela, precisamos indicar se o Wildfly será controlado pelo eclipse ou se queremos
subir o servidor pelo terminal. Para que o eclipse controle a instalação, deixe a configuração como
indicado na figura abaixo:

Na próxima tela, no campo Home Directory , escolha o diretório onde você copiou o JBoss Wildfly
( /home/soaXXXX/wildfly-8.x.Final ), além disso, no campo Configuration file , coloque o
valor standalone-full.xml :

Ao final do processo, você deverá ver o JBoss AS WildFly na aba Servers :

38 3.5 EXERCÍCIOS: INSTALAÇÃO DO JBOSS AS 8 - WILDFLY


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3. Clique na opção do WildFly na lista de servidores e depois clique no ícone de start.

Verifique se o WildFly está rodando entrando em http://localhost:8080/ .

4. Como estamos rodando o servidor de dentro do Eclipse, é útil usar aqui mais uma view de Console,
assim conseguimos monitorar, ao mesmo tempo, a saída do JBoss e a saída dos nossos futuros testes.

Para isso, basta clicar no antepenúltimo ícone da view Console (na setinha) e escolher New Console
View. Você pode arrastar essa janela para um local mais apropriado e utilizar a opção Pin Console
para que esse console não seja trocado por outro, caso ocorra uma modificação. O instrutor falará
mais detalhes a respeito da manipulação dessa grande quantidade de janelas.

3.6 EXERCÍCIO - CONFIGURAÇÃO DO USUÁRIO ADMINISTRADOR DO


WILDFLY
1. Abra o navegador e acesse o painel de gerenciamento do JBoss:

http://localhost:9990/

Esse é o console do administrador (admin console) que possui várias informaçõesa úteis para o
administrador de servidor.

Como estamos utilizando uma instalação nova do JBoss AS, provavelmente seremos redirecionados
para uma página de erro informando que não temos usuários criados habilitados à acessar o console
de administração.

2. Para criar um outro usuário no Wildfly siga os passos abaixo:

Após ter instalado (descompactado) o Wildfly, precisamos cadastrar um novo usuário com as
permissões devidas para podermos acessar o admin console. Abra um novo terminal e volte ao
diretório bin da sua instalação do WildFly. Execute o script add-user.sh :

./add-user.sh

Ao executar o script, será perguntado o tipo de usuário que você deseja adicionar:

3.6 EXERCÍCIO - CONFIGURAÇÃO DO USUÁRIO ADMINISTRADOR DO WILDFLY 39


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Apenas pressione [Enter] sem digitar nada. Isto fará com que a opção padrão (opção a) seja
mantida.

Em seguida, crie um usuário (username) e uma senha (password). Utilizaremos caelum para as
duas opções. Como a senha que estamos utilizando é muito simples, o script nos perguntará se
realmente queremos utilizá-la, digite yes e depois confirme a senha novamente.

Por fim, precisamos definir os grupos do usuário que está sendo criado, simplesmente aperte a tecla
[ENTER] .

Agora precisamos confirmar a criação do usuário digitando yes :

Depois de confirmar a criação do usuário, o script perguntará se esse usuário pode fazer conexões
remotas com o servidor, responda no :

40 3.6 EXERCÍCIO - CONFIGURAÇÃO DO USUÁRIO ADMINISTRADOR DO WILDFLY


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Finalmente, acesse novamente o console admin:

http://localhost:9990/

Será necessário que você se autentique com os dados de usuário e senha que você acabou de criar:

Essa é a tela de administração do wildfly:

3.7 CONFIGURANDO O JBOSS WILDFLY NO ECLIPSE EM CASA


No Eclipse, caso ainda não esteja aberta, abra a view Servers. Para tal, digite Ctrl+3 e em
seguida escreva: Servers .

3.7 CONFIGURANDO O JBOSS WILDFLY NO ECLIPSE EM CASA 41


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Clique com o botão direito dentro da aba Servers e escolha New/Server.

Caso não possua a opção JBoss Community -> WildFly 8.x , clique em Download
additional server adapters :

Aguarde o preenchimento da lista e selecione **JBossAS Tools** :

42 3.7 CONFIGURANDO O JBOSS WILDFLY NO ECLIPSE EM CASA


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Clique em Next > e aceite os termos do plugin:

Clique em Finish e proceda com a instalação dando OK no box de confirmação da


instalação.

Ao final do procedimento, será solicitado que você reinicie o Eclipse. Faça isso e volte
novamente para a aba Servers .

3.8 SOAP - SIMPLE OBJECT ACCESS PROTOCOL


Como foi discutido anteriormente, as mensagens trocadas entre o sistema da Nota Fiscal Eletrônica e

3.8 SOAP - SIMPLE OBJECT ACCESS PROTOCOL 43


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
os sistemas das empresas que querem se integrar ao sistema do governo para gerar as notas fiscais devem
ser em XML basicamente por dois motivos: o processamento de conteúdo em formato texto é
normalmente mais simples de se implementar e a maioria das tecnologias atuais que são utilizadas para
desenvolver software oferecem ótimo suporte ao processamento de XML.

Agora, o próximo passo é definir um padrão para o conteúdo XML trocado entre sistemas. Isso é
interessante pois o governo poderia publicar esse padrão e todas as empresas interessadas na integração
implemetariam seus sistemas baseados nele. Isso poderia ser feito definindo um Schema para as
mensagens. Na verdade, já existe algo com esse intuito, o SOAP.

O SOAP define que uma mensagem deve estar dentro de um Envelope. Dentro do Envelope podemos
colocar um Header e um Body.

O Header é opcional e deve conter informações referentes à infraestrutura da aplicação, por exemplo
a senha para autenticação em um serviço. O Body da mensagem é a parte mais importante, onde as
informações da lógica de negócio estarão. Tanto a requisição quanto a resposta devem respeitar esse
formato.

Exemplo de mensagem de envio:

<?xml version="1.0" ?>


<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Header>
<ns2:senhaDoOperador xmlns:ns2="http://webservices.caelum.com.br/">
123456
</ns2:senhaDoOperador>
</S:Header>
<S:Body>
<ns2:calculaParcela xmlns:ns2="http://webservices.caelum.com.br/">
<valorTotal>100.0</valorTotal>
<quantidade>2</quantidade>

44 3.8 SOAP - SIMPLE OBJECT ACCESS PROTOCOL


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
</ns2:calculaParcela>
</S:Body>
</S:Envelope>

Exemplo da mensagem de resposta:


<?xml version="1.0" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:calculaParcelaResponse xmlns:ns2="http://webservices.caelum.com.br/">
<return>51.0</return>
</ns2:calculaParcelaResponse>
</S:Body>
</S:Envelope>

O ideal é nunca termos a necessidade de enxergar esses XMLs. Conhecê-los um pouco vai ajudar a
entender melhor o funcionamento dos Web Services.

Juntamente à API do Java 6.0 ou mais atual, vem uma implementação da especificação SAAJ, que
permite escrever mensagens SOAP de forma relativamente simples, sem ter que trabalhar diretamente
com XML.

3.9 EXERCÍCIO OPCIONAL: SAAJ


Atenção! Esse exercício é apenas para ver como uma mensagem SOAP pode ser construída sem ter
de mexer em uma API de XML diretamente, mas é claro que não usaremos algo nesse nível: uma
ferramenta (no caso o JAX-WS) fará o trabalho de maneira muito mais apropriada escondendo todos os
detalhes do SOAP.

1. Crie uma mensagem de envio para o serviço e mostre-a na tela através da API SAAJ. Para isso, vá no
projeto fj36-livraria e crie a classe TesteSAAJ , no pacote br.com.caelum.estoque.soap :
public class TesteSAAJ {

public static void main(String[] args) throws Exception {

MessageFactory factory = MessageFactory.newInstance();

SOAPMessage message = factory.createMessage();


SOAPBody body = message.getSOAPBody();

QName qualifiedName = new QName("http://ws.estoque.caelum.com.br/",


"getQuantidade", "ns2");

SOAPBodyElement element = body.addBodyElement(qualifiedName);

SOAPElement codigoProduto = element


.addChildElement(new QName("codigo"));
codigoProduto.setValue("ARQ");

SOAPElement quantidade = element.


addChildElement(new QName("quantidade"));
quantidade.setValue("2");

message.writeTo(System.out);

3.9 EXERCÍCIO OPCIONAL: SAAJ 45


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
}
}

Todos imports são do pacote javax.xml.* .

2. Execute o método main pelo Eclipse. O Tomcat não precisa estar rodando.

3.10 WSDL: O CONTRATO DO SEU WEB SERVICE


Falta ainda um passo importante: definir o que exatamente esse SOAP do nosso serviço transportará.
Precisamos de um contrato para isso, que funcione como a interface de negócio funcionava para os
nossos EJBs.

WSDL (Web Service Description Language) é uma linguagem também baseada em XML,que
descreve serviços na Web. Em um WSDL temos:

Declaração dos tipos do xml que são transportados no serviço


Mensagens que serão recebidas e enviadas, de acordo com os tipos já relacionados
Operações disponíveis, definindo os parâmetros de entrada e saída, usando as mensagens.
Definição de vínculos entre as operações e o protocolo de transmissão
Definição do endereço a ser acessado

46 3.10 WSDL: O CONTRATO DO SEU WEB SERVICE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3.10 WSDL: O CONTRATO DO SEU WEB SERVICE 47
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Segundo a especificação do W3C, tudo em um WSDL deve estar dentro de uma definition. Na
própria tag declaramos os namespaces que usaremos no WSDL, como por exemplo o
http://schemas.xmlsoap.org/wsdl/soap/ da definição das tags de SOAP.

<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://webservices.caelum.com.br/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.xmlsoap.org/wsdl/"
targetNamespace="http://webservices.caelum.com.br/"
name="CalculadorDeParcelaService">

<!-- outras tags aqui -->

</definitions>

Em definitions adicionamos uma seção chamada types, para definimos os tipos que o serviço usará.
Para descrever os campos usamos um XML Schema (ou XSD). Confira o exemplo abaixo:
<types>
<xs:schema xmlns:tns="http://webservices.caelum.com.br/"
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0"
targetNamespace="http://webservices.caelum.com.br/">

<xs:element name="calculaParcela" type="tns:calculaParcela"></xs:element>


<xs:element name="calculaParcelaResponse" type="tns:calculaParcelaResponse">
</xs:element>

<xs:complexType name="calculaParcela">
<xs:sequence>
<xs:element name="valorTotal" type="xs:double"></xs:element>
<xs:element name="quantidade" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>

<xs:complexType name="calculaParcelaResponse">
<xs:sequence>
<xs:element name="return" type="xs:double"></xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
</types>

STYLE DOCUMENT X RPC-STYLE

Ao usarmos o estilo de binding chamado de RPC, podemos usar mensagens SOAP para fazer
chamadas a métodos/funções/procedures. Elas serão trafegadas com o nome da operation que está
sendo chamada. Ao contrário do estilo document que somente os parâmetros da mensagem são
enviados. Neste estilo, construímos uma mensagem mais genérica e não só para RPC.

48 3.10 WSDL: O CONTRATO DO SEU WEB SERVICE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
LITERAL VS ENCODED

A diferença entre literal e encoded resume-se a transportar ou não o tipo de cada parâmetro.
Exemplo de uma mensagem SOAP Literal:
<soap:envelope>
<soap:body>
<!-- Nome da operação -->
<multiply>
<!-- primeira mensagem(input)/parametro -->
<a>2.0</a>
<!-- segunda mensagem(input)/parametro -->
<b>7</b>
</multiply>
</soap:body>
</soap:envelope>

Como os tipos não estão explícitos na mensagem, eles ficarão no schema do WSDL.

Exemplo de uma mensagem SOAP Encoded:


<soap:envelope>
<soap:body>
<!-- Nome da operação -->
<multiply>
<!-- primeira mensagem(input)/parametro -->
<a xsi:type="xsd:float">2.0</a>
<!-- segunda mensagem(input)/parametro -->
<b xsi:type="xsd:float">7</b>
</multiply>
</soap:body>
</soap:envelope>

3.10 WSDL: O CONTRATO DO SEU WEB SERVICE 49


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
DOCUMENT/LITERAL WRAPPED PATTERN

Ao usarmos Document-Style podemos usar SOAP para trabalharmos de forma mais genérica,
por exemplo, usando JMS. Mas se precisarmos usar Document com RPC, usamos um padrão
conhecido como Document/Literal Wrapped. Nele, os elementos do nosso serviço farão parte do
schema do WSDL (já que estamos usando literal). E com isso conseguiremos validar a mensagem
SOAP. Iremos envolver os parâmetros com o nome do elemento definido no schema. Exemplo:

SOAP:

<soap:envelope>
<soap:body>
<multiply>
<a>2.0</a>
<b>7</b>
</multiply>
</soap:body>
</soap:envelope>

WSDL:

<types>
<schema>
<element name="multiply">
<complexType>
<sequence>
<element name="a" type="xsd:float"/>
<element name="b" type="xsd:float"/>
</sequence>
</complexType>
</element>
</schema>
</types>

<a>2.0</a> <!-- first parameter -->


<b>7</b> <!-- second parameter -->
</multiply>
</soap:body>
</soap:envelope>

Document
- permite construir a mensagem de qq forma, nao só para RPC
- SoapAction fica vazio

Encoded:

* tipos vão junto da mensagem SOAP, nao há esquema

``` xml
<soma>
<a xsi:type="xsd:int">10</a>
<b xsi:type="xsd:int">10</b>
</soma>

50 3.10 WSDL: O CONTRATO DO SEU WEB SERVICE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Literal:

não mostra os tipos na mensagem, pois a mensagem segue o schema

<a>10</a>
<b>10</b>

No literal dá pra fazer um wrapper (para os elementos)

e assim também passa a aparecer o nome do método


<soma>
<a>10</a>
<b>10</b>
</soma>

Existe Document/literal, RPC/literal, RPC/encoded, (ninguem usa Document/encoded) E tem


Document/literal/Wrapped para simular RPC com Document-Style -->

O próximo item do definition é a parte de mensagem. Nessa seção definimos todas as mensagens que
serão enviadas e recebidas pelos serviços. Nas mensagens, usamos os tipos declarados na seção type para
definir seus parâmetros. Exemplo de uma mensagem de envio e uma de recebimento:

<message name="calculaParcela">
<part name="parameters" element="tns:calculaParcela"></part>
</message>

<message name="calculaParcelaResponse">
<part name="parameters" element="tns:calculaParcelaResponse"></part>
</message>

Depois das mensagens, temos que escrever quais serviços receberão e responderão cada mensagem.
Essa configuração é feita na seção portType. Aqui, além de juntar as mensagens com os serviços, ainda
agrupamos logicamente as operações disponíveis.

<portType name="CalculadorDeParcela">
<operation name="calculaParcela">
<input message="tns:calculaParcela"></input>
<output message="tns:calculaParcelaResponse"></output>
</operation>
</portType>

Falta ainda definir pelo menos o protocolo (HTTP ou outro) de comunicação e o endereço a ser
acessado. O protocolo é definido na tag binding. Além do protocolo, especificamos como será o estilo da
mensagem a ser enviada e como a aplicação que requisitou o serviço deve enviar a mensagem, de forma
literal (usando os tipos do schema do WSDL) ou encoding (definição padrão SOAP).

<binding name="CalculadorDeParcelaPortBinding" type="tns:CalculadorDeParcela">


<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document"></soap:binding>
<operation name="calculaParcela">
<soap:operation soapAction=""></soap:operation>
<input>
<soap:body use="literal"></soap:body>
</input>

3.10 WSDL: O CONTRATO DO SEU WEB SERVICE 51


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<output>
<soap:body use="literal"></soap:body>
</output>
</operation>
</binding>

Por fim, definimos o endereço de acesso do serviço, fazendo o vínculo com os serviços disponíveis
no binding
<service name="CalculadorDeParcelaService">
<port name="CalculadorDeParcelaPort" binding="tns:CalculadorDeParcelaPortBinding">
<soap:address location="http://localhost:8080/webservice/oi"></soap:address>
</port>
</service>

3.11 WEB SERVICES COM JAX-WS


JAX-WS (Java API for XML Web Services) é uma especificação para construção de Web Services e
clientes desses serviços. Suas maiores vantagens são a simplicidade e as ferramentas disponíveis junto à
API.

A principal implementação do JAX-WS é a que vem junto da JDK (desde a versão 6), ou seja, não
precisamos baixar nenhuma biblioteca adicional.

Antes disso, era necessário baixar os JARs de uma implementação como o Metro, o Apache Axis e o
Apache CXF. Hoje em dia esses e outros implementam a especificação JAX-WS.

Tecnicamente, para expor um serviço, deveríamos começar escrevendo o WSDL, porém com o JAX-
WS não precisamos dessa abordagem top-down. Podemos configurar nosso serviço usando anotações, e
o JAX-WS gerará o WSDL.

Temos duas anotações importantes, @WebService e @WebMethod . A primeira configura uma classe
para ser exposta como um Web Service, a segunda define que um método será exposto como uma
operação do serviço.

@WebService
public class CalculadorDeParcela {

@WebMethod
public double calculaParcela(double valorTotal, int quantidade) {
if (quantidade < 0) {
return 0;
}
double oValorTotal = valorTotal * (1 + (quantidade / 100.0));
return oValorTotal / quantidade;
}
}

Bastam essas duas anotações para expor esse método como um serviço web. O único problema são
os nomes dos parâmetros. Como o Java não consegue descobrir nomes de parâmetros de métodos por
reflection, o WSDL será gerado para receber arg0 e arg1 , dificultando o entendimento para os nossos

52 3.11 WEB SERVICES COM JAX-WS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
clientes. Para escapar disso, usamos a anotação @WebParam e definimos os nomes dos parâmetros no
serviço.

O mesmo se aplica para o retorno do método. No WSDL será gerado uma messagem para
representar o resultado do método. Podemos definir o nome da mensagem com a anotação
@WebResult .

Caso seja preciso mudar o nome do método ( operation ) no WSDL gerado, podemos usar o
atributo operationName da anotação @WebMethod .
//...
@WebMethod(operationName="calculaParcelaSOAP")
@WebResult(name="valorParcela")
public double calculaParcela(
@WebParam(name = "valorTotal") double valorTotal,
@WebParam(name = "quantidade") int quantidade) {

//...

Por padrão todos os métodos da classe CalculadorDeParcela são publicados no Web Service, mas
podemos excluir um método pela anotação @WebMethod(exclude=true) .

No WSDL temos que configurar os tipos que usaremos no serviço, porém já fizemos isso no Java
(tipo de retorno e tipo de parâmetros). Não seria melhor aproveitar isso?

O JDK traz a ferramenta wsgen, que vai gerar classes e artefatos necessários para implantar um Web
Service dada uma classe Java devidamente anotada:
wsgen -cp bin -s src -wsdl br.com.caelum.webservices.CalculadorDeParcela

-s - diretório dos arquivos .java gerados


-cp - classpath
-wsdl - gera o WSDL

Com esse comando, geramos classes de requisição e resposta do CalculadorDeParcela no sub-


pacote jaxws. A opção -s serve para guardarmos o código fonte no diretório src.

Falta agora publicar o serviço em um endereço. Para isso, usamos a classe Endpoint . Essa classe
possui um método de factory estático que recebe dois parâmetros, uma String para o endereço do
serviço e um Object que é o serviço propriamente dito, no nosso caso uma instância do
CalculadorDeParcela .

public class Publicador {


public static void main(String[] args) {
Endpoint.publish("http://localhost:10000/calculadorDeParcela",
new CalculadorDeParcela());
}
}

Assim que executarmos esse código, temos nosso serviço exposto, pronto para ser acessado.

3.11 WEB SERVICES COM JAX-WS 53


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Acessando a url http://localhost:10000/calculadorDeParcela?wsdl no browser, poderemos
ver o WSDL que foi gerado para nosso serviço. A partir desse WSDL também podemos gerar todas as
classes java necessárias para criar um cliente desse serviço, usando a ferramenta como wsimport,
também disponível juntamente com o JDK que veremos mais para frente.

IMPLEMENTAÇÃO JAX-WS, O APACHE CXF

O Wildfly já vem com uma implementação da especificação JAX-WS, o CXF:

http://cxf.apache.org/

CXF é uma das principais implementações disponíveis e se originou da união dos projeto Celtic
e XFire. Além de dar suporte ao JAX-WS também implementa o JAX-RS, outra especificação Java
EE que veremos no curso.

3.12 EXERCÍCIOS: DISPONIBILIZANDO WEB SERVICE COM JAX-WS


Nesse exercício criaremos um web service com JAX-WS rodando no servidor de aplicação JBoss
Wildfly. Usaremos uma forma simples do criar serviço que abstrai todos os detalhes dos padrões XML,
SOAP e WSDL. Criaremos uma classe e configuraremos através de anotações o acesso remoto.

Para melhorar a execução e deixarmos o serviço bem administrado dentro do servidor de aplicação,
usamos um EJB Session Bean Stateless. Assim ganhamos vários recursos do EJB Container que podem
ser interessantes para um projeto real.

Entre os recursos mais interessantes encontramos persistência com JPA, gerenciamento de


transação, administração do ciclo da vida, injeção de dependências, agendamentos entre outros. O
treinamento Persistência com JPA, Hibernate e EJB lite (FJ-25) mostra como usar todos os recursos
disponíveis.

1. No Eclipse crie um novo projeto do tipo Dynamic Web Project chamado fj36-webservice e escolha
o servidor Wildfly como Target Runtime.

54 3.12 EXERCÍCIOS: DISPONIBILIZANDO WEB SERVICE COM JAX-WS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
2. No projeto fj36-webservice, na pasta src e no pacote br.com.caelum.estoque.ws crie a classe
ItemEstoque, que representa o nosso modelo, com os atributos codigo e quantidade , e seus
getters e setters:

package br.com.caelum.estoque.ws;

public class ItemEstoque {

private String codigo;


private Integer quantidade;

ItemEstoque() {
}

public ItemEstoque(String codigo, Integer quantidade) {


this.codigo = codigo;
this.quantidade = quantidade;
}

//GETTERS E SETTERS
}

3. Ainda na pasta src e no mesmo pacote anterior, crie a classe EstoqueWS que usará a anotação
@Stateless do EJB. Ela representará o nosso endpoint. Ela recebe uma lista de códigos de produto e

3.12 EXERCÍCIOS: DISPONIBILIZANDO WEB SERVICE COM JAX-WS 55


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
devolve, para cada produto, a quantidade disponível:
package br.com.caelum.estoque.ws;

@Stateless
public class EstoqueWS {

//simulando um repositorio ou banco de dados


private Map<String, ItemEstoque> repositorio = new HashMap<>();

public EstoqueWS() {
//populando alguns dados, mapeando codigo para quantidade
repositorio.put("SOA", new ItemEstoque("SOA", 5));
repositorio.put("TDD", new ItemEstoque("TDD", 1));
repositorio.put("RES", new ItemEstoque("RES", 2));
repositorio.put("LOG", new ItemEstoque("LOG", 4));
repositorio.put("WEB", new ItemEstoque("WEB", 1));
repositorio.put("ARQ", new ItemEstoque("ARQ", 2));
}

public ItemEstoque getQuantidade(String codigo) {


return repositorio.get(codigo);
}

4. Anote a classe EstoqueWS e o método getQuantidade para configurar o Web service. Use as
anotações do JAX-WS (pacote javax.jws ):
@WebService
@Stateless
public class EstoqueWS {

@WebMethod
public ItemEstoque getQuantidade(String codigo) {
...

5. Verifique se o projeto já está associado com o servidor Wildfly e, depois disso, reinicie o servidor.
Verifique no console se o Web Service foi disponibilizado:
INFO [org.jboss.ws.cxf.metadata] (MSC service thread 1-3) JBWS024061:
Adding service endpoint metadata: id=EstoqueWS
address=http://localhost:8080/fj36-webservice/EstoqueWS
implementor=br.com.caelum.estoque.ws.EstoqueWS
serviceName={http://ws.estoque.caelum.com.br/}EstoqueWSService
portName={http://ws.estoque.caelum.com.br/}EstoqueWSPort
annotationWsdlLocation=null
wsdlLocationOverride=null
mtomEnabled=false

6. Acesse, via browser, a URL http://localhost:8080/fj36-webservice/EstoqueWS?wsdl

Repare que o WSDL que foi gerado automaticamente baseado na classe EstoqueWS .

7. Analise WSDL.

Para que existe o elemento binding?


Qual é o objetivo do portType?

56 3.12 EXERCÍCIOS: DISPONIBILIZANDO WEB SERVICE COM JAX-WS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Como saber quais são os tipos válidos?

<!--@note

binding - soap:binding é a associação do SOAP com o protocolo utilizado, em nosso caso, o


HTTP, aqui tbm aparece o style="Document"; wsdl:binding pode ser considerado a
implementação do portType, define quais operações da interface estão visiveis (alem do encoding,
literal ou encoded), provides specific details on how a portType operation will actually be
transmitted over the wire

portype - definição da interface.

através do XSD presente no WSDL, por isso o estilo Document/Literal.

port é o stub/proxy

fault a exceção,
message os dados que são trocados (paramentros/retorno)

operation é o método

Em algum momento deixe claro a tradução das palavras Java <-> WS (fazendo RPC):

Parametro, Retorno <> Message

Metodo <> Operation


Interface <> Porttype
Proxy <> Port

Exception <> Fault

-->

3.13 TESTANDO O WEB SERVICE COM SOAPUI


Criamos o Web Service através do JAX-WS dentro do servidor Wildfly, chegou a hora de testar o
nosso serviço. Testar significa criar um cliente que entende a interface WSDL e sabe enviar uma
mensagem SOAP.

Uma ferramenta de testes que se destaca no mercado pela simplicidade é o SoapUI. SoapUI é uma
solução open source para testes funcionais de serviços SOAP ou REST. Com ela podemos rapidamente
analisar o WSDL e criar algumas requisições SOAP, tudo pela interface gráfica.

Para começar a usar o SoapUI basta acessar o site e baixar a versão atual:

http://www.soapui.org/

3.13 TESTANDO O WEB SERVICE COM SOAPUI 57


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
SoapUI vem com duas versões: SoapUI e SoapUI Pro. A versão Pro é paga e oferece alguns recursos a
mais, como relatórios e métricas, depuração de testes entre outros.

Após ter baixado e instalado o SoapUI podemos criar um novo projeto do tipo SOAP pela interface
gráfica:

No diálogo basta definir o nome do projeto e a localização do WSDL:

Após confirmação já podemos testar o serviço. O SoapUI gera automaticamente uma requisição

58 3.13 TESTANDO O WEB SERVICE COM SOAPUI


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
SOAP baseado nas informações do WSDL:

3.14 EXERCÍCIOS: CONSUMINDO O WEB SERVICE


1. Entre no diretório Desktop/Caelum/36. Copie a pasta soapui-4.x.x para sua pasta pessoal
( soaXXXX ).

2. Abra a pasta do soapui que você acabou de copiar e entre na pasta bin .

Dê um duplo clique no arquivo soapui.sh e clique em Executar em terminal para exibir a interface do
soapUI:

3. No soapUI que você acabou de abrir, crie um novo projeto. Aperte CRTL + N e chame o projeto de
EstoqueWS. No campo Initial WSDL cole a URL do web service:

http://localhost:8080/fj36-webservice/EstoqueWS?wsdl

Confirme o dialogo para criar o novo projeto.

3.14 EXERCÍCIOS: CONSUMINDO O WEB SERVICE 59


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4. No projeto, dentro do elemento getQuantidade clique duas vezes no elemento Request 1. O soapUI
abrirá uma nova janela que mostra a mensagem SOAP a ser enviada para o serviço:

5. Dentro do XML SOAP, no elemento arg0 , no lugar de ? coloque o código do livro, por exemplo:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.estoque.caelum.com.br/">
<soapenv:Header/>
<soapenv:Body>
<ws:getQuantidade>
<arg0>ARQ</arg0>
</ws:getQuantidade>
</soapenv:Body>
</soapenv:Envelope>

Submeta a requisição SOAP, apertando o botão verde no lado esquerdo da janela.

6. Verifique a resposta no outro lado da janela. Deve aparecer a mensagem SOAP com as informações
de quantidade no estoque:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getQuantidadeResponse xmlns:ns2="http://ws.estoque.caelum.com.br/">
<return>
<codigo>ARQ</codigo>
<quantidade>2</quantidade>
</return>
</ns2:getQuantidadeResponse>
</soap:Body>
</soap:Envelope>

60 3.14 EXERCÍCIOS: CONSUMINDO O WEB SERVICE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3.15 GRANULARIDADE DO SERVIÇO
Na livraria eletrônica, depois que o cliente terminar de escolher todos os livros, precisamos verificar
a disponibilidade de cada um dos itens do carrinho no sistema de estoque e para isso, precisamos
consumir o serviço SOAP que acabamos de desenvolver. Esse serviço só consegue buscar a quantidade
de um produto por vez, então para consultarmos a disponibilidade de todos os itens do carrinho,
chamaremos o serviço diversas vezes para atender uma única compra.

Tanto a implementação utilizando o RMI quanto a nova com o SOAP sofrem o mesmo problema:
Toda vez que queremos invocar o serviço, precisamos enviar dados pela rede de computadores da
empresa para nos comunicarmos com a aplicação de estoque o que pode fazer com que a aplicação da
livraria não tenha um desempenho aceitável.

No caso do RMI, esse problema de desempenho é menor pois toda a comunicação é feita utilizando
um protocolo binário especializado do Java, mas na chamada SOAP trabalhamos com o protocolo
textual HTTP transferindo dados com o formato XML, o que faz com que o problema de desempenho
fique ainda mais grave.

Ao projetar um serviço, precisamos levar em consideração como o serviço será utilizado pelas
aplicações consumidoras. Na aplicação da livraria, queremos consumir o serviço para consultar todos os
livros que estão no carrinho de compras, portanto ao invés de criarmos um serviço que recebe apenas
um código de livro, deveríamos ter criado um serviço um pouco maior, que consegue receber, por
exemplo, uma lista contendo todos os códigos que precisam ser consultados:

public List<ItemEstoque> getQuantidades(List<String> codigos){


// implementação
}

Toda vez que vamos desenvolver um novo serviço, precisamos considerar qual deve sua
granularidade.

Se a granularidade do serviço for muito pequena, no geral, precisaremos fazer mais invocações o que
pode diminuir o desempenho da aplicação.

Se a granularidade for muito grande, podemos tráfegar dados demais pela rede fazendo com que
cada chamada para o serviço fique mais demorada.

3.16 SOA FALLACIES


Ao projetar serviços, muitos programadores e arquitetos assumem algumas características sobre a
comunicação entre aplicações que na verdade são conhecidas como falácias do SOA (SOA fallacies):

A rede é estável: Muitos fatos podem fazer com que um serviço fique indisponível (falhas de
hardware, falta de energia, manutenção na rede), então não podemos assumir a estabilidade da rede de

3.15 GRANULARIDADE DO SERVIÇO 61


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
computadores.

A latência é zero: Latência é o tempo gasto para que um dado chegue ao seu destino. Quando
programamos serviços, como todas as informações passam pela rede de computadores, precisamos levar
em consideração a latência que existe na comunicação entre o cliente e o serviço.

A banda é infinita: Para evitar o problema dos serviços com granularidade muito pequena, temos a
tendência de tentar aumentar a quantidade de dados que são devolvidos pelo serviço para diminuir o
número de acessos, mas com isso podemos acabar aumentando a quantidade de dados trafegados pela
rede, fazendo com que a aplicação novamente perca desempenho.

A comunicação pela rede é segura: A comunicação na rede está sujeita a diversos tipos diferentes de
ataque e, por isso, é importante colocar a segurança como um dos requisitos do serviço que será
desenvolvido.

A topologia não muda: Redes de computadores mudam constantemente, a todo momento


conectamos uma máquina nova ou trocamos um equipamento antigo por um mais moderno. Então ao
desenvolver serviços não devemos nos acoplar a estrutura da rede.

Existe apenas um administrador: Quando estamos trabalhando com integração, precisamos sempre
lembrar que nos comunicaremos com outras aplicações ou infraestrutura que são gerenciadas por outras
equipes e empresas.

O custo do transporte de dados é zero: Para que o transporte dos dados trafegados pelo serviço seja
rápido e confiável, precisamos de uma boa infraestrutura de redes, o que faz com que os custos para a
empresa sejam aumentados.

A rede é homogênea: A rede de computadores pode ter as mais diversas configurações de hardware,
é muito difícil termos uma rede homogênea.

3.17 EXERCÍCIOS - GRANULARIDADE DO SERVIÇO


1. Modifique a implementação do método getQuantidade da classe EstoqueWS do projeto fj36-
webservice para que ele receba como argumento a lista de todos os códigos que serão consultados:

public List<ItemEstoque> getQuantidade(List<String> codigos) {


List<ItemEstoque> itens = new ArrayList<>();

if(codigos == null || codigos.isEmpty()) {


return itens;
}

for(String codigo : codigos) {


if(repositorio.containsKey(codigo)) {
itens.add(repositorio.get(codigo));
}
}
return itens;

62 3.17 EXERCÍCIOS - GRANULARIDADE DO SERVIÇO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
}

Depois de fazer essa modificação reinicie o wildfly.

2. Como fizemos modificamos a assinatura do método getQuantidade , o WSDL que representa o


contrato do serviço também foi modificado para refletir a atualização no código. Abra novamente o
navegador e entre no endereço http://localhost:8080/fj36-webservice/EstoqueWS?wsdl
para ver o novo wsdl que foi publicado pelo JAX-WS.

Repare que agora na seção wsdl:types do novo contrato do serviço, temos o seguinte código:
<xs:complexType name="getQuantidade">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="arg0"
type="xs:string">
</xs:element>
</xs:sequence>
</xs:complexType>

Esse trecho indica que o método recebe uma lista com uma quantidade ilimitada de xs:string s
<xs:complexType name="getQuantidadeResponse">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="return"
type="tns:itemEstoque">
</xs:element>
</xs:sequence>
</xs:complexType>

Por esse trecho de código, sabemos que o tipo getQuantidadeResponse representa uma lista de
elementos do tipo tns:itemEstoque .

3. Agora vamos testar novamente o serviço implementado com o SoapUI. Como o contrato do serviço
foi alterado, precisamos atualizar a requisição preparada pelo SoapUI.

No soapUI selecione no lado esquerdo o item EstoqueWSServiceSoapBinding e aperte F5.

3.17 EXERCÍCIOS - GRANULARIDADE DO SERVIÇO 63


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Depois que a atualização terminar, teste novamente a requisição para apenas um livro:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.estoque.caelum.com.br/">
<soapenv:Header/>
<soapenv:Body>
<ws:getQuantidade>
<arg0>ARQ</arg0>
</ws:getQuantidade>
</soapenv:Body>
</soapenv:Envelope>

4. Submeta uma nova requisição passando dois códigos diferentes, por exemplo:
<arg0>ARQ</arg0>
<arg0>SOA</arg0>

Verifique a resposta.

5. (conceitual) Como consumidor de um Web Service, o que você acha de um elemento no XML que
possui o nome arg0 ou return ?

3.18 MAIS RECURSOS DO JAX-WS


Nosso serviço já está funcional porém pouco expressivo. Um serviço deve oferecer uma interface
fácil de entender, mesmo sendo gerado com uma ferramenta como JAX-WS.

Como criamos o serviço a partir de uma classe Java, o Wildfly (nesse caso a implementação JAX-WS
CXF) leu a assinatura dela e gerou o WSDL. O problema é que não há todas as informações na classe
para gerar um WSDL expressivo.

Para resolver este problema o JAX-WS oferece uma serie de anotações para definir essas meta-
informações que ajudam a deixar a mensagem SOAP mais legível. As anotações @WebMethod ,
@WebResult e @WebParam possuem atributos para alterar o nome dos elementos na mensagem SOAP:

@WebMethod(operationName="ItensPeloCodigo")
@WebResult(name="ItemEstoque")
public List<ItemEstoque> getQuantidade(
@WebParam(name = "codigo") List<String> codigos) {

O @WebMethod e @WebParam mudam a mensagem SOAP de ida (request):


<soapenv:Envelope ....>
<soapenv:Body>
<v1:ItensPeloCodigo>
<codigo>ARQ</codigo>
</v1:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>

O retorno é alterado pelo @WebResult(name="ItemEstoque") - onde aparecia o elemento


<return> agora tem <ItemEstoque> :

64 3.18 MAIS RECURSOS DO JAX-WS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<soap:Envelope ...>
<soap:Body>
<ns2:ItensPeloCodigoResponse ...>
<ItemEstoque>
<codigo>ARQ</codigo>
<quantidade>2</quantidade>
</ItemEstoque>
</ns2:ItensPeloCodigoResponse>
</soap:Body>
</soap:Envelope>

3.19 EXERCÍCIOS: PERSONALIZANDO O WEB SERVICE


1. Vamos usar algumas anotações do JAX-WS para deixar o WSDL e as mensagens SOAP mais
expressivas.

No Eclipse, no projeto fj36-webservice abra a classe EstoqueWS e procure o método


getQuantidade .

Use as anotações @WebMethod, @WebResult e @WebParam para melhorar a mensagem SOAP.


As anotações possuem atributos para definir o nome do elemento XML:

@WebMethod(operationName="ItensPeloCodigo")
@WebResult(name="ItemEstoque")
public List<ItemEstoque> getQuantidade(
@WebParam(name = "codigo") List<String> codigos) {

Reinicie o servidor Wildfly.

2. Como alteramos o serviço, e com isso o WSDL, é preciso atualizar o cliente.

No soapUI selecione no lado esquerdo o item EstoqueWSServiceSoapBinding e aperte F5.

Confirme o dialogo e depois escolha o novo método ItensPeloCodigo. É gerado um novo elemento
Request 1 já com a mensagem SOAP atualizada.

3.19 EXERCÍCIOS: PERSONALIZANDO O WEB SERVICE 65


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3. Abaixo do elemento ItensPeloCodigo clique duas vezes no elemento request. Repare que agora a
mensagem SOAP está mais legível, sem o elemento arg0 e usando o nome da operação
ItensPeloCodigo :

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.estoque.caelum.com.br/">
<soapenv:Header/>
<soapenv:Body>
<ws:ItensPeloCodigo>
<codigo>?</codigo>
</ws:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>

4. Submeta uma vez a mensagem SOAP. Não esqueça de substituir ? por um código do livro.

Repare que a resposta SOAP também está mais expressiva. So invés do elemento <return> aparece
<ItemEstoque> :

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:ItensPeloCodigoResponse
xmlns:ns2="http://ws.estoque.caelum.com.br/">
<ItemEstoque>
<codigo>ARQ</codigo>
<quantidade>2</quantidade>
</ItemEstoque>
</ns2:ItensPeloCodigoResponse>
</soap:Body>
</soap:Envelope>

3.20 A IMPORTÂNCIA DO VERSIONAMENTO


No desenvolvimento de software estamos sujeitos a alterações constantes. Uma vez disponibilizado o
serviço vem a pergunta de como evoluí-lo sem quebrar os clientes existentes?

O mais fácil de se fazer, é tentar não quebrar os clientes. Ou seja, quando precisarmos alterar o
código, deixá-lo compatível com o contrato antigo.

Um exemplo concreto disso seria, ao invés de alterar um nome de método, deixar o método antigo
na interface e adicionar um novo. Dentro dos padrões de projeto SOA isso se chama compatible change.

http://soapatterns.org/design_patterns/compatible_change

Em geral, há várias formas de versionar um serviço.

Versionamento pelo XML Element ou Attribute


Nessa forma de versionamento inserimos mais um elemento no XML da mensagem SOAP
exclusivamente para representar a versão. Para fazer uma analogia, é parecido com o
serialVersionUID da classe java no processo da serialização onde criamos mais um atributo na classe

66 3.20 A IMPORTÂNCIA DO VERSIONAMENTO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
para o versionamento da mesma.

Uma vantagem é que poderíamos ter algum intermediário/proxy que analisa esse elemento para
rotear ao endpoint correto. Isso também é chamado content-based routing um padrão de integração que
veremos mais para frente.

Versionamento pelo XML Namespace


A forma mais padronizada é usar a XML targetNamespace do WSDL para definir a versão do
contrato. Com JAX-WS em mãos basta usar o atributo targetNamespace da anotação @WebService :

@WebService(targetNamespace="http://caelum.com.br/estoquews/v1")

v1 é a versão do contrato. Qualquer alteração que quebraria a compatibilidade (maior change) deve
causar a alteração da versão. Para alterações que são compatíveis (minor change) deveríamos usar a tag
<documentation> no WSDL. Segue um exemplo:

<wsdl:definitions ....
targetNamespace="http://caelum.com.br/estoquews/v1">
<documentation>Version 1.1</documentation>

Infelizmente o JAX-WS não especificou uma forma padrão de definir o conteúdo da tag
<documentation> .

Versionamento pela URI do endpoint


Outra forma comum é usar a URI do serviço para embutir a versão utilizada. A vantagem é que isso
é transparente e simples de implementar. Como o WSDL não possiu nenhum elemento ou atributo
oficial da W3C para conter a versão do contrato estamos livres a usar o URI do serviço. Seguem dois
exemplo de possíveis URIs:

```http://caelum.com.br/webservices/2013/10/EstoqueWS

A identificação na URI facilita se precisarmos realmente manter um serviço com versões diferentes
no ar. Por outro lado, o cliente precisa conhecer as URIs de cada versão.

SOA DESIGN PATTERN

O importante é usar uma forma consistente de versionamento para todos os Web Services na
empresa que se chama Canonical Versioning:

http://soapatterns.org/design_patterns/canonical_versioning

A ideia de usar alguma identificação da versão também ganhou um nome nos SOA Patterns:
http://soapatterns.org/design_patterns/version_identification

3.20 A IMPORTÂNCIA DO VERSIONAMENTO 67


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
SERVICE REPOSITORY

Um service repositoriy ajuda a administrar os serviços disponíveis e associar a eles meta


informações como versão ou diretivas de segurança, além de funções de monitoramento. É uma
especie de registro e substitui o antigo UDDI.

3.21 SEGURANÇA NO WEB SERVICE


Segurança na Web é um assunto bastante amplo e complexo. Nessa discussão vamos nos limitar no
que importa para implementar um Web Service seguro. Vamos dividir o tópico em duas partes: A
primeira parte é relacionada com autenticação e autorização do cliente, e a segunda com a
confidencialidade e integridade dos dados.

Para autenticar o cliente, ele deve enviar na requisição HTTP as credenciais, por exemplo através de
um login e senha ou access token. Um access token nada mais é do que uma identificação do usuário, algo
parecido com a session id usado no mundo HTTP. Com esses dados de autenticação em mãos o servidor
pode verificar a existência do usuário e a validade da conta dele. Uma vez identificado o usuário,
podemos autorizá-lo, ou seja conferir as permissões para aquele recurso. É muito comum associar
grupos com o usuário (role-based) para liberar ou negar o acesso a um recurso.

Para garantir a integridade e confidencialidade dos dados, a grande maioria dos serviços na Web
usam HTTPS. Integridade significa que a mensagem não pode ser manipulada por terceiros (tampered)
para, por exemplo, inserir algum conteúdo falso. A confidencialidade por sua vez se preocupa para que
nenhum terceiro possa ver a mensagem, assim seu conteúdo é mantido em segredo.

No mundo dos Web Services a integridade da mensagem é garantida por uma assinatura digital.
Assinar significa usar uma chave secreta e inserir um valor cifrado. Ou seja, é usado uma chave em
combinação com um algoritmo de criptografia (RSA ou DSA) para cifrar os dados (criar um cipher text).
Cifrar siginifica que os dados ficam ilegíveis para pessoas não autorizadas e só os destinatários podem
extrair as informações usando a chave em questão.

Há dois tipos de chaves: chaves simétricas e assimétricas. Chaves simétricas são mais simples e
compartilhadas entre emissor e destinatário. Uma chave assimétrica é composta de duas chaves, uma
privada e outra pública. Composta significa que as chaves são ligadas matematicamente. A chave pública
é distribuída livremente, a chave privada fica apenas com o dono. Tudo que foi cifrado pela chave
pública só pode ser decifrado pela privada e vice-versa.

Voltando para o tópico de integridade: Para impedir uma alteração indevida é inserido um valor
cifrado na mensagem (SignatureValue). Este valor é gerado baseado no Hash da mensagem (também
chamado de digest ou checksum). O Hash por sua vez é calculado analisando o conteúdo da mensagem.

68 3.21 SEGURANÇA NO WEB SERVICE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Qualquer alteração dos dados da mensagem causaria um novo hash. Se um terceiro altera então a
mensagem, consequentemente mudaria o hash (também chamado DigestValue).

O SignatureValue foi cifrado com a chave privada, usando o hash da mensagem. Ou seja, o dono que
possui a chave privada, envia a assinatura junto com a mensagem. O destinatário, ao receber a
mensagem, usa a chave pública para decifrar a assinatura. O resultado é o hash que o emissor calculou.
Basta recalcular o hash e comparar com o hash do emissor. Qualquer alteração mudaria o valor e
provaria que a integridade da mensagem foi comprometida. Repare também que além da integridade, o
destinatário sabe através da assinatura quem criou a mensagem.

Confidencialidade se preocupa que o conteúdo da mensagem é mantido em segredo. Nem sempre


queremos enviar as informações em texto aberto (plain-text). Para esconder os dados usamos
criptografia. Novamente aplicando um algoritmo de criptografia e uma chave (simétreca ou assimétrica).
A diferença agora é que todo conteúdo será cifrado para deixar a mensagem ilegível.

HTTPS garante confidencialidade e integridade dos dados usando assinaturas digitais, chaves
assimétricas e simétricas. Muitos detalhes serão escondidos e não interessam no dia-a-dia do
desenvolvedor, mas pode ser útil para entender as configurações relacionadas com a segurança. No
próximo capítulo veremos como habilitar o HTTPS dentro de um servidor Java EE.

3.22 ENTENDENDO E HABILITANDO HTTPS


Ao acessar algum site com HTTPS automaticamente é estabelicido uma comunicação segura. Por
exemplo, ao acessar https://www.itau.com.br podemos ver no navegador um cadeado que indica a
comunicação segura:

Quando o navegador envia uma requisição HTTPS, ele pede um certificado digital do servidor
(challenge). Um certificado digital associa uma chave pública a uma entidade. Ele é apenas um wrapper
para a chave pública e adiciona algumas informações sobre a entidade como o nome, cidade, pais,
validade, etc.

3.22 ENTENDENDO E HABILITANDO HTTPS 69


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Como já falamos antes, uma chave assimétrica é composta de duas chaves. A chave privada fica
sempre com o dono (ou a entidade, nesse contexto). A chave pública consta no certificado junto com as
outras informações da entidade. Para garantir a integridade e autenticidade do certificado, a entidade
assina o mesmo usando a chave privada.

Para garantir a confidencialidade e autencidade de um certificado ele é emitido por uma autoridade
de certificação (Certification Authority). Veremos mais para frente como criar um certificado na mão, no
entanto, para usar um certificado confiável é preciso recebe-lo da autoridade. No HTTPS, a autoridade é
o terceiro que podemos confiar. Essa autoridade garante e verifica que realmente existe a entidade e
confere as informações antes de emitir o certificado. A autoridade também assina o certificado e garante
assim a integridade e autencidade das informações.

70 3.22 ENTENDENDO E HABILITANDO HTTPS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Quando o navegador accessa um site pelo HTTPS, ele recebe o certificado e sabe se o mesmo foi
emitido por uma autoriadade confiável. O browser sabe quais são as autoridades a confiar, isso já vem
embutido no navegador. Caso o certificado não tenha sido assinado por uma autoridade confiável, será
apresentado uma alerta para o usuário. Nesse caso o usuário precisa confirmar explicitamente que deseja
continuar no site. Pelo ponto de vista do navegador, não há como garantir a integridade e autenticidade
das informações do certificado.

Ao confirmar, o certificado é salvo dentro de um truststore (ou keystore). Um truststore é um tipo de


arquivo protegido onde ficam salvos todos os certificados conhecidos.

Como já falamos, dentro do certificado se encontra a chave pública. O emissor poderia usar essa
chave para cifrar o conteúdo das proximas requisições. O problema é que a criptografia usando chaves
assimétricas é custosa pois usa chaves relativamente grandes. Por isso a chave pública é apenas usada
para criar uma chave simétrica, ou seja uma chave compartilhada. O motivo de usar as chaves
assimétricas é apenas para criar e trocar uma chave simétrica! Essa chave compartilhada é usada para
cifrar as próximas requisições. Podemos dizer então que o HTTPS é um sistema híbrido que usa chaves
assiméticas e simétricas!

3.23 GERANDO O CERTIFICADO


Quando usamos HTTPS o navegador já vem preparado para gerenciar e armazenar os certificados e
estabelecer uma comunicação segura. Ele já tem o seu truststore No entanto, em uma aplicação Java
quem faz a requisição HTTPS é a JVM. Por isso o JRE possui o seu próprio mecanismo para armazenar
certificados e uma ferramenta chamada keytool para criar e gerenciar chaves e certificados.

Através do keytool podemos gerenciar os certificados que ficam salvos dentro de um truststore ou
keystore (Java Key Store - JKS). O keytool sabe criar chaves assiméticas ou simétricas, definir o
algoritmo, tamanho das chaves entre várias outras configurações.

Normalmente o keytool se encontra dentro da pasta bin do JRE e pode ser utilizado via linha de
comando. Para, por exemplo, gerar um certificado válido por 180 dias, usando algoritmo RSA podemos
usar o seguinte comando:
keytool -genkey -keyalg RSA -alias livraria
-keystore caelum_keystore.jks
-storepass caelum -validity 180

Ao executar é preciso responder algumas perguntas sobre a entidade (nome, empresa, cidade etc)
que está sendo associada com a chave.
What is your first and last name?
[Unknown]: Nico Steppat
What is the name of your organizational unit?
[Unknown]: Caelum Rio
What is the name of your organization?
[Unknown]: Caelum

3.23 GERANDO O CERTIFICADO 71


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
What is the name of your City or Locality?
[Unknown]: Rio de Janeiro
What is the name of your State or Province?
[Unknown]: RJ
What is the two-letter country code for this unit?
[Unknown]: BR
Is CN=Nico Steppat, OU=Caelum Rio, O=Caelum, L=Rio de Janeiro, ST=RJ, C=BR correct?
[no]: yes

Enter key password for <livraria>


(RETURN if same as keystore password):
Re-enter new password:

Como resultado será gerado um certificado que guarda as chaves pública e privada. Repare que
usamos duas senhas, uma para o keystore, nesse caso caelum , e outra para o certificado (também
caelum ).

Um keystore pode guardar vários certificados e podemos listar seu conteúdo com o comando:
keytool -list -v -keystore caelum_keystore.jks

Ou mostrar um certificado pelo alias:

keytool -list -v -keystore caelum_keystore.jks -alias livraria

Enter keystore password:


Alias name: livraria
Creation date: 06/01/2014
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Nico Steppat, OU=Caelum Rio, O=Caelum, L=Rio de Janeiro, ST=RJ, C=BR
Issuer: CN=Nico Steppat, OU=Caelum Rio, O=Caelum, L=Rio de Janeiro, ST=RJ, C=BR
Serial number: 3927b02e
Valid from: Mon Jan 06 13:12:01 BRST 2014 until: Sat Jul 05 12:12:01 BRT 2014

Caso necessário, podemos exportar um certificado do keystore usando o comando:


keytool -certreq -alias livraria
-keystore caelum_keystore.jks -file livraria.cer

livraria, 06/01/2014, PrivateKeyEntry,


Certificate fingerprint (SHA1): F2:FC:60:95:03:45:96:FA:1C:50:EA:19:1D

Igualmente podemos importar um certificado já existente. Isso pode ser necessário caso uma
autoridade de certificação tenha emitido o certificado:
keytool -importkeystore -srckeystore certificate.pfx
-destkeystore caelum_keystore.jks -srcstoretype pkcs12

O keytool oferece muito mais funcionalidades, uma boa documentação se encontra no site:

http://www.sslshopper.com/article-most-common-java-keytool-keystore-commands.html

3.24 HABILITANDO HTTPS NO SERVIDOR

72 3.24 HABILITANDO HTTPS NO SERVIDOR


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Após ter criado o certificado (ou importado um certificado já existente) podemos habilitar HTTPS
no servidor Apache Tomcat. Para tal é preciso descomentar o conector com HTTPS habilitado. No
arquivo conf/server.xml da distribuição do Tomcat basta adicionar um novo conector . No XML já
tem algumas configurações comentadas para simplificar o trabalho:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"


maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="${user.home}/caelum_keystore.jks"
keystorePass="caelum"
keyAlias="livraria" />

Ao acessar o Tomcat o navegador reclamará do nosso certificado. Isto por que o nosso certificado
não foi emitido por uma autoridade de certificação, e sim gerado manualmente pelo keytool .

Por fim, falta habilitar a segurança de transporte na aplicação web. No arquivo de configuração da
aplicação, ou seja no web.xml , é preciso adicionar o elemento security-constraint :
<security-constraint>
<web-resource-collection>
<web-resource-name>aplicação protegida</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>

<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>

Dessa maneira, ao acessar a aplicação web sempre será utilizado o HTTPS.

3.25 INTRODUÇÃO AOS PADRÕES WS-*


Há vários requisitos não funcionais ou qualidades de serviço (QoS) que fazem parte do

3.25 INTRODUÇÃO AOS PADRÕES WS-* 73


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
desenvolvimento de Web Services e são importantes para a maioria de aplicações. O exemplo clássico é a
segurança, outros são transação ou auditoria. No mundo dos Web Services são as extensões (extensions)
do SOAP que definem como implementar a qualidade desejada de maneira padronizada e independente
da linguagem.

Por exemplo, se queremos enviar uma mensagem SOAP dentro de uma transação distribuída
podemos escolher a extensão em questão e adicionar essa capacidade a mensagem SOAP.

Segue uma lista das principais extensões:

WS-Policy: para expressar restrições e requisitos, por exemplo uma politica de segurança
WS-Security: Para definir como aplicar segurança a mensagem SOAP
WS-Addressing: para definir informações sobre destinatário, remetente e roteamento da
mensagem SOAP, é útil para comunicação assíncrona
entre vários outros

As extensões normalmente são chamadas de WS-* por causa da grande quantidade de especificações
e documentos relacionadas. A complexidade e má fama dos Web Services se origina dessas extensões.

Como o SOAP é extensível podemos adicionar diferentes extensões a uma mensagem, sempre dentro
de elemento <header> . Várias extensões podem ser combinadas para conseguir a qualidade de serviço
desejada.

3.26 CONHECENDO O WS-SECURITY


Quando pensamos em uma comunicação segura a escolha mais fácil seria usar o HTTPS. Ou seja,
usando segurança no nível de transporte adicionando uma camanda SSL no stack TCP/IP. No entanto
isso só funciona na Web. O padrão SOAP é mais genérico e independente do HTTP/HTTPS.

O problema é que uma mensagem SOAP nem sempre trafega só na Web e pode fazer um caminho
longo entre outros intermediários depois da comunicação na Web. HTTPS foi pensado apenas entre o
cliente Web e servidor Web (point-to-point). Há casos que queremos segurança End-to-End, sendo
totalmente agnóstico ao protocolo de transporte usado. Uma mensagem SOAP deve trafegar de maneira
integra e confiável, independente se utilizamos AMQP, Stomp, SMTP ou HTTP.

Em geral, o SOAP não depende da Web, uma mensagem poderia ser enviada com qualquer outro
protocolo de transporte. Nesses casos também devemos garantir a segurança que é a razão pela qual o
SOAP definiu sua própria forma de segurança, o WS-Security.

Mesmo assim o WS-Security se baseia em padrões existentes. Usa-se assinaturas digitais e chaves
assimétricas e simétricas para garantir a autenticidade, integridade e confidencialidade dos dados.

3.27 EXERCÍCIOS: AUTORIZAÇÃO E VERSIONAMENTO


74 3.26 CONHECENDO O WS-SECURITY
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Vamos testar um cabeçalho da mensagem SOAP. Para identificar e autorizar o usuário enviaremos
um token dentro do cabeçalho. Assim separamos os dados de segurança (meta informações) dos dados
principais (payload) dentro da mensagem.

Um token ou access token nada mais é do que um número gerado pelo sistema que identifica o
usuário. Ao invés de enviar o login e a senha do usuário entre requisições, enviaremos apenas esse token.
Isso é algo muito comum na integração entre sistemas.

1. No Eclipse, no projeto fj36-webservice abra a classe EstoqueWS. No método getQuantidade()


crie mais um parâmetro do tipo String que representa o token.

Adicione apenas o segundo parâmetro. Repare que a anotação @WebParam usa o atributo
header=true:

public List<ItemEstoque> getQuantidade(


@WebParam(name = "codigo") List<String> codigos,
@WebParam(name = "tokenUsuario", header = true) String token) {

2. No início do método getQuantidade(..) adicione a verificação do token. Caso o token não exista,
jogaremos uma exceção:
if(token == null || !token.equals("TOKEN123")) {
throw new AutorizacaoException("Nao autorizado");//vamos gerar essa classe
}

Estamos usando um token fixo. Em uma aplicação real poderia ser utilizado um banco de dados para
verificar a existência do token.

3. O código ainda não compila. Gere a exceção no mesmo pacote. Anote-a com @WebFault :

@WebFault(name="AutorizacaoFault")
public class AutorizacaoException extends RuntimeException {

private static final long serialVersionUID = 1L;

public AutorizacaoException(String message) {


super(message);
}
}

4. Antes de publicar o Web Service vamos introduzir o versionamento pelo namespace para facilitar a
sua evolução.

Na classe EstoqueWS procure a anotação @WebService. Nela adicione o atributo targetNamespace


que define a URI com versão:

@Stateless
@WebService(targetNamespace="http://caelum.com.br/estoquews/v1")
public class EstoqueWS {

5. Reinicie o servidor Wildfly e acesse pelo navegador:

3.27 EXERCÍCIOS: AUTORIZAÇÃO E VERSIONAMENTO 75


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
http://localhost:8080/fj36-webservice/EstoqueWS?wsdl

Verifique o namespace novo no início do WSDL. Também procure o elemento tokenUsuario que
faz parte do soap:header .

6. No soapUI atualize o cliente. Selecione EstoqueWSServiceSoapBinding e aperte F5.

Abra o XML SOAP e coloque o token e o codigo no lugar das ? . Por exemplo:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v1="http://caelum.com.br/estoquews/v1">
<soapenv:Header>
<v1:tokenUsuario>TOKEN123</v1:tokenUsuario>
</soapenv:Header>
<soapenv:Body>
<v1:ItensPeloCodigo>
<codigo>ARQ</codigo>
</v1:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>

Submeta a requisição SOAP e verifique a resposta.

7. Altere a requisição e submeta um token inválido. Teste com soapUI. Qual é a resposta?

3.28 CRIAÇÃO DO CLIENTE COM JAX-WS


Conseguimos testar o nosso serviço web com a ajuda do soapUI. A ferramenta leu o WSDL e criou
todo o cliente, incluindo a mensagem SOAP. Agora queremos usar o serviço de estoque na aplicação
web. Ou seja, para decobrir a quantidade de livros impressos disponíveis faremos uma chamada remota.
Já fizemos isso com RMI, vamos então substituir RMI com SOAP/WSDL.

Com a instalação do JRE já vem também uma implementação do JAX-WS. No caso da JRE da Oracle
a implementação se chama Metro (http://metro.java.net/). O Metro possui uma ferramenta, o
wsimport , que consegue gerar classes que acessam o serviço de uma maneira transparente. Basta passar
o endereço do WSDL e o wsimport cria todas as classes necessárias para chamar o serviço remoto.

Para gerar as classes (stubs) executamos na linha de comando:


wsimport -s src -p br.com.caelum.estoque.soap
http://localhost:8080/fj36-webservice/EstoqueWS?wsdl

Repare nos parâmetros que passamos:

-s - diretório dos arquivos .java gerados


-p - pacote das classes geradas

As classes geradas:

76 3.28 CRIAÇÃO DO CLIENTE COM JAX-WS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Existem outras ferramentas, como o próprio soapUI, para gerar os stubs do cliente a partir do
WSDL, como as que vêm com as outras implementações da especificação do JAX-WS, como por
exemplo Axis ou CXF. Até no Eclipse ou Netbeans existem Wizards para criar as classes pela interface
gráfica.

Com essas classes prontas já podemos criar o cliente para nosso serviço. A interface EstoqueWS está
pronta pra receber o stub que vai acessar verdadeiramente o serviço.

Para criar o stub, temos a classe EstoqueWSService . Ela serve como "Fábrica" de stubs. Um Port
nada mais é do que um sinônimo para stub:
EstoqueWS ws = new EstoqueWSService().getEstoqueWSPort();

Com a variável ws podemos executar o serviço, ou seja, fazer a chamada remota.

3.29 EXERCÍCIOS: USANDO O WEB SERVICE NA LIVRARIA


1. Na nossa livraria vamos trocar a chamada remota RMI pela chamada do web service SOAP. Além
disso, faremos apenas uma chamada remota ao mostrar o carrinho que carrega todas as informações.

A partir do WSDL publicado gere as classes clientes do serviço EstoqueWS . Para tal:

Abra o terminal ( ctrl + alt + t )

Entre no diretório do projeto workspace/fj36-livraria

Digite:

wsimport -s src -p br.com.caelum.estoque.soap


http://localhost:8080/fj36-webservice/EstoqueWS?wsdl

No Eclipse, atualize o projeto fj36-livraria (aperte F5).

Verifique se no pacote br.com.caelum.estoque.soap foram criadas as classes para consumir o


web service.

EstoqueWS.java

3.29 EXERCÍCIOS: USANDO O WEB SERVICE NA LIVRARIA 77


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
EstoqueWSService.java
ItemEstoque.java
ItensPeloCodigo.java
ItensPeloCodigoResponse.java
ObjectFactory.java
package-info.java

2. IMPORTANTE: abra a classe Carrinho, pacote br.com.caelum.livraria.modelo e comente


todo o método verificarDisponibilidadeDosItensComRmi().

Arrume os imports (ctrl + shift + o). Não continue sem os imports organizados.

3. Ainda na classe Carrinho crie um método novo que encapsula toda a chamada SOAP. Nesse método
vamos usar as classes geradas pelo wsimport :

public void verificarDisponibilidadeDosItensComSoap() {

EstoqueWS estoqueWS = new EstoqueWSService().getEstoqueWSPort();


List<String> codigos = this.getCodigosDosItensImpressos();

ItensPeloCodigo parameter = new ItensPeloCodigo();


parameter.getCodigo().addAll(codigos);

ItensPeloCodigoResponse resposta =
estoqueWS.itensPeloCodigo(parameter, "TOKEN123");
List<ItemEstoque> itensNoEstoque = resposta.getItemEstoque();

for (final ItemEstoque itemEstoque : itensNoEstoque) {


atualizarQuantidadeDisponivelDoItemCompra(itemEstoque);
}
}

Atenção: A classe ItemEstoque agora vem do pacote br.com.caelum.estoque.soap.* , não


mais de rmi .

O método atualizarQuantidadeDisponivelDoItemCompra(..) está comentado na classe Carrinho.


Descomente-o e organize os imports.

Atenção: A classe Predicate é do pacote com.google.common.base.Predicate .

O método apenas associa a quantidade do livro no estoque com o ItemCompra do carrinho.

4. Por último, abra a classe CarrinhoController, no pacote br.com.caelum.livraria.controller e


procure o método listar().

Comente a linha que faz a verificação com RMI e adicione a chamada com SOAP, usando o método
criado no Carrinho :

this.carrinho.verificarDisponibilidadeDosItensComSoap();

5. Se tudo estiver salvo e compilando, reinicie o servidor Tomcat. Fique atento ao console.

78 3.29 EXERCÍCIOS: USANDO O WEB SERVICE NA LIVRARIA


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Acesso: http://localhost:8088/fj36-livraria

Adicione algum livro no carrinho e verifique o estoque.

3.30 ABORDAGEM: CONTRACT FIRST - WEB SERVICE JAVA A PARTIR


DE UM WSDL
Nos exercícios anteriores, geramos um cliente para nosso serviço a partir de um WSDL.

No entanto, o comando wsimport não apenas cria o cliente de um Web Service como também gera
a estrutura necessária para criar a implementação de um serviço, respeitando o WSDL importado.

Para usar o Contract first com a ferramenta wsimport é preciso definir o local do WSDL e onde
gerar as classes Java:
wsimport -s src EstoqueWS.wsdl

A interface é gerada a partir do contrato:


@WebService(name = "EstoqueWS",
targetNamespace = "http://caelum.com.br/estoquews/v1")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@XmlSeeAlso({
ObjectFactory.class

3.30 ABORDAGEM: CONTRACT FIRST - WEB SERVICE JAVA A PARTIR DE UM WSDL 79


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
})
public interface EstoqueWS {

/**
* @param tokenUsuario
* @param parameters
* @return
* returns br.com.caelum.estoquews.v2.ItensPeloCodigoResponse
*/
@WebMethod(operationName = "ItensPeloCodigo")
@WebResult(name = "ItensPeloCodigoResponse",
targetNamespace = "http://caelum.com.br/estoquews/v1",
partName = "parameters")
public ItensPeloCodigoResponse itensPeloCodigo(
@WebParam(name = "ItensPeloCodigo",
targetNamespace = "http://caelum.com.br/estoquews/v1",
partName = "parameters")
ItensPeloCodigo parameters,
@WebParam(name = "tokenUsuario",
targetNamespace = "http://caelum.com.br/estoquews/v1",
header = true,
partName = "tokenUsuario")
String tokenUsuario);

Repare que a interface EstoqueWS possui as mesmas anotações do exercício anterior. A


implementação deste serviço agora é feita de maneira natural, implementando uma interface.

Depois de chamar o wsimport para gerar as classes, devemos criar uma classe para o serviço,
fazendo-a implementar a interface gerada. Depois disso, utilizaremos a anotação @WebService com o
atributo endpointInterface para especificar que essa é a implementação daquele outro serviço.

3.31 CONTRACT FIRST VS CONTRACT LAST


Produzir um serviço a partir de um WSDL pode ser usado quando temos um serviço já exposto e
queremos migrá-lo para outra linguagem/plataforma por exemplo ou quando você tiver escrito o WSDL
do zero, sem ser a partir de uma classe.

Uma vantagem do Contract first é que equipes diferentes podem começar a trabalhar ao mesmo
tempo, uma trabalhando na implementação do servidor, e outra no cliente.

Outra vantagem é uma definição mais clara do serviço. Ou seja, como não estamos escrevendo
alguma implementação, o foco é o contrato e seus detalhes. Tópicos como versionamento do serviço,
granularidade e os tipos expostos são discutidos muito antes da implementação.

Por último, teremos menos problemas com o mapeamento dos tipos XML para Java e vice-versa.
Como definimos o XSD (definição dos tipos no mundo XML) antes da implementação concreta, não
ficamos acoplados no mapeamento das bibliotecas como JAX-WS, diminuindo os problemas de
incompatiblidade.

80 3.31 CONTRACT FIRST VS CONTRACT LAST


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
A longo prazo o nosso serviço fica menos frágil e mais fácil de evoluir. A API exposta é sempre o
WSDL e o XSD. Ambos os documentos devem ser legíveis e bem escritos. A implementação é apenas um
detalhe.

Como desvantagem podemos notar que é preciso conhecer bem as especificações relacionadas aos
Web Services. Com Contract first não basta conhecer algumas anotações e gerar as classes. Contract last é
muito mais simples pois abstrai todo o mundo SOAP/WSDL.

3.32 UM POUCO DO MODELO CANÔNICO


O Canonical Data Model é um dos padrões mais importantes no SOA. Já falamos que a geração de
documentos e esquemas, de maneira automatizada, gera problemas de compatbilidade e complica a
evolução. O correto é focar na interação dos serviços, ou seja, na definição da interface e no modelo de
dados.

O Modelo canônico diz que devemos padronizar os dados que são compartilhados entre aplicações.
Com isso separamos a responsabilidade de apresentar os dados para a troca de informações das
aplicações. Se alguma aplicação precisa se integrar com outra, ela deve utilizar uma definição já existente
(canonical schema), ou, se não há definição, pensar no modelo canônico antes - sempre focando no
reuso dos modelos existentes. Vários serviços aproveitam assim o mesmo modelo.

As aplicações automaticamente vão precisar escrever menos código de mapeamento e


transformação. O reaproveitamento será maior, o design simplificado e o tempo de desenvolvimento
diminuirá. A padronização se espalha pela empresa.

Por exemplo, a nossa loja vai gerar um pedido. Esse pedido será enviado para outros sistemas
(pagamentos, financeiro, estoque etc). É algo compartilhado. Faz todo sentido padronizar a apresentação
do pedido fora da aplicação.

Ou seja, depois ter criado a classe Pedido baseado em boas práticas do mundo OO (DDD),
podemos padronizar e definir o modelo canônico (XSD mas sem geração) e só depois estabelecer o
contrato (WSDL, contract first).

O mapeamento XSD para classe só vem depois e pode ser reutilizado pois é baseado no modelo
compartilhado.

3.32 UM POUCO DO MODELO CANÔNICO 81


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
ENTERPRISE INTEGRATION PATTERN - CANONICAL DATA MODEL

Design a Canonical Data Model that is independent from any specific application. Require each
application to produce and consume messages in this common format.

http://www.eaipatterns.com/CanonicalDataModel.html

3.33 EXERCÍCIOS: CONSUMINDO O WEB SERVICE DOS CORREIOS


1. Os Correios disponibilizam um serviço para cálculo de valores de frete no Brasil, que está
documentado em: http://www.correios.com.br/webservices/ .

Acesse esse endereço e clique no link para fazer o download da especificação do serviço.

2. Na especificação, temos o endereço do WSDL


serviço, que é:do
http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx?WSDL . Acesse essa URL
também em seu navegador e veja o conteúdo do WSDL.

3. Para consumirmos esse novo serviço, vá ao Terminal e entre no diretório raiz do projeto fj36-
livraria , nesse diretório realize o wsimport para gerarmos o Stub e as classes necessárias para
acessarmos o serviço dos correios:
wsimport -s src/ -d build/ -p br.com.caelum.correios.soap
http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx?WSDL

Vá ao seu projeto no Eclipse, dê um F5 e veja as novas classes geradas pelo wsimport . Todas,
menos ConsumidorServicoCorreios foram geradas.

4. No projeto já tem uma classe preparada para encapsular a chamada do Web Service. Abra a classe
ConsumidorServicoCorreios e procure o método calculaFrete . Nele adicione:

CalcPrecoPrazoWSSoap servico =
new CalcPrecoPrazoWS().getCalcPrecoPrazoWSSoap();

5. Nessa variável servico , possuímos o método calcPrecoPrazo , que nos retorna um objeto
contendo os dados referentes à nossa pesquisa, como o valor, prazo de entrega e assim por diante.
No entanto, esse método recebe 15 parâmetros, o que fará com que a chamada ao método fique com
um código extenso. Para simplificar, já existem alguns atributos na classe.

Adicione abaixo da variável servico :


CResultado resultado = servico.calcPrecoPrazo(
semCodigoEmpresa, semSenhaEmpresa,
codigoSedex, cepOrigemCaelumSP, cepDestino,
peso3kg, formatoEncomendaCaixa,
comprimento20cm, altura10cm, largura15cm, diametro10cm,

82 3.33 EXERCÍCIOS: CONSUMINDO O WEB SERVICE DOS CORREIOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
semEntregueEmMaos, semValorDeclarado, semAvisoRecebimento);

List<CServico> servicosPesquisados = resultado.getServicos().getCServico();


valorFrete = servicosPesquisados.get(0).getValor();
System.out.printf("Frete para %s eh de %s %n", cepDestino, valorFrete);

Todos esses parâmetros e a forma do retorno estão especificados na documentação, acessada


anteriormente no site dos Correios.

6. Abra a classe Carrinho e procure o método atualizarFrete(..) (use crtl + o para achar o
método). Nesse método vamos usar a classe ConsumidorServicoCorreios :
ConsumidorServicoCorreios servicoCorreios = new ConsumidorServicoCorreios();
this.valorFrete = servicoCorreios.calculaFrete(novoCepDestino);

7. Reinicie o Tomcat e acesse a loja:

http://localhost:8088/fj36-livraria

Escolha um livro impresso e calcule o frete pelo formulário!

8. (conceitual) O serviço não nos oferece uma forma de pesquisarmos os valores para diferentes
combinações de CEPs de origem e destino, mas e se precisássemos analisar os valores para 15
diferentes combinações de CEPs, teríamos que invocar várias vezes o serviço?

Se quiser, envolva a chamada ao método calcPrecoPrazo em um for que execute 15 vezes.


Repare que o tempo de execução pode se tornar inaceitável para uma aplicação.

Isso acontece, pois o serviço disponibilizado possui uma granularidade muito fina. Para resolver essa
questão, o serviço poderia receber um objeto contendo vários pares de CEPs de origem e destino,
com isso, só chamaríamos o serviço uma única vez, e ele poderia nos devolver todas essas
informações de uma única vez. Dessa forma, reduziríamos de 15 para apenas 1 chamada ao serviço.

Ao criar um serviço, é importantíssimo pensar em sua granularidade, evitando onerar os clientes


e o próprio servidor com diversas chamadas ao serviço.

9. (conceitual) Abra o WSDL dos correios:

http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx?WSDL

Além do problema de granularidade, qual é o target namespace? Como foi definido o


versionamento? O serviço do correios é um exemplo de contract first ou contract last?

3.33 EXERCÍCIOS: CONSUMINDO O WEB SERVICE DOS CORREIOS 83


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 4

WEB SERVICES RESTFUL COM JAX-RS

"REST isn't some obscure thing that nobody supports; it's the way the Web already works, just formalized a
bit and with some do's and don'ts." -- John Cowan

Nesse capítulo, você entenderá como criar Web Services RESTful baseados na especificação JAX-RS
do Java EE. Você aprenderá os principios e vantagens de serviços REST comparado com serviços SOAP.

4.1 UMA ALTERNATIVA - O ESTILO ARQUITETURAL REST


Ao trabalhar com Web Services, percebemos que temos diversos padrões em cima do protocolo
HTTP, como XML, SOAP e WSDL. Todos esses padrões visam facilitar e, obviamente, padronizar o
trabalho com Web Services. Mas essa quantidade de padrões muitas vezes dificulta a compatibilidade
entre os serviços e o entendimento do negócio.

Imagine um Web Service que, no final das contas, precise devolver uma ou duas informações dentro
de um XML. Temos que escrever pelo menos três vezes mais XMLs para que o consumidor deste Web
Service possa lê-lo automaticamente.

Um caso mais comum é que, mesmo que a resposta do Web Service seja gigante e em relação a ele o
XML de configuração seja curto, a complexidade do Web Service obriga o fornecedor do Web Service a
oferecer muita documentação extra para usar o serviço de forma eficiente.

A Web é o maior sistema distribuído que foi criado. Ela escalou para nível global e é onipresente no
dia de qualquer um. Por meio dela podemos integrar sistemas novos sem quebrar os antigos. Mas por
quê a Web conseguiu crescer tanto enquanto outros protocolos e ideias falharam?

Por trás da Web há o protocolo HTTP que segue o estilo arquitetural REST. A ideia básica é que
existe um conjunto fixo de operações permitidas (verbs/verbos) e as diversas aplicações se comunicam
aplicando este conjunto fixo de operações em recursos (nouns/resource) existentes, podendo ainda pedir
por um recurso, recebendo, na verdade, uma representação dele em um determinado formato.

A sigla REST vêm de Representational State Transfer e surgiu da tese de doutorado de Roy Fielding,
descrevendo as ideias que levaram à criação do protocolo HTTP. A Web é o maior exemplo de uso de
uma arquitetura REST, onde os verbos são as operações disponíveis no protocolo (GET, POST,
DELETE, PUT, OPTION...), os recursos são identificados pelas URLs e as representações podem ser
definidas com o uso de Mime Types(texto, XML, JSON e outros).

84 4 WEB SERVICES RESTFUL COM JAX-RS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4.2 ORIENTADO AO RECURSO
REST não é uma especificação nem uma tecnologia, é um modelo arquitetural. Neste modelo, o
pensamento da aplicação gira em torno dos recursos. Depois de definir os recursos, usamos os verbos
disponíveis (no HTTP temos o GET, POST, PUT e outros) para manipular estes recursos.

Uma das ideias da arquitetura REST é aproveitar ao máximo o protocolo de comunicação (HTTP)
usando-o direito. Nesse escopo, um dos princípios que o REST prega é a utilização das URIs de acordo
com sua denominação, URI é a sigla para Uniform Resource Identifier (Identificador Uniforme de
Recurso). Pensando voltado a recursos, podemos imaginar que cada recurso tem um identificador único
perante os usúarios. Casando com a ideia de manipular os recursos usando os verbos do protocolo, se
cada recurso tem um único endereço, basta fazer uma requisição do tipo POST para criar um novo
recurso ou fazer uma requisição GET para buscar o recurso. Tudo gira em torno do recurso.

Pensando no problema de reservar uma passagem aérea:

Criaremos uma ação que receberá os parâmetros do voo, do passageiro e outras informações.
Criamos um endereço para o recurso chamado reservas e, se quisermos fazer uma reserva, faremos uma
requisição do tipo POST (para a criação) e informaremos os dados necessários nessa requisição.

Perceba que não usaremos tecnologias nem linguagem diferentes, apenas vamos pensar de forma
diferente, agora nosso foco não é mais nas ações e sim nos recursos do sistema.

4.3 DIFERENTES REPRESENTAÇÕES


Os recursos em geral estarão "guardados" ou gerados no servidor, então o cliente da aplicação não
pode simplesmente pegá-lo, no máximo ele pode vizualizá-lo. Porém, essa visualização pode ocorrer de
várias maneiras, por meio de uma página HTML, uma interface desktop, mobile e etc.

Parte da ideia REST foca que em cada recurso pode ter suas representações. Cada representação pode
ter seu formato específico, por exemplo, via XML, HTML, JSON ou outra.

Três exemplos de representação de um livro


XML

<livro>
<nome>RESTful Web Services</nome>
<preco>60.0</autor>
</livro>

JSON
{"livro": {
"nome":"RESTful Web Services"
"preco":"60.0"
}

4.2 ORIENTADO AO RECURSO 85


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
}

Texto Puro
Nome: RESTful Web Services, Preço:60.0

Perceba que o recurso propriamente dito continua sendo um livro, porém com diferentes
representações.

4.4 MÉTODOS HTTP


Ao desenhar aplicações REST, pensamos nos recursos a serem disponibilizados pela aplicação e em
seus formatos, em vez de pensar nas operações.

As operações disponíveis para cada um dos recursos no protocolo HTTP são:

GET: retorna uma representação do recurso


POST: cria ou altera o recurso
PUT: cria ou altera o recurso
DELETE: remove o recurso
outras menos comuns, como HEAD e OPTIONS

Os quatro principais verbos do protocolo HTTP são comumente associados às operações de CRUD
em sistemas Restful (POST -> INSERT, GET -> SELECT, PUT -> UPDATE, DELETE -> DELETE). Há
uma grande discussão dos motivos pelos quais usamos POST para criação (INSERT) e PUT para
alteração (UPDATE). A razão principal é que o protocolo HTTP especifica que a operação PUT deve ser
idempotente, já POST não.

IDEMPOTÊNCIA E SAFE

Operações idempotentes são operações que podem ser chamadas uma ou mais vezes, sempre
com o mesmo resultado final.

Uma operação é chamada SAFE se ela não altera nenhuma representação.

Idempotência e SAFE são propriedades das operações e fundamentais para a escalabilidade da


Web.

4.5 HIPERMÍDIA
Os recursos serão apresentados por meio de representações. Seguindo os princípios RESTful,
representações devem ser interligadas umas com a outras. Isso é chamado hipermídia e conhecido na

86 4.4 MÉTODOS HTTP


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Web através de hyperlinks. No nosso exemplo, a representações de um livro poderia conter a URI dos
autores. Como resultado disso, é possível navegar entre os recursos.

Mais sobre hipermídia no blog da Caelum:

http://blog.caelum.com.br/hipermidia-e-contratos-dinamicos-menor-acoplamento/

RESTFUL

Qualquer sistema que aplique as ideias do estilo arquitetural REST, pode ser chamado de
RESTful. Existe uma intensa discussão na comunidade sobre quando um sistema pode ser
considerado RESTful ou não, porém, na maioria dos casos, basta apenas implantar uma parte do
REST (em especial pensar em recursos, verbos fixos e ligações entre apresentações) para ser
chamado de RESTful.

4.6 VANTAGENS RESTFUL


Por quê (ou quando) usar uma arquitetura REST? A primeira coisa que deveríamos saber é quais são
as vantagens do REST: http://en.wikipedia.org/wiki/Representational_State_Transfer#Claimed_benefits

Protocolos menos complexos: Não precisamos de tantos protocolos para enviar e receber
informações, basta que as duas partes estejam de acordo com os recursos e representações
(formatos) disponíveis.
Mais poder e flexibilidade: Por não precisarmos nos preocupar com dezenas de protocolos,
temos maior liberdade na hora de devolver um recurso, por exemplo, se usarmos SOAP
precisamos necessariamente devolver um recurso no formato de XML, já com a ideia do
REST, podemos devolver um objeto JSON direto para uma página na Web.
Arquitetura amplamente disponível: Em linhas gerais, a arquitetura REST é mais simples, essa
simplicidade permite que sua adoção seja mais fácil, com uma curva de aprendizado menor.
Com isso é mais fácil disponibilizar seus serviços através do REST.
Menos overhead de protocolo: As requisições em uma arquitetura REST são menores, pois
trabalhamos com menos protocolos, por exemplo, não precisamos enviar todas informações
definidas no protocolo SOAP, basta padronizar com o cliente o XML que ele receberá para os
recursos.

4.7 WEB-SERVICES REST COM JAX-RS


Não é necessário mais do que uma Servlet para criar uma arquitetura REST, mas também não é
necessário mais do que uma servlet para criar a maioria das aplicações web, e mesmo assim usamos

4.6 VANTAGENS RESTFUL 87


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
algumas ferramentas e especificações que facilitam nosso trabalho.

Para trabalhar com a arquitetura REST foi criada uma especificação chamada JAX-RS (JSR-311).
Essa especificação garante diversas vantagens:

Reconhecimento do método HTTP invocado.


Automaticamente devolve um recurso em determinado formato.
Identificação do formato desejado pelo cliente.

Conversão automática de tipos:

XML

Objeto Java
JSON
Texto puro

Existem três implementações dessa especificação:

Jersey - https://jersey.dev.java.net/ - Implementação da própria Oracle, considera a RI da


especificação.
Resteasy - http://www.jboss.org/resteasy/ - Implementação do grupo JBoss.
CXF - http://cxf.apache.org/ - Implementação da Apache

REST SEM JAX-RS

JAX-RS não é a unica forma de criar serviços REST no mundo Java. Os controladores MVC
como Spring MVC e VRaptor também oferecem recursos para atender o REST. Ambos possuem
extenções como Spring Hateoas e VRaptor Restfulie que facilitem ainda mais o trabalho com REST
e o protocolo HTTP.

4.8 BOTANDO PRA RODAR


Como toda especificação, devemos escolher uma implementação, no nosso caso usaremos o
Resteasy da JBoss.

A ideia do JAX-RS é criar uma Servlet (Front Controller) que receba as requisições e defina qual
método será chamado de acordo com a URI, método HTTP escolhido e formato de resposta.

Então a primeira etapa é configurar essa Servlet com o caminho desejado no web.xml da aplicação.
Segue a configuração com o Jersey, que é apenas necessária quando se trata de um servlet container na
versão 2.5 ou mais antigo:

88 4.8 BOTANDO PRA RODAR


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<servlet>
<servlet-name>RestJersey</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>
br.com.caelum.loja.recursos
</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>RestJersey</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

Todos os recursos dentro do pacote br.com.caelum.loja.recursos serão carregados


automaticamente.

Com RestEasy temos uma configuração parecida, novamente só necessária para servlet containers
2.5 ou mais antigo:
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

A especificação JAX-RS oferece uma classe javax.ws.rs.core.Application , que podemos


estender para informar quais classes usam anotações. Nessa classe, podemos aumentar as
funcionalidades do JAX-RS e configurar, por exemplo, quais classes vão ler e procurar os métodos que
serão invocados, entre outras possibilidades. A classe será anotada com @ApplicationPath e
automaticamente o Front controller do JAX-RS é carregado, sem usar a declaração no web.xml :
@ApplicationPath("/")
public class LojaApplication extends Application {

4.9 USANDO RESOURCES COM JAX-RS


O JAX-RS, assim como a maioria das recentes especificações, é baseado em anotações.

A primeira anotação importante do JAX-RS é a @PATH , nela definimos qual o caminho que será
reconhecido pela classe anotada.
@Path("/loja")
public class LojaResource {
//...

4.9 USANDO RESOURCES COM JAX-RS 89


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
}

Neste caso, qualquer requisição que comece com /loja , será a classe LojaResource que cuidará.

Ainda não definimos nenhum recurso para nenhum método desta classe. Para isso, criamos um
método que retorna algum objeto Java, por exemplo Livro e nele anotamos a qual método HTTP ele
responderá.
@Path("/loja")
public class LojaResource {

@GET
public List<Livro> getLivros() {
//código omitido
return livros;
}
}

Para definir o restante da URI deste recurso, basta usarmos a mesma anotação @Path , agora para o
método.

@GET
@Path("/livros")
public List<Livro> getLivros() {
//código omitido
return livros;
}

Finalmente, temos que definir qual será o tipo de recurso devolvido, no geral temos que definir o
content-type da resposta HTTP. A anotação usada aqui é a @Produces , passando como parâmetro
qual o mime-type do retorno.
@GET
@Path("/livros")
@Produces("text/plain")
public List<Livro> getLivrosTexto() {
return getLista();
}

4.10 OUTROS TIPOS DE RETORNO


Com a anotação @Produces , podemos definir outros tipos de retorno, como por exemplo, XML
(application/xml), JSON (application/json) ou texto puro (text/plain). A princípio, teríamos que devolver
uma String já com o resultado final, ou seja o XML ou o JSON final. Porém, uma outra especificação nos
ajuda a fazer a conversão do objeto para XML/JSON automaticamente, o JAX-B.

A entidade que deve ser serializada para XML deve estar devidamente anotada com o JAX-B, que
veremos mais na frente com mais detalhes.

@XmlRootElement
public class Livro{

//....
}

90 4.10 OUTROS TIPOS DE RETORNO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Agora com as classes anotadas podemos retornar esses objetos em diferentes formatos:

@Path("/loja")
public class LojaResource {

@GET
@Path("/livros")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Livro> getLivrosXmlOuJson() {
return //busca livros em algum lugar;
}

Veja também o artigo sobre JAX-RS no blog da Caelum:

http://blog.caelum.com.br/2009/12/15/arquitetura-rest-com-java-jax-rs/

4.11 BUSCANDO UM RECURSO ESPECÍFICO


A API do JAX-RS ainda permite usarmos URI dinâmicas, por exemplo, podemos extrair o nome ou
ID de um livro através da URI. Na anotação @Path podemos definir uma variável (URI Template), que
será recebida como parâmetro no método. O nome dessa variável deve estar ligado, por meio da
anotação @PathParam , ao parâmetro do método invocado:

@GET
@Path("/livros/{id}")
@Produces(MediaType.APPLICATION_XML)
public Livro getLivro(@PathParam("id") Long id) {
//código omitido
}

Acessando o link /loja/livros/3 teremos os dados do livro com a ID 3 no formato XML.

O resultado da requisição pode ser mais flexível ainda, o cliente pode escolher pelo cabeçalho da
requisição o formato que ele deseja. Por exemplo, o cabeçalho accept:application/json retornaria o
livro no formato JSON.
@GET
@Path("/livros/{id}")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Livro getLivro(@PathParam("id") Long id) {
//código omitido
}

Também podemos ler um parâmetro da requisição, para isso existe a anotação @QueryParam :
URI: /loja/livros?autor=Joao

@GET
@Path("/livros")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Livro getLivroDoAutor(@QueryParam("autor") String autor) {
//código omitido
}

4.11 BUSCANDO UM RECURSO ESPECÍFICO 91


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4.12 USANDO O JAX-RS COM EJB3
Como usaremos o JAX-RS dentro do servidor de aplicação Wildfly, podemos aproveitar o melhor do
container EJB e deixar o gerenciamento para ele. Assim o container EJB administra todo o ciclo da vida e
fornece mais serviços como transação, persistência com JPA e entre vários outros.

Para usar EJB 3 no recurso JAX-RS basta usar a anotação @Singleton na classe. Assim o recurso se
torna um EJB Session Bean Singleton:
@Path("/loja")
@Singleton
public class LojaResource {
...

4.13 EXERCÍCIOS: JAX-RS COM RESTEASY


Nesse exercício vamos criar um recurso com JAX-RS para receber e gerenciar pagamentos online. O
nosso serviço terá o nome fantasia Payfast.

1. Para começar a usar o JAX-RS, preparamos algumas classes que servem como modelo da aplicação.
Usaremos essas classes nos exercícios seguintes.

No Eclipse, selecione o projeto fj36-webservice e vá no menu File -> Import

Dentro da janela de Import, escolha General -> Archive File e clique em Next:

No campo From archive file clique em Browse, selecione na pasta Desktop/caelum/36/ o arquivo
fj36-webservice.zip e clique em Finish

Foram importadas três classes: Transacao , Pagamento e Link , dentro do pacote


br.com.caelum.payfast.modelo .

2. Ainda no projeto fj36-webservice, crie uma nova classe PagamentoService , dentro do pacote
br.com.caelum.payfast.rest , e extenda javax.ws.rs.core.Application :

@ApplicationPath("/")
public class PagamentoService extends Application {

Através dessa classe configuraremos a Path (caminho raíz) da aplicação. Ela é necessária para
inicializar o JAX-RS.

3. Vamos definir um recurso com JAX-RS. Esse resource será responsável pelo gerenciamento de
pagamentos.

No mesmo pacote br.com.caelum.payfast.rest crie mais uma classe, chamando-a de


PagamentoResource :

92 4.12 USANDO O JAX-RS COM EJB3


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@Path("/pagamentos")
@Singleton
public class PagamentoResource {

Mapeamos essa classe para receber requisições com o caminho /pagamentos .

Além disso, trata-se de um EJB Singleton.

EJB - ENTERPRISE JAVA BEAN

Os EJBs representam uma especificação central dentro do JavaEE e oferecem uma série de
serviços, como transação, persistência com JPA, injeção de dependências, agendamentos e
muito mais.

Esses serviços são vistos no curso Persistência com JPA, Hibernate e EJB lite que faz parte
da Formação JavaEE e da Jornada Backend:

https://www.caelum.com.br/jornada-back-end/

4. Na classe PagamentoResource , adicione dois atributos para simular um repositório.

private Map<Integer,Pagamento> repositorio = new HashMap<>();


private Integer idPagamento = 1;

Em um projeto real, o repositório seria o banco de dados.

No construtor, vamos "popular" um pagamento no repositório:


public PagamentoResource() {
Pagamento pagamento = new Pagamento();
pagamento.setId(idPagamento++);
pagamento.setValor(BigDecimal.TEN);
pagamento.comStatusCriado();
repositorio.put(pagamento.getId(), pagamento);
}

5. Com um pagamento no repositório, podemos criar o primeiro serviço REST.

Adicione um método buscaPagamento(..), que atenda ao método HTTP GET pelo caminho /id e
devolve (produces) um XML:
@GET
@Path("/{id}")
@Produces({MediaType.APPLICATION_XML}) //cuidado javax.ws.rs
public Pagamento buscaPagamento(@PathParam("id") Integer id) {
return repositorio.get(id);
}

Atenção: Todos os imports são do pacote javax.ws.rs .

4.13 EXERCÍCIOS: JAX-RS COM RESTEASY 93


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Repare que devolvemos um Pagamento , o JAX-RS deve gerar o XML.

6. Reinicie o Wildfly e acesse pelo navegador a URI:

http://localhost:8080/fj36-webservice/pagamentos/1

Deve aparecer o XML gerado:


<pagamento>
<id>1</id>
<valor>10</valor>
</pagamento>

7. Tente gerar um JSON. Na classe PagamentoResource , no método buscaPagamento substitua:

@Produces({MediaType.APPLICATION_XML})

por
@Produces({MediaType.APPLICATION_JSON})

Reinicie o Wildfly e acesse novamente a URI:

http://localhost:8080/fj36-webservice/pagamentos/1

8. JAX-RS permite que o serviço devolva XML ou JSON . Para tal, altere a anotação @Produces e
coloque ambos:

@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})

No entanto, como o servidor decide qual formato deve devolver? A solução está no protocolo HTTP
que possui um cabeçalho Accept . Nele o cliente especifica qual content-type deseja receber.

Vamos testar o content-type application/json no terminal, através comando curl. Com curl,
podemos manipular a requisição HTTP na linha de comando. Abra um terminal e digite (tudo em
uma linha):
curl -i -H "Accept: application/json"
http://localhost:8080/fj36-webservice/pagamentos/1

No terminal, deve aparecer a resposta HTTP com os dados do pagamento em formato JSON:

HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: application/json

{"id":1,"status":null,"valor":10,"links":[]}

Observações do comando curl :

i – para mostrar os cabeçalhos da resposta


H – para definir o cabeçalho da requisição

94 4.13 EXERCÍCIOS: JAX-RS COM RESTEASY


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
9. Envie pelo curl uma requisição HTTP que pede application/xml como formato.
10. (Opcional) Envie uma requisição HTTP com uma id de pagamento que não existe.

4.14 INSERINDO DADOS


A API de JAX-RS também permite que os dados vindos do cliente sejam automaticamente
convertidos para o formato Java. Seguindo a regra, a classe do objeto que desejamos receber já deve estar
anotada pelo JAXB.

Outra vantagem é que podemos enviar esses dados em XML ou JSON, que ele também converterá,
apenas devemos avisar ao método que ele consome um formato específico.

@Path("/pagamentos")
public class PagamentoResource {

//.....

@POST
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response criarPagamento(Transacao transacao) {
//....
}
}

Se fizermos uma requisição POST com o content-type application/xml ou application/json, será


invocado o método criarPagamento e os dados serão automaticamente convertidos.

Observe um exemplo usando a biblioteca commons HTTPClient:


HttpClient httpClient = new DefaultHttpClient();

HttpPost post = new HttpPost("http://localhost:8080/fj36-webservice/pagamentos");


post.addHeader("content-type", "application/xml"); //application/json

StringEntity xml = new StringEntity(


"<transacao>"+
"<titular>Jim Webber</titular>"+
"<valor>66.9</valor>"+
"</transacao>");
post.setEntity(xml);

HttpResponse response = httpClient.execute(post);

System.out.println(response.getStatusLine());

Imprime o possível resultado:


HTTP/1.1 201 Created

4.15 EXERCÍCIOS: CRIAÇÃO DE RESOURCES DE MANEIRA RESTFUL


O serviço de pagamentos deve receber uma transação financeira para criar um pagamento a partir de
seus dados.

4.14 INSERINDO DADOS 95


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Queremos integrar o maior número de clientes possíveis, incluindo web sites que usam JavaScript.
Por isso vamos utilizar JSON como formato.

Já vimos como usar o método HTTP GET. O GET, sendo SAFE, deve ser utilizado para acessar dados
e jamais para alguma operação que altere o estado no servidor.

Esse papel cabe ao POST, que é muito usado como factory resource, ou seja, serve para a criação de
novos recursos no lado servidor. Então, para criar um novo pagamento, utilizaremos HTTP POST.

1. No projeto fj36-webservice, abra a classe PagamentoResource e adicione um novo método


criarPagamento(..) , que recebe (consumes) uma Transacao :

@POST
@Consumes({MediaType.APPLICATION_JSON})
public Response criarPagamento(Transacao transacao) {

A classe Response é do JAX-RS e representa um Builder para definir a resposta HTTP que
usaremos mais para frente.

2. No método criarPagamento(..) , instancie um Pagamento com o valor da transação,


adicionando-o no repositório:
Pagamento pagamento = new Pagamento();
pagamento.setId(idPagamento++);
pagamento.setValor(transacao.getValor());

repositorio.put(pagamento.getId(), pagamento);

System.out.println("PAGAMENTO CRIADO " + pagamento);

3. No fim do método, crie a resposta. Dessa vez utilize a classe Response para definir o formato e a
URI do novo recurso.
return Response.created(new URI("/pagamentos/" + pagamento.getId()))
.entity(pagamento)
.type(MediaType.APPLICATION_JSON_TYPE)
.build();

Atenção: A classe URI é do pacote java.net.* .

Repare que a classe Response possui uma interface fluente para criar a resposta HTTP, seguindo o
padrão de projeto Builder.

4. A classe URI exige um tratamento de erro. Jogue a exceção para frente, coloque-a na assinatura do
método criarPagamento(..) (faça Add throws declaration e não faça try-catch ).

A assinatura do método ficará assim:


public Response criarPagamento(Transacao transacao)
throws URISyntaxException {

96 4.15 EXERCÍCIOS: CRIAÇÃO DE RESOURCES DE MANEIRA RESTFUL


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Arrume os imports e verifique se tudo está salvo e compilando! Para publicar as mudanças reinicie o
Wildfly.

5. Para testar o novo método, use novamente o comando curl. Dessa vez envie os dados da transação
como JSON dentro da requisição do tipo POST. No terminal digite (tudo em uma linha só):

curl -i -H "Content-type: application/json"


-X POST -d '{"valor":"39.9","titular":"Fulano"}'
http://localhost:8080/fj36-webservice/pagamentos

A resposta deve ser algo parecido como a abaixo:


HTTP/1.1 201 Created
Connection: keep-alive
Location: http://localhost:8080/fj36-webservice/pagamentos/2
Transfer-Encoding: chunked
Content-Type: application/json

{"id":2,"status":null,"valor":39.9,"links":[]}

Repare o cabeçalho Location , que aponta para o novo recurso. Copie a URI e acesse o recurso pelo
navegador (ou use curl).

Mais observações do comando curl :

X – para definir nome do método HTTP (GET, POST etc)


d – para passar o json/parâmetros com aspas simples para o cabeçalho da requisição

6. O que deve ser feito para aceitar JSON ou XML?

4.16 COREOGRAFIA DE SERVIÇOS UTILIZANDO HATEOAS


Um pagamento nasce a partir de uma transação com estado CRIADO e pode ser CONFIRMADO ou
CANCELADO pelo cliente. Confirmar representa um "próximo passo" na vida do pagamento e pode ou
não ser seguido pelo cliente. A ideia principal é que um recurso informe ao cliente quais os próximos
passos ou relacionamentos, e atrás de cada relacionamento há um serviço transformador de dados.

Uma vez criado um pagamento vamos então receber os dados dele E também os relacionamentos:
{"id":3,"status":"CRIADO","valor":29.9,
"links":[
{"rel":"confirmar","uri":"/pagamentos/3","method":"PUT"},
{"rel":"cancelar","uri":"/pagamentos/3","method":"DELETE"}
]
}

No entanto, um pagamento no estado CONFIRMADO, podemos pedir apenas informações sobre o


pagamento. Assim, a apresentação do recurso, além dos dados, também leva sempre as informações
sobre o que é permitido executar:

{"id":3,"status":"CONFIRMADO","valor":29.9,
"links":[

4.16 COREOGRAFIA DE SERVIÇOS UTILIZANDO HATEOAS 97


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
{"rel":"self","uri":"/pagamentos/3","method":"GET"}
]
}

Essa forma de juntar os dados às ações é conhecida como hypermedia e é a essência do HATEOAS -
Hypermedia as the Engine of Application State.

4.17 EXERCÍCIOS: APLICANDO HATEOAS


Na classe Pagamento , já existem métodos preparados que manipulam o estado do objeto e que
definem os próximos passos. Para representar um passo, já foi criado uma classe Link , que encapsula a
URI, método e o nome do relacionamento.

1. No projeto fj36-webservice, abra a classe PagamentoResource e vá até o método


criarPagamento(..) .

Nele, adicione a chamada do método que marca o pagamento como CRIADO:


...

@POST
@Consumes({MediaType.APPLICATION_JSON})
public Response criarPagamento(Transacao transacao)
throws URISyntaxException {
Pagamento pagamento = new Pagamento();
pagamento.setId(idPagamento++);
pagamento.setValor(transacao.getValor());

pagamento.comStatusCriado(); // NOVA LINHA AQUI


...

Reinicie o JBoss Wildfly.

2. Execute no terminal o comando curl, que monta um HTTP POST para o recurso pagamento,
enviando um JSON. ATENÇÃO: anote a ID do pagamento gerado, pois precisaremos dela mais
adiante:

DICA: você encontra todos os comandos curl usados no treinamento em


caelum/36/etc/exemplos-curl.txt. Basta copiar e colar no terminal quando necessário.

curl -i -H "Content-type: application/json"


-X POST -d '{"valor":"29.9","titular":"Fulano"}'

http://localhost:8080/fj36-webservice/pagamentos

Como resposta, recebemos os dados do pagamento e os links que definem os próximos passos.
Dependendo do seu teste, você pode receber uma ID diferente:
HTTP/1.1 201 Created
Connection: keep-alive
Location: http://localhost:8080/fj36-webservice/pagamento/3
Transfer-Encoding: chunked
Content-Type: application/json

98 4.17 EXERCÍCIOS: APLICANDO HATEOAS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
{"id":3,"status":"CRIADO","valor":29.9,
"links":[
{"rel":"confirmar","uri":"/pagamentos/3","method":"PUT"},
{"rel":"cancelar","uri":"/pagamentos/3","method":"DELETE"}
]
}

Ainda não definimos o método PUT para confirmar o pagamento, nem o DELETE.

3. Na classe PagamentoResource , crie um novo método para confirmar o pagamento. O método deve
responder ao verbo PUT e receber a id do pagamento pelo caminho:
@PUT
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON) //cuidado javax.ws.rs
public Pagamento confirmarPagamento(@PathParam("id") Integer pagamentoId) {
Pagamento pagamento = repositorio.get(pagamentoId);
pagamento.comStatusConfirmado();
System.out.println("Pagamento confirmado: " + pagamento);
return pagamento;
}

4. Teste no terminal. Passe a ID do pagamento criado com o POST anterior. No exemplo abaixo,
usaremos a ID 3:
curl -i -H "Content-type: application/json"
-X PUT http://localhost:8080/fj36-webservice/pagamentos/3

5. (opcional) Defina o método DELETE, que cancela o pagamento.

6. Desafio: Mais correto seria usar o método HTTP PATCH ou PUT para cancelar um pagamento, pois
não estamos removendo o recurso, apenas alterando. No entanto, não existe uma anotação @PATCH
do JAX-RS.

O desafio é criar uma nova anotação @PATCH . Na definição dessa anotação ( @interface ) use a
anotação @HttpMethod("PATCH") .

Teste a nova anotação no método cancelarPagamento(..) .

4.18 HYPERMIDIA COM SPRING HATEOAS


No exemplo anterior, usamos uma solução caseira para realizarmos coreografia colocando os
próximos passos no recurso. O que podemos fazer é usar alguma das soluções do mercado como o
Restfulie. Restfulie é um plugin do VRaptor que facilita a definição dos relacionamentos de um recurso.

Nesta seção, veremos como usar o Spring Hateoas no exemplo do pagamento. Em um @Controller
tradicional do SpringMVC o que faremos é usar a anotação RequestMapping para definir o caminho,
método HTTP e media types, por exemplo:
@Controller
public class PagamentoController {

4.18 HYPERMIDIA COM SPRING HATEOAS 99


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@RequestMapping(value="/pagamentos", method=RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,
produces=MediaType.APPLICATION_JSON_VALUE)
public void criarPagamento(@RequestBody Pagamento pagamento) {
//codigo omitido
}
}

Para usar Spring Hateoas a primeira tarefa é usar a anotação @RestController :

@RestController
public class PagamentoController {

Uma vez definido como RestController podemos devolver um pagamento envolvendo-o usando a
classe Resource. Ela permitirá adicionarmos os próximos passos (links) na entidade.

Queremos que ao ser criado um novo pagamento, recebemos o recurso com o link de outra action
do nosso Controller. Como por exemplo, a do método cancelarPagamento.

@RestController
public class PagamentoController {

@RequestMapping(value="/pagamentos", method=RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,
produces=MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<Resource<Pagamento>> criarPagamento(
@RequestBody Pagamento pagamento) {

System.out.println("Pagamento "
+ pagamento.getId()
+ " criado com sucesso!");

Resource<Pagamento> resource = new Resource<>(pagamento);

resource.add(
linkTo(
methodOn(PagamentoController.class)
.cancelarPagamento(pagamento.getId()))
.withRel("cancelar"));

return new ResponseHttpEntity<Resource<Pagamento>>(resource,


HttpStatus.CREATED);
}

@RequestMapping(value="/pagamentos/{id}", method=RequestMethod.POST)
public HttpEntity<Pagamento> cancelarPagamento(@PathVariable int id) {
// código omitido
}
}

O que estamos fazendo é adicionando links aos recursos baseando-se no caminho definido em
@RequestMapping de cada método.

Ao enviarmos um pagamento em JSON para a URI /pagamentos :


{"total":20.0}

Receberemos como resposta algo como:

100 4.18 HYPERMIDIA COM SPRING HATEOAS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
{"total":20.0,"id":0,"_links":{
"cancelar":{"href":"http://localhost:8080/pagamentos/3"}
}
}

4.19 CONTROLANDO O XML GERADO PELO JAX-RS


Vimos que o JSON devolvido pelo JAX-RS contém a lista de links que pode ser utilizado pelos
usuários do serviço para decidir quais são as interações possíveis com o recurso, porém se mandarmos o
cabeçalho Accept: application/xml , o servidor nos devolverá um pagamento no formato xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pagamento>
<id>2</id>
<status>CRIADO</status>
<valor>39.9</valor>
</pagamento>

Como podemos ver, o XML gerado não possui a lista de links que podem ser utilizados para operar
nos recursos do servidor. Para mudarmos esse comportamento, precisamos configurar a biblioteca que é
utilizada pelo JAX-RS para gerar o XML dos objetos que são devolvidos pelo serviço, o JAX-B.

No JAX-B, por padrão, apenas atributos que possuem tanto getters quanto setters são mapeados para
o XML e, por isso, como a classe Pagamento não possui um setter para a lista de links, essa propriedade
não é colocada no xml gerado. Para mudar esse comportamento, precisamos configurar o JAX-B através
de anotações.

A anotação que informa o JAX-B que ele deve mapear atributos ao invés de getters e setters no
formulário gerado é a @XmlAccessorType :

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pagamento {

Depois dessa configuração, se testarmos novamente o serviço, veremos que o xml gerado contém a
lista de links do pagamento:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pagamento>
<id>2</id>
<status>CRIADO</status>
<valor>39.9</valor>
<links>
</links>
</pagamento>

Repare que a lista de links está vazia, isso acontece por que a classe link também não possui setters
para seus atributos, para corrigirmos o problema, utilizaremos novamente a anotação
@XmlAccessorType(XmlAccessType.FIELD) :

4.19 CONTROLANDO O XML GERADO PELO JAX-RS 101


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@XmlAccessorType(XmlAccessType.FIELD)
public class Link {
// código da classe
}

O xml depois dessa segunda modificação fica da seguinte forma:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<pagamento>
<id>2</id>
<status>CRIADO</status>
<valor>39.9</valor>
<links>
<rel>confirmar</rel>
<uri>/pagamentos/1</uri>
<method>PUT</method>
</links>
<links>
<rel>cancelar</rel>
<uri>/pagamentos/1</uri>
<method>DELETE</method>
</links>
</pagamento>

Repare que no XML do pagamento cada informação fica dentro de uma tag com o mesmo nome do
atributo da classe. Ao invés de utilizar essa nomenclatura padrão, podemos configurar o nome das tags
geradas no XML utilizando a anotação @XmlElement :
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pagamento {
private int id;

@XmlElement(name="estadoAtual")
private String status;
}

Com essa modificação, o XML do pagamento conterá a tag estadoAtual ao invés da tag status :

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<pagamento>
<id>2</id>
<estadoAtual>CRIADO</estadoAtual>
<valor>39.9</valor>
<links>
<rel>confirmar</rel>
<uri>/pagamentos/1</uri>
<method>PUT</method>
</links>
<links>
<rel>cancelar</rel>
<uri>/pagamentos/1</uri>
<method>DELETE</method>
</links>
</pagamento>

No XML, podemos colocar as informações do Pagamento em tags internas do xml ou como


atributos da própria tag pagamento , para colocarmos, por exemplo, o id como atributo da tag
pagamento, utilizamos a anotação @XmlAttribute :

102 4.19 CONTROLANDO O XML GERADO PELO JAX-RS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pagamento {
@XmlAttribute
private int id;

@XmlElement(name="estadoAtual")
private String status;
}

Com isso o xml fica da seguinte forma:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<pagamento id="1">
<estadoAtual>CRIADO</estadoAtual>
<valor>39.9</valor>
<links>
<rel>confirmar</rel>
<uri>/pagamentos/1</uri>
<method>PUT</method>
</links>
<links>
<rel>cancelar</rel>
<uri>/pagamentos/1</uri>
<method>DELETE</method>
</links>
</pagamento>

4.20 EXERCÍCIO ANOTAÇÕES DO JAX-B


1. Utilize a anotação @XmlAccessorType para fazer com que o JAX-B gere o XML do Pagamento
através dos atributos ao invés dos getters e setters.

Utilize também as anotações @XmlAttribute para transformar o id em um atributo da tag


pagamento:

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pagamento {
@XmlAttribute
private int id;

private String status;


}

2. Anote também a classe Link com @XmlAccessorType(XmlAccessType.FIELD) :


@XmlAccessorType(XmlAccessType.FIELD)
public class Link {

3. Agora tente buscar o xml de um pagamento com o comando curl do terminal:

curl -i -H "Accept: application/xml" -X GET


http://localhost:8080/fj36-webservice/pagamentos/1

4.20 EXERCÍCIO ANOTAÇÕES DO JAX-B 103


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4.21 JAX-RS 2.0 CLIENT API
JAX-RS 2.0 trouxe uma API dedicada para o lado do cliente, que facilita muito a execução de
requisições HTTP para Web Services Restful. A API é fluente e possui 3 classes principais: Client ,
WebTarget e Invocation .

A partir do Client , que é criado pelo ClientBuilder , definimos o target . O target nada
mais é do que a URI do nosso recurso.
Client cliente = ClientBuilder.newClient();

WebTarget target = cliente.target(".../pagamentos/1");

A partir desse ponto já podemos criar um request especificando o método HTTP, pedindo o
formato:
Invocation httpGet = target.request(MediaType.APPLICATION_JSON).buildGet();
Pagamento pagamento = httpGet.invoke(Pagamento.class);

Repare que pedimos um JSON, no entanto o JAX-RS devolve um objeto Pagamento .

Da mesma forma, podemos enviar um HTTP POST para criar um pagamento:


Transacao transacao = new Transacao();
transacao.setValor(new BigDecimal("39.0"));
...

Client cliente = ClientBuilder.newClient();


Pagamento resposta = cliente.target(".../pagamentos")
.request()
.buildPost(Entity.json(transacao))
.invoke(Pagamento.class);

4.22 VERSIONAMENTO DE SERVIÇOS REST


Repare que, devido ao uso de URI para descrever o recurso, a interface uniforme para definir as
ações e os formatos padronizados como JSON ou XML, não há a necessidade de uma descrição mais
formal da interface (WSDL), como existe nos serviços SOAP.

Seguindo o REST, aproveitamos uma boa parte de padrões já existentes. No outro lado, sem nenhum
descritor formal, não há geração das classes stubs para simplificar a chamada remota. Por isso, o JAX-RS
2 definiu uma API de cliente.

Pensando no versionamento do serviço: No WSDL temos o namespace para definir a versão do


serviço. Mas como funciona isso nos serviços REST?

O mais comum é usar a URI para o versionamento. Até existem alguns serviços SOAP que também
utilizam a URI. Nesse caso codificamos a versão dentro da URI, por exemplo:

http://localhost:8080/fj36-webservice/v1/pagamentos

104 4.21 JAX-RS 2.0 CLIENT API


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
A cada atualização do serviço, incompatível com a versão anterior, incrementa-se o número da
versão, no nosso caso, por exemplo, passaria de v1 para v2, e assim por diante. Isso é simples de
implementar, fácil de rotear e transparente para quem usa o serviço. Inclusive serviços como PayPal,
Twitter e algumas API de Google usam esta estratégia.

Para implementar isso podemos usar a anotação @Path do recurso PagamentoResource :


@Path("/v1/pagamentos")
public class PagamentoResource {

Pronto, fizemos o versionamento de recurso pela URI!

4.23 EXERCÍCIOS: CLIENTE RESTFUL COM JAX-RS 2.0


Nesse exercício, usaremos a API client side do JAX-RS 2.0 e integraremos o web service REST na
livraria.

1. Vamos preparar nosso projeto fj36-livraria com as dependências do JAX-RS/Resteasy.

Copie os JARs para a pasta WebContent/WEB-INF/lib do seu projeto dessa forma:

Vá ao diretório caelum/36/jars/lib-jaxrs .

Selecione todos os JARs, clique com o botão direito e escolha Copy (ou CTRL+C ).

Cole todos os JARs na pasta WebContent/WEB-INF/lib do projeto fj36-livraria ( CTRL+V ).

Caso o Eclipse acuse a existência de algum JAR, clique em Yes To All.

2. No projeto fj36-livraria , abra a classe ClienteRest , do pacote


br.com.caelum.livraria.rest . Ela encapsulará todas as chamadas remotas.

Repare que usaremos o serviço de pagamento:

http://localhost:8080/fj36-webservice/pagamentos/1

3. Na mesma classe, procure o método criarPagamento(..) . Nele, faça a chamada ao Web Service,
usando a API do JAX-RS 2.0.

Os imports são do pacote javax.ws.rs.client :


public Pagamento criarPagamento(Transacao transacao) {

Client cliente = ClientBuilder.newClient();


Pagamento resposta = cliente.target(SERVER_URI + ENTRY_POINT)
.request()
.buildPost(Entity.json(transacao))
.invoke(Pagamento.class);

System.out.println("Pagamento criado, id: " + resposta.getId());

4.23 EXERCÍCIOS: CLIENTE RESTFUL COM JAX-RS 2.0 105


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
return resposta;
}

4. Procure o método confirmarPagamento(..) . Faça uma chamada remota usando os dados do


pagamento criado:

Atenção: A classe Link vem do pacote br.com.caelum.livraria.modelo.* .


public Pagamento confirmarPagamento(Pagamento pagamento) {

Link linkConfirmar = pagamento.getLinkPeloRel("confirmar");


Client cliente = ClientBuilder.newClient();

Pagamento resposta = cliente.target(SERVER_URI + linkConfirmar.getUri())


.request()
.build(linkConfirmar.getMethod())
.invoke(Pagamento.class);

System.out.println("Pagamento confirmado, id: " + resposta.getId());

return resposta;
}

Repare que usamos os dados do pagamento para executar a confirmação.

Observação: Os métodos criarPagamento(..) e confirmarPagamento(..) são utilizados pelo


Carrinho . Você pode descobrir o Call Hierarchy ao selecionar o nome do método e apertar CTRL
+ ALT + H .

5. Reinicie o Tomcat e acesse a livraria:

http://localhost:8088/fj36-livraria

Escolha um livro, adicione-o no carrinho e insira os dados de cartão (basta um caracter em cada
campo de texto). Pague com Payfast para simular a criação do pagamento.

Finalize o pedido para confirmar a compra. Verifique o console do Tomcat. Descubra a id do


pagamento e acesse pelo navegador o serviço:

http://localhost:8080/fj36-webservice/pagamentos/{idPagamento}

4.24 CLIENTE JAX-RS COM XML


Além de conseguir fazer a comunicação através do formato JSON, o cliente do JAX-RS 2.0 também
pode ser utilizado com o formato XML, essa integração é feita através das configurações do JAX-B.

Para conseguirmos enviar e receber XML do servidor, precisamos configurar uma classe com as
anotações corretas do JAX-B, o que pode ser uma atividade complicada dependendo do tamanho e da
complexidade do XML. Para facilitar a configuração e validação do XML que será trocado no serviço,
muitas aplicações fornecem um arquivo de validação de XML chamado XML Schema Definition

106 4.24 CLIENTE JAX-RS COM XML


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
(XSD).

Com o arquivo XSD, podemos descobrir quais são as tags permitidas e os tipos de dados que podem
ser passados para cada tag. Utilizando a ferramenta xjc do JAX-B, conseguimos gerar classes anotadas
a partir das definições contidas no XSD.

xjc pagamento.xsd -d src -p br.com.caelum.payfast.generated

Com esse comando estamos gerando a classe Pagamento configurada com as anotações do JAX-B
dentro do pacote br.com.caelum.payfast.generated da pasta src .

Agora que temos uma classe anotada, podemos utilizá-la com o cliente REST do JAX-RS da mesma
forma que fizemos com o JSON:

Client client = ClientBuilder.newClient();


br.com.caelum.payfast.generated.Pagamento pagamento = cliente
.target("http://localhost:8080/fj36-webservice/pagamentos/1")
.request()
.buildGet()
.invoke(br.com.caelum.payfast.generated.Pagamento.class);

4.25 EXERCÍCIO OPCIONAL - CLIENTE JAX-RS COM XML


1. Vamos gerar o XSD para a classe Pagamento do projeto fj36-webservices . Dentro do projeto,
crie uma nova classe chamada GeraXSDPagamento com o seguinte código:
public class GeraXSDPagamento {
public static void main(String [] args) {
JAXBContext context = JAXBContext.newInstance(Pagamento.class);
context.generateSchema(new SchemaOutputResolver() {
@Override
public Result createOutput(String namespaceUri,
String suggestedFileName)
throws IOException {

return new StreamResult(new File("pagamento.xsd"));


}
});
}
}

2. Execute a classe GeraXSDPagamento com o atalho Ctrl + F11 escolhendo a opção Java
Application .

Depois que o programa for executado, faça um Refresh no projeto fj36-webservices (atalho
F5 ) e procure o arquivo pagamento.xsd na raíz do projeto, esse é o arquivo que descreve o
formato do XML que representa o pagamento.

Copie o arquivo pagamento.xsd para a pasta raíz do projeto fj36-livraria

3. Agora vamos utilizar o comando xjc da jdk para gerar a classe anotada que representa o
pagamento. No terminal do sistema, entre na pasta do projeto fj36-livraria e execute o seguinte

4.25 EXERCÍCIO OPCIONAL - CLIENTE JAX-RS COM XML 107


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
comando:

xjc pagamento.xsd -d src -p br.com.caelum.payfast.generated

Depois de executar o comando, volte para o eclipse e faça um refresh no projeto fj36-livraria ,
veja que um novo pacote chamado br.com.caelum.payfast.generated foi criada no projeto com
a classe Pagamento configurada com as anotações do JAX-B.

4. Agora vamos criar uma nova classe para testar o cliente do JAX-RS com XML. Para isso, dentro do
projeto fj36-livraria , crie uma nova classe chamada ConsultaPagamentoXML com o seguinte
código:
public class ConsultaPagamentoXML {
public static void main(String[] args) {
Client client = ClientBuilder.newClient();

// a classe Pagamento é do pacote br.com.caelum.payfast.generated

Pagamento resposta = cliente.target(SERVER_URI + "/pagamentos/1")


.request()
.buildGet()
.invoke(Pagamento.class);

System.out.printf("%d %f %s\n",
resposta.getId(),
resposta.getValor(),
resposta.getEstadoPagamento());
}
}

Execute essa classe como Java Application e veja a saída impressa no terminal.

4.26 UM POUCO SOBRE MICROSERVICES


Quando estamos trabalhando com integração, logo surge algumas dúvidas sobre como separar os
limites de um projeto. Em uma empresa, surge a necessidade de se implementar um sistema que cuide
da informatização de seu processo produtivo onde cada setor será representado no tal sistema.

E afim de resolver essa questão é muito comum ver no mercado, alguns sistemas que possuem
códigos muito diferente e mal documentado cujo são responsáveis por toda a empresa. O problema é
que algum setor pode precisar de alguma regra de negócio, ou dado específico de outro. O resultado são
módulos pouco coesos onde, por exemplo, um desenvolvedor novo na equipe, não saberia ao certo onde
ir pra modificar alguma regra de negócios que seja usada tanto pelo RH quanto pelo financeiro.

Esse tipo de sistema, que chamamos de Enterprise Resource Planning, ou ERP, que é super comum
de ser encontrado é uma boa alternativa a integração de sistemas já que, lidar com protocolos legados e,
muitas vezes mal documentados, é um trabalho árduo e desafiante.

Em alguns casos, a arquitetura de sistemas monolíticos (ERP) pode aparentar ser mais fácil de se
implementar e de mais rápido retorno, já que a base de dados é unica e não há código duplicado entre

108 4.26 UM POUCO SOBRE MICROSERVICES


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
módulos.

As desvantagens começam a aparecer quando questões sobre disponibilidade, balanceamento de


carga, modularização entre outras coisas, são levantadas. Em um ERP caso ocorra algum erro bruto em
alguma tarefa que o derrube do ar, todo o sistema vai junto. O que torna claro que temos um único
ponto de falha (Single Point of Failure). Além do que, estudar a base de código se torna-se um verdadeiro
desafio, como já discutimos aqui.

Por essas questões, alguns arquitetos preferem dividir essa grande e soberana aplicação em aplicações
menores onde cada uma pode cuidar de um setor da empresa, cada uma possuirá sua responsabilidade
bem definida.

A ideia é que essas aplicações menores carreguem consigo toda a infraestrutura necessária para sua
implantação, desde a suite de testes até a base de dados. Seguindo esse foco, surgiram tecnologias como
SpringBoot que ajudam na implementação dessas aplicações fornecendo um container e uma base de
dados embutidos.

Essa arquitetura, que chamamos de microserviços, não é uma receita de bolo já que são livres para
variarem de acordo com cada cenário, podendo por exemplo, usar bandos de dados de integração
(compartilhados entre os serviços) além de microserviços que se comunicam entre si, entre outros. Cabe
ao projetista avaliar qual a melhor solução para o seu projeto.

4.26 UM POUCO SOBRE MICROSERVICES 109


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 5

MENSAGERIA COM JAVA MESSAGE


SERVICE E HORNETQ

"A imaginação é mais importante que o conhecimento." -- Albert Einstein

Nesse capítulo, você entenderá sobre a especificação JMS e o provedor HornetQ que está integrado
no Wildfly.

Suponha que uma empresa se organizou em três departamentos: vendas, financeiro e estoque. Esses
departamentos são extremamente independentes e com alto grau de autonomia para escolher as
tecnologias utilizadas em seus sistemas de TI. Cada departamento desenvolveu o seu próprio sistema.

Quando um cliente faz uma compra, o sistema do departamento de vendas deve pedir ao sistema do
financeiro a emissão da nota fiscal referente à compra. Além disso, o sistema do estoque deve ser avisado
para encaminhar os produtos à casa do cliente.

Então, a empresa precisa de que os sistemas dos três departamentos, que funcionam muito bem
independentemente, sejam integrados de alguma forma sem trazer prejuízos. A empresa contratou uma
consultoria, que preparou uma solução para o problema de integração. A ideia proposta pela consultoria
é fazer os sistemas se comunicarem através de troca de mensagens. Dessa forma, os sistemas
continuarão bem independentes e com alto grau de autonomia. Em outras palavras, os sistemas não
conhecerão o funcionamento interno um do outro.

5.1 ASSÍNCRONO VS SÍNCRONO


Há duas maneiras de dois sistemas se comunicarem: através de interações síncronas ou assíncronas.
Nas interações síncronas, o sistema que vai enviar uma mensagem ao outro sistema fica bloqueado,
esperando a resposta chegar. Já nas interações assíncronas, o emissor da mensagem não fica bloqueado,
ou seja, pode processar alguma coisa útil enquanto a resposta não chega.

Como os sistemas da empresa não devem conhecer o funcionamento interno um do outro, então
eles não conseguiriam determinar nem estimar com precisão quanto tempo levaria para a resposta de
uma mensagem chegar. Assim, quando um sistema envia uma mensagem para outro, não é interessante
que ele fique travado esperando a resposta, pois o tempo que vai demorar é imprevisível. Portanto, para
integrar sistemas que são bem independentes, o estilo de comunicação assíncrono é o mais indicado, já

110 5 MENSAGERIA COM JAVA MESSAGE SERVICE E HORNETQ


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
que não faz o emissor ficar travado.

5.2 MIDDLEWARE ORIENTADO À MENSAGENS (MOM)


Há algumas estratégias para implementar interações assíncronas entre dois sistemas. A mais comum
é utilizar filas de mensagens. Digamos que o sistema do departamento de vendas quer se comunicar com
o sistema do financeiro. Para isso acontencer, basta que o sistema do departamento de vendas coloque
uma mensagem em uma fila de mensagens que está associada ao sistema do financeiro.

Eventualmente, as interações entre dois sistemas precisam ser feitas nas duas direções. Nesse caso, é
interessante utilizar duas filas de mensagens. O primeiro sistema manda mensagens para o segundo
através da primeira fila e o segundo manda mensagens para o primeiro através da segunda fila. Até seria
possível implementar as interações nos dois sentidos com uma fila só, porém a implementação ficaria
muito complexa.

Consequentemente, o número de filas de mensagens pode ser grande o suficiente para exigir um
gerenciamento mais elaborado, com o intuito de evitar que mensagens sejam danificadas ou perdidas.
Quem deve ficar com a responsabilidade de gerenciar as filas de mensagens? Essa responsabilidade
poderia ser colocada em alguns dos sistemas já existentes da empresa. Porém, não parece fazer sentido
que algum dos sistemas fique com essa tarefa, já que eles não foram planejados para isso. Para não
sobrecarregar os sistemas já existentes com tarefas de infraestrutura e dividir melhor as
responsabilidades, um outro sistema será criado e ficará responsável por gerenciar as trocas de
mensagens entre os sistemas dos departamentos da nossa empresa.

Os sistemas, ou programas, que são especializados em gerenciar filas de mensagens para realizar a
comunicação entre sistemas diversos são chamados de Middleware Orientado à Mensagens (MOM).

5.2 MIDDLEWARE ORIENTADO À MENSAGENS (MOM) 111


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
As responsabilidades de um MOM são basicamente duas: gerenciar a criação e remoção de filas de
mensagens e garantir a entrega das mensagens. Além disso, ele pode executar algum tipo de tratamento
nas mensagens. Por exemplo, quando o emissor envia as mensagens em um formato e o destinatário
trabalha com outro formato de mensagens, o MOM poderia converter as mensagens para o formato
adequado na ida ou na volta, tirando dos sistemas essa tarefa de infraestrutura.

O MOM faz com que o emissor fique extremamente desacoplado do destinatário. Perceba que, quem
envia uma mensagem, não precisa conhecer obrigatoriamente quem vai receber a mensagem. É como se
o emissor pensasse assim: "Eu vou depositar uma mensagem nessa fila do MOM e nem quero saber
quem vai ficar responsável pelo tratamento". Isso é interessante, pois no caso do sistema destinatário ter
que ser trocado por outro, o emissor não é afetado, uma vez que ele nem sabia quem iria receber as
mensagens que ele enviava.

As mensagens podem ser de texto mesmo ou possuir objetos complexos, mas um ponto fundamental
é que elas contenham somente dados. Em nenhum momento o remetente sabe para quem será entregue
a mensagem. A mensagem é enviada a um serviço intermediário, que garante a entrega e avisa o outro
sistema automaticamente.

O conceito de MOM é genérico e independente de qualquer tecnologia específica. Porém, como


nesse treinamento estamos interessados em tecnologias Java, vamos restringir a nossa discussão a
MOMs Java. Inclusive, o Java criou uma especificação para MOMs, Java Message Service (JMS), que
define as trocas de mensagens entre sistemas remotos. Ou seja, JMS define uma infraestrutura (Message
Orientated Middleware - MOM ou Message Broker) que suas aplicações podem usar para a
comunicação.

112 5.2 MIDDLEWARE ORIENTADO À MENSAGENS (MOM)


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
JMS é diferente dos sistemas baseados em RPC (Remote Procedure Call), como JAX-RPC ou RMI.
Neles, o remetente deve conhecer o destinatário (IP e porta, por exemplo), chamar um método
específico passando os dados como parâmetros (conhecendo a interface do serviço), depois fica
aguardando a resposta sincronizadamente.

5.3 MODELOS DE ENTREGAS: EMAIL OU LISTA DE DISCUSSÃO?


As pessoas estão acostumadas com interações assíncronas no dia a dia. Há dois exemplos clássicos,
que são os emails e as listas de discussões. Em ambos, o emissor não precisa ficar bloqueado esperando o
destinatário receber a mensagem e a processar. Se você tivesse que ficar "travado" ao enviar um email,
então nunca iria mandar um email na vida, mesmo porque pode demorar para o receptor ficar online e
ler o email. Nesse sentido, as listas de discussões são análogas.

A diferença entre as listas de discussões e os emails é que quando uma mensagem é enviada para
uma lista, não sabemos nem quem são nem quantos são os destinatários da mensagem. Inclusive, novos
destinatários podem ser registrados na lista de discussão.

O estilo de troca de mensagens do email é denominado Queue (Fila). Nesse estilo, o emissor envia
uma mensagem para uma caixa postal associada a um endereço de email. É importante perceber que as
mensagens podem ser retiradas da caixa postal por uma pessoa ou até mesmo por um programa com
inteligência suficiente para executar o tratamento das mensagens. É importante mencionar que poderia
ter mais do que uma pessoa querendo receber as mensagens, mas nunca a mesma mensagem será
enviada para duas pessoas, apenas uma vai consumí-la.

5.3 MODELOS DE ENTREGAS: EMAIL OU LISTA DE DISCUSSÃO? 113


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
O estilo de troca de mensagens das listas de discussão é chamado de Topic. Nesse estilo, quem envia
uma mensagem é denominado publisher e quem se registra a um Topic para receber mensagens é
denomidado subscriber.

A especificação JMS define esses dois modelos de trocas de mensagens.

ENTERPRISE INTEGRATION PATTERN - MESSAGING

Use Messaging to transfer packets of data frequently, immediately, reliably, and asynchronously,
using customizable formats.

http://www.eaipatterns.com/Messaging.html

114 5.3 MODELOS DE ENTREGAS: EMAIL OU LISTA DE DISCUSSÃO?


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
ENTERPRISE INTEGRATION PATTERN - POINT-TO-POINT CHANNEL

Send the message on a Point-to-Point Channel, which ensures that only one receiver will receive a
particular message.

http://www.eaipatterns.com/PointToPointChannel.html

5.4 CRIAÇÃO DE QUEUES E TOPICS NO JMS


Tipicamente, as Queues e Topics são criadas pelo administrador do provedor do JMS. O provedor
do JMS é o programa que implementa a especificação JMS. Por exemplo, o Wildfly tem um provedor
interno para JMS (assim como todo servidor de aplicação Java EE), mas também existem provedores
standalone como o ActiveMQ da Apache e o MQ Series da IBM por exemplo. No JMS, as Queues e
Topics são chamados de Destinations. Infelizmente, a maneira de criar um Destination (Queue ou
Topic) é particular de cada provedor de JMS. Para que os emissores e destinatários possam acessar os
Destinations, o provedor do JMS os registra no serviço de nomes JNDI.

5.5 EXERCÍCIOS: CONFIGURAÇÃO DO HORNETQ E A PRIMEIRA FILA


Já vimas a necessidade da nossa livraria se integrar com outras aplicações, como o estoque e o
sistema de pagamento. Surge um novo problema, ao finalizar um pedido do tipo ebook, é preciso gerar o
livro em vários formatos como PDF, mobi e ePub, sempre dedicado para um cliente específico.

A geração dos ebooks demora, dependendo do livro leva minutos para ser finalizado. Mas não basta
só gerar como também disponibilizá-os para download, em um serviço com Amazon S3. Não podemos
fazer tudo isso sincronomente (ou seja, ao finalizar o pedido), é simplesmente inviável.

Vamos usar a mensageria com JMS para gerar os ebooks assincronamente e descoplar a aplicação
livraria da que gera os livros.

1. Abra novamente o console do sistema operacional e entre novamente na pasta instalação do


wildfly/bin dentro dela, precisamos novamente executar o script add-user.sh para criar um
novo usuário que será utilizado para acessar a fila de mensagens.

A criação desse usuário segue exatamente os mesmos passos descritos, mas dessa vez o tipo de
usuário será Application User , com nome jms , senha jms2 e no grupo guest .

2. Vamos agora criar uma nova fila pela interface administrativa do Wildfly. Para isso, abra o
navegador e acesse o endereço http://localhost:9990 . Na página que é aberta, precisamos nos
autenticar utilizando o usuário administrativo que foi criado no início do curso (usuário e senha

5.4 CRIAÇÃO DE QUEUES E TOPICS NO JMS 115


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
caelum ).

Depois da autenticação, Clique no link Profile no lado superior esquerdo da tela.

No menu lateral, clique na opção Messaging, e em seguida em Destinations. Na tela de providers,


selecione a opção view.

Nesta tela iremos criar uma nova Fila. Clique no botão add, e preencha o formuário com os dados
abaixo e clique em save:

Name: FILA.GERADOR

JNDI Name: java:jboss/exported/jms/FILA.GERADOR

5.6 JMS 2.0


116 5.6 JMS 2.0
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Juntamente com o JavaEE 7 veio uma nova versão da API de JMS, simplificando em muito o código
necessário para o envio e recebimento de mensagens. Vamos conhecer os componentes da nova API:

5.7 CONNECTIONFACTORY
Para que um emissor ou um destinatário se comunique com o provedor do JMS, é necessário
estabelecer conexões (Connections). As conexões são criadas a partir de uma fábrica
(ConnectionFactory). A fábrica é configurada pelo administrador do provedor e pode ser utilizada por
diversas aplicações ao mesmo tempo.

Para que as aplicações possam pegar a ConnectionFactory, o provedor do JMS a registra no serviço
de nomes JNDI. No caso do JBoss, o nome utilizado é jms/RemoteConnectionFactory.

No caso do WildFly, devemos fornecer algumas configurações de segurança antes de criarmos uma
fila ou tópico. Apenas usuários registrados no servidor são capazes de pegar uma instância de uma fila
ou tópico. Para cadastrar um novo usuário apto a pegar uma instância de um desses objetos, devemos
rodar o script add-user.sh localizado na pasta bin do JBoss. Precisamos criar somente um usuário que
possa obter instâncias de filas e tópicos, ou seja, escolhemos um Application User quando o script
perguntar que tipo de usuário desejamos criar.

Em seguida, obtemos uma ConnectionFactory do JBoss:

InitialContext ic = new InitialContext();

ConnectionFactory queueConnectionFactory =
(ConnectionFactory)ic.lookup("jms/RemoteConnectionFactory");

5.8 JMSCONTEXT
No JMS 2.0 foi criada uma nova interface JMSContext para abstratir toda a complexidade
necessária para interagir com filas e tópicos. Podemos obter uma instancia de JMSContext a partir da
ConnectionFactory . Note que agora é necessário identificar o usuário registrado (passando seu user e
sua respectiva senha) na hora de obtermos o JMSContext .
String usuario = "jms";
String senha = "jms2";
JMSContext context = connectionFactory.createContext(usuario,senha);

5.9 ENVIANDO UMA MENSAGEM


Para criar novas mensagens precisamos de um JMSProducer , que pode ser obtido do JMSContext
:
JMSProducer producer = context.createProducer();

É possível enviar diversos conteúdos em uma mensagem, como texto puro, objetos, mapas, streams,

5.7 CONNECTIONFACTORY 117


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
etc.

Além do conteúdo este método também recebe o destino da mensagem (fila ou tópico). Para acessar
o destino da mensagem usaremos novamente o JNDI para buscar a referência para fila de destino:

InitialContext ic = new InitialContext();

ConnectionFactory connectionFactory =
(ConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
Queue queue = (Queue) ic.lookup("jms/FILA.GERADOR");

try( JMSContext context = connectionFactory.createContext("jms","jms2")){


JMSProducer producer = context.createProducer();
producer.send(queue, "uma mensagem de texto!");
}

5.10 CONSUMINDO MENSAGENS ENVIADAS PARA A FILA


Agora que já temos o código que consegue enviar mensagens para a fila que foi configurada no
HornetQ, vamos desenvolver o código necessário para consumir as mensagens que foram enviadas.

Para isso, precisamos de um novo objeto do JMS 2.0 do tipo JMSConsumer através do método
createConsumer do JMSContext . Esse método recebe como argumento qual é a destination (fila ou
tópico) do qual queremos ler as mensagens:
Queue fila = ic.lookup("jms/FILA.GERADOR");
JMSContext context = connectionFactory.createContext(usuario, senha);
JMSConsumer consumer = context.createConsumer(fila);

Além de criar o consumer, precisamos também definir qual é o código que será utilizado para tratar
as mensagens. Fazemos isso através do método setMessageListener passando para esse método uma
implementação da interface MessageListener
public class TratadorDeMensagem implements MessageListener {
public void onMessage(Message msg) {
TextMessage textMessage = (TextMessage) msg;
try {
System.out.println("recebendo mensagem: "
+ textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}

Esse MessageListener deve ser passado para o setMessageListener do JMSConsumer

consumer.setMessageListener(new TratadorDeMensagem());

Para terminarmos, precisamos avisar o JMS que o consumer está pronto para receber as mensagens
enviadas para a fila utilizando o método start do JMSContext
Queue fila = ic.lookup("jms/FILA.GERADOR");
JMSContext context = connectionFactory.createContext(usuario, senha);

118 5.10 CONSUMINDO MENSAGENS ENVIADAS PARA A FILA


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
JMSConsumer consumer = context.createConsumer(fila);
consumer.setMessageListener(new TratadorDeMensagem());

context.start();

Quando não quisermos mais tratar mensagens, precisamos invocar o método stop do
JMSContext para pararmos de receber as mensagens.

MAIS INFORMAÇÕES SOBRE O JMS 2

Você pode aprender mais sobre o JMS 2 no post do blog da caelum:

http://blog.caelum.com.br/a-nova-api-do-jms-2-0-no-java-ee-7/

ENTERPRISE INTEGRATION PATTERN - COMPETING CONSUMERS

Create multiple Competing Consumers on a single channel so that the consumers can process
multiple messages concurrently.

http://www.eaipatterns.com/CompetingConsumers.html

5.11 EXERCÍCIOS: CONSUMINDO MENSAGENS DA FILA


1. No Eclipse, crie um novo projeto do tipo Java Project chamado fj36-jms.

5.11 EXERCÍCIOS: CONSUMINDO MENSAGENS DA FILA 119


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
2. Nesse projeto, precisaremos dos jars do cliente do HornetQ que pode ser encontrado em um jar
dentro da pasta do Wildfly.

No projeto fj36-jms , crie uma nova pasta chamada lib

Entre no diretório de instalação do Wildfly e dentro dele procure a pasta bin/client

Copie o arquivo jboss-client.jar para a pasta lib do fj36-jms

Adicione o JAR copiado ao classpath da aplicação: Clique com o botão direito no jar e selecione a
opção Build Path > Add to Build Path .

3. Vamos agora configurar o JNDI da aplicação para que ele consiga buscar a ConnectionFactory e a
fila de mensagem que foram configuradas no servidor de aplicação.

Dentro da pasta src do fj36-jms , crie um novo arquivo chamado jndi.properties e dentro
dele adicione o seguinte conteúdo (esse arquivo pronto pode ser encontrado dentro da pasta
/caelum/cursos/36/etc/jndi.properties ):

java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory
java.naming.provider.url=http-remoting://localhost:8080

120 5.11 EXERCÍCIOS: CONSUMINDO MENSAGENS DA FILA


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4. Agora que temos tudo configurado, vamos criar a classe que envie mensagens para o
FILA.GERADOR . Dentro do fj36-jms, crie uma nova classe chamada EnviadorParaFila dentro do
pacote br.com.caelum.jms com o seguinte código:
public class EnviadorParaFila {
public static void main(String[] args) throws NamingException{
InitialContext ic = new InitialContext();
ConnectionFactory factory =
(ConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
Queue queue = (Queue) ic.lookup("jms/FILA.GERADOR");
try(JMSContext context = factory.createContext("jms", "jms2")) {
JMSProducer producer = context.createProducer();
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
String line = scanner.nextLine();
producer.send(queue, line);
}
scanner.close();
}
}
}

Não se esqueça de inicializar o Wildfly antes de testar esse código. Após rodar, você deve digitar no
console do Eclipse o texto da mensagem. Para enviar basta apertar Enter. Para terminar a execução
aperte CTRL-D .

5. Faça uma classe para implementar o tratamento das mensagens enviadas pelos emissores, ou seja,
crie a classe TratadorDeMensagem , que implementa a interface MessageListener , no pacote
br.com.caelum.jms .

Atenção: Todos os imports são do pacote javax.jms.* .


public class TratadorDeMensagem implements MessageListener {
public void onMessage(Message msg) {
TextMessage textMessage = (TextMessage) msg;
try {
System.out.println("Tratador recebendo mensagem: "
+ textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}

6. Faça uma classe para registrar o tratador de mensagens na fila gerador, também dentro do pacote
br.com.caelum.jms :

public class RegistraTratadorNaFila {

public static void main(String[] args) throws Exception {

InitialContext ic = new InitialContext();

ConnectionFactory factory =
(ConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
Queue queue = (Queue) ic.lookup("jms/FILA.GERADOR");
try(JMSContext context = factory.createContext("jms", "jms2")) {

5.11 EXERCÍCIOS: CONSUMINDO MENSAGENS DA FILA 121


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
JMSConsumer consumer = context.createConsumer(queue);
consumer.setMessageListener(new TratadorDeMensagem());
context.start();
Scanner teclado = new Scanner(System.in);
System.out.println("Tratador esperando as mensagens na fila JMS.");

teclado.nextLine(); //Aperte ENTER para parar

teclado.close();
context.stop();
}
}
}

Execute a classe e veja que no terminal da aplicação as mensagens que foram enviadas para a fila
estão sendo exibidas no terminal.

5.12 PARA SABER MAIS - JMS 1.0


Antes do JMS 2 tínhamos um trabalho maior para enviar e receber mensagens pois a classe
JMSContext e suas facilidades não existiam.

Para enviarmos uma mensagem tinhamos que abrir uma conexão para a fila de mensagens
utilizando a ConnectionFactory:

InitialContext ic = new InitialContext();


QueueConnectionFactory factory =
(QueueConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
String usuario = "jms";
String senha = "jms2";

QueueConnection connection = factory.createQueueConnection(usuario, senha);

Depois de criarmos a connection, precisamos criar um objeto do tipo Session utilizando o método
createQueueSession , passando com primeiro argumento um booleano que indica se queremos
associar o processamento da mensagem a uma transação da JTA, no caso da aplicação ser executada no
servidor de aplicação, e um segundo argumento que indica quando é que a mensagem pode ser
considerada como tratada pelo JMS.

QueueSession session =
connection.createQueueSession(false, Session.AUTO_ACKNOWLODGE);

Para enviar uma mensagem criada por uma Session a uma Queue obtida no JNDI, é necessário pedir
um Sender para a Session. Já para enviar uma mensagem a um Topic, é necessário obter um Publisher
com a Session.

QueueSender sender = session.createSender(queue);


sender.send(textMessage);

Para receber uma mensagem, é necessário definir o trecho de código que deve ser executado quando
a mensagem chegar. Isso é feito criando um MessageListener.

QueueReceiver queueReceiver = session.createReceiver(queue);

122 5.12 PARA SABER MAIS - JMS 1.0


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
queueReceiver.setMessageListener(new TratadorDeMensagem());

Além disso, para que as mensagens passem através da Connection , é necessário dar o start :
connection.start();

5.13 PARA SABER MAIS - COMPONENTES DO JMS 1.0


ConnectionFactory : Serve como fábrica de conexões. Deve existir no servidor e será
acessada pelo cliente usando o serviço JNDI.

Connection : Ligação para o Servidor (Broker). Representa um fluxo de mensagens. Os


métodos principais são: start() e stop() .

Session : Gerencia as mensagens enviadas. Os principais métodos são: rollback() ,


commit() e createTextMessage() , para criar uma mensagem.

Destination : Topic ou Queue, serviço intermediário (storehouse ou repositório de


mensagens) que recebe a mensagem do produtor e despacha para o consumidor.

Message : Representa uma mensagem.

Producer : Envia mensagens.

Consumer : Recebe mensagens.

5.14 ASSINATURAS DURÁVEIS


Por padrão, quando trabalhamos com tópicos, os subscribers recebem as mensagens somente
quando estiverem online. Para resolvermos isso, podemos utilizar Durable Susbscribers que, ao
contrário dos Subscribers normais, recebem mensagens inclusive offline. As mensagens que chegam ao
tópico enquanto um Durable Subscriber está offline são armazenadas pelo JMS. E quando o Durable
Subscriber fica online, novamente o JMS se encarrega de enviar todas as mensagens armazenadas.

Para ser um Durable Subscriber, é necessário definir um nome para o JMSContext e da subscription
(assinatura) pela API JMS. Assim, o provider JMS sabe para quem deve guardar as mensagens.

Para configurar o nome do JMSContext basta usar o método setClientId(..) :

5.13 PARA SABER MAIS - COMPONENTES DO JMS 1.0 123


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
JMSContext context = factory.createContext(usuario, senha);
context.setClientID("Financeiro");

A interface JMSContext possui o método createDurableConsumer(..) que recebe além do


tópico, o nome da assinatura:
JMSContext context = ....
context.setClientID("Financeiro");
JMSConsumer durableConsumer =
context.createDurableConsumer(topico, "AssinaturaNotas");

Nesse exemplo, configuramos uma assinatura com o nome Financeiro_AssinaturaNotas . Se


queremos terminar a assinatura, basta usar o método unsubscribe :
context.unsubscribe("AssinaturaNotas");

5.15 EXERCÍCIOS: DURABLE SUBSCRIBER


1. Acesse novamente a interface administrativa do Wildfly pelo endereço http://localhost:9990
do navegador. Dentro da página que é aberta, autentique-se com o usuário administrativo que foi
criado no início do curso (usuário: caelum , senha: caelum ).

2. Dentro da interface administrativa, clique novamente o link Profile no canto esquerdo do menu
superior.

Na página que é aberta, procure no menu lateral a opção Messaging > Destinations . e depois
clique novamente no link View :

Agora estamos novamente na janela que utilizamos para criar a fila FILA.GERADOR . Para criarmos
um tópico, clique no link Topics :

124 5.15 EXERCÍCIOS: DURABLE SUBSCRIBER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Em seguida clique no botão Add para adicionar um novo tópico no Wildfly. Preencha as seguintes
informações no formulário que é exibido:
Name: TOPICO.LIVRARIA
JNDI Name: java:jboss/exported/jms/TOPICO.LIVRARIA

3. Ainda no projeto fj36-jms, crie uma nova classe RegistraFinanceiroNoTopico :


public class RegistraFinanceiroNoTopico {
public static void main(String[] args) throws Exception {
InitialContext initialContext = new InitialContext();
ConnectionFactory factory = (ConnectionFactory)
initialContext.lookup("jms/RemoteConnectionFactory");
Topic topico = (Topic)
initialContext.lookup("jms/TOPICO.LIVRARIA");
try(JMSContext context = factory.createContext("jms", "jms2")) {
context.setClientID("Financeiro");
JMSConsumer consumer =
context.createDurableConsumer(topico, "AssinaturaNotas");

5.15 EXERCÍCIOS: DURABLE SUBSCRIBER 125


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
consumer.setMessageListener(new TratadorDeMensagem());
context.start();
Scanner teclado = new Scanner(System.in);
System.out.println("Financeiro esperando as mensagens");
System.out.println("Aperte enter para fechar a conexão");
teclado.nextLine();
teclado.close();
context.stop();

}
}
}

Depois de criar a classe execute-a como Java Application . Quando a classe for executada, o
código lançará uma exceção pois usuários do grupo guest , por padrão, não possuem permissão
suficiente para criar uma assinatura durável.

Para corrigirmos isso, entre novamente na interface administrativa do Wildfly,


http://localhost:9990 e autentique-se com o usuário e senha caelum .

Depois de se autenticar, entre novamente na página que utilizamos para criar as filas e os tópicos.
Link Profile no menu superior e em seguida Messaging > Destinations no menu lateral. E na
página principal clique em View .

Dentro da página de criação de filas, clique no link Security Settings :

Clique na role guest que é exibida na tabela e em seguida clique no link Edit :

126 5.15 EXERCÍCIOS: DURABLE SUBSCRIBER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
No formulário de edição, clique no link Advanced e depois habilite as opções CreateDurable? e
DeleteDurable? e em seguida clique no botão Save :

Depois de fazer essas modificações, execute novamente o programa. Dessa vez a criação da
assinatura durável deve acontecer sem problemas.

4. Agora vamos criar o código que publica mensagens no tópico do HornetQ:


public class EnviaMensagemParaOTopico {
public static void main(String[] args) throws Exception {
InitialContext ic = new InitialContext();
ConnectionFactory factory =
(ConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
Topic topico = (Topic) ic.lookup("jms/TOPICO.LIVRARIA");

try(JMSContext context = factory.createContext("jms", "jms2")) {


JMSProducer producer = context.createProducer();

5.15 EXERCÍCIOS: DURABLE SUBSCRIBER 127


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()){
String line = scanner.nextLine();

producer.send(topico, line);
}
scanner.close();
}
}
}

5. Execute as classes RegistraFinanceiroNoTopico e EnviaMensagemParaOTopico e tente enviar


algumas mensagens.

Abra o terminal do programa RegistraFinanceiroNoTopico e verifique que as mensagens


enviadas realmente chegaram com sucesso.

6. Agora teste se o subscriber realmente é durável.

No Eclipse, termine a execução do RegistraFinanceiroNoTopico (basta selecionar o console e


apertar Enter)

Envie algumas mensagens através do terminal da classe EnviaMensagemParaOTopico .

Rode a classe RegistraFinanceiroNoTopico no Eclipse e verifique o console. A mensagem deve


chegar ao tratador.

5.16 ROTEAMENTO BASEADO NO CONTEÚDO


Na nossa livraria, nem todos os pedidos são do tipo ebook. Isso depende da escolha do cliente, ele
pode simplesmente querer um livro tradicional impresso. Logo não é preciso avisar a aplicação que gera
os ebooks. Nesse caso queremos um envio de mensagens mais seletivo.

A ideia é verificar o conteúdo do pedido e adicionar um cabeçalho na mensagem JMS, que marca a
mensagem como ebook ou não. Para adicionar um cabeçalho na mensagem que será enviada para o
tópico, podemos utilizar o método setProperty da classe JMSProducer :

JMSProducer producer = // inicializa o producer


producer.setProperty("formato", "ebook");

Com esse código, estamos fazendo com que todas as mensagens enviadas por esse producer incluam
o cabeçalho formato com o valor ebook .

Agora só falta registrar um Subscriber que recebe apenas mensagens com este cabeçalho. Na
criação do JMSConsumer , podemos utilizar uma segunda versão do método createDurableConsumer
que recebe um parâmetro que indica qual é a condição que queremos incluir no cabeçalho da
mensagem.

JMSContext context = // inicializa o JMSContext


JMSConsumer consumer = context.createDurableConsumer(

128 5.16 ROTEAMENTO BASEADO NO CONTEÚDO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
topico, "AssinaturaEbook", "formato='ebook'", false);

Assim, o provedor JMS roteia apenas mensagens com aquele cabeçalho para o consumidor. Criamos
uma assinatura durável e seletiva. O valor "formato='ebook'" também é chamado de Message
Selector.

ENTERPRISE INTEGRATION PATTERN - SELECTIVE CONSUMER

Make the consumer a Selective Consumer, one that filteres the messages delivered by its channel so
that it only receives the ones that match its criteria.

http://www.eaipatterns.com/MessageSelector.html

5.17 EXERCÍCIOS: ROTEAMENTO COM SELECTORES


1. Vamos testar o envio de mensagens para vários subscribers e rotear pelo conteúdo da mensagem.

No projeto fj36-jms, copie a classe RegistraFinanceiroNoTopico e chame a cópia de


RegistraGeradorNoTopico . A nova classe é bem parecida, apenas mude apenas a id do cliente e o
nome da assinatura:
public class RegistraGeradorNoTopico {

public static void main(String[] args) throws Exception {

InitialContext initialContext = new InitialContext();


ConnectionFactory factory = (ConnectionFactory)
initialContext.lookup("jms/RemoteConnectionFactory");
Topic topico = (Topic)
initialContext.lookup("jms/TOPICO.LIVRARIA");
try(JMSContext context = factory.createContext("jms", "jms2")) {
context.setClientID("GeradorEbook");
JMSConsumer consumer = context.createDurableConsumer(
topico, "AssinaturaEbook", "formato='ebook'", false);
consumer.setMessageListener(new TratadorDeMensagem());
context.start();

Scanner teclado = new Scanner(System.in);


System.out.println("Gerador esperando as mensagens do tópico ...");
System.out.println("Aperte enter para fechar a conexão");

teclado.nextLine();
teclado.close();
context.close();
}
}
}

2. Execute as classes RegistraFinanceiroNoTopico , RegistraGeradorNoTopico e


EnviaMensagemParaOTopico

5.17 EXERCÍCIOS: ROTEAMENTO COM SELECTORES 129


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
E envie mensagens para o topico. No Eclipse verifique os consoles. A mensagem deve aparecer
apenas no Financeiro.

3. Modifique o código da classe EnviaMensagemParaOTopico e faça com que ela envie mensagens
com o cabeçalho formato=ebook :

public class EnviaMensagemParaOTopico {


public static void main(String[] args) throws Exception {
InitialContext ic = new InitialContext();
ConnectionFactory factory =
(ConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
Topic topico = (Topic) ic.lookup("jms/TOPICO.LIVRARIA");

try(JMSContext context = factory.createContext("jms", "jms2")) {


JMSProducer producer = context.createProducer();
producer.setProperty("formato", "ebook");

Scanner scanner = new Scanner(System.in);


while(scanner.hasNextLine()){
String line = scanner.nextLine();

producer.send(topico, line);
}
scanner.close();
}
}
}

Depois dessas modificações, execute novamente a classe EnviaMensagemParaOTopico e envie


novas mensagens para o TOPICO.LIVRARIA , dessa vez elas devem chegar tanto para o financeiro
quanto para o gerador de ebooks.

5.18 PERSISTINDO MENSAGENS


Por padrão mensagens enviadas para filas do HornetQ são persistidas pelo servidor, ou seja, se o
Wildfly for reiniciado as mensagens que ainda não foram tratadas não serão perdidas.

É possível alterar este padrão através do arquivo de configuração do servidor, o standalone-


full.xml que fica dentro da pasta standalone/configuration do Wildfly. A configuração de
persistência de mensagens fica dentro da tag persistence-enabled :
<subsystem xmlns="urn:jboss:domain:messaging:2.0">
<hornetq-server>
<persistence-enabled>true</persistence-enabled>

<!-- outras configurações -->


</hornetq-server>
</subsystem>

5.19 RECEBENDO MENSAGENS NO SERVIDOR DE APLICAÇÃO


Nesse capítulo, aprendemos como receber mensagens de filas e tópicos em uma aplicação Java SE

130 5.18 PERSISTINDO MENSAGENS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
com a API do JMS 2.0, mas o código para receber a mensagem é bastante repetitivo, além disso, dentro
do MessageListener se quiséssemos utilizar o banco de dados, transações ou qualquer recurso do
servidor, precisaríamos fazer o gerenciamento manual.

Para facilitar o tratamento de mensagens dentro de um servidor de aplicações, podemos utilizar um


novo tipo de EJB chamado Message Driven Bean. O Message Driven Bean para o JMS é uma classe que
implementa a interface MessageListener e anotada com @MessageDriven :
@MessageDriven
public class GeradorMDB implements MessageListener {
public void onMessage(Message msg) {
// implementação
}
}

Dentro da anotação @MessageDriven , precisamos informar ao servidor de aplicação se queremos


receber a mensagem de uma fila ou tópico, qual é o endereço JNDI dessa fila/topico entre outras
informações. Cada uma dessas configurações é passada dentro da anotação
@ActivationConfigProperty . Dentro dela precisamos informar o nome e o valor da configuração que
será passada para o JMS.

As configurações válidas que podem ser passadas para @ActivationConfigProperty são:

acknowledgeMode: Essa configuração pode ter os valores Auto_acknowledge ou


Dups_ok_acknowledge

messageSelector: Define o message selector do Message Driven Bean

destinationType: Define se queremos receber mensagens de uma fila ( javax.jms.Queue ) ou de


um tópico ( javax.jms.Topic )

destinationLookup: Define qual é o nome JNDI que deve ser utilizado para fazer o lookup da
fila/tópico que será utilizado.

connectionFactoryLookup: Nome JNDI da connection factory do JMS

subscriptionDurability: Define se o consumer do JMS será durável (Durable) ou não (NonDurable).

subscriptionName: Nome que será utilizado para a criação da subscription no caso de utilizarmos
um durable subscription

clientId: Define o clientID do JMS Context para a durable subscription.

Poderíamos implementar o recebimento de mensagens para o Gerador com o seguinte código:

@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName = "destinationLookup",
propertyValue = "jms/TOPICO.LIVRARIA"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Topic"),

5.19 RECEBENDO MENSAGENS NO SERVIDOR DE APLICAÇÃO 131


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@ActivationConfigProperty(propertyName = "acknowledgeMode",
propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "subscriptionDurability",
propertyValue = "Durable"),
@ActivationConfigProperty(propertyName = "subscriptionName",
propertyValue = "AssinaturaEbook"),
@ActivationConfigProperty(propertyName = "clientId",
propertyValue = "GeradorEbook"),
@ActivationConfigProperty(propertyName = "messageSelector",
propertyValue = "formato='ebook'")
})
public class GeradorMDB implements MessageListener {
public void onMessage(Message msg) {
// implementação
}
}

5.20 EXERCÍCIOS OPCIONAIS: MESSAGE DRIVEN BEAN


1. Dentro do projeto fj36-webservice crie uma nova classe chamada FinanceiroMDB dentro do
pacote br.com.caelum.jms :
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName = "destinationLookup",
propertyValue = "jms/TOPICO.LIVRARIA"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Topic")
})
public class FinanceiroMDB implements MessageListener {
public void onMessage(Message msg) {
try {
TextMessage message = (TextMessage) msg;
System.out.printf("Gerando notas para %s\n", message.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}

2. Vamos migrar também o Gerador de ebooks para um Message Driven Bean com um seletor de
mensagens:
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName = "destinationLookup",
propertyValue = "jms/TOPICO.LIVRARIA"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Topic"),
@ActivationConfigProperty(propertyName = "messageSelector",
propertyValue = "formato='ebook'")
})
public class GeradorMDB implements MessageListener {
public void onMessage(Message msg) {
try {
TextMessage message = (TextMessage) msg;
System.out.printf("Gerando ebooks para %s\n", message.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}

132 5.20 EXERCÍCIOS OPCIONAIS: MESSAGE DRIVEN BEAN


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
}

3. Agora que as classes foram migradas, teste novamente o envio de mensagens para o tópico através da
classe EnviadorDeMensagensParaOTopico . Envie mensagens com e sem o header que define o
formato como ebook e veja o que o console do Wildfly imprime para cada caso.

5.21 INTEGRANDO O SPRING COM O JMS


Agora que já sabemos como funciona o envio de mensagens em uma aplicação com o JMS, vamos
modificar o projeto da livraria para que ela consiga enviar mensagens para a aplicação no Wildfly.

Como vimos, para enviarmos mensagens pelo JMS, precisamos do ConnectionFactory e do


destino da mensagem (fila ou tópico), dois recursos que precisam ser buscados com o JNDI. Para o
projeto web, a busca pode ser implementada da mesma forma que fizemos no console application,
utilizando o jndi.properties e a classe InitialContext diretamente.

Mas com o Spring MVC, podemos utilizar a estrutura do próprio framework para fazer a injeção de
dependências de recursos disponibilizados pelo JNDI. Para isso, precisamos configurar o componente
do spring chamado JndiTemplate dentro do arquivo de configuração do spring, o spring-
context.xml :

<bean id="jmsJndiTemplate" class="org.springframework.jndi.JndiTemplate">


<property name="environment">
<props>
<prop key="java.naming.factory.initial">
org.jboss.naming.remote.client.InitialContextFactory
</prop>
<prop key="java.naming.provider.url">
http-remoting://localhost:8080
</prop>
</props>
</property>
</bean>

E agora dentro do código de um controller ou outro bean gerenciado pelo spring, podemos fazer a
injeção do JndiTemplate que foi configurado:
@Controller
public class JMSController {

@Autowired
private JndiTemplate jndi;
}

Dentro dos métodos desse controller, utilizamos o seguinte código para fazer o lookup de um
recurso:
ConnectionFactory factory = (ConnectionFactory)
jndi.lookup("jms/RemoteConnectionFactory");

Para evitar o lookup manual no JndiTemplate , podemos configurar um outro componente do

5.21 INTEGRANDO O SPRING COM O JMS 133


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
spring chamado JndiObjectFactoryBean para automatizar a busca:
<bean id="hornetQConnectionFactory"
class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiTemplate" ref="jmsJndiTemplate"/>


<property name="jndiName" value="jms/RemoteConnectionFactory"/>
</bean>

Também podemos configurar o lookup dos tópicos e filas que foram criados no HornetQ:
<bean id="topicoLivraria"
class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiTemplate" ref="jmsJndiTemplate"/>


<property name="jndiName" value="jms/TOPICO.LIVRARIA"/>
</bean>

Dentro do bean gerenciado pelo spring, podemos fazer a injeção direta da ConnectionFactory e
do Topic do jms:
@Controller
public class JMSController {

@Autowired
private ConnectionFactory factory;

@Autowired
private Topic topico;
}

TRABALHANDO COM DIVERSOS TÓPICOS

Com a configuração do JndiObjectFactoryBean conseguimos injetar o tópico dentro de um


bean do spring. Mas e se fosse necessário utilizar diversos tópicos? Nesse caso, para conseguirmos
especificar qual é o destination correto, precisamos utilizar a anotação @Qualifier do spring
passando o id do destination que precisa ser injetado.

Por exemplo, se quisermos injetar o topicoLivraria que foi mapeado no spring-


context.xml , podemos utilizar o seguinte código:

@Qualifier("topicoLivraria")
@Autowired
private Topic topico;

5.22 EXERCÍCIOS: ENVIO DE MENSAGENS NA LIVRARIA


Vamos voltar ao projeto fj36-livraria. Ao finalizar um pedido, envie uma mensagem JMS para o
HornetQ, que, por sua vez, notifica os consumidores.

1. Abra o arquivo src/META-INF/spring-context.xml do projeto fj36-livraria e descomente o

134 5.22 EXERCÍCIOS: ENVIO DE MENSAGENS NA LIVRARIA


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
seguinte trecho de código que configura o lookup no JNDI do spring:

<bean id="jmsJndiTemplate" class="org.springframework.jndi.JndiTemplate">


<property name="environment">
<props>
<prop key="java.naming.factory.initial">
org.jboss.naming.remote.client.InitialContextFactory
</prop>
<prop key="java.naming.provider.url">
http-remoting://localhost:8080
</prop>
</props>
</property>
</bean>

<bean id="hornetQConnectionFactory"
class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiTemplate" ref="jmsJndiTemplate"/>


<property name="jndiName" value="jms/RemoteConnectionFactory"/>
</bean>

<bean id="topicoLivraria"
class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiTemplate" ref="jmsJndiTemplate"/>


<property name="jndiName" value="jms/TOPICO.LIVRARIA"/>
</bean>

2. Copie o JAR do cliente do Wildfly para a pasta WEB-INF/lib do seu projeto fj36-livraria dessa
forma:

Vá ao diretório <pasta do Wildfly>/bin/client .

Selecione o JAR jboss-client.jar, clique com o botão direito e escolha Copy (ou CTRL+C ).

No Eclipse, no projeto fj36-livraria, selecione a pasta WebContent/WEB-INF/lib e aperte


CTRL+V .

3. Vá ao pacote br.com.caelum.livraria.jms e abra a classe EnviadorMensagemJms e dentro dela


injete a ConnectionFactory e o Topic do JMS utilizando a anotação @Autowired do spring:

public class EnviadorMensagemJms implements Serializable {

@Autowired
private ConnectionFactory factory;

@Autowired
private Topic topico;

// resto do código da classe


}

4. Ainda na classe EnviadorMensagemJms , procure o método enviar(..) . e nele implemente o


envio de mensagens para o tópico utilizando o JMS com base nos dados do pedido:

public void enviar(Pedido pedido) {

5.22 EXERCÍCIOS: ENVIO DE MENSAGENS NA LIVRARIA 135


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
System.out.println("JMS: Enviando pedido:" + pedido);
try(JMSContext context = factory.createContext("jms", "jms2")) {
JMSProducer producer = context.createProducer();
producer.setProperty("formato", pedido.getFormato());

producer.send(topico, pedido.toString());
}
}

5. Para testar a aplicação certifique-se que tanto o tomcat quanto o Wildfly estão inicializados e entre
na url:

http://localhost:8088/fj36-livraria

Escolha um livro e finalize a compra. Ao finalizar, o sistema enviará uma mensagem JMS. Verifique
os consoles dos consumidores no Eclipse.

6. (Opcional) Teste também o roteamento. Escolha um livro no formato EBOOK e finalize o pedido.

5.23 PARA SABER MAIS SHARED CONSUMER DO JMS 2


Tópicos são ideais quando a aplicação precisa avisar vários subscribers sem saber quais e quantos
são. É uma forma desacoplada de integração. O JMS especificou esse conceito de publish-subscriber e
oferece, com tópicos duráveis, uma forma de garantir a entrega das mensagens ao subscriber.

O problema era que no JMS 1 não podiamos ter dois subscribers com o mesmo client ID. Pela
especificação, o id do cliente era único. Qualquer tentativa de registar um novo subscriber com o mesmo
id gerava um erro.

Mas dois subscribers com o mesmo id pode fazer sentido para balancear a carga, por exemplo, para
gerar os ebooks em paralelo. Outra motivação é aumentar a disponibilidade. Quando um subscriber está
indisponível, outro pode assumir a tarefa.

Já no JMS 2, podemos utilizar o Shared Consumer para fazer com que vários subscribers tenham o
mesmo client ID para conseguir consumir as mensagens do tópico:

Para criar um shared subscriber utilizamos o método createSharedConsumer ou


createSharedDurableConsumer (no caso de uma assinatura durável) passando como argumento o
tópico que será utilizado e o nome da subscription:
try(JMSContext context = factory.createContext(usuario, senha)) {
//sem clientId

Topic topico = // inicializa o tópico


JMSConsumer consumer = context.createSharedConsumer(
topico, "AssinaturaEbook");
}

Para mais informações sobre o shared subscriber, leia o post no blog da Oracle:

136 5.23 PARA SABER MAIS SHARED CONSUMER DO JMS 2


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
http://www.oracle.com/technetwork/articles/java/jms2messaging-1954190.html

5.24 ACTIVEMQ, RABBITMQ, APACHE APOLLO E OUTROS


O conceito de MOM já existia antes do JMS e é muito usado no mundo dos Mainframes. JMS é
somente uma padronização Java desse conceito. Como JMS é uma especificação, há várias
implementações dela (JMS Providers), como por exemplo, as implementações open source da Apache
(ActiveMQ,) do Glassfish (Open Message Queue) ou do JBoss (HornetQ). Existem várias outras
implementações open source e comercias, por exemplo da IBM (MQ Series) ou da Oracle AQ.

Algumas implementações estendem a funcionalidades do JMS e oferecem mais funções como


garantia de entrega da mensagem em tempo real, load balancing, auditoria e segurança.

O Apache Apollo é um novo MOM mantido pela Apache Software Foundation que se basea nas
ideias do ActiveMQ mas usa uma outra arquitetura internamente. Apache Apollo ainda não possui
todos os feature do ActiveMQ nem tantos usuários mas pode ser considerado o sucessor do ActiveMQ.

Mais sobre Apollo se encontra no site:

http://activemq.apache.org/apollo/index.html

Um outro MOM popular é o RabbitMQ que foi escrito em Erlang e usa AMQP como Wire-level
protocol. Ele se destaca pela simples configuração e por oferecer vários clientes. O RabbitMQ é mantido
pela SpringSource, ou seja, se integra muito bem com o Spring. Mais informações estão disponíveis no
site:

http://www.rabbitmq.com/

5.25 MENSAGERIA NA CLOUD


Também há serviços que oferecem mensageria como Plataform-as-a-service. Nesse caso não é
preciso se preocupar com a configuração do broker. Detalhes da configuração relacionada com o
desempenho, a redundância ou a segurança estão escondidos atrás de um serviço. Usamos apenas este
serviço aproveitando aquela funcionalidade, seguindo a ideia principal do SOA.

Normalmente esses serviços só cobram pelo uso e não há nenhum custo antecipado. Além disso, eles
dão garantias fortes sobre o funcionamento do serviço (Service Level Aggreement - SLA).

Alguns serviços disponíveis são:

Amazon SQS
IronIO/MQ
Oracle Messaging Cloud Service
entre outros

5.24 ACTIVEMQ, RABBITMQ, APACHE APOLLO E OUTROS 137


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
No blog da Caelum existe um artigo que mostra como funciona um cliente do Amazon SQS:

http://blog.caelum.com.br/mensageria-com-amazon-sqs/

5.26 MENSAGENS INDEPENDENTES DE PLATAFORMA: AMQP, STOMP


E REST
Vamos supor agora que precisamos fazer nosso sistema se comunicar de forma assíncrona com uma
aplicação desenvolvida em outra linguagem/plataforma. Imagine um cenário onde precisamos notificar
assincronamente uma aplicação Ruby sobre alguma operação realizada em nossa livraria. Como
poderíamos fazer isso? Se desejássemos fazer essa comunicação de forma síncrona, o que poderíamos
usar?

Assim como fazemos para nos integrar de forma síncrona entre plataformas diferentes utilizando o
protocolo SOAP, seria interessante se pudéssemos contar com um protocolo semelhante para troca de
mensagens assíncronas. Para atender a essa necessidade surgiu o Advanced Message Queuing Protocol
(Protocolo Avançado para Fila de Mensagens), conhecido como AMQP.

Basicamente, o AMQP define regras de comunicação na camada de aplicação, que permitem


transmitir dados de um ponto A a um ponto B, sem se preocupar com a linguagem/plataforma e,
principalmente, de forma independente de um fornecedor específico. O AMQP está baseado em
características fortes como: confiabilidade, segurança e interoperabilidade, em cima de um padrão aberto
que serve como ponto de partida para diversas soluções.

Apesar de ser um padrão aberto para troca de mensagens, ao contrário do JMS o AMQP não define
ou especifica uma API que diz como código deve ser escrito. O AMQP define regras de comunicação na
camada de aplicação assim como outros conhecidos _wire-level protocol_s: IIOP (CORBA), JRMP
(RMI) e SOAP (Web Services).

Embora o AMQP tenha uma proposta muito interessante e promissora, atualmente o mercado está
bastante dividido entre as versões 0.91 e 1.0 do protocolo, que não conversam entre si, ou seja, produtos
implementados utilizando as definições da versão 0.91 do AMQP não são capazes de se comunicar com
produtos que implementam as definições da versão 1.0.

Por conta disso, conheceremos agora uma solução que, embora não tão rebuscada quanto o AMQP,
tem sido bastante utilizada para comunicação assíncrona entre diferentes plataformas: o Stomp (Simple
Text Oriented Messaging Protocol).

O Stomp é um protocolo baseado em texto, com um design simples e que utiliza muitas ideias
extraídas do HTTP, sobre como se comunicar remotamente. O Stomp tem como premissa que clientes
sejam fáceis de implementar e que com pouca implementação, seja possível se conectar e consumir
mensagens de um broker. Você pode, inclusive, consumir mensagens a partir de um cliente TELNET!

138 5.26 MENSAGENS INDEPENDENTES DE PLATAFORMA: AMQP, STOMP E REST


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Segue um exemplo de uma mensagem com cabeçalhos usando Stomp:

SEND
destination:/topico/TOPICO.LIVRARIA
content-type:text/plain

Oi Fila!
^@

http://stomp.github.io/stomp-specification-1.2.html

A forma de integração entre as aplicações continua sendo a mesma representada na figura acima,
mesmo se estivéssemos utilizando o AMQP. Ou seja, a aplicação Java continua se comunicando com o
HornetQ via JMS da forma que já estamos acostumados e a aplicação Ruby se comunica com o HornetQ
utilizando protocolo específico.

Além do Stomp e AMQP existem Brokers que oferecem uma interface REST para acessar uma fila ou
tópico. Entre esses brokder está o HornetQ. Usando REST tem a grande vantagem de aproveitar toda
infraestrutura do protocolo HTTP e ganhar assim conectividade e interopabilidade. Podemos, por
exemplo, continuar usando a ferramenta curl para testar as filas e tópicos do HornetQ!

Vamos ver um pequeno exemplo para mostrar como funciona REST com HornetQ.A interface REST
do HornetQ exige uma requisição do tipo HEAD ou GET como ponto de partida. Na URI da requisição
colocamos o nome da fila ou tópico. A fila/tópico se tornam um recurso. Veja o exemplo da requisição:

HEAD /queues/jms.queue.FILA.GERADOR HTTP/1.1


Host: localhost

A resposta do HornetQ devolve o código 200, caso exista a fila, e alguns cabeçalhos personalizados.

5.26 MENSAGENS INDEPENDENTES DE PLATAFORMA: AMQP, STOMP E REST 139


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Esses cabeçalhos da resposta representam os próximos passos a seguir. Por exemplo, se quisermos criar
uma nova mensagm devemos usar o cabeçalho msg-create :
HTTP/1.1 200 Ok

msg-create: http://example.com/queues/jms.queue.FILA.GERADOR/create
msg-pull-consumers: http://example.com/queues/jms.queue.FILA.GERADOR/pull-consumers
msg-push-consumers: http://example.com/queues/jms.queue.FILA.GERADOR/push-consumers

... outros cabeçalhos omitidos

Para criar uma nova mensagem basta então enviar um HTTP POST com os dados da mensagem no
corpo da requisição usando a URI definido no msg-create :
POST /queues/jms.queue.FILA.GERADOR/create
Host: example.com
Content-Type: application/xml

<pedido>
<data>2013-12-05</data>
<itens>
...
</itens>
</pedido>

Como resposta recebemos o código 201 e novamente um cabeçalho para criar a próxima mensagem:

HTTP/1.1 201 Created


msg-create-next: http://example.com/queues/jms.queue.FILA.GERADOR/create

Através da interface REST podemos definir também outras propriedades da mensagem como a
prioridade ou expiração da mensagem entre várias outras configurações. Mais informações na
documentação do HornetQ:

http://docs.jboss.org/hornetq/2.4.0.Final/docs/user-manual/html/rest.html

A imagem abaixo ilustra a comunicação heterogênea através dos protocolos Stomp, AMQP e REST
(HTTP) usando plataformas diferentes:

140 5.26 MENSAGENS INDEPENDENTES DE PLATAFORMA: AMQP, STOMP E REST


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
5.26 MENSAGENS INDEPENDENTES DE PLATAFORMA: AMQP, STOMP E REST 141
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 6

CRIAÇÃO DO MODELO CANONICAL

"Se nós não tivéssemos defeitos, não teríamos tanto prazer em notá-los nos outros." -- François de La
Rochefoucauld

6.1 MENSAGENS - XML


Quando um sistema de uma empresa se integra a um outro sistema, ambos devem trocar
informações (dados). Por exemplo, a nossa loja deve enviar ao sistema financeiro ou logístico, dados
sobre livros vendidos ou sobre algum serviço prestado. Dessa forma, é fundamental que um formato
para as mensagens que são trocadas entre os sistemas seja definido.

Esse formato poderia ser binário, ou seja, as mensagens definidas em "0" e "1". Se fosse dessa
maneira, haveria muitas possibilidades de otimização em relação à performance. Porém, a
implementação dos sistemas que querem se integrar, por exemplo, ao sistema do governo, seria bem
mais complicada, pois nem toda tecnologia oferece recursos simples e adequados para se trabalhar com
dados em formato binário.

Normalmente, as tecnologias utilizadas hoje em dia no desenvolvimento de software trabalham


melhor com dados em formato texto. Um dos formatos mais conhecidos e com maior suporte nas mais
variadas tecnologias é o XML. Então é extremamente razoável que as mensagens trocadas entre sistemas
implementados em tecnologias diferentes sejam definidas em XML.

XML (eXtensible Markup Language) é um formato de texto com diversos usos e, em especial, pode
guardar dados, configurações e informações com metainformações hierarquizadas. Metainformação
nada mais é do que uma informação sobre a informação, assim como uma anotação permite adicionar
uma metainformação a classes, métodos e atributos. Por exemplo, se é necessário armazenar o título de
um livro, o formato XML permite que tanto o valor do campo quanto o nome do campo que guarda esse
valor sejam explicitamente definidos e agrupados.

<titulo>Arquitetura Java</titulo>

O formato XML permite também que dados compostos sejam armazenados de maneira hierárquica.
Por exemplo, suponha que seja necessário armazenar um livro com seu título e valor. Nesse caso, basta
encadear tags, como mostra o exemplo abaixo:
<livro>
<titulo>Arquitetura Java</titulo>

142 6 CRIAÇÃO DO MODELO CANONICAL


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<valor>29.90</valor>
</livro>

É importante observar que, uma vez que os dados são armazenados juntamente com
metainformação, se alguém quiser recuperar um determinado campo, basta procurar pelo nome do
campo.

Resumindo as principais características do XML:

Linguagem de Marcação (delimitação): Todos os campos de um XML possuem delimitadores.


Por motivos de flexibilidade e de extensibilidade, o XML optou por usar tags para delimitar os
campos.

Exemplo:
<nome>José</nome>

Possui fins gerais e extensíveis: Não temos limitação sobre o tipo de informação que podemos
guardar em arquivo XML, basta a aplicação que for ler o XML saber o que procurar. Há
diversos exemplos de XML que guardam informações (usados para troca de dados),
documentos (como arquivos do OpenOffice), configurações (veja a configuração do Tomcat) e
até imagens (por exemplo, arquivos *.svg).

Hierárquico: Um XML é uma estrutura intrinsecamente hierárquica, como uma árvore, com
campos dentro de outros campos.

Exemplo:

<empresa>
<nome>Pamonha de Piracicaba Ltda.</nome>
<cnpj>00.000.000/0001-00</cnpj>
<funcionarios>
<funcionario>
<nome>João da Silva</nome>
</funcionario>
<funcionario>
<nome>Maria da Silva</nome>
</funcionario>
</funcionarios>
</empresa>

LINGUAGEM DE MARCAÇÃO

Uma linguagem de marcação não necessariamente precisa ser delimitada por tags (\), ela pode
ser definida de qualquer maneira que demonstre onde um campo começa e onde ele termina:
João da Silva|39|Maria da Silva|37

6.1 MENSAGENS - XML 143


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
6.2 MODELO CANONICAL E O SEU CONTEXTO
A nossa loja já está gerando um pedido quando a compra foi finalizada. Esse pedido será enviado
para outros sistemas que ficam escutando no topico JMS (ebook, financeiro, royalities etc). Faz todo
sentido padronizar a apresentação do pedido fora da aplicação já que ele será compartilhado.

Ou seja, depois ter criado a classe Pedido baseado em boas práticas do mundo OO (DDD),
podemos padronizar e definir o modelo canônico que é normalmente um XSD.

Importante é saber que, ter um Modelo Canonical não significa ter um único modelo para todos os
serviços e integrações. Um modelo tem um contexto que vem do mundo de negócio (como a finalização
de uma compra, no nosso exemplo) e o modelo canonical padroniza a troca de informações para este
contexto. Esse contexto já foi descrito bem antes do mundo SOA surgir, é o Bounded Context do livro
Domain Driven Design - Eric Evans.

ENTERPRISE INTEGRATION PATTERN - CANONICAL DATA MODEL

Design a Canonical Data Model that is independent from any specific application. Require each
application to produce and consume messages in this common format.

http://www.eaipatterns.com/CanonicalDataModel.html

6.3 INTRODUÇÃO AO JAXB


Como foi dito anteriormente, praticamente todas as tecnologias utilizadas hoje em dia para
desenvolver software oferecem ótimo suporte para trabalhar com XML. Em particular, o Java definiu
uma especificação justamente para possibilitar ao desenvolvedor Java manipular dados no formato XML
facilmente. Essa especificação é chamada de Java Architecture for XML Binding (JAXB).

As principais funcionalidades definidas pela especificação JAXB são:

Mapear os tipos do Java para tipos do XML e vice-versa.


Transformar um objeto Java em um XML (marshal).
Transformar um XML em um objeto Java (unmarshal).

Para que os objetos Java sejam corretamente transformados em XML e vice-versa, é necessário
acrescentar algumas anotações nas classes correspondentes. Por exemplo, usando a classe para modelar
livros:
public class Livro implements Serializable{

private static final long serialVersionUID = 1L;

144 6.2 MODELO CANONICAL E O SEU CONTEXTO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
private String codigo;
private String titulo;
private String nomeAutor;
private BigDecimal valor;

Para que os objetos dessa classe possam ser transformados em XML, acrescentamos a anotação
@XmlRootElement nela.

@XmlRootElement
public class Livro {

Feito isso, o próximo passo é instanciar objetos da classe Livro e os transformar em XML. O
responsável por executar o processo de marshal no JAXB é o Marshaller, e este é obtido através de um
JAXBContext que possui as informações sobre as classes anotadas.

Livro livro = new Livro();


livro.setCodigo("ARQ");
livro.setTitulo("Arquitetura Java");
livro.setValor(new BigDecimal("29.90"));

JAXBContext context = JAXBContext.newInstance(Livro.class);


Marshaller marshaller = context.createMarshaller();
marshaller.marshal(livro, new FileOutputStream("livro.xml"));

Ao executar esse código, o arquivo livro.xml é gerado e armazena os dados do livro. O conteúdo
desse arquivo deve ser mais ou menos assim:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<livro>
<codigo>ARQ</codigo>
<nomeAutor>Paulo Silveira</nomeAutor>
<titulo>Arquitetura Java</titulo>
<valor>29.90</valor>
</livro>

Em seguida, o arquivo livro.xml pode ser lido e transformado em objeto novamente. O


responsável pelo processo de unmarshal é o Unmarshaller, que assim como o Marshaller, é obtido
através de um JAXBContext.
JAXBContext context = JAXBContext.newInstance(Livro.class);
Unmarshaller unmarshaller = context.createUnmarshaller();

Livro livro = (Livro) unmarshaller.unmarshal(new File("livro.xml"));


System.out.println(livro.getNome());

6.4 SERIALIZANDO DADOS COMPOSTOS


Agora, suponha que todo livro tem uma categoria. Nessa caso, faz sentido que os objetos da classe
Livro estejam associados a objetos de uma classe chamada Categoria . Em outras palavras, faz
sentido ter um atributo do tipo Categoria na classe Livro .

@XmlRootElement
public class Livro {

6.4 SERIALIZANDO DADOS COMPOSTOS 145


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
//...
private Categoria categoria;
}

public class Categoria {


private String nome;

// GETTERS E SETTERS
}

Ao executar o processo de marshal em um objeto da classe livro, automaticamente os dados da


categoria são também adicionados no XML. O XML deve ficar mais ou menos assim:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<livro>
<codigo>ARQ</codigo>
<nomeAutor>Paulo Silveira</nomeAutor>
<titulo>Arquitetura Java</titulo>
<valor>29.90</valor>
<categoria>
<nome>TI</nome>
</categoria>
</livro>

6.5 USANDO OBJECTFACTORY


O método newInstance da classe JAXBContext é sobrecarregado. Há uma versão que recebe uma
String que representa o nome do pacote, por exemplo:

JAXBContext context = JAXBContext.newInstance("br.com.caelum.jaxb");

Para o contexto conhecer as classes do pacote, devemos criar um arquivo chamado jaxb.index , e
nele adicionar os nomes das classes a serem reconhecidas:
Livro
Categoria

Alternativamente podemos definer uma classe ObjectFactory dentro do pacote. Nesse caso o
JAX-B instancia as classes em questão pela fábrica. A classe ObjectFactory fica assim:
@XmlRegistry
public class ObjectFactory {

public static Livro createLivro() {


return new Livro();
}
}

6.6 EXERCÍCIOS: SERIALIZAÇÃO PARA XML COM JAX-B


1. Crie um novo projeto Java chamado fj36-jaxb.

2. Crie um Java Bean para modelar livros no pacote br.com.caelum.jaxb e anote-o com

146 6.5 USANDO OBJECTFACTORY


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@XmlRootElement , para que os livros possam ser transformados em XML.

@XmlRootElement
public class Livro {

private String codigo;


private String titulo;
private String nomeAutor;
private BigDecimal valor;

//Getters e Setters aqui através de CTRL+3 -> GGAS


}

3. Faça uma classe para testar o processo de marshal de um livro no pacote br.com.caelum.jaxb.
public class TesteMarshal {

public static void main(String[] args) throws Exception {


Livro livro = new Livro();
livro.setCodigo("ARQ");
livro.setTitulo("Arquitetura Java");
livro.setNomeAutor("Paulo Silveira");
livro.setValor(new BigDecimal("29.90"));

JAXBContext context = JAXBContext.newInstance(Livro.class);


Marshaller marshaller = context.createMarshaller();
marshaller.marshal(livro, new FileOutputStream("livro.xml"));
}
}

Você pode formatar o XML usando uma propriedade do Marshaller :


marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

4. Execute a classe TesteMarshal. Selecione o projeto fj36-jaxb e digite F5 para que a estrutura de
diretórios seja atualizada e o arquivo livro.xml apareça. Veja o conteúdo desse arquivo.

5. Faça uma classe para testar o processo de unmarshal do livro que foi transformado em XML no
pacote br.com.caelum.jaxb.

public class TesteUnmarshal {

public static void main(String[] args) throws JAXBException {

JAXBContext context = JAXBContext.newInstance(Livro.class);


Unmarshaller unmarshaller = context.createUnmarshaller();

Livro livro = (Livro) unmarshaller.unmarshal(new File("livro.xml"));


System.out.println(livro.getTitulo());
}
}

Execute a classe TesteUnmarshal .

6. Faça um Java Bean para modelar categorias no pacote br.com.caelum.jaxb.


public class Categoria {
private String nome;

6.6 EXERCÍCIOS: SERIALIZAÇÃO PARA XML COM JAX-B 147


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
// COLOQUE OS GETTERS E SETTERS AQUI!!!
}

Adicione um atributo na classe Livro, para associar os livros às categorias. Adicione também os
respectivos get e set .
@XmlRootElement
public class Livro {
//...

private Categoria categoria;

// GETTERS E SETTERS

7. Modifique o TesteMarshal para fazer o marshal de uma categoria associada a um livro. Acrescente o
código a seguir no teste, após a linha livro.setValor(..) :
Categoria categoria = new Categoria();
categoria.setNome("TI");
livro.setCategoria(categoria);

Execute esse teste novamente e verifique o arquivo livro.xml .

(Opcional) Modifique a classe TesteUnmarshal para que ele imprima o nome da categoria do livro.
Execute-a e veja o resultado.

8. (opcional) Por padrão, o JAX-B usa os getters e setters para saber quais propriedades fazem parte do
XML gerado. É possível redefinir este padrão de acesso pela anotação @XmlAccessorType . Além
disso, podemos definir a ordem das propriedades pela anotação @XmlAccessorOrder .

Altere a classe Livro de forma:


@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)
public class Livro {

Execute a classe TesteMarshal novamente e verifique o arquivo livro.xml .

6.7 DEFINIÇÃO DE TIPOS - XML SCHEMA DEFINITION (XSD)


Vimos que um objeto Java pode facilmente ser serializado em forma de XML e vice-versa, por meio
do JAXB. Os objetos são construídos de acordo com o que foi definido na sua classe. Da mesma maneira
que podemos encarar uma classe como uma "receita" para criação de objetos, seria interessante haver
uma maneira de definir uma "receita" para criar documentos XML. No XML, o Schema é analogo às
classes da orientação a objetos. Um Schema define tudo sobre tags que podem ser utilizadas dentro de
um XML. Por exemplo: nome e tipo dos atributos da tag, conteúdo do corpo da tag, onde a tag pode ser
inserida, obrigatoriedade, entre outros.

148 6.7 DEFINIÇÃO DE TIPOS - XML SCHEMA DEFINITION (XSD)


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Vejamos um exemplo concreto de um Schema XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="livro" type="livro"/>

<xs:complexType name="livro">
<xs:sequence>
<xs:element name="valor" type="xs:decimal" minOccurs="0"/>
<xs:element name="titulo" type="xs:string" minOccurs="0"/>
<xs:element name="nomeAutor" type="xs:string" minOccurs="0"/>
<xs:element name="codigo" type="xs:string" minOccurs="0"/>
<xs:element name="categoria" type="categoria" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="categoria">
<xs:sequence>
<xs:element name="nome" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

Um detalhe que normalmente pode dar um nó na cabeça é o fato dos Schemas, que servem para
definir XML, serem definidos em XML. Isso é possível porque há um Schema de Schemas. O leitor pode
notar que, na segunda linha do XML acima, há a indicação do Schema para definir outros Schemas.
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">

Além dos tipos simples que já existem no XML e que podem ser utilizados dentro de um Schema, há
a possibilidade de definir novos tipos, que são os chamados tipos complexos. Um tipo complexo pode
ser baseado em outros tipos complexos e/ou em tipos simples. Vejamos um exemplo que define um tipo
complexo, chamado categoria:

<xs:complexType name="categoria">
<xs:sequence>
<xs:element name="nome" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

No Schema do tipo complexo categoria, está definido que toda categoria é composta de um elemento
chamado nome, do tipo simples string. Inclusive, é definida a quantidade mínima de vezes que o
elemento nome deve aparecer em uma categoria. Um outro exemplo de definição de um tipo complexo é
o Schema para os livros.
<xs:complexType name="livro">
<xs:sequence>
<xs:element name="valor" type="xs:decimal" minOccurs="0"/>
<xs:element name="titulo" type="xs:string" minOccurs="0"/>
<xs:element name="nomeAutor" type="xs:string" minOccurs="0"/>
<xs:element name="codigo" type="xs:string" minOccurs="0"/>
<xs:element name="categoria" type="categoria" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

6.7 DEFINIÇÃO DE TIPOS - XML SCHEMA DEFINITION (XSD) 149


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Nesse outro exemplo, perceba que o tipo complexo livro foi definido com base em elementos de
outro tipo complexo (categoria) e em elementos de alguns tipos simples. Inclusive é possível notar a
utilização do elemento sequence, que serve para definir a ordem na qual os elementos que formam um
livro devem aparecer.

Por fim, os elementos que devem ser utilizados fora de qualquer outro elemento são definidos
diretamente no elemento schema.

...
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="livro" type="livro"/>
...

Então, o próximo passo é verificar como a especificação JAXB nos ajuda na geração de Schema. Um
Schema está para um conjunto de classes assim como um XML está para um conjunto de objetos. Logo,
devemos gerar um Schema a partir de um conjunto de classes, assim como geramos um XML a partir de
um conjunto de objetos. Veja o exemplo abaixo:

JAXBContext context = JAXBContext.newInstance(Livro.class);


context.generateSchema(new SchemaOutputResolver() {
@Override
public Result createOutput(String namespaceUri,
String suggestedFileName) throws IOException {
StreamResult result = new StreamResult(new File("schema.xsd"));
return result;
}
});

Esse código determina que a classe Livro e as suas dependentes devem ser mapeadas em um XSD,
que vai ser colocado no arquivo chamado schema.xsd.

PARA SABER MAIS

Algumas customizações podem ser feitas no XSD gerado. Por exemplo, o nome de um tipo
complexo.

@XmlType(name="cat")
public class Categoria ...

6.8 IDENTIFICAÇÃO PELO NAMESPACE


Outro conceito importante que encontramos no XML é o de namespace. Um namespace serve para
definir o contexto de um elemento. Isso é útil para diferenciar dois elementos com o mesmo nome, que
servem para conceitos diferentes. Uma analogia ao namespace do XML são os pacotes do Java. Veja o
exemplo abaixo:
<caelum:aluno xmlns:caelum="http://www.caelum.com.br/fj36">

150 6.8 IDENTIFICAÇÃO PELO NAMESPACE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<caelum:nome>José da Silva</caelum:nome>
<caelum:telefone>1234-1234</caelum:telefone>
<caelum:curso>FJ-36</caelum:curso>
</caelum:aluno>

A definição do namespace para os elementos que queremos criar deve ficar no XSD. Essa informação
é inserida com o uso do atributo targetNamespace. O targetNamespace associa um namespace com o
Schema que está sendo craido:
<xs:schema
version="1.0"
targetNamespace="http://www.caelum.com.br/fj36"
...

Para utilizar os elementos definidos nesse Schema, é necessário adicionar o namespace no XML por
meio do atributo xmlns.
<caelum:livro xmlns:caelum="http://www.caelum.com.br/fj36">

Perceba, inclusive, que foi dado um "apelido" para o namespace, com o intuito de não usar o nome
verdadeiro dele toda vez que um elemento desse namespace for utilizado (xmlns:caelum).

Novamente, o JAXB oferece uma maneira de definir um targetNamespace de um XSD. Para definir
um targetNamespace, é necessário criar um arquivo chamado package-info.java, com um conteúdo
parecido com o do código abaixo.

@XmlSchema(namespace="http://www.caelum.com.br/fj36")
package br.com.caelum.jaxb;

import javax.xml.bind.annotation.XmlSchema;

Pela anotação também podemos configurar o prefixo utilizado:

@javax.xml.bind.annotation.XmlSchema (
xmlns = {
@javax.xml.bind.annotation.XmlNs(prefix = "caelum",
namespaceURI="http://www.caelum.com.br/fj36"),

@javax.xml.bind.annotation.XmlNs(prefix="xs",
namespaceURI="http://www.w3.org/2001/XMLSchema")
}
)
package br.com.caelum.jaxb;

Agora, ao gerar o XSD com os exercícios feitos nas seções anteriores, um código parecido com o que
segue abaixo seria criado. Perceba a utilização do apelido para o targetNamespace (tns).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
targetNamespace="http://www.caelum.com.br/fj36"
xmlns:tns="http://www.caelum.com.br/fj36"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="livro" type="tns:livro"/>

<xs:complexType name="livro">
<xs:sequence>

6.8 IDENTIFICAÇÃO PELO NAMESPACE 151


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
...
</xs:sequence>
</xs:complexType>

<xs:complexType name="categoria">
<xs:sequence>
...
</xs:sequence>
</xs:complexType>
</xs:schema>

USANDO OBJECTFACTORY PARA A CRIAÇÃO DO MODELO

O JAXB exige, por padrão, um construtor sem argumentos na classe que foi mapeada para o
XML. No entanto, há uma forma de fugir desse construtor e usar uma fábrica. Assim estamos livre
de usar qualquer construtor no modelo.

Qual fábrica a usar é indicado pela anotação @XmlType :


@XmlRootElement
@XmlType(
propOrder={"valor", "titulo", "nomeAutor", "codigo", "categoria"},
factoryClass=LivroFactory.class,
factoryMethod="createLivro")
public class Livro {

A classe LivroFactory possiu o método createLivro() que instancia o Livro :

public class LivroFactory {

public static Livro createLivro() {


return new Livro(); //usando qualquer construtor
}

Repare, quando geramos o cliente de um Webservice através do wsimport , essa fábrica foi
criada automaticamente e foi chamada de ObjectFactory .

6.9 EXERCÍCIOS: SCHEMA E NAMESPACE


1. Faça uma classe para testar o processo de geração de Schema no pacote br.com.caelum.jaxb, do
projeto fj36-jaxb.
public class TesteGeraSchema {
public static void main(String[] args) throws Exception {
JAXBContext context = JAXBContext.newInstance(Livro.class);
context.generateSchema(new SchemaOutputResolver() {
@Override
public Result createOutput(String namespaceUri,
String suggestedFileName) throws IOException {
StreamResult result = new StreamResult(new File("schema.xsd"));
return result;

152 6.9 EXERCÍCIOS: SCHEMA E NAMESPACE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
}
});
}
}

2. Execute o teste, atualize o projeto com o F5 e veja o arquivo schema.xsd gerado.

3. (Opcional) Utilize a anotação @XmlType para mudar o nome do tipo complexo que representa a
Categoria .

@XmlType(name="CAT")
public class Categoria {
....
}

4. Crie um arquivo chamado package-info.java no pacote e br.com.caelum.jaxb com o seguinte


código:
@javax.xml.bind.annotation.XmlSchema(
namespace="http://www.caelum.com.br/fj36")
package br.com.caelum.jaxb;

O package-info.java é o substituto do antigo package-info.html , onde colocávamos o


javadoc do pacote. Agora costumamos colocar anotações referentes a todo o pacote nesse arquivo
java, que fica sem classes.

5. Execute a classe TesteGeraSchema e veja o arquivo schema.xsd gerado.

6. Execute a classe TesteMarshal e veja o arquivo livro.xml gerado.

7. (Opcional) Alternativamente, existe o comando xjc para gerar classes Java a partir de um arquivo
XSD.

Abra um terminal e entre na pasta raiz do seu projeto fj36-jaxb. Execute:

xjc schema.xsd -d src -p br.com.caelum.generated

Com este comando será criado o diretório generated e, dentro dele, as classes java relativas ao
XSD. Atualize o seu projeto no Eclipse e verifique o novo package.

6.10 EXERCÍCIOS OPCIONAIS: VALIDAÇÃO COM XSD


1. No Eclipse, no projeto fj36-jaxb, abra o arquivo schema.xsd. Nele, crie uma restrição para o
elemento codigo . Adicione no final do arquivo, mas antes do elemento </xs:schema> :
<xs:simpleType name="codigo">
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z]{3}" />
</xs:restriction>
</xs:simpleType>

Repare que criamos um novo tipo com o nome codigo . Trata-se de uma string de três

6.10 EXERCÍCIOS OPCIONAIS: VALIDAÇÃO COM XSD 153


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
caracteres maiúsculos.

2. Ainda no arquivo schema.xsd altere o elemento codigo . Procure xs:element name="codigo"


e altere os atributos type e minOccurs :
<xs:element name="codigo" type="tns:codigo" minOccurs="1" />

Dessa forma, o elemento codigo é obrigatório e se baseia nas restrições do tipo tns:codigo .

3. Crie a classe ValidationHandler no pacote br.com.caelum.jaxb :


public class ValidationHandler implements ErrorHandler {

@Override
public void error(SAXParseException exception) throws SAXException {
System.out.println(exception.getMessage());
}

//Outros métodos warning e fatalError, com a mesma implementação

4. Crie uma nova classe TesteValidacao , no pacote br.com.caelum.jaxb , e gere o método main .

Faça a validação:

Atenção: A classe Validator é do pacote javax.xml.validation.* .

public class TesteValidacao {

public static void main(String[] args)


throws JAXBException, SAXException, IOException {

Livro livro = new Livro();


livro.setCodigo("arq"); //codigo deve ser maiúsculo

JAXBContext context = JAXBContext.newInstance(Livro.class);


JAXBSource source = new JAXBSource(context, livro);

SchemaFactory sf = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("schema.xsd"));

Validator validator = schema.newValidator();


validator.setErrorHandler(new ValidationHandler());
validator.validate(source);

}
}

Rode a classe e verifique o console. Como o código do livro é inválido e devem aparecer mensagens
de validação.
...
cvc-type.3.1.3: The value 'arq' of element 'codigo' is not valid.

5. Crie mais um tipo específico no XSD para o elemento valor :

154 6.10 EXERCÍCIOS OPCIONAIS: VALIDAÇÃO COM XSD


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<xs:simpleType name="valor">
<xs:restriction base="xs:decimal">
<xs:minExclusive value="0"/>
<xs:fractionDigits value="2"/>
</xs:restriction>
</xs:simpleType>

Altere o elemento valor . Procure xs:element name="valor" e altere os atributos type e


minOccurs :

<xs:element name="valor" type="tns:valor" minOccurs="1" />

Teste a validação do valor. Crie um livro com um valor negativo.

6. (Opcional) Você também pode validar o arquivo XML com o mesmo código. Na classe
TesteValidacao basta substituir a linha:

validator.validate(source);

com:
validator.validate(new StreamSource(new File("livro.xml")));

6.11 SERIALIZANDO JSON: MOXY, JACKSON E JETTISON


Como podemos serializar objetos Java para XML, também podemos serializar objetos para JSON e
vice-versa. De fato, já vimos isso no capítulo JAX-RS. O provedor JAX-RS usa por baixo dos panos uma
biblioteca que faz o binding entre Java e JSON. Essa biblioteca depende do provedor JAX-RS, pois não há
um padrão Java EE.

Por exemplo, a implementação referencial do JAX-RS, o Jersey, usa MOXy para JSON-Binding.
MOXy é um projeto do Eclipse Foundation que segue a especificação JAX-B e consegue gerar XML
tanto quanto JSON. Segue um pequeno exemplo:
JAXBContext ctx = JAXBContext.newInstance(Pagamento.class);

Unmarshaller um = ctx.createUnmarshaller();
um.setProperty("eclipselink.media-type", "application/json");
um.setProperty("eclipselink.json.include-root", false);

Pagamento pagamento = (Pagamento) um.unmarshal(new File("pagamento.json"));

Outra biblioteca com o mesmo propósito é o Jackson. Com ele, usamos um ObjectMapper para ler
e escrever JSON:
ObjectMapper mapper = new ObjectMapper();
Pagamento pgmt = mapper.readValue(new File("pagamento.json"), Pagamento.class);
pgmt.comStatusConfirmado();
mapper.writeValue(new File("pagamento-confirmado.json"), pgmt);

6.11 SERIALIZANDO JSON: MOXY, JACKSON E JETTISON 155


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
JSON-PROCESSING: JSR-353

No JavaEE 7, entrou uma nova especificação para o processamento de JSON. Qualquer


processamento JSON, antes do Java EE 7, era baseado em uma biblioteca de terceiro, como o
Jettison.

A especificação JSON-P tem o mesmo propósito do JAX-B, do mundo XML. JSON-P define
uma API de baixo nível para a escrita e leitura de documentos JSON e possui uma API de
Streaming, parecido com o StAX, e um object model, parecido com o DOM.

JSON-P não é uma API de Binding como o JAX-B. O Java EE ainda não possui uma
especificação que padroniza as funcionalidades que MOXy ou Jackson oferecem.

6.12 EXERCÍCIOS OPCIONAIS: SERIALIZANDO JSON COM JACKSON


1. Copie a pasta caelum/36/jars/lib-jackson para a pasta raíz do seu projeto fj36-jaxb e configure
o classpath.

2. No projeto fj36-jaxb, crie uma nova classe TesteMarshalJson , no pacote br.com.caelum.jaxb ,


com o método main . O método instancia um ObjectMapper para escrever JSON baseado no
Livro :

public class TesteMarshalJson {

public static void main(String[] args) throws JsonGenerationException,


JsonMappingException, IOException {

Livro livro = new Livro();


livro.setCodigo("ARQ");
// outros setters

ObjectMapper mapper = new ObjectMapper();


mapper.writeValue(new File("livro.json"), livro);

}
}

3. Execute o método main e verifique se o arquivo livro.json foi gerado na raíz do projeto (F5).

156 6.12 EXERCÍCIOS OPCIONAIS: SERIALIZANDO JSON COM JACKSON


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4. Também teste o método readValue(..) do ObjectMapper .

6.13 VALIDANDO JSON COM JSON SCHEMA


Nos últimos anos o formato JSON ficou muito popular para trocar informações, principalmente por
causa do uso extenso de AJAX atrvés do navegador. O JSON ficou tão popular pois define uma estrutura
simples, fácil de se entender e menos verboso do que XML. Hoje em dia o JSON é onipresente e é
amplamente utilizado nos serviços REST.

No entanto, uma grande desvantagem do JSON é a falta de um contrato que define quais dados são
esperados e formaliza as regras de validação. Como existe no mundo XML o XSD com essa
responsabilidade também deve ter algo no mundo JSON para validar e documentar o formato de dados.
Com solução foi criado o JSON Schema que possui o mesmo papel do XSD.

Por exemplo, imagine que queremos definir as regras para o JSON abaixo:

{
"id": 3,
"codigo": "ARQ",
"titulo": "Arquitetura Java",
"nomeAutor": "Paulo Silveira",
"valor": 29.90
}

Com o JSON Schema em mãos descrevemos o objeto propriedade por propriedade, definindo
claramente qual tipo cada uma possui:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Livro",
"description": "Um livro do aplicação FJ-36 Livraria",
"type": "object",
"properties": {
"id": {
"description": "identificador do Livro",
"type": "integer"
},
"codigo": {
"description": "codigo do Livro",
"type": "string"
},
"titulo": {
"description": "o titulo do Livro",
"type": "string"
},
"nomeAutor": {
"description": "Nome do Autor",
"type": "string"
},
"valor": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
}
},

6.13 VALIDANDO JSON COM JSON SCHEMA 157


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
"required": ["id", "titulo", "nomeAutor", "valor"]
}

O suporte de bibliotecas e ferramentas para utilizar o JSON Schema não é tão amplo quanto ao XSD
mas com tempo vão surgir mais opções. Uma biblioteca no mundo Java que sabe validar um JSON
baseado no Schema é o json-schema-validator. Veja como o código é parecido com o do JAX-B:
JsonNode schemaNode = JsonLoader.fromString(jsonSchema);
JsonNode data = JsonLoader.fromString(jsonData);

JsonSchemaFactory factory = JsonSchemaFactory.defaultFactory();

JsonSchema schema = factory.fromSchema(schemaNode);


ValidationReport report = schema.validate(data);

https://github.com/fge/json-schema-validator

Uma ótima documentação e introdução ao JSON Schema se encontra em:

http://spacetelescope.github.io/understanding-json-schema/index.html

6.14 EXERCÍCIOS: ENVIANDO MENSAGENS XML PELA LOJA


Nesse exercício vamos melhorar a mensagem JMS. O conteúdo da mensagem será um XML gerado
pelo JAX-B. Ou seja, ao finalizar a compra, serializamos o objeto pedido para XML.

ENTERPRISE INTEGRATION PATTERN - DOCUMENT MESSAGE

Use a Document Message to reliably transfer a data structure between applications.

http://www.eaipatterns.com/DocumentMessage.html

1. No Eclipse, volte ao projeto fj36-livraria. Abra a classe SerializadorXML e descomente o método


toXml(..) :

public String toXml(Pedido pedido) {

2. No método, use o JAXB para gerar um XML baseado no pedido. O código é bem parecido com o
anterior, com a diferença que usamos um Writer que guarda o XML gerado:
try {
Marshaller marshaller =
JAXBContext.newInstance(Pedido.class).createMarshaller();
StringWriter writer = new StringWriter();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(pedido, writer);
return writer.toString();
} catch (JAXBException e) {

158 6.14 EXERCÍCIOS: ENVIANDO MENSAGENS XML PELA LOJA


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
throw new RuntimeException(e);
}

3. Abra a classe EnviadorMensagemJms . No método enviar(..) , procure a linha que cria a


mensagem de texto:
producer.send(topico, pedido.toString());

Altere o código para usar o SerializadorXml e enviar uma mensagem XML:

String xml = new SerializadorXml().toXml(pedido);


System.out.println(xml);
producer.send(topico, xml);

4. Não esqueça de usar a anotação @XmlRootElement na classe Pedido .

Além disso, as classes de modelo não possuem getters e setters para todos os atributos. Por isso use a
anotação @XmlAccessorType(XmlAccessType.FIELD) nas classes Pedido , ItemCompra ,
Pagamento e Livro .

5. Antes de testar, verifique se o Tomcat e JBoss estão rodando.

Acesse a aplicação:

http://localhost:8088/fj36-livraria

Escolha um ou mais livros e finalize a compra. Verifique no console se o XML aparece.

6. Alguns dados do nosso modelo não interessam para o XML gerado.

Deixe os atributos seguintes como transient, usando a anotação @XmlTransient em cada atributo:

id e quantidadeEstoque da classe ItemCompra

imagem , descricao e id da classe Livro

Vamos deixar o XML ainda mais expressivo. Repare que cada item é apresentado pela tag itens
(no plural). Isto é porque o atributo da coleção Java na classe Pedido possui este nome:
private Set<ItemCompra> itens;

Para melhorar use no atributo as seguintes anotações:


@XmlElementWrapper(name="itens")
@XmlElement(name="item")
private Set<ItemCompra> itens;

Assim, será serializado um elemento <itens> e, para cada ItemCompra , um elemento <item> .

Reinicie o Tomcat e finalize uma compra. Verifique o console e perceba as diferenças no XML
gerado.

6.14 EXERCÍCIOS: ENVIANDO MENSAGENS XML PELA LOJA 159


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 7

APLICANDO ENTERPRISE INTEGRATION


PATTERN

"A imaginação é mais importante que o conhecimento." -- Albert Einstein

7.1 FRAMEWORKS DE INTEGRAÇÃO


Integração é algo complexo! Já sentimos essa complexidade em nosso projeto ao utilizarmos
diferentes estilos de integração como RPC e Mensageria. Inclusive, usamos protocolos e formatos
diferentes com inúmeras tecnologias como RMI, SOAP, REST, JMS, AMQP ou STOMP. Além disso, há
outras formas de integração mais tradicionais como o acesso ao banco de dados compartilhado e troca
de arquivos, ainda muito comum em sistemas legados.

Um framework de integração ajuda a diminuir a complexidade e o impacto do código de integração


escrito, já que não precisaremos "reinventamos a roda". Em suma, seguir padrões de integração
beneficia a manutenção de nosso código.

Um exemplo concreto disso surge em nossa aplicação. Temos a integração da nossa loja com o
sistema que gera os ebooks. Ao serializar um pedido com JAX-B, todos os dados serão apresentados no
XML, inclusive dados do pagamento. No entanto, o gerador não deve conhecer as informações sobre
valor da compra ou cartão de credito.

Podemos então criar e enviar para o gerador uma nova mensagem XML apenas com as informações
de seu interesse. É neste ponto que um framework de integração pode simplificar nosso trabalho.

7.2 APACHE CAMEL


Essencialmente, o Apache Camel é um roteador (routing engine) e a tarefa do desenvolvedor é
configurar através de um builder as regras de roteamento. O desenvolvedor decide de onde vem as
mensagens/dados, para onde enviar e o que fazer com a mensagem no meio desse processo (mediation
engine).

O Camel não obriga o desenvolvedor a usar algum formato ou modelo de dados especifico. Foi
pensado para ser flexivel o suficiente para integrar qualquer sistema sempre através da mesma API, não
importando se a mensagem é SOAP ou JSON. O Camel oferece vários componentes que já sabem ler e

160 7 APLICANDO ENTERPRISE INTEGRATION PATTERN


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
enviar os dados para uma grande quantidade de sistemas; dá suporte a mais de 80 protocolos e formatos;
e, para definir as regras de roteamento, o Camel possui sua própria linguagem, uma DSL (Domain
Specific Language).

Além destes benefícios, como veremos, o Camel é modular e fácil de estender, tanto que podemos
criar nossos próprios componentes. É um framework leve que pode ser embutido em qualquer aplicação.
Por causa das facilidades e caracteristicas ricas de roteamento, o Camel se tornou a base de outros
projetos open source como Apache ServiceMix ou JBoss Fuse.

Além disso, o Camel se integra muito bem com o Spring, ideal para a nossa livraria!

7.3 OS PRIMEIROS COMPONENTS E ENDPOINTS


Como já falamos, o Camel vem com uma série de componentes prontos para serem utilizados. Para
ser mais concreto, há componentes que sabem enviar uma mensagem JMS, ler ou escrever um arquivo,
acessar o banco de dados, integrar Web Service SOAP/REST ou até se comunicar com GMail, Twitter ou
Facebook.

Segue o link da lista atual de todos os componentes disponíveis:

http://camel.apache.org/components.html

Quando falamos em ler dados de um ponto concreto (arquivo, banco, JMS etc) e rotear para um
outro ponto (arquivo, banco, JMS etc) usamos a terminologia Endpoints. Então um Endpoint nada mais
é do que um produtor ou consumidor de dados e são apresentados através dos componentes.

No entanto, é importante mencionar que nem todos os components são Endpoints. Há


componentes, ainda, que se preocupam com o processamento e transformação de dados como veremos
mais para frente.

Por último, os componentes nada mais são do que JARs que devem estar disponíveis dentro do
classpath para que possam ser configurados através da linguagem própria do Camel: a Camel DSL.

7.4 CAMEL DSL


Os Enpoints e as regras intermediárias de roteamento, processamento ou transformação são
configuradas através da Camel DSL. Para usar a Camel DSL devemos primeiro instanciar um
CamelContext :

CamelContext context = new DefaultCamelContext();

Com o context em mãos podemos adicionar uma nova rota. Uma rota é configurada usando o
RouteBuilder :

7.3 OS PRIMEIROS COMPONENTS E ENDPOINTS 161


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Atenção: A classe RouteBuilder é do pacote org.apache.camel.builder.* .

context.addRoutes(new RouteBuilder() {
@Override
public void configure() throws Exception {

}
});

Dentro do método configure() podemos usar a Camel DSL. Através dela definimos de onde vem
os dados e para onde vão. Vamos usar o primeiro componente do Camel, o File Component. Esse
componente sabe ler os arquivos de uma pasta periodicamente e escrever todo o conteúdo para outra
pasta.

Através da configuração abaixo verificamos a pasta entrada a cada 5 segundos e escrevemos para a
pasta saida . Repare que usamos os métodos from(..) e to(..) que fazem parte da Camel DSL:
from("file:entrada?delay=5s").
to("file:saida");

Fizemos um simples roteamento para conhecer a DSL. No meio desse roteamento podemos aplicar
outros componentes. No exemplo abaixo logamos o conteúdo da mensagem, ou seja, do arquivo que
está sendo lido da pasta entrada . O conteúdo da mensagem é acessível através da expressão ${body} :

from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${body}").
to("file:saida");

Podemos encadear quantos componentes quisermos. Também podemos encaminhar a mensagem


para outras saidas. Veja o exemplo abaixo:
from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${body}").
to("file:saida").
log(LoggingLevel.INFO, "Continuando com a mensagem ${body}").
to("file:saida2");

Veremos mais para frente outros componentes para processar e transformar a mensagem.

ENTERPRISE INTEGRATION PATTERN - PUBLISH-SUBSCRIBE CHANNEL

Send the event on a Publish-Subscribe Channel, which delivers a copy of a particular event to each
receiver.

http://www.eaipatterns.com/PublishSubscribeChannel.html

162 7.4 CAMEL DSL


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
APACHE CAMEL VS SPRING INTEGRATION

Apache Camel não é o único framework de integração. Outro popular framework de integração
é o Spring Integration:

http://projects.spring.io/spring-integration/

O Camel se destaca pela grande quantidade de componentes e facilidades da Camel DSL. Segue
um link que compara ambos:

http://java.dzone.com/articles/light-weight-open-source

7.5 EXERCÍCIOS: ROTEAMENTO COM APACHE CAMEL


1. No Eclipse, crie um novo projeto Java chamado fj36-camel.

2. Copie as JARs do Apache Camel para a pasta raíz do seu projeto fj36-camel, dessa forma:

Vá ao diretório Desktop/caelum/36/jars .

Selecione a pasta lib-camel, clique com o botão direito e escolha Copy (ou CTRL+C ).

Cole a pasta na raíz do projeto: selecione fj36-camel e aperte CTRL+V .

Adicione todas as JARs da pasta lib-camel no classpath da aplicação. Selecione as JARS, botão
direito: Build Path -> Add to Build Path.

3. No projeto, crie a classe TesteRoteamento no pacote br.com.caelum.camel e gere o método


main .

4. No método main instancie o CamelContext :


CamelContext context = new DefaultCamelContext();

Baseado no context , adicione uma nova rota usando o RouteBuilder :


context.addRoutes(new RouteBuilder() {

@Override
public void configure() throws Exception {

}
});

5. Configure a primeira rota. Use um file endpoint que lê a cada 5 segundos uma pasta e copia o
conteúdo para outra pasta.

7.5 EXERCÍCIOS: ROTEAMENTO COM APACHE CAMEL 163


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Dentro do método configure() , use os métodos do RouteBuilder para configurar a entrada
( from ) e a saída ( to ):
from("file:entrada?delay=5s").
to("file:saida");

Mais sobre o componente file na documentação: http://camel.apache.org/file2.html

6. Por fim, inicialize o contexto, espere 30 segundos e pare o contexto.

Adicione o código abaixo dentro do main , mas fora do método addRoutes() :

context.start();

Thread.sleep(30 * 1000);

context.stop();

7. Vá ao diretório Desktop/caelum/36/etc e copie a pasta entrada para raíz do projeto fj36-


camel .

Nessa pasta já existem alguns arquivos XML que usaremos para aprender mais recursos do Camel.

8. Rode o método main . Depois atualize o projeto, todo os XMLs devem estar na pasta saida .

9. Entre os dois Endpoints, adicione um simples componente de LOG.

Adicione a chamada do método log entre from(..) e to(..) :


from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${id}").
to("file:saida");

10. Para a mensagem de log aparecer, copie o arquivo Desktop/caelum/36/etc/log4j.properties


para a pasta src .
11. Mova os arquivos da pasta saida para a pasta entrada antes de realizar um novo teste.

12. Rode o main . A mensagem de log deve aparecer no console.

Podemos adicionar quantos endpoints quisermos, seguindo o modelo publish-subscriber.

7.6 APLICANDO OS PRIMEIROS PADRÕES DE INTEGRAÇÃO


Para integrar duas aplicações há mais do que uma possível solução. É tarefa do arquiteto decidir qual
solução escolher e como implementar. Durante anos de desenvolvimento foram documentadas formas
de integração com o objetivo de padronizar e descrever vantagens e desvantagens de cada solução. Como
resultado foi criado o livro Enterprise Integration Patterns dos autores Gregor Hohpe e Bobby Woolf. O
livro é um catálogo de padrões de projetos relacionados com os problemas na integração de aplicações.
O catálogo também está disponível no site:

164 7.6 APLICANDO OS PRIMEIROS PADRÕES DE INTEGRAÇÃO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
http://www.enterpriseintegrationpatterns.com/

A ideia do livro é a mesma de outros catálogos de padrões de projetos: criar um vocabulário comum,
documentar visualmente os padrões e descrever as vantagens/desvantagens das soluções para
desenvolvedores e arquitetos poderem ter uma base de conhecimento.

O Apache Camel, como framework de integração, implementa a maioria dos padrões e oferece
componentes concretos deles. Entender os padrões de integração ajuda muito a entender o Apache
Camel. Até na documentação do Camel aparecem padrões de integração e exemplos concretos de como
aplicá-los:

http://camel.apache.org/enterprise-integration-patterns.html

Começaremos a usar alguns padrões de integração para entender melhor o Apache Camel. Entre
qualquer roteamento podemos aplicar regras que processam ou transformam a mensagem (mediation
engine).

Continuando com o exemplo anterior podemos aplicar uma regra de transformação simples
seguindo o padrão Message Translator:

from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${id}").
transform(body(String.class).regexReplaceAll("nomeAutor", "autor")).
to("file:saida");

Com a mesma simplicidade podemos usar o método setBody() para manipular o conteúdo da
mensagem:
from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${id}").
transform(body(String.class).regexReplaceAll("nomeAutor", "autor")).
setBody(
body().
append(
new SimpleDateFormat("dd/MM/yy").format(new Date())
)
).
to("file:saida");

Claro que nem sempre uma regra de transformação é tão simples. Por isso podemos inserir um bean
que fará a transformação programaticamente. O Camel possui um componente de bean binding
poderoso e com ele podemos executar qualquer método no processamento:
from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${id}").
transform(body(String.class).regexReplaceAll("nomeAutor", "autor")).
bean(TransformerBean.class, "transform").
to("file:saida");

A classe TransformerBean é um simples POJO que possui o método transform . O método pode
receber uma String , ou mais especificamente, um Exchange, um objeto do Camel que devolve a

7.6 APLICANDO OS PRIMEIROS PADRÕES DE INTEGRAÇÃO 165


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
mensagem. Exchange é nada mais do que um container para a mensagem que possui algumas meta-
informações:
public class TransformerBean {

public void transform(Exchange exchange) {


System.out.println(exchange.getIn().getMessageId());
String mensagem = exchange.getIn().getBody(String.class);
System.out.println(mensagem);
}
}

Para transformações mais complexas o Camel oferece o componente Velocity. Velocity é um template
engine bem parecido com o JSP. Aliás, JSP foi criado baseado nas ideias do Velocity. Como o JSP,
Velocity oferece uma linguagem de expressões que usamos dentro dos templates para gerar HTML,
JSON ou XML de maneira simples:
from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${id}").
transform(body(String.class).regexReplaceAll("nomeAutor", "autor")).
bean(TransformerBean.class, "transform").
setHeader("dataAtual",
constant(
new SimpleDateFormat("dd/MM/yyyy").format(new Date())
)
).
to("velocity:NotaFiscal.vm").
to("file:saida");

O template define o novo conteúdo aproveitando objetos já disponíveis para a expression language
como ${headers} e ${body} :
<!-- Arquivo NotaFiscal.vm-->
<nota data="${headers.dataAtual}">
${body}
</nota>

ENTERPRISE INTEGRATION PATTERN - MESSAGE TRANSLATOR

Use a special filter, a Message Translator, between other filters or applications to translate one
data format into another.

http://www.eaipatterns.com/MessageTranslator.html

7.7 TRATAMENTO DE EXCEÇÕES


Quando trabalhamos com aplicações externas é preciso lidar com possíveis problemas. Não
podemos assumir que as aplicações sempre respondem como esperado. Não estamos sob controle dessas
aplicações e não há como garantir a exatidão das respostas, por isso é preciso se proteger desde início.

166 7.7 TRATAMENTO DE EXCEÇÕES


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Além disso, estamos falando em comunicação remota, a rede pode falhar a qualquer momento ou um
timeout pode ocorrer.

O Apache Camel já vem out-of-the-box com um sistema rico de tratamento de erros e exceções,
novamente seguindo boas práticas e padrões de projetos.

Por exemplo, pode acontecer um problema na rede durante o envido do pedido da loja. Nesse caso
faz sentido reenviar o pedido, pois a rede pode voltar a funcionar e não queremos perder a compra. Para
situações como esta podemos configurar um fluxo especial que é acionado em caso de problemas.

Na mensageria isso é conhecido como dead letter queue ou dead letter channel . Essa fila
ou channel recebe as mensagens que não foram entregues por causa de um problema na
entrega/processamento.

No Apache Camel devemos associar o deadLetterChannel com um errorHandler e adicionar


no início do método configure() do RouteBuilder :
context.addRoutes(new RouteBuilder() {

@Override
public void configure() throws Exception {

errorHandler(
deadLetterChannel("file:falha")
);

from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${id}").
transform(body(String.class).regexReplaceAll("nomeAutor", "autor")).
bean(ValidadorPedido.class, "validar").
to("file:saida");
}
}

O deadLetterChannel recebe automaticamente a mensagem em caso de exceção e grava a


mensagem na pasta falha . Porém ainda há um problema, se a validação falhar a mensagem já foi
alterada. Ou seja, o deadLetterChannel não recebe recebe a mensagem original e sim a mensagem
modificada. Para resolver isso podemos deixar explícito que queremos receber a mensagem inalterada:
errorHandler(
deadLetterChannel("file:falha").
useOriginalMessage()
);

Quando recebemos a mensagem no deadLetterChannel faz sentido enviar novamente a


mensagem? Isso depende um pouco da causa mas pensando em problemas de rede, pode ser uma
alternativa. Novamente podemos configurar isso confortavelmente:
errorHandler(
deadLetterChannel("file:falha").
useOriginalMessage().
maximumRedeliveries(2).
redeliveryDelay(10000)

7.7 TRATAMENTO DE EXCEÇÕES 167


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
);

Nesse caso definimos duas tentativas em um intervalo de 10 segundos.

ENTERPRISE INTEGRATION PATTERN - DEAD LETTER CHANNEL

When a messaging system determines that it cannot or should not deliver a message, it may elect
to move the message to a Dead Letter Channel.

http://www.eaipatterns.com/DeadLetterChannel.html

O Apache Camel também oferece um tratamento de erro para uma exceção específica. Nesse caso
devemos utilizar o método onException(..) . Isto é ideal para problemas de IO/Rede onde faz sentido
tentar o reenvio da mensagem quantas vezes necessário.

O uso desse método é bem parecido com o do erroHandler() :


onException(IOException.class).
maximumRedeliveries(2).
redeliveryDelay(10000).
useOriginalMessage().
to("file:exception");

Uma diferença é que por padrão o onException() não consome a mensagem. Ele funciona como
um listener que será notificado quando uma exceção for lançada, mas sem alterar o fluxo de mensagem
(não tira a mensagem da rota original). Se isso for necessário é preciso deixar explícito através do
método handle(true) para o Camel tirar a mensagem do fluxo padrão.

onException(IOException.class).
handled(true). //para consumir a mensagem
maximumRedeliveries(2).
redeliveryDelay(10000).
useOriginalMessage().
to("file:exception");

7.8 VALIDAÇÃO DE XSD COM CAMEL


É muito comum, no dia-a-dia, usarmos XML quando precisamos nos integrar com um legado, ou
até mesmo sistemas internos ou corporativos. Muitas empresas, possuem protocolos internos baseados
em XML para tráfego dos dados. Ou quando trabalhamos usando um modelo canônico, recebemos
dados de que precisamos ter a certeza de que eles seguem o modelo definido no schema.

Nesses casos em que precisamos garantir a integridade dos dados que recebemos, é essencial que
realizemos validação e para isso o Camel possui um componente chamado validator.

Para usá-lo precisamos ter um arquivo .xsd numa pasta específica. Com isso, ao recebermos dados

168 7.8 VALIDAÇÃO DE XSD COM CAMEL


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
mandaremos para ser validado contra o arquivo presente nessa pasta.

from("file:entrada?delay=5s").
...
to("validator:file:xsd/pedido.xsd");

7.9 EXERCÍCIOS: VALIDAÇÃO DE MENSAGENS, REDELIVERY E DEAD


LETTER CHANNEL
1. Vamos validar nosso XML usando um componente do Camel chamado Validator. Para isso, crie
uma pasta chamada xsd em nosso projeto e cole o arquivo pedido.xsd que se encontra na pasta
Desktop/caelum/36/etc .

2. Queremos que o XML passe primeiro pela validação. Se for inválido mande o arquivo para a pasta
falha. Caso tudo esteja em ordem, mande para a pasta saída.

Para chamarmos o Camel Validator vamos usar o prefixo validator seguido pelo local onde se
encontra o arquivo xsd:

No projeto fj36-camel , altere a classe TesteRoteamento :


from("file:entrada?delay=5s").
to("validator:file:xsd/pedido.xsd").
to("file:saida");

3. O que irá acontecer caso haja um erro de validação? Vamos fazer uso do pattern Dead Letter
Channel jogando os arquivos inválidos em uma pasta específica.

Crie uma pasta chamada falha e digite o código abaixo:

errorHandler(
deadLetterChannel("file:falha").
useOriginalMessage()
);

// from("file:entrada?delay=5s").
// restante do código

4. Coloque os arquivos XML novamente na pasta entrada e rode o exemplo. Veja que o arquivo
4_pedido.xml foi mandado para a pasta falha.

Repare que no pedido.xsd o pagamento é definido como obrigatório.


<xs:element name="pagamento" type="pagamento" minOccurs="1"/>

Quando realizamos a validação dos arquivos da pasta entrada, o 4_pedido.xml não possui um
pagamento. O que o torna inválido.

5. Vamos pedir ao Camel para tentar validar a mensagem novamente, caso haja erro. Adicionando uma
quantidade de tentativas e o intervalo em milissegundos.

7.9 EXERCÍCIOS: VALIDAÇÃO DE MENSAGENS, REDELIVERY E DEAD LETTER CHANNEL 169


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
errorHandler(
deadLetterChannel("file:falha").
useOriginalMessage().
maximumRedeliveries(2).
redeliveryDelay(2000).
retryAttemptedLogLevel(LoggingLevel.ERROR)
);

Mova novamente os quatro arquivos para a pasta entrada. Após isso, rode o exemplo. Repare que
o Log, onde é mostrado o erro de validacão de XML, é executado três vezes. A primeira vez que ele
tenta executar e as outras duas tentativas.

6. Vamos inserir um pagamento no arquivo 4_pedido.xml colocando, logo abaixo de </itens> o


seguinte trecho de código:
<pagamento>
<status>CONFIRMADO</status>
<valor>29.90</valor>
</pagamento>

Mova novamente todos os arquivos para a pasta entrada e teste novamente. Atualize o projeto e
repare que agora todos os arquivos xml se encontram na pasta saída.

7.10 MARSHALL E UNMARSHAL DE DADOS


Como framework de integração o Camel é capaz de trabalhar com vários formatos, trabalhar com a
serialização e deserialização de objetos, ou fazer marshal e unmarshal de dados.

Por exemplo, se quisermos ler arquivos gerados pela serialização Java IO e depois geral um XML pelo
XStream podemos declarar no DSL:
from("file://pedidos"). //ler arquivos dentro da pasta pedidos
unmarshal().serialization(). //unmarshal usando Java IO
marshal().xstream(). //marshal com XStream
to("jms:queue.pedido"); //enviar para o XML para fila

Isso também funciona ao contrário:

from("file://pedidos"). //ler arquivos dentro da pasta pedidos


unmarshal().xstream(). //unmarshal usando XStream
marshal().serialization(). //marshal com Java IO
to("jms:queue.pedido"); //enviar para o XML para fila

Se quisermos usar o JAX-B é preciso criar o contexto antes. Baseado no contexto JAX-B é criado um
DataFormat , aqui o JaxbDataFormat :

JAXBContext contextoJaxb = JAXBContext.newInstance(Pedido.class);


JaxbDataFormat formatoJaxb = new JaxbDataFormat();
formatoJaxb.setContext(contextoJaxb);

Uma vez criado podemos repassar o formato para os métodos unmarshal(..) e marshal(..) :

from("file://pedidos"). //lendo os arquivo XML da pasta pedido

170 7.10 MARSHALL E UNMARSHAL DE DADOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
unmarshal(formatoJaxb).
//processamento do objeto
marshal(formatoJaxb).
to("jms:queue.pedido");

Bem parecido podemos fazer marhal/unmarshal para JSON:

...
unmarshal().json()
...
marhsal().json()
...

Até podemos usar o SoapJaxbDataFormat que recebe o pacote das classes e a interface do Web
Service:
SoapJaxbDataFormat soap = new SoapJaxbDataFormat(
"br.com.caelum.ws", new ServiceInterfaceStrategy(EstoqueWSInterface.class));

Estes foram só alguns exemplos, existem muito mais facilidades que o Camel oferece para trabalhar
com diversos formatos.

Segue o link da documentação sobre os DataFormat s:

http://camel.apache.org/data-format.html

7.11 POLLING EM SERVIÇOS HTTP


Existem situações em que gostariamos de mostrar informações em tempo real ao nosso cliente sobre
o estado de algum serviço que consumimos. O problema é que o HTTP não nos permite realizar long-
live connections com o servidor. A ligação só pode ser aberta pelo cliente e sua vida dura o tempo de uma
requisição, nada mais. Quando não temos a disposição uma interface de comunicação bidirecional como
WebSocket ou FlashSocket, a solução é consultar o servidor de tempos em tempos em busca de alguma
mudança em seu estado. Caso haja, recebemos a resposta e a exibimos ao cliente. Essa técnica é
conhecida como Polling e é super disseminada em aplicações Web.

Uma outra situação onde o Polling nos faz de bom grado, é quando desejamos consumir recursos
que devolvem toneladas de JSON/XML e queremos exibí-los em aplicações mobile. Como a maioria dos
usuários fazem uso de internet mobile usando tecnologias como 3G/4G, devemos ter cuidado com o
tamanho das requisições não deixando com que o cliente consuma todas as informações
disponibilizadas pelo serviço. Afinal, nem todas necessariamente podem ser de seu interesse naquele
momento.

Um dos motivos de fazermos uso de Gerenciadores de Banco de Dados para persistência de


informações e até mesmo integração entre sistemas, é que ganhamos meios fáceis para acessar pequenas
partes desses dados de cada vez. Se trabalhassemos com arquivos, por exemplo, precisariamos carregá-
los por completo em memória para depois realizarmos o parse e resgatarmos a informação de que
precisamos.

7.11 POLLING EM SERVIÇOS HTTP 171


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Uma solução para isso, é realizarmos o Polling no serviço e salvarmos as informações em um banco
de dados. Aproveitando de sua interface para acesso aos dados de forma fácil. E por fim, criamos um
Endpoint no qual os clientes podem acessar os dados de que precisarem.

O Camel nos ajuda a realizar Polling em serviços usando um componente chamado HTTP.

Para fazermos uso desse componente, criaremos uma rota com o endereço do serviço que iremos
consumir.
from("http://servico.com.br/livros")

E para dizer ao Camel que queremos fazer requisições em um intervalo de 2 segundos, usamos o
método delay passando como argumento o tempo em milisegundos.
delay(2000)

Depois disso podemos fazer alguma coisa com os dados recebidos, como por exemplo, fazer o
unmarshal do JSON.
from("http://servico.com.br/livros")
.delay(2000)
.unmarshal()
.json()

7.12 MESSAGE SPLITTER


Ao consumirmos um serviço que nos retorne um grupo de dados, precisamos utilizar uma estrutura
de dados (um Array ou um ArrayList ) para representá-lo. Como por exemplo, em nosso caso,
podemos ter a lista de livros mais procurados do catálogo. Mas nós queremos adicionar esses livros no
banco de dados, um de cada vez. O que fazer?

Precisamos dividir (split) a mensagem (nossa lista) baseada em algum critério, ou seja, queremos que
a cada iteração de nossa lista de livros, tenhamos um novo elemento (um livro) em mãos. O Camel nos
ajuda a fazer isso através do componente split .
from("http://servico.com.br/livros")
.delay(2000)
.unmarshal()
.json()
.split(???)

Porém temos que dizer ao split o critério de divisão. Queremos que ele divida o corpo da nossa
mensagem, ou seja, a nossa ArrayList . Portanto passaremos como argumento uma Expression do
Camel que irá representar o corpo da mensagem.
from("http://servico.com.br/livros")
.delay(2000)
.unmarshal()
.json()
.split(body())

172 7.12 MESSAGE SPLITTER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
O split é inteligente e internamente percebe que trata-se de uma List e faz uso do iterator
para pegar cada elemento. Porém se precisarmos de um controle mais fino do critério de divisão,
podemos usar passar qualquer Expression :
.split(body().tokenize(";")) // dividindo String a cada ponto-e-vírgula
.split(xpath("/pedido/itens")) // pegando os itens do pedido usando XPath

ENTERPRISE INTEGRATION PATTERN - SPLITTER

Use a Splitter to break out the composite message into a series of individual messages, each
containing data related to one item.

http://www.eaipatterns.com/Sequencer.html

7.13 EXERCÍCIO: REALIZANDO POLLING EM UM SERVIÇO


1. No projeto fj36-livraria crie uma nova action na classe LojaController que irá produzir a lista de
livros:
@RequestMapping(value="/livros/mais-vendidos",
method=RequestMethod.GET)
public String livrosMaisVendidos() {

2. Precisamos dizer o formato da nossa resposta (equivalente ao @Produces do JAX-RS), para isso
vamos usar o atributo produces da anotação @RequestMapping :
@RequestMapping(value="/livros/mais-vendidos",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public String livrosMaisVendidos() {

Também queremos que o retorno do método (o JSON) seja o corpo da resposta. Devemos então,
sinalizar isso ao Spring anotando o método com @ResponseBody .
@ResponseBody
@RequestMapping(value="/livros/mais-vendidos",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public String livrosMaisVendidos() {

3. Agora vamos a implementação do nosso serviço, devolvendo uma lista aleatória de livros. Mas para
isso, devemos adicionar as dependências do XStream. Para isso, pegue os jars da pasta
caelum/36/jars/lib-camel-polling-server e adicione na pasta WEB-INF/lib do projeto fj36-livraria.

7.13 EXERCÍCIO: REALIZANDO POLLING EM UM SERVIÇO 173


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@ResponseBody
@RequestMapping(value="/livros/mais-vendidos",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public String livrosMaisVendidos() {

List<Livro> livrosMaisVendidos = this.entityManager.createQuery(


"select livro from Livro livro", Livro.class).getResultList();

XStream xstream =
new XStream(new JettisonMappedXmlDriver());

String json = xstream.toXML(livrosMaisVendidos);


return json;
}

Vamos iniciar o Tomcat e acessar a URL:

http://localhost:8088/fj36-livraria/loja/livros/mais-vendidos

O resultado deve ser parecido com o JSON abaixo:


{"list":
[{"br.com.caelum.livraria.modelo.Livro":
[{ "id":1,
"codigo":"ARQ",
"titulo":"Introdução à Arquitetura e Design de Software",
"tituloCurto":"Arquitetura Java",
....
}
{ "id":2,
"codigo":"SOA",
"titulo":"SOA aplicado: Integrando com web services e além",
"tituloCurto":"SOA",
....
}]
}]
}

4. Precisamos ter em nosso projeto, os modelos Livro e Formato usados pelo serviço. Para isso, vamos
exportar as classes br.com.caelum.livraria.modelo.Livro e
br.com.caelum.livraria.modelo.Formato do projeto fj36-livraria para um .jar .
Selecionando-as e clicando com o botão direito > Export... > Java > JAR file

No projeto fj36-camel , adicione o .jar que geramos no classpath.

Além disso copie as bibliotecas da pasta Desktop/caelum/36/jars/lib-camel-polling para o projeto


fj36-camel .

5. Agora iremos implementar nosso cliente. Crie uma classe chamada TestePolling e crie uma rota
que fará requisições ao nosso Endpoint a cada segundo.
public class TestePolling {
public static void main(String[] args) {
CamelContext ctx = new DefaultCamelContext();
ctx.addRoutes(new RouteBuilder() {

174 7.13 EXERCÍCIO: REALIZANDO POLLING EM UM SERVIÇO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@Override
public void configure() throws Exception {
from("http://localhost:8088/fj36-livraria/loja/livros/mais-vendidos")
.delay(1000)

6. Queremos que o Camel deserialize os livros que estão em JSON. Vamos chamar o método
unmarshal() e o método json() . Também precisamos adaptar o corpo da mensagem colocando
a lista de livros. Para tratarmos a mensagem, criaremos uma instância anônima de Processor :
.unmarshal()
.json()
.process(new Processor() {
@Override
public void process(Exchange exchange) {
List<?> listaDeLivros = (List<?>) exchange.getIn().getBody();

ArrayList<Livro> livros = (ArrayList<Livro>)


listaDeLivros.get(0);

Message message = exchange.getIn();


message.setBody(livros);
}
})

7. Vamos criar uma nova rota que irá receber a mensagem com a lista de livros e dividí-la (split) em
partes (em cada livro), no entanto, essa rota não existe ainda. Por isso, vamos mockear essa rota
usando o componente mock. A nossa nova rota se chamará livros , então mock:livros .
Adicione no final, depois do process:

.log("${body}")
.to("mock:livros");

Mais a frente iremos continuar esse processo, inserindo o nome dos autores dos livros obtidos em
um banco de dados.

8. Inicie o contexto do Camel, adicione depois do método configure :


ctx.start();

new Scanner(System.in).nextLine();

ctx.stop();

7.14 POLLING NO BANCO DE DADOS


Um estilo de integração ainda muito comum é usar um banco de dados compartilhado. Nesse banco
há uma tabela onde uma aplicação grava registros e a outra consome. Ou seja, a segunda aplicação lê os
registros dessa tabela, consome-os e apaga depois.

Essa forma de integrar uma aplicação, apesar de simples e rápida de implementar, possui várias
desvantagens. Não é responsabilidade do banco integrar aplicações e sim guardar o estado da aplicação.
Usando uma tabela compartilhada, muitas vezes com Stored Procedures e Triggers associados, significa

7.14 POLLING NO BANCO DE DADOS 175


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
que temos que manter esse código por muito tempo. Esse tipo de código é fácil de esquecer, mal testado
e documentado, pois não faz parte do Business Tier.

Além disso, um banco não pode oferecer uma interface rica e encapsular a lógica de negócio para
outras aplicações reutilizarem. Ao invés de usar um banco de dados devemos favorecer uma solução
baseada em mensageria ou RPC já vistos nesse curso.

De qualquer forma, o Apache Camel possui componentes para acessar o banco e fazer polling de
dados. Como a nossa aplicação já possui a JPA configurada através do Spring, basta usar a DSL para
definir a entidade e o delay.

Veja como fica o código:


from("jpa://br.com.caelum.livraria.modelo.Pedido" +
"?consumerdelay=5000&consumeLockEntity=false").
//outros processamentos
to("jms:queue:pedido");

O Camel vai acessar o banco buscando os pedidos a cada 5 segundos e, após ter consumido a
entidade com sucesso, o Camel apagará o registro no banco.

ENTERPRISE INTEGRATION PATTERN - SHARED DATABASE

Integrate applications by having them store their data in a single Shared Database.

http://www.eaipatterns.com/SharedDataBaseIntegration.html

7.15 CAMEL JDBC


Uma das caracteristicas principais do Camel, é ser modular. Possuíndo diversos componentes úteis
para o cotidiano do desenvolvedor como persistência em banco de dados, manipulação de arquivos,
consumir WebService entre outras coisas.

Neste capítulo, aprenderemos a usar um componente muito poderoso para trabalharmos com banco
de dados: CamelJDBC.

Para executar um comando JDBC com Camal faremos uso da Simple Expression Language do Camel
que permite definir qualquer SQL:

from(...)
.setBody(simple("insert into Livros(nomeAutor) values ('Paulo Silveira')"))

Claro que queremos definir parâmetros no SQL! Por exemplo, para usar o nome do autor através do
header da mensagem usaremos:

176 7.15 CAMEL JDBC


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
from(...)
.setBody(simple("insert into Livros(nomeAutor) values (:?nomeAutor')"))

Isso só vai funcionar se disponibilizamos a variável nomeAutor dentro do cabeçalhos. Isso pode ser
feito através do um processador:
from(...)
.process(new Processor() {
@Override
public void process(Exchange exchange) {
Message inbound = exchange.getIn();

String nomeAutor = "Leonardo";

inbound.setHeader("nomeAutor", nomeAutor);
}
})
.setBody(simple("insert into Livros(nomeAutor) values (:?nomeAutor')"))

Por último, precisamos mandar essas queries para um datasource que deve estar devidamente
configurada:
.to("jdbc:mysqlDataSource?useHeadersAsParameters=true")

Perceba que usamos um parâmetro no datasource chamado useHeadersAsParameters que, como


o próprio nome indica, utiliza como parâmetro da query os Headers que forem setados, permitindo um
código mais elegante.

7.16 EXERCÍCIO: INSERINDO OS LIVROS NO BANCO DE DADOS


1. Precisaremos persistir cada livro no banco de dados. Crie um novo banco que usaremos nesse
exercício. Digite no terminal:
mysql -u root;
create database fj36_camel;
use fj36_camel;
create table Livros (
nomeAutor TEXT
);

2. Vamos criar uma nova rota com o nome livros que grava os dados no banco.

Na rota anterior altere o to("mock:livros") para to("direct:livros") . O direct:livros


indica o início do uma nova rota com o nome livros.

3. Logo apos da primeira rota, dentro do método configure() adicione:


from("direct:livros")
.to("jdbc:mysqlDataSource?useHeadersAsParameters=true");

O componente jdbc não está configurado ainda, faremos isso mais para frente!

4. A nova rota recebe a lista de livros e vai dividí-la (split) em partes (em cada livro).

7.16 EXERCÍCIO: INSERINDO OS LIVROS NO BANCO DE DADOS 177


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Logo após do from("direct:livros") adicione:

.split(body());

5. Para usar o componente camel-jdbc, precisamos mandar uma mensagem pro banco com a Query
que queremos disparar. Como queremos inserir o nome de cada autor no banco, precisamos passá-lo
com parâmetro do insert através de cabeçalhos.

Vamos criar um Processor que colocará o nome do autor como cabeçalho da mensagem. Logo
após do split(body()) adicione:

.process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
Message inbound = exchange.getIn();

Livro livro = (Livro) inbound.getBody();

String nomeAutor = livro.getNomeAutor();


inbound.setHeader("nomeAutor", nomeAutor);
}
})

Vamos passar a query no corpo usando o método setBody :

.setBody(???)

Como queremos passar o parâmetro dinamicamente, vamos usar o método simple que irá
processar a Simple Expression Language do Camel.
.setBody(
simple("insert into Livros (nomeAutor) values (':?nomeAutor')")
)

6. Precisamos enviar a query pro banco de dados. Para isso, vamos criar um DataSource que o Camel
usará para mandar a query.

No início do método main adicione a definição do DataSource:

MysqlConnectionPoolDataSource mysqlDs = new MysqlConnectionPoolDataSource();

mysqlDs.setDatabaseName("fj36_camel");
mysqlDs.setServerName("localhost");
mysqlDs.setPort(3306);
mysqlDs.setUser("root");
mysqlDs.setPassword("");

// CamelContext ctx = new DefaultCamelContext();


// código omitido

Para que o Camel consiga trabalhar com este objeto, precisamos colocá-lo no seu contexto. Para isso,
vamos fazer uso da classe JndiContext para passarmos um contexto no construtor de
DefaultCamelContext .

JndiContext jndi = new JndiContext();


jndi.rebind("mysqlDataSource", mysqlDs);

178 7.16 EXERCÍCIO: INSERINDO OS LIVROS NO BANCO DE DADOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CamelContext ctx = new DefaultCamelContext(jndi);

7. Rode o exercício! Não se esqueça de iniciar o Tomcat.

Segue uma vez a rota completa para comparação:


from("direct:livros")
.split(body())
.process(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
Message inbound = exchange.getIn();

Livro livro = (Livro) inbound.getBody();

String nomeAutor = livro.getNomeAutor();


inbound.setHeader("nomeAutor", nomeAutor);
}
})
.setBody(
simple("insert into Livros (nomeAutor) values (':?nomeAutor')")
)
.to("jdbc:mysqlDataSource?useHeadersAsParameters=true");

8. (Opcional) Faça um select na tabela Livros :

select * from Livros;

Veja que o nome dos autores foram inseridos no banco de dados!

7.17 INTEGRAÇÃO DO CAMEL COM SPRING


O Apache Camel vem com uma ótima integração com Spring e aproveita boa parte da infraestrutura
que o Spring Framework oferece. Por exemplo, Camel usa por padrão as transações do Spring para JPA e
JMS, aproveita a injeção de dependências, o componente Spring Remoting e Converters, entre várias
outras facilidades.

http://camel.apache.org/spring.html

Para usar o Camel com Spring basta cadastrar o CamelContext no XML de configuração do Spring:
<camel:camelContext />

Como usamos o prefixo camel devemos registrar o namespace correto no início do XML do Spring:
<?xml version="1.0" encoding="UTF-8"?>
<beans 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">

<camel:camelContext />

7.17 INTEGRAÇÃO DO CAMEL COM SPRING 179


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
...
</bean>

Com essa configuração o Spring automaticamente sobe o CamelContext , ou seja, não é preciso
instanciar pelo DefaultCamelContext , basta injetar o CamelContext onde é preciso. Por exemplo:
@Component
public class ConfiguracaoCamelBean {

@Autowired
private CamelContext context;

A integração do Camel com Spring não pára por ai. O Camel possui um componente que nos
habilita a usar qualquer bean do Spring. Por exemplo, tendo o seguinte bean configurado no XML do
Spring:

<bean id="validadorPedido" class="br.com.caelum.fj36.ValidadorPedido"/>

Podemos usar este bean na DSL do Camel pelo método beanRef :


from("file:entrada?delay=5s")
.bean("validadorPedido", "validar")
...

Uma vez configurado o Spring podemos deixar toda configuração da rota dentro do XML do Spring:
<camel:route>
<camel:from uri="file:entrada?delay=5s"/>
<camel:bean ref="validadorPedido" method="validar"/>
<camel:to uri="file:saida"/>
</camel:route>

7.18 ENVIANDO E CONSUMINDO MENSAGENS JMS


O Apache Camel possui um suporte extenso para trabalhar com JMS, tanto para enviar quanto para
receber mensagens JMS. O envio de mensagens JMS fica mais simples pois o Camel oferece o
JmsComponent que abstrai boa parte do código boilerplate da especificação JMS.

Antes de utilizarmos esse novo componente, precisamos informar como ele encontrará a
ConnectionFactory e os destinos do JMS. Como o Camel possui uma integração nativa com o Spring,
toda a configuração do JmsComponent pode ser feita dentro do spring-context.xml .

Dentro do arquivo do arquivo de configuração do spring, precisamos configurar um bean do tipo


JmsConfiguration , dentro da configuração desse bean, precisamos informar qual é a
ConnectionFactory que será utilizada:

<bean id="camelJMSConfig"
class="org.apache.camel.component.jms.JmsConfiguration">

<property name="connectionFactory" ref="hornetQConnectionFactory"/>

180 7.18 ENVIANDO E CONSUMINDO MENSAGENS JMS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
</bean>

Veja que essa é a mesma ConnectionFactory que foi configurada no capítulo sobre JMS. Além
disso, precisamos informar ao Camel como ele fará a busca pelos Destinations (Queue e Topic) do
JMS. Na busca dos destinations, o JmsComponent do Camel utiliza internamente o
DestinationResolver
do spring. Para buscarmos os destinations no JNDI, configuraremos o
JndiDestinationResolver informando qual é o JndiTemplate do spring que será utilizado na busca:

<bean id="camelDestinationResolver"
class="org.springframework.jms.support.destination.JndiDestinationResolver">

<property name="jndiTemplate" ref="jmsJndiTemplate"/>


</bean>

E agora precisamos passar o DestinationResolver para o JmsConfiguration :


<bean id="camelJMSConfig"
class="org.apache.camel.component.jms.JmsConfiguration">

<property name="connectionFactory" ref="hornetQConnectionFactory"/>


<property name="destinationResolver" ref="camelDestinationResolver"/>
</bean>

Depois que a configuração do componente JMS foi feita, podemos utilizá-la para criar rotas dentro
do camel utilizando o prefixo jms: informando qual é o destination que será utilizado e o nome jndi
desse destination:
from("jms:topic:jms/TOPICO.LIVRARIA")
.to(destino);

A configuração do endpoint do Camel utilizando uma fila ficaria da seguinte forma:


from("jms:queue:jms/FILA.GERADOR")
.to(destino);

Mas como a fila do HornetQ precisa do login e senha do usuário do Wildfly, precisamos passar os
parâmetros username e password para a rota:
from("jms:topic:jms/TOPICO.LIVRARIA?username=jms&password=jms2")
.to(destino);

from("jms:queue:jms/FILA.GERADOR?username=jms&password=jms2")
.to(destino);

Com o componente JMS configurado, podemos também enviar uma mensagem JMS diretamente
pelo Camel utilizando o ProducerTemplate que recebe o destino (fila ou tópico) e os dados a enviar:

ProducerTemplate template = context.createProducerTemplate();


template.sendBody(
"jms:topic:jms/TOPICO.LIVRARIA?username=jms&password=jms2", pedido);

O método sendBody(..) é sobrecarregado e possui outras versões para enviar outros tipos de
dados ou adicionar cabeçalhos na mensagem JMS. Repare que toda a complexidade da API JMS está
escondida, não é preciso lidar com Connection, Session, Publisher, Message etc.

7.18 ENVIANDO E CONSUMINDO MENSAGENS JMS 181


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Analogamente a ProducerTemplate existe um ConsumerTemplate .

7.19 EXERCÍCIOS: CONSUMINDO MENSAGENS JMS COM APACHE


CAMEL
1. No Eclipse, no projeto fj36-livraria copie as JARs do Apache Camel dessa forma:

Vá ao diretório Desktop/caelum/36/jars
Entre na pasta lib-camel, escolha todos os JARS e copie para a pasta WebContent/WEB-INF/lib

2. Abra o arquivo spring-context.xml , que se encontra na pasta src/META-INF .

Nele, descomente o namespace do Apache Camel que faz parte do cabeçalho:


http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd

Descomente também a declaração do camel-context :


<camel:camelContext />

Dessa maneira, o Spring vai inicializar o Apache Camel.

3. Agora vamos configurar o JmsComponent que será utilizado pela aplicação da livraria. Dentro do
spring-context.xml , descomente o seguinte trecho de código:

<bean id="camelDestinationResolver"
class="org.springframework.jms.support.destination.JndiDestinationResolver">

<property name="jndiTemplate" ref="jmsJndiTemplate"/>


</bean>

<bean id="camelJMSConfig"
class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="hornetQConnectionFactory"/>
<property name="destinationResolver" ref="camelDestinationResolver"/>
</bean>

4. Abra a classe ConfiguracaoCamel . Nela, injete o CamelContext :

@Autowired
private CamelContext context;

5. No método init , configure as rotas que serão utilizadas pelo Camel:


context.addRoutes(new RouteBuilder() {

public void configure() {


from("jms:topic:jms/TOPICO.LIVRARIA?username=jms&password=jms2").
log(LoggingLevel.INFO,"CAMEL: Recebendo MSG ${id}").
to("jms:queue:jms/FILA.GERADOR?username=jms&password=jms2");
}
});

6. Ainda no método init() inicialize o CamelContext :

182 7.19 EXERCÍCIOS: CONSUMINDO MENSAGENS JMS COM APACHE CAMEL


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
context.start();

7. No método destroy() pare o CamelContext :


context.stop();

8. Inicialize o Tomcat e o Wildfly.

No console do Tomcat deve aparecer uma mensagem que o Apache Camel subiu:
21:14:52,116 INFO SpringCamelContext:1534 - Apache Camel 2.12.1
(CamelContext: camel-1) started in 1.045 seconds

9. Acesse a loja e finalize uma compra:

http://localhost:8088/fj36-livraria

Depois verifique no console do Tomcat se a mensagem foi enviada.

10. Crie um Message Driven Bean na aplicação fj36-webservice para ler as mensagens que são
enviadas para a fila jms/FILA.GERADOR do HornetQ.

7.20 PIPES E FILTERS


O XML do pedido possui todas as informações sobre os itens vendidos e os dados do pagamento. No
entanto, para a geração dos ebooks os dados do pagamento não interessam. Isso até pode representar um
problema de segurança se repassarmos estes dados sensíveis para o sistema de geração de ebooks ou
qualquer outro sistema.

Claro que o Camel já possiu uma forma elegante para extrair ou separar dados em formato XML. Na
verdade, no mundo XML já há um padrão para procurar um elemento ou atributo dentro de um XML.
Essa tarefa foi atribuída ao XPath que define uma sintaxe para pesquisar no XML. Java já implementou o
XPath e Apache Camel integra essa implementação.

Com XPath podemos procurar ou selecionar elementos do XML. O que faremos com o resultado
depende do componente Camel que utiliza o XPath. Para filtrar o XML podemos usar o método
filter da Camel DSL. Por exemplo, pode fazer sentido descobrir se o pagamento realmente está
confirmado, pois só assim podemos continuar o fluxo da mensagem.
from("file:entrada").
filter().
xpath("/pedido/pagamento/status[text()='CONFIRMADO']").
to("file:saida");

Repare que usamos o caracter / para navegar no XML e a função text() para extrair o texto do
elemento status .

Existem várias outras funções e operadores. O XPath possui uma sintaxe rica para procurar no XML
a partir de seus elementos, atributos e conteúdo. Mais informações com exemplos e explicações práticas

7.20 PIPES E FILTERS 183


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
podem ser encontradas no site:

http://www.w3schools.com/xpath/

XPath não é só usado pelo método filter() , podemos também dividir o conteúdo baseado na
expressão XPath. Nesse caso usamos o método split() . O exemplo abaixo retorna todos os livros do
pedido, ou seja, sem as informações dos itens e pagamento:
from("file:entrada").
split().
xpath("/pedido/itens/item/livro").
to("file:saida");

Como já falamos, o Apache Camel implementa a maioria dos Enterprise Integration Pattern. Split
e Filter são mais dois na longa lista de padrões.

ENTERPRISE INTEGRATION PATTERN - FILTER

Use a special kind of Message Router, a Message Filter, to eliminate undesired messages from a
channel based on a set of criteria.

http://www.eaipatterns.com/Filter.html

ENTERPRISE INTEGRATION PATTERN - MESSAGE ROUTER

Insert a special filter, a Message Router, which consumes a Message from one Message Channel
and republishes it to a different Message Channel channel depending on a set of conditions.

http://www.eaipatterns.com/MessageRouter.html

7.21 EXERCÍCIOS OPCIONAIS: FILTRO E DIVISÃO DE CONTEÚDO


1. Na classe ConfiguracaoCamel , adicione um filter com XPath que aceite apenas mensagens
com pelo menos um ebook:
from("jms:topic:jms/TOPICO.LIVRARIA?username=jms&password=jms2").
log(LoggingLevel.INFO,"CAMEL: Recebendo MSG ${id}").
filter().
xpath("/pedido/itens/item/formato[text()='EBOOK']").
to("jms:queue:jms/FILA.GERADOR?username=jms&password=jms2");

2. Ainda na mesma classe, aplique uma divisão do conteúdo usando o método split() . O objetivo é
enviar apenas os dados dos itens para a fila:

184 7.21 EXERCÍCIOS OPCIONAIS: FILTRO E DIVISÃO DE CONTEÚDO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
from("jms:topic:jms/TOPICO.LIVRARIA?username=jms&password=jms2").
log(LoggingLevel.INFO,"CAMEL: Recebendo MSG ${id}").
filter().
xpath("/pedido/itens/item/formato[text()='EBOOK']").
split().
xpath("/pedido/itens").
to("jms:queue:jms/FILA.GERADOR?username=jms&password=jms2");

3. Acesse a loja e finalize uma compra, lembre-se que, para receber a mensagem, a compra deve ter pelo
menos um ebook:

http://localhost:8088/fj36-livraria

Verifique no terminal do Wildfly se a mensagem chegou no Message Driven Bean que recebe as
mensagens da fila jms/FILA.GERADOR .

7.22 INTEGRAR SERVIÇOS SOAP E REST


Apache Camel também oferece facilidades para se comunicar com serviços Web, seja REST ou
SOAP.

Para enviar uma requisição HTTP, por exemplo, e chamar um serviço REST, existe um componente
do Camel que integra a biblioteca HttpClient do Apache Commons.

Se quisermos usar a versão 3 do HttpClient basta escrever:


from("http://localhost:8080/fj36-webservice/pagamento/1").
unmarshal(formatoJaxb). //usando formato JAX-B como visto
//processamento do objeto
to("file:pagamentos"); //gravando na pasta pagamentos

Nesse exemplo enviamos uma requisição GET que devolve um XML do pagamento. Para usar a
versão 4 do HttpClient basta trocar de http para http4 .

from("http4://localhost:8080/fj36-webservice/pagamento/1").
unmarshal(formatoJaxb). //usando formato JAX-B como visto
//processamento do objeto
to("file:pagamentos");

Desse jeito é fácil enviar uma requisição GET, basta usar a URI dentro do método from(..) ou
to(..) . Porém para enviar um POST é preciso alterar um cabeçalho da mensagem. Veja o exemplo
que usa um POST para enviar um JSON:
from("file:pagamentos"). //lendo XML da pasta pagamentos
marshal().xmljson(). //marshal para JSON
setHeader(Exchange.HTTP_METHOD, constant("POST")).
setHeader(Exchange.CONTENT_TYPE, constant("application/json")).
to("http://localhost:8080/fj36-webservice/pagamento");

Para utilizar um serviço Web SOAP/WSDL o Camel usa o componente CXF. O CXF é uma
biblioteca popular, também da Apache, que segue a especificação JAX-WS. Na verdade, já usamos CXF
através do JAX-WS quando publicamos o serviço Web no servidor, pois CXF é a implementação do

7.22 INTEGRAR SERVIÇOS SOAP E REST 185


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
JBoss Wildfly.

O CXF é acionado pelo prefixo cxf dentro da Camel DSL:


cxf:endereços-servico?opções

Por exemplo, para enviar uma requisição SOAP para um serviço escrevemos dentro do método
to(..) :

to("cxf:http://localhost:8080/fj36-webservice/EstoqueWS?"
+ "serviceClass=br.com.caelum.estoquews.v1.EstoqueWS"
+ "&"
+ "dataFormat=MESSAGE")

A serviceClass define a interface do serviço. Essa classe normalmente é gerada pela ferramenta
wsimport ou wsdl2java quando criamos as classes de cliente:

wsimport -keep -s src http://localhost:8080/fj36-webservice/EstoqueWS?wsdl

O parâmetro dataFormat=MESSAGE significa que vamos fornecer a mensagem SOAP. Ou seja, o


CXF não criará o XML SOAP.

Para enviar uma mensagem ao serviço de EstoqueWS basta enviar o XML abaixo:

<soapenv:Envelope ...>
<soapenv:Header>
<v1:tokenUsuario>TOKEN123</v1:tokenUsuario>
</soapenv:Header>
<soapenv:Body>
<v1:ItensPeloCodigo>
<codigo>ARQ</codigo>
</v1:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>

Assim, podemos configurar a rota completa. Ela lê as mensagens SOAP de uma pasta e envia para o
serviço de estoque:

//dentro da pasta requisição devem estar as mensagens SOAP


from("file:requisicao?delay=5s").
marshal().string().
to("cxf:http://localhost:8080/fj36-webservice/EstoqueWS?"
+ "serviceClass=br.com.caelum.estoquews.v1.EstoqueWS"
+ "&"
+ "dataFormat=MESSAGE").
unmarshal().string().
log("${body}");

A resposta SOAP aparece no console pois logamos o resultado pelo método log(..) :
<soap:Envelope ...>
<soap:Body>
<ns2:ItensPeloCodigoResponse ...>
<ItemEstoque>
<codigo>ARQ</codigo>
<quantidade>2</quantidade>
</ItemEstoque>

186 7.22 INTEGRAR SERVIÇOS SOAP E REST


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
</ns2:ItensPeloCodigoResponse>
</soap:Body>
</soap:Envelope>

Podemos aproveitar o SoapJaxbDataFormat para desempacotar a resposta SOAP:


SoapJaxbDataFormat soap =
new SoapJaxbDataFormat("br.com.caelum.estoquews.v1",
new ServiceInterfaceStrategy(EstoqueWS.class, true));

Uma vez criado o DataFormat para mensagens SOAP só falta usar o mesmo no método
unmarshal(..) :

from("file:requisicao?delay=5s").
marshal().string().
to("cxf:http://localhost:8080/fj36-webservice/EstoqueWS?"
+ "serviceClass=br.com.caelum.estoquews.v1.EstoqueWS"
+ "&"
+ "dataFormat=MESSAGE").
unmarshal().string().
unmarshal(soap).
process(new Processor() {

@Override
public void process(Exchange exchange) throws Exception {

ItensPeloCodigoResponse itens = exchange.getIn().


getBody(ItensPeloCodigoResponse.class);
System.out.println(itens.getItemEstoque().get(0).getQuantidade());
}
});

No Processor pegamos a quantidade de livros disponíveis no estoque.

O SoapJaxbDataFormat também pode ser usado para o processo de marshal e gerar a mensagem
SOAP. No entanto, vamos utilizar uma outra forma comum de criar a mensagem. Já vimos como
funciona o Velocity nesse capítulo. É fácil através desse template engine gerar a mensagem SOAP.

Abaixo o template que usa a Expression Language para inserir o token ( ${headers.token} ) e
codigo ( ${body} ):

<!-- salvo dentro do classpath, por exemplo como soap_request.vm -->


<soapenv:Envelope ...>
<soapenv:Header>
<v1:tokenUsuario>${headers.token}</v1:tokenUsuario>
</soapenv:Header>
<soapenv:Body>
<v1:ItensPeloCodigo>
<codigo>${body}</codigo>
</v1:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>

A rota é bem parecida com a anterior, mas usa agora Velocity. Novo também é que a rota deve ser
inicializada através de uma chamada síncrona indicada pelo prefixo direct :

from("direct:start").
setHeader("token", constant("TOKEN123")).

7.22 INTEGRAR SERVIÇOS SOAP E REST 187


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
to("velocity:soap_request.vm").
log(LoggingLevel.INFO, "Requisição SOAP: ${body}").
to("cxf:http://localhost:8080/fj36-webservice/EstoqueWS?"
+ "serviceClass=br.com.caelum.estoquews.v1.EstoqueWS"
+ "&"
+ "dataFormat=MESSAGE").
unmarshal().string().
log("Resposta SOAP: ${body}");

A chamada síncrona pode ser feita pelo ProducerTemplate que envia o corpo da mensagem para a
rota direct:start :

ProducerTemplate template = context.createProducerTemplate();


template.sendBody("direct:start", "ARQ");

Como resposta recebemos a mensagem SOAP com a quantidade dos livros disponíveis.

7.23 ORQUESTRAÇÃO DE TAREFAS E SERVIÇOS


Já vimos como definir uma rota através do RouteBuilder . No entanto, só usamos até agora uma
rota dentro do método configure() . Podemos criar quantas rotas desejarmos e até mesmo definir um
fluxo entre elas. Dessa maneira é possível compor várias rotas para definir um processo maior, o que
também é chamado de orquestração.

No exemplo a seguir decidimos, baseado no estado do pagamento, qual rota seguir. Repare que
usamos a sintaxe direct:nomeDoEndpoint para encadear as rotas:

from("file:entrada").
choice().
when().
xpath("/pedido/pagamento/status[text()='CRIADO']").
to("direct:criado").
when().
xpath("/pedido/pagamento/status[text()='CONFIRMADO']").
to("direct:confirmado").
otherwise().
to("direct:cancelado");

from("direct:criado").
log(LoggingLevel.INFO, "Direct:criado ${body}").
//mais tarefas para pedidos criados
to("jms:topic:pagamentos");

from("direct:confirmado").
log(LoggingLevel.INFO, "Direct:confirmado ${body}").
//mais tarefas para pedidos confirmados
to("jms:queue:estoque");

from("direct:cancelado").
log(LoggingLevel.INFO, "Direct:cancelado ${body}").
//mais tarefas para pedidos cancelados
to("file:pedidos-cancelados");

Assim podemos dividir o fluxo em várias pequenas rotas, cada uma com sua reponsabilidade.

188 7.23 ORQUESTRAÇÃO DE TAREFAS E SERVIÇOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
7.24 AGREGAÇÃO DE MENSAGENS
Há situações que precisamos combinar mensagens de fontes diferentes ou juntar várias mensagens
em uma só. Por exemplo, podemos imaginar que os itens do pedido e o pagamento estão em XML
distintos, disponíveis em pastas diferentes. Nesse caso devemos definir duas rotas que leiam os XMLs,
uma rota para cada pasta. Cada rota envia o XML para uma terceira que agrega os dados e encaminha o
pedido completo ao destinatário.

Para tal, o Apache Camel oferece o componente Aggregate com o qual podemos definir quando e
como as mensagens devem ser combinados. Veja o exemplo a seguir já um pouco mais complexo:
from("file://itens"). //le os arquivos XML com itens apenas
unmarshal().string().
to("direct:combinarItensComPagamento");

from("file://pagamentos"). //le os arquivos XML com pagamentos apenas


unmarshal().string().
to("direct:combinarItensComPagamento");

from("direct:combinarItensComPagamento").
log("${body}").
aggregate( //agregando mensagens
constant(true), //usa qualquer mensagem
new ConcatenaAggregationStrategy()).
completionSize(2). //agregando sempre 2 mensagens seguidas
to("jms:topico:pedidos");

Na terceira rota usamos o método aggregate(..) que recebe dois parâmetros. O primeiro,
constant(true) , significa que aceitamos qualquer mensagem, sem critério. O segundo define como
queremos combinar as mensagens. Para isso existem estratégias representadas por uma classe. Por fim,
falamos que queremos sempre agregar duas mensagens em seguida.

A classe ConcatenaAggregationStrategy é responsável por juntar as duas mensagens. Nesse


exemplo estamos concatenando apenas o corpo das mensagem, mas poderíamos fazer algo mais
complexo:

public class ConcatenaAggregationStrategy implements AggregationStrategy {

public Exchange aggregate(Exchange primeiraMsg, Exchange segundaMsg) {

if (primeiraMsg == null) {
return segundaMsg;
}

String primeiroBody = primeiraMsg.getIn().getBody(String.class);


String segundoBody = segundaMsg.getIn().getBody(String.class);

primeiraMsg.getIn().setBody(primeiroBody + segundoBody);
return primeiraMsg;
}
}

O problema, ainda, é que aceitamos qualquer mensagem para agregar. Pode ser que o Camel

7.24 AGREGAÇÃO DE MENSAGENS 189


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
primeiro mande todas as mensagens de itens e depois os pagamentos. Não há garantia de ordem. Por
isso vamos criar um criterio ou identificador que liga uma mensagem de itens com a do pagamento. Em
uma aplicação real já existaria algo que podéssemos usar para saber quais itens estão relacionados com
qual pagamento.

No nosso exemplo vamos imaginar que há um id igual para itens e pagamentos. Ou seja, o nosso
critério para agregar as mensagens é o id . Esse id será extraída e colocado no cabeçalho da mensagem.

Veja como ficam as rotas:


from("file:itens?delay=5s").
unmarshal().string().
//extrai a id e coloca no header
setHeader("id", xpath("/itens/id/text()")).
to("direct:combinarItensComPagamento");

from("file:pagamentos?delay=5s").
unmarshal().string().
//extrai a id e coloca no header
setHeader("id", xpath("/pagamento/id/text()")).
to("direct:combinarItensComPagamento");

from("direct:combinarItensComPagamento").
//coloca a id como nome do arquivo
setHeader(Exchange.FILE_NAME, header("id")).
log("${body}").
aggregate(
header("id"), //combina mensagens com a mesma id
new ConcatenaAggregationStrategy()).
completionSize(2).
to("file:pedidos");

Mais exemplos e configurações podem ser encontradas na documentação do Aggregator:

http://camel.apache.org/aggregator2.html

7.25 EXERCÍCIOS OPCIONAIS: ORQUESTRAÇÃO E TEMPLATE


1. Copie todos os JARS da pasta Desktop/caelum/36/jars/lib-camel-template para a pasta
WebContent/WEB-INF/lib/ do projeto fj36-livraria .

2. No projeto fj36-livraria , crie um novo arquivo nota.vm na raíz da pasta src . Nele, adicione
o template de transformação:
<nota data="${headers.data}">
${body}
</nota>

3. Abra a classe ConfiguracaoCamel e adicione dentro do método configure() , mais uma rota:

from("direct:notas").
setHeader("data",
constant(new SimpleDateFormat("dd/MM/yyyy").format(new Date()))).
split().

190 7.25 EXERCÍCIOS OPCIONAIS: ORQUESTRAÇÃO E TEMPLATE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
xpath("/pedido/pagamento").
convertBodyTo(String.class).
to("velocity:nota.vm").
log(LoggingLevel.INFO,"CAMEL: MSG tranformado com Velocity ${body}");

4. Na rota anterior, dispache a mensagem para o endpoint direct:notas .

Adicione apenas a linha 2:


from("jms:topic:jms/TOPICO.LIVRARIA?username=jms&password=jms2").
to("direct:notas").
log(LoggingLevel.INFO,"CAMEL: Recebendo MSG ${id}").
filter().
xpath("/pedido/itens/item/formato[text()='EBOOK']").
split().
xpath("/pedido/itens").
to("jsm:queue:jms/FILA.GERADOR?username=jms&password=jms2");

Estamos repassando a mensagem para o endpoint direct:notas antes da transformação.

5. Reinicie o Tomcat, acesse a loja e finalize uma compra:

http://localhost:8088/fj36-livraria

Verifique no console se aparece no log a mensagem XML:


<nota data="11/12/2013">
<pagamento>
<id>14</id>
<status>CONFIRMADO</status>
<valor>29.90</valor>
<links/>
</pagamento>
</nota>

7.26 FRAMEWORK DE INTEGRAÇÃO OU ENTERPRISE SERVICE BUS?


Nesse momento poderia parecer que o Camel é um ESB (Enterprise Service Bus) e, realmente,
algumas das funções de um ESB tradicional o Apache Camel também possui. O próprio site do Camel
define o framework como a small, lightweight embeddable ESB.

A explicação do site também deixa clara a diferença: O Apache Camel roda embutido dentro da
aplicação. Um ESB é um container/servidor separado, um produto mais complexo com várias
funcionalidades. Além do roteamento e mediação já vistos, um ESB adiciona outros recursos como
monitoramento e gerenciamento de processos, balanceamento de carga e alta disponibilidade, oferece
um registro de serviços, força segurança e disponibiliza ferramentas (tooling), entre outras funções. Isso
também faz com que um ESB seja um produto mais complexo, enquanto o Apache Camel foca no
roteamento e mediação.

Exemplos de ESBs que usam o Apache Camel por baixo são:

Red Hat JBoss Fuse (http://www.jboss.org/products/fuse)

7.26 FRAMEWORK DE INTEGRAÇÃO OU ENTERPRISE SERVICE BUS? 191


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Apache Service Mix (http://servicemix.apache.org/)
Talend Enterprise ESB (http://www.talend.com/products/esb)

Claro que existem ESBs que possuem a sua própria routing engine. Alguns ESBs populares que não
utilizam o Camel são o Mule ESB e o Oracle Service Bus.

192 7.26 FRAMEWORK DE INTEGRAÇÃO OU ENTERPRISE SERVICE BUS?


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 8

ENTERPRISE SERVICE BUS

"Para obtermos êxito no mundo temos de parecer idiotas mas sermos espertos." -- Baron de Montesquieu

8.1 O QUE É UM ESB?


Podemos pensar que o Enterprise Service Bus é uma espécie de servidor com a tarefa de conectar
aplicações. Através de um ESB (ou barramento) as aplicações podem trocar informações sem conhecer
uma a outra, ou seja de maneira desacoplada. A responsabilidade de lidar com os inúmeros formatos de
dados e protocolos relacionados com o mundo de integração fica central com o ESB.

O ESB é um intermediário na comunicação que faz o roteamento das mensagens entre os Endpoints
e pode transformar os dados para adaptar os formatos, ou inserir novas informações. Ele pode subir
novos serviços baseado em outros e orquestrar um fluxo de mensagens. Toda comunicação se concentra
então nesse barramento que é uma das vantagens principais. A centralização facilita o monitoramento, o
tratamento de erro ou a aplicação de regras de segurança. Um ESB é um componente chave dentro da
arquitetura baseada em serviços.

[TODO: img]

8.2 QUANDO USAR UM ESB?


Um ESB pode ser útil onde há alguns serviços ou aplicações (pontos de integração) para integrar. Ele
também é adequado para cenários que necessitam um baixo acoplamento, escalabilidade e robustez.

Abaixo está uma lista de verificação para descobrir se um ESB realmente é preciso:

Você precisa integrar três ou mais aplicações/serviços?


Você vai precisar de ligar mais aplicações no futuro?
Você precisa usar mais de um tipo de protocolo de comunicação?
Você precisa de recursos de roteamento de mensagens, como a bifurcação e agregação de
fluxos de mensagens ou roteamento baseado em conteúdo?
Você precisa publicar serviços para consumo por outras aplicações?

No link abaixo tem a lista completa com mais perguntas que podem ajudar nessa difícil decisão:

http://blogs.mulesoft.org/to-esb-or-not-to-esb/

8 ENTERPRISE SERVICE BUS 193


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
8.3 EXERCÍCIOS: INSTALAÇÃO DO ANYPOINTSTUDIO
1. Entre no diretório Caelum/36/mule , disponível no seu Desktop .

Copie o arquivo AnypointStudio-xxx.tar.gz para a pasta pessoal ( /home/soaXXXX ).

Extraia o arquivo na mesma pasta.

2. Abra um novo terminal e entre no diretório AnypointStudio . Para rodar o AnypointStudio


execute:

chmod +x AnypointStudio
./AnypointStudio

Confirme a pasta padrão do workspace . Aperte o botão skip para fechar o diálogo de ajuda e feche
a tela de boas-vindas. para

8.4 FLUXO COM MULE ESB


No Mule, as regras de onde virão os dados e para onde vão são definidas dentro de um fluxo. Um
fluxo é parecido com uma rota do Apache Camel.

Com Mule, geralmente criamos no início do projeto uma entrada de dados (inbound) que recebe
dados (mensagens) de uma fonte externa. Ao receber é criado assim uma instância de um fluxo. Cada vez
que é recebida uma mensagem, uma nova instância do fluxo é criada.

As entradas são definidas através de um conector. No Mule, uma fonte de mensagens pode aceitá-las
de vários canais de transporte. Por exemplo, você pode usar um Endpoint HTTP com SOAP e juntar as
informações para criar uma mensagem composta. Quais são as fontes concretas pode variar muito.

O Mule tem um amplo suporte aos protocolos e padrões no mundo de integração.

Exchange Patterns
Os conectores do Mule são na verdade os endpoints, que significa que recebem dados (inbound) ou
enviam dados (outbound). Os Inbound endpoints normalmente aparecem no início do fluxo, outbound
endpoints aparecem no meio ou no fim de um fluxo. Entre os endpoints podem ser utilizados os padrões
de comunicação unidirecional (one-way) ou bidirecional, seguindo o modelo request-response.

Configuração do fluxo
Um fluxo é configurado no XML com ajuda do AnypointStudio. Ele começa com o elemento `
seguido pela definição da entrada do fluxo, os processadores de dados e estratégias de tratamento de
erro:

A estrutura básica de um fluxo é:

194 8.3 EXERCÍCIOS: INSTALAÇÃO DO ANYPOINTSTUDIO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<flow name="">
- 0..1 MessageSource
- 1..n MessageProcessor(s)
- 0..1 ExceptionStrategy
</flow>

8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO


1. No AnypointStudio crie um novo projeto do tipo Mule Project chamado fj36-estoque-proxy.

Não é preciso alterar nenhuma outra configuração para criar o projeto. O AnypointStudio já vem
com o Mule ESB integrado.

2. Dentro do AnypointStudio, na paleta de componentes, procure o conector HTTP:

8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO 195


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Selecione o conector HTTP e arraste-o para o canvas:

196 8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO 197
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Nas propriedades do conector HTTP altere o nome para EstoqueWS Listener.

Observação: O conector representa o componente que recebe a requisição HTTP. O Mule também
chama esse tipo de componente de HTTP inbound endpoint.

3. Para o conector funcionar corretamente devemos criar uma configuração que define o IP, porta e
caminho raiz.

Ainda nas propriedades clique no ícone + para adicionar uma nova Connector configuration:

No diálogo altere apenas o item Base Path para fj36-webservice e confirme. Isso faz com que o
Mule levante um listener HTTP recebendo todas as requisições para /fj36-webservice na porta
8081.

198 8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4. Através do conector HTTP vamos obter mais detalhes das requisições permitidas.

Falta ainda o caminho e o método HTTP. Nas propriedades do conector, selecione o item path
/EstoqueWS e insira os POST no item Allowed Methods.

5. Para trabalhar com serviços SOAP o Mule já vem integrado com o componente CXF. Na paleta
procure pelo CXF e arraste-o para o fluxo dentro do elemento process.

8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO 199


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Nas propriedades renomeie o componente para CXF Proxy e no item Operation escolha Proxy
Service.

6. Para ver a mensagem SOAP precisamos adicionar o componente Logger ao fluxo. Na paleta de
componentes procure pelo Logger e arraste-o para o lado direito do CXF Proxy:

200 8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Nas propriedades do elemento adicione a seguinte mensagem usando a Mule Expression Language
(MEL):
#[message.payloadAs(java.lang.String)]

Não esqueça salvar o fluxo.

7. O primeiro fluxo está definido. Para rodar a aplicação clique com o botão direito, no Package
Explorer, sobre a aplicação fj36-estoquews-proxy , Run As e Mule Application.

Fique atento ao console para perceber possíveis problemas.

8. Abra o SoapUI e crie um novo projeto SOAP com o nome ClienteEstoque.

No Initial WSDL aponte para a pasta Desktop/Caelum/36/mule/EstoqueWS.wsdl (é o WSDL do


nosso serviço EstoqueWS , porém use a porta 8081 ).

Envie uma requisição SOAP pelo SoapUI. Após da execução verifique o AnypointStudio, a
mensagem SOAP deve aparecer no console!

9. Selecione o componente CXF Proxy e altere o elemento Payload de body para envelope.

8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO 201


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Chame novamente o fluxo pelo SoapUI e verifique o console no AnypointStudio. Qual é a diferença
entre envelope e body?

10. No AnypointStudio, no canvas principal, escolha a aba Configuration XML:

Repare que o fluxo nada mais é do que uma configuração no XML:


<mule ...>
<http:listener-config name="HTTP_Listener_Configuration"
host="0.0.0.0" basePath="/fj36-webservice" .../>

<flow name="fj36-estoque-proxyFlow1">
<http:listener config-ref="HTTP_Listener_Configuration"
path="/EstoqueWS" doc:name="EstoqueWS Listener"
allowedMethods="POST"/>
<cxf:proxy-service doc:name="CXF Proxy" payload="envelope"/>
<logger message="#[message.payloadAs(java.lang.String)]"
level="INFO" doc:name="Logger"/>

</flow>
</mule>

Podemos configurar o fluxo usando o XML que é a forma mais rápida, mas para tal é preciso
conhecer bem os componentes. A interface gráfica facilita a descoberta de componentes e dá uma
visão geral do fluxo.

8.6 ROTEAMENTO PELO NAMESPACE


Ao receber uma mensagem no fluxo, uma tarefa cotidiana é analisar o conteúdo e tomar alguma
decisão no fluxo. Pode ser que aplicacaremos regras de validação diferentes, inserimos algumas
infomações novas ou roteamos a mensagem baseado no conteúdo.

O Mule vem bem preparado para este tipo de tarefa. Podemos filtrar, transformar, validar ou dividir
o conteúdo confortávelmente dentro do AnypointStudio. Para analisar o namespace de uma mensagem
XML é utilizado XPath.

Nesse exemplo queremos verificar o namespace da mensagem SOAP. No XPath podemos accessar
qualquer elemento através da expressão //*

Por exemplo, para mostrar todos os nomes de elementos de um XML usamos:


//*:name()

202 8.6 ROTEAMENTO PELO NAMESPACE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Para mostrar todos os nomes sem prefixo do namespace usamos a função local-name() :

//*:local-name()

Para extrair o namespace dos elementos usamos a função namespace-uri() . Por exemplo, para
encontrar todos os elementos com o namepace http://caelum.com.br/estoquews/v1 podemos
escrever:
//*[namespace-uri()='http://caelum.com.br/estoquews/v1']

Para descobrir se um elemento (aqui Envelope ) faz parte de um namespace específico:

//*[local-name()='Envelope' and
namespace-uri()='http://schemas.xmlsoap.org/soap/envelope/']

Dessa maneira podemos verificar a existência de um namespace no XML. Falta agora tomar uma
decisão baseado no resultado. Para isso o Mule oferece o componente Choice que recebe a expressão
XPath e faz o roteamento do fluxo se for verdadeira.

<choice doc:name="Choice">

<when expression="#[xpath3( ... )]">


<!-- fluxo omitido -->

</when>

<when expression="#[xpath3( ... )]">


<!-- fluxo omitido -->
</when>
<otherwise>
<!-- fluxo padrao -->
</otherwise>

Repare que o componente when recebe uma Mule Expression que chama a função xpath3 . Essa
função recebe, como String, a expressão XPath, por exemplo:
#[xpath3("//*[namespace-uri()='http://caelum.com.br/estoquews/v1']")]

8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO


Nesse exercício faremos um roteamento da mensagem SOAP enviada para o Mule ESB.

Durante do curso criamos um serviço SOAP para o controle do estoque. Para chamar o serviço
usamos a URL /fj36-webservice/EstoqueWS . O versionamento foi feito através do namespace
http://caelum.com.br/estoquews/v1 .

Vamos simular uma segunda versão do Service estoque com o novo namespace
http://caelum.com.br/estoquews/v2 . A ideia é que o cliente do serviço chame o Mule ESB sempre
usando a mesma URL /fj36-webservice/EstoqueWS e na mensagem SOAP. Através do namespace
definimos a versão do serviço.

8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO 203


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Analisaremos o namespace da mensagem através do Mule ESB para descobrir qual serviço concreto
devemos chamar e roteamos a mensagem para a versão correta do servico de estoque.

1. Para o roteamento da mensagem usaremos o componente Choice. Na paleta de componentes


procure pelo Choice e arraste-o componente ao lado direito do Logger.

2. Para testar vamos usar novamente o componente Logger. Procure o componente Logger na paleta e
arraste-o para dentro do componente Choice.

Selecione o novo Logger renomeando-o para Logger V1 :. Nas propriedades do Logger V1 defina a
mensagem como: Chamando serviço EstoqueWS V1.

204 8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3. O componente Choice já está ligado ao Logger, falta definir a condição de chamada do Logger V1.
Vamos procurar dentro da mensagem SOAP através do XPath se o namespace possui a string
caelum.com.br/estoquews/v1 .

Para associar essa expressão com o Choice, selecione o Choice e procure dentro das propriedades o
elemento when:

Clique no when e adicione a seguinte expressão (DICA: Você pode copiar a expressão da pasta
caelum/36/mule/exemplo-xpath.txt . Aconselhamos colar diretamente no arquivo XML. O Mule
faz um escape dos caracteres quando colamos através da interface gráfica).
#[xpath3(&quot;//*[contains(namespace-uri(),
'http://caelum.com.br/estoquews/v1')]&quot;,

8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO 205


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
payload, 'BOOLEAN' )]

Observação: Usamos a Expression Language do Mule para chamar a função xpath3 que recebe
como primeiro parâmetro a expressão XPath. O segundo parâmetro é o payload da mensagem, ou
seja a mensagem SOAP. Por fim, o último parâmetro é o tipo do retorno (um booleano).

4. Vamos simular uma requisição para o serviço de estoque na versão v1 . Para tal, procure o
componente HTTP dentro da paleta e arraste-o ao lado do Logger V1. Selecione o componente
renomeando-o para HTTP V1:

Nas propriedades defina uma Connector Configuration:

206 8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Nela configure o Host como localhost e a Port como 8088:

Confirme o diálogo, adicione ainda o PATH como /v1/EstoqueWS e o método HTTP como POST

8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO 207


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
5. Repita o processo para definir o segundo roteamento para a versão V2.

Se prefir, podemos copiar e colar o primeiro elemento when e depois fazer os ajustes (trocar V1 por
V2 na expressão XPath, URL e nos nomes).

Segue o resultado final na canvas:

6. Rode a aplicação dentro do AnypointStudio e verifique o console para descobrir possíveis erros.

7. Através do Mule ESB estamos roteando para o serviço de estoque nas versões v1 e v2 . Para testar
o roteamento vamos rodar dois serviços que simulam os endpoints do estoque.

Copie o JAR Desktop/caelum/36/mule/mule-mocks.jar para sua pasta pessoal. Abra um

208 8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
terminal e rode o JAR:
java -jar mule-mocks.jar

Isso irá subir os dois webservices SOAP (além de um pequeno servidor web):
*** Serviços SOAP rodando
http://localhost:8088/v1/EstoqueWS?wsdl
http://localhost:8088/v2/EstoqueWS?wsdl

Observação: O código fonte dos serviços está disponível em Desktop/caelum/36/mule/fj36-mule-


mocks-src.zip . O projeto usa JAX-WS para publicar os serviços e um SOAP Handler para mostrar
o cabeçalho. Os arquivos WSDL e o XSD publicados estão dentro da pasta src do projeto.

8. Uma vez o fluxo e os endpoints do estoque no ar, podemos testar o roteamento. No SoapUI execute
o request SOAP criado anterioramente.

Volte ao AnypointStudio e verifique no console para descobrir qual versão do serviço foi roteado.

9. Volte ao SoapUI e manipule o namespace que define a versão do serviço (o namepace faz parte do
elemento envelope ). Altere o namespace para a versão v2 .

O namespace completo fica:


http://caelum.com.br/estoquews/v2

Execute novamente o request SOAP e verifique a saída no AnypointStudio.

10. (opcional) Altere uma vez o namespace para uma versão inexistente, por exemplo:
http://caelum.com.br/estoquews/v3

Execute o request pelo SoapUI e verifique o resultado.

11. (opcional) Vamos alterar o código HTTP para 404 caso não consigamos rotear para um endpoint.

Na paleta de componentes procure por Property e arraste-o para o elemento Default. Nas
configurações do componente escolha Set Property. Ainda nas configurações coloque no campo
Name o valor http.status , e no campo Value o código 404.

8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO 209


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Salve o fluxo e execute novamente o request pelo SoapUI. Repare que a resposta possui o código 404,
caso o namespace aponte para um serviço inexistente.

Mais para frente veremos mecanismos mais sofisticado para lidar com erros.

8.8 TRANSFORMAÇÃO DA MENSAGEM PELO XSLT


Para este exercício já preparamos o arquivo XSLT que define as regras de transformação da
mensagem SOAP. O objetivo é adicionar um cabeçalho na mensagem SOAP, algo muito comum na
integração.

O ESB pode abstrair a complexidade de criar cabeçalhos mais sofisticados como os do WS-Security
ou transformar cabeçalhos já existentes. O ESB funciona como mediator entre cliente e serviço aplicando
regras do arquivo XSLT.

210 8.8 TRANSFORMAÇÃO DA MENSAGEM PELO XSLT


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
8.9 EXERCÍCIOS OPCIONAL: APLICANDO XSLT PARA ADICIONAR
NOVOS HEADERS SOAP
1. Copie o arquivo soap-header.xslt que se encontra na pasta caelum/36/mule para a pasta
src/main/resources do seu projeto.

2. Na paleta de componentes procure pelo componente XSLT e arraste-o entre os componentes CXF
Proxy e Logger.

Renome o componente XSLT para SOAP Header e ainda nas propriedades escolha a opção XSL File
para selecionar o arquivo soap-header.xslt :

3. Verifique se o fluxo foi salvo. O Mule ESB e os serviços do estoque devem estar rodando. Fique
atento ao console do AnypointStudio.

No SoapUI envie um request SOAP, por exemplo, para o serviço do estoque na versão v1 .

Volte ao AnypointStudio e procure no console a mensagem SOAP. Como estamos logando a


mensagem já transformada deve aparecer o cabeçalho que foi adicionado através do XSLT.

4. (Opcional) Abra o arquivo XSLT utilizado e o analise. Temos 3 templates: o primeiro copia e XML
inteiro, o segundo é responsável por adicionar o elemento tokenUsuario dentro de um elemento
soap:Header já existente e o terceiro cria um novo soap:Header com o elemento
tokenUsuario .

8.10 ORQUESTRAÇÃO DE SERVIÇOS

8.9 EXERCÍCIOS OPCIONAL: APLICANDO XSLT PARA ADICIONAR NOVOS HEADERS SOAP 211
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Ao fazer uma compra na livraria é gerado um XML que possui os dados de pagamentos e os itens de
compra. Nos capítulos anteriores vimos como usar JMS para enviar esse XML e consumir
assincronamente.

Nesse exercícios veremos como publicar um endpoint REST com Mule que pode ser utilizada pela
livraria para enviar o XML do pedido. Vamos criar um fluxo que trata o XML e se integra com outros
sistemas, como o sistema financeiro e o gerador de EBook. Além disso, vamos separar os dados do XML
e transformá-lo para JSON e SOAP, algo muito comum no mundo das integrações.

Novamente utilizaremos o SoapUI para testar e enviar o request HTTP com a diferença que o
request não usa SOAP e sim um POX (plain old xml) com os dados do pedido.

8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS


1. No AnypointStudio crie um novo projeto fj36-pedidos. Verifique as configurações e confirme o
diálogo.

2. O primeiro passo nesse projeto é criar o endpoint HTTP. Dentro do AnypointStudio, na paleta de
componentes, procure o componente HTTP:

212 8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Selecione o conector HTTP e arraste-o para o canvas. Nas propriedades do conector HTTP altere o
nome para Pedidos Listener.

8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS 213


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Ainda nas propriedades clique no ícone + para adicionar uma nova Connector configuration: para
configurar IP, porte e o caminho.

No diálogo altere o item Base Path para fj36-pedidos e a porta para 8082 . Isso faz com que o
Mule levante um listener HTTP recebendo todas as requisições para /fj36-pedidos na porta 8082.
Após isso, confirme.

214 8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3. Ainda nas propriedades do conector HTTP (Pedidos Listener), no item Allowed Methods, insira o
método POST .

O connector HTTP é configurado para receber requisições do tipo HTTP POST para
http://localhost:8082/fj36-pedidos .

8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS 215


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4. Para testar o novo endpoint faltam ainda o usar um logger e definir a resposta.

Primeiro procure o componente Logger e arraste ao lado do Pedido Listener (dentro do process).

Nas propriedades configure a mensagem de log como:


Recebendo XML: #[message.payloadAs(java.lang.String)]

5. Agora vamos configurar a resposta. Procure na paleta pelo componente Set Payload e arraste-o
abaixo do Logger.

216 8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Através desse componente vamos definir a resposta HTTP. Nas propriedades coloque no elemento
Value e valor:
<resposta>ok</resposta>

6. Ainda relacionado com a resposta, procure na palete pelo componente Property e arraste-o ao lado
do componente Set Payload (dentro do response):

8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS 217


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Nas propriedades escolha Set Property, no elemento Name digite http.status seguido pelo Value 202:

218 8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Observação: O código HTTP 202 significa Accepted e deve ser utilizado quando uma requisição foi
aceita para ser processada mas o processamento não foi concluido ainda ou ocorre algum momento
depois.

7. Garanta que tudo foi salvo e rode o fluxo pelo AnypointStudio. Para isso, selecione o projeto fj36-
pedidos e depois Run As -> Mule Application

Fique atento ao console para descobrir possíveis erros.

Segue o XML do fluxo para comparação:

<http:listener-config name="HTTP_Listener_Configuration"
host="0.0.0.0" port="8082" doc:name="HTTP Listener Configuration"
basePath="/fj36-pedidos"/>

<flow name="fj36-pedidosFlow">

8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS 219


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
<http:listener config-ref="HTTP_Listener_Configuration"
path="/" allowedMethods="POST" doc:name="Pedidos Listener"/>

<logger message="#[message.payloadAs(java.lang.String)]"
level="INFO" doc:name="Logger"/>
<response>
<set-payload value="&lt;resposta&gt;ok&lt;/resposta&gt;"
doc:name="Set Property"/>
<set-property propertyName="http.status" value="202"
doc:name="Property"/>
</response>
</flow>

8. Se o fluxo estiver na ar podemos testá-lo. Abra o SoapUI e crie um novo Rest Project com a URI:
http://localhost:8082/fj36-pedidos

9. Na janela de configuração do request, defina o método HTTP POST e altere o Media Type para
application/xml.

Abaixo do Media Type cole o conteúdo do arquivo pedido.xml que se encontra no


Desktop/caelum/36/mule .

Observação: O XML utilizado é igual aos outros gerados pela livraria. Possui os dados do pagamento
e os itens comprados.

10. Submeta a resposta pelo SoapUI. Você deve receber uma resposta 202 com um
<resposta>ok</resposta> .

Também verifique o console do AnypointStudio para ver o XML do pedido no log.

8.12 EXERCÍCIOS: FILTER, SPLIT E TRANSFORMAÇÃO DE DADOS


Nesse exercício, integraremos o serviço de geração dos Ebook com o nosso fluxo já existente.

O serviço Ebook é um servico Rest que recebe um JSON para cada item de compra. Ou seja, se há
alguma venda do tipo Ebook no XML do pedido devemos separar a mensagem pelos itens e enviar um
JSON com os dados de cada item para o serviço.

Vamos representar essa integração do serviço Ebook através de um Sub Flow. Sub Flows ajudem
separar a responsabilidade e facilitam o entendimento do fluxo. Mãos a obra!

1. Vá a paleta e procure o componente Sub Flow e arraste-o abaixo do fluxo de pedidos. Selecione o
sub flow e renomeie-o para ebook_flow.

2. O primeiro passo no ebook_flow é separar os itens do pedido e para isso, vamos usar o componente
Splitter. Um Splitter é capaz de dividir uma mensagem em várias mensagens menores.

Procure o componente Splitter e arraste-o para o sub flow.

220 8.12 EXERCÍCIOS: FILTER, SPLIT E TRANSFORMAÇÃO DE DADOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Selecione o Splitter e coloque no elemento Expression o critério da divisão do conteúdo. Usaremos
XPath baseado no elemento //item :
#[xpath3('//item', payload, 'NODESET')]

3. O segundo passo é filtrar os itens. Não temos interesse em itens que não sejam do tipo EBOOK. Para
tal tarefa procure o componente Filter Reference e jogue-o ao lado do Splitter.

8.12 EXERCÍCIOS: FILTER, SPLIT E TRANSFORMAÇÃO DE DADOS 221


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Um filter funciona de forma parecida como um endpoint HTTP e precisa de uma configuração
global. Nas propriedades, no elemento Global Reference clique no icone + para criar uma nova
Expression (dentro do elemento Filters selecione Expression)

222 8.12 EXERCÍCIOS: FILTER, SPLIT E TRANSFORMAÇÃO DE DADOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Na janela chame a expression de EbookExpression e cole a expressão XPath:

#[xpath3("/item/formato/text() = 'EBOOK'",
message.payloadAs(java.lang.String),
'BOOLEAN')]

4. Após ter aplicado o splitter e filter temos no nosso sub flow só mensagens com um item do tipo
ebook. O próximo passo é transformar o item de XML para JSON.

Para tal usaremos o componente XML to JSON que mapea cada elemento XML para um elemento
JSON. Jogue esse componente ao lado do filter.

8.12 EXERCÍCIOS: FILTER, SPLIT E TRANSFORMAÇÃO DE DADOS 223


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Aproveite também e coloque o logger ao lado do transformer para mostrar a mensagem no console.
Como Message configure:
Ebook Flow: #[message.payloadAs(java.lang.String)]

5. Falta ainda associar o nosso sub flow com o flow principal. Essa associação é feita pelo componente
Flow Reference. Procure o componente e arraste-o ao lado do logger no flow principal. No elemento
Flow Name escolha o ebook_flow, já no Display Name chame de Ebook Flow.

6. Ainda falta chamar o serviço externo HTTP pelo sub flow, aquele endpoint que receberá o JSON,
mas nada de já testarmos nosso fluxo.

Garanta que tudo esteja salvo e reinicie o servidor Mule ESB.

Vá ao SoapUI e envie novamente um request HTTP POST com o XML do pedido. A resposta deve
ser a mesma: <resposta>ok</resposta>

Depois verifique o console, além do XML do pedido deveria aparecer um JSON para cada item do
tipo ebook.

224 8.12 EXERCÍCIOS: FILTER, SPLIT E TRANSFORMAÇÃO DE DADOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
8.13 EXERCÍCIOS: MULTICAST COM SCATTER-GATHER PATTERN
Nesse exercício criaremos mais um sub flow. Esse segundo sub flow chamará um serviço legado
SOAP. Nele vamos gerar e enviar uma mensagem SOAP baseado no template XSLT.

1. Vamos representar essa segunda integração mais um vez através de um Sub Flow.

Vá até a paleta e procure pelo componente sub flow e arraste-o abaixo do outro sub flow. Selecione o
novo sub flow e renomeie-o para financeiro_flow.

2. No financeiro_flow vamos gerar uma mensagem SOAP baseado no template XSLT. Procure pelo
componente XSLT e arraste-o para dentro do sub flow financeiro_flow.

8.13 EXERCÍCIOS: MULTICAST COM SCATTER-GATHER PATTERN 225


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
3. Vá a pasta 36/mule e copie o arquivo pedido-para-soap.xslt para a raíz do seu projeto fj36-pedidos.

Selecione o componente XSLT, no elemento XSL File escolhe o arquivo copiado.

Observação: Abra uma vez o arquivo XSLT. Repare que estamos construindo uma mensagem SOAP
selecionando valores ( xsl:value ). Esses valores farão parte do soap:body . Repare também que
estamos transformando os itens do pedido em itens da nota.

4. Para verificar a mensagem SOAP gerada coloque o componente Logger em seguido.

Logue o payload da mensagem através da expressão:


Financeiro: #[message.payloadAs(java.lang.String)]

5. Por fim, temos que chamar o novo sub flow. Isto é, cada vez que recebemos um pedido no fluxo
principal devemos chamar os dois sub flows: Ebook e financeiro .

226 8.13 EXERCÍCIOS: MULTICAST COM SCATTER-GATHER PATTERN


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
O componente que tem a responsabilidade de espalhar uma mensagem para outros sub flows é o
Scatter-Gather (espalhar e coletar). Procure o componente na paleta e arraste-o para dentro do flow
principal fj36-pedidosFlow :

6. Procure na paleta pelo Flow Reference e arraste o componente para dentro do elemento Scatter-
Gather. No Flow Reference selecione o Flow Name financeiro_flow .

Faça a mesma configuração para o ebook_flow. Scatter-Gather.

O resultado final deve ser igual a imagem a seguir:

8.13 EXERCÍCIOS: MULTICAST COM SCATTER-GATHER PATTERN 227


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
7. Garanta que tudo esteja salvo e reinicie o servidor Mule ESB.

8. Vá ao SoapUI e envie novamente um request HTTP POST com o XML do pedido. A resposta deve
ser a mesma: <resposta>ok</resposta>

Depois verifique o console, além do XML do pedido deveria aparecer um JSON para cada item do
tipo ebook e a mensagem SOAP.

8.14 EXERCÍCIOS OPCIONAIS: CHAMANDO SERVIÇOS


1. Configure para os dois subflows a chamada HTTP.

228 8.14 EXERCÍCIOS OPCIONAIS: CHAMANDO SERVIÇOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Use o componente HTTP em cada sub flow e configure uma (apenas uma)
Http_Request_Configuration com os valores: localhost , porta 8089 e basePath /mule

Em cada componente HTTP configure o path para /ebook e /financeiro , respectivamente.

8.14 EXERCÍCIOS OPCIONAIS: CHAMANDO SERVIÇOS 229


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
O Ebook Sub flow chamará http://localhost:8089/mule/ebook O Financeiro Sub flow chamará
http://localhost:8089/mule/financeiro

2. Ao chamar os serviços externos HTTP vamos receber uma resposta HTTP. Ambas as respostas são
recolhidas pelo componente Scatter-Gather. No entanto, ele não sabe o que fazer com elas.

Vamos criar uma simples estratégia de agregação.

Dentro da pasta src/main/java cria uma nova classe SimpleResponseStrategy :


public class SimpleResponseStrategy implements AggregationStrategy {

@Override
public MuleEvent aggregate(AggregationContext context) throws MuleException {

for (MuleEvent event : context.collectEventsWithExceptions()) {


throw new RuntimeException(event.toString());
}

//criado um payload
DefaultMuleEvent evento = new DefaultMuleEvent(
context.getOriginalEvent(),
context.getOriginalEvent().getFlowConstruct()
);
evento.getMessage().setPayload("<resposta>ok</resposta>");

return evento;
}

3. Falta associar a estratégia com o componente Scatter-Gather. Selecione o componente e, nas


propriedades, clique no elemento From Class para escolher a classe SimpleResponseStrategy :

230 8.14 EXERCÍCIOS OPCIONAIS: CHAMANDO SERVIÇOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4. No fluxo principal apague o componente Set Payload pois estamos definindo a resposta dentr da
classe SimpleResponseStrategy.

5. Através do Mule ESB estamos roteando para os serviços HTTP (ebook e financeiro).

Para testar o roteamento vamos rodar dois serviços que simulam os endpoints, novamente pelo JAR.

Observação: Se ainda não subiu o serviço de testes, copie o JAR Desktop/caelum/36/mule/mule-


mocks.jar para sua pasta pessoal. Abra um terminal e rode o JAR:

java -jar mule-mocks.jar

Isso irá subir os serviços HTTP /mule/financeiro e /mule/ebook na porta 8089.

6. Salve o fluxo e reinice o Mule ESB. Volte ao SoapUI e submeta uma nova requisição, enviando o
XML de pedido.

Verifique o console.

8.15 TRATAMENTO DE ERRO


O Mule ESB oferece várias formas de tratamento de erro. Em geral, dentro de Mule, quando um fault
ou exceção acontece, é aplicada uma estrategia para o tratamento. As estratégia é nada mais do que um
fluxo especial que é automaticamente chamado quando uma exceção acontece. Nele podemos definir
detalhes sobre o tratamento como logging, rollback e redelivery.

8.16 EXERCÍCIOS OPCIONAIS: EXCEPTION STRATEGY


1. Antes de repassar a mensagem para os sub fluxos vamos executar uma validação do XML que pode
ou não causar um exceção.

Copie o arquivo Desktop/caelum/36/mule/pedido.xsd para raíz do projeto fj36-pedidos .

8.15 TRATAMENTO DE ERRO 231


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
2. O primeiro no fluxo passo é cadastra o validador como elemento global no Mule. Na tela principal
do AnypointStudio clique na aba Global Elements:

Depois clique no botão Create e no diálogo selecione o componente Schema Validation. Nele
configure o pedido.xsd no Schema Location.

3. Volte a aba Message Flow. Procure pelo componente Message que faz parte dos Filters.

Arraste o filtro Message ao lado do componente Scatter-Gather:

232 8.16 EXERCÍCIOS OPCIONAIS: EXCEPTION STRATEGY


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
4. Selecione o componente Message e, nas propriedades, habilite o Throw On Unaccepted.

Além disso, no elemento Nested Filter adicione um Core-Filter. Para isso, clique no botão +, selecione
Core-Filter, e a Schema_Validation:

8.16 EXERCÍCIOS OPCIONAIS: EXCEPTION STRATEGY 233


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Pronto! O componente Schema Validation está configurado para ser usado. Podemos pensar no
tratamento de erro.

5. Vamos usar um Catch Exception Strategy que captura uma exceção no fluxo e permite definir um
tratamento personalizado.

Na paleta de componentes procure pelo Catch e arraste o Catch Exception Strategy para dentro do
elemento Error handling do fluxo principal:

6. Arraste um Logger para dentro do Catch Exception Strategy que deve imprimir a mensagem Falha
no fluxo com o Level ERROR .

234 8.16 EXERCÍCIOS OPCIONAIS: EXCEPTION STRATEGY


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
7. Quando uma exceção acontece, vamos setar um código de erro HTTP e devolver a mensagem da
exceção na resposta HTTP. Para isso, procure pelo componente Property e arraste-o para a
estratégia:

8.16 EXERCÍCIOS OPCIONAIS: EXCEPTION STRATEGY 235


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Altere o http.status para `400%%:

Observação: O código HTTP 400 significa Bad request (Solicitação Imprópria).

8. Além do código de erro HTTP vamos devolver a mensagem de erro. Como já foi feito
anterioramente use o componente Set Payload:

236 8.16 EXERCÍCIOS OPCIONAIS: EXCEPTION STRATEGY


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Nesse componente use a expression language no Value:
Erro: #[exception.getSummaryMessage()]

9. Salve o fluxo e reinicie o Mule ESB. Volte ao SoapUI e submeta uma nova requisição, enviando o
XML de pedido. Tudo deve continuar funcionando.

Verifique o console.

No SoapUI, apague um elemento do XML do pedido. Como por exemplo, o formato. Isso deve gerar
um erro de validação no nosso fluxo. Submeta uma nova requisição e verifique o resultado.

8.16 EXERCÍCIOS OPCIONAIS: EXCEPTION STRATEGY 237


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 9

APÊNDICE: OAUTH 2.0

No capítulo sobre serviços REST, desenvolvemos o payfast, o serviço de pagamentos utilizado pela
livraria para fazer cobranças no cartão de crédito de seus usuários, mas no serviço que implementamos,
qualquer pessoa pode criar e confirmar pagamentos utilizando, por exemplo, o terminal do sistema
operacional.

Para melhorarmos a segurança do payfast, precisamos de alguma forma permitir que apenas
usuários e aplicações cadastradas e devidamente autenticadas possam acessar o serviço de pagamento.
Para definir como a troca de credenciais deve ser feita, foi criada uma especificação chamada OAuth que
está atualmente em sua versão 2.0.

Mas implementar a especificação OAuth 2.0 diretamente no projeto não é uma tarefa fácil e por isso,
a Apache criou o projeto Oltu como uma implementação do OAuth.

9.1 PARTICIPANTES DO FLUXO DO OAUTH


O OAuth define as interações que devem ser feitas entre 4 participantes (Roles):

Resource Owner: É o dono dos dados que serão compartilhados. No projeto do curso, o
Resource Owner seria o usuário que está tentando fechar uma compra na livraria.

Resource Server: A aplicação que contém os dados ou recursos que queremos acessar em
nome do Resource Owner. No curso, o Resource Server seria aplicação do Payfast que contém
a conta do usuário.

Authorization Server: A aplicação que valida as credenciais do Resource Owner e do Client


Application. O Resource Server e o Authorization Server podem ser tanto a mesma aplicação
quanto aplicações separadas, a comunicação entre esses dois servidores não é definida na
especificação OAuth 2.0. No projeto do curso, o Authorization Server ficará junto com o
Payfast.

Client Application: Representa a aplicação que está tentando acessar os recursos do Resource
Owner. No projeto, o Client Application é a aplicação da livraria que está tentando acessar a
conta do usuário no payfast. Dentro do OAuth o Client Application precisa se cadastrar
previamente com o Authorization Server para gerar um client_id e um client_secret
que identificam unicamente a aplicação.

238 9 APÊNDICE: OAUTH 2.0


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
9.2 ACCESS TOKEN
No OAuth, para que a aplicação cliente (livraria) consiga acessar o payfast (Resource Server) em
nome do usuário (cliente que está fazendo compras na loja), a livraria precisa obter a permissão do
usuário que é representada através de um token chamado access token.

No Payfast (Resource Server), a aplicação deve validar o access token presente na chamada do serviço
antes de permitir o acesso a um recurso protegido. Caso o token de acesso seja inválido, a aplicação deve
devolver o código http 401 (Unauthorized) para indicar o erro na requisição.

Dentro do código do Resource Server, podemos obter o Access Token da requisição do OAuth
utilizando o método getAccessToken() da classe OAuthAccessResourceRequest do Apache Oltu:

HttpServletRequest request = // pega a request

OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);
String accessToken = oauthRequest.getAccessToken();

Podemos, então, modificar o código do serviço de cadastro de pagamento fazendo com que ele valide
o access token antes de criar o pagamento, mas para isso precisaremos do HttpServletRequest , um
objeto que pode ser obtido utilizando-se a injeção de dependências pelo CDI:

@Path("/pagamento")
public class PagamentoResource {
@Inject
private HttpServletRequest request;

// outros atributos e métodos

public Response criarPagamento(Transacao transacao) {

try {
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);
String accessToken = oauthRequest.getAccessToken();

if(access token válido) {


Executa a lógica normalmente
} else {
return Response.status(Status.UNAUTHORIZED).build();;
}
} catch (OAuthSystemException | OAuthProblemException e) {
throw new BadRequestException(e);
}
}
}

Esse mesmo código pode ser replicado para todos os outros métodos que precisam fazer a segurança
através do access token do oauth.

9.1 PARTICIPANTES DO FLUXO DO OAUTH 239


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
MAIS SOBRE O CDI

Você pode aprender mais sobre o CDI na formação Java EE da caelum.

INJEÇÃO DA REQUEST SEM O CDI

Em ambientes em que não temos disponível o CDI 1.1, mas ainda precisamos do
HttpServletRequest dentro de um serviço Jax-RS, podemos utilizar a anotação
@javax.ws.rs.core.Context para fazer a injeção dos componentes definidos na servlet:

@Context
private HttpServletRequest request;

Além do HttpServletRequest , também podemos injetar o HttpServletResponse ,


ServletContext e o ServletConfig .

9.3 EXERCÍCIO
1. Precisamos inicialmente colocar os jars para o servidor do apache Oltu no projeto fj36-
webservice .

Entre na pasta Caelum/36/jars/lib-oltu-server e copie todos os jars para a pasta


WebContent/WEB-INF/lib do projeto fj36-webservice .

2. Crie uma nova classe chamada TokenDao no pacote br.com.caelum.payfast.oauth2 do projeto


fj36-webservice .

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class TokenDao {
private List<String> accessTokens = new ArrayList<>();

public void adicionaAccessToken(String token){


System.out.println("Adicionando token " + token);
accessTokens.add(token);
}

public boolean existeAccessToken(String token){


System.out.println("Verificando token " + token);
return accessTokens.contains(token);
}
}

3. Faça a injeção do TokenDao e do HttpServletRequest na classe PagamentoResource do

240 9.3 EXERCÍCIO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
projeto fj36-webservice :

public class PagamentoResource {

@Inject
private TokenDao tokenDao;

@Inject
private HttpServletRequest request;

// resto do código da classe


}

4. Ainda dentro da classe PagamentoResource , modifique a implementação do método


criarPagamento para que ele valide o access token da requisição antes de cadastrar o novo
pagamento. Se o token for inválido ou não existir, o código deve devolver como resposta o código de
erro 401 (Unauthorized):
public Response criarPagamento(Transacao transacao){
Response unauthorized = Response.status(Status.UNAUTHORIZED).build();

try {
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);
String accessToken = oauthRequest.getAccessToken();

if(tokenDao.existeAccessToken(accessToken)) {
// aqui fica o código de cadastro do pagamento

// Pagamento pagamento = new Pagamento();


// pagamento.setId(nextId());
// ...
} else {
return unauthorized;
}
} catch (OAuthProblemException | OAuthSystemException e) {
throw new BadRequestException(unauthorized, e);
}

5. Para testar a validação do token, tente criar um novo pagamento através da aplicação da livraria, o
cliente do JAX-RS lançará uma exceção indicando que o servidor devolveu a resposta Unauthorized.

9.4 OBTENÇÃO DO ACCESS TOKEN


A especificação OAuth 2.0 define diversos estratégias que podem ser utilizadas para se obter o Access
Token. Essas estratégias são conhecidas como grants.

9.5 RESOURCE OWNER PASSWORD CREDENTIAL GRANT


O primeiro grant que estudaremos no curso é o chamado Resource Owner Password Credential
Grant. O usuário irá fornecer as suas credenciais de autenticação no Payfast para a livraria, que irá

9.4 OBTENÇÃO DO ACCESS TOKEN 241


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
autenticar-se fazendo uma requisição ao Authorization Server (payfast) enviando suas credenciais
( client_id e client_secret ) e as do usuário.

No servidor de pagamento, precisamos validar as informações que foram enviadas na requisição


contra as informações que estão cadastradas. Se elas estiverem corretas, podemos gerar o access token
que será devolvido para a aplicação da livraria.

As informações de uma requisição OAuth podem ser recuperadas utilizando-se a classe


OAuthTokenRequest do Apache Oltu. Essa classe pode ser construída a partir de um
HttpServletRequest que representa a requisição OAuth feita para o servidor:

HttpServletRequest req = // pega a requisição web

OAuthTokenRequest oauthRequest = new OAuthTokenRequest(req);


String clientId = oauthRequest.getClientId();
String clientSecret = oauthRequest.getClientSecret();
String loginUsuario = oauthRequest.getUsername();
String senhaUsuario = oauthRequest.getPassword();

Depois de obtermos essas informações da requisição, precisamos apenas validá-las utilizando o


banco de dados da aplicação. Se a validação ocorrer com sucesso, podemos gerar a resposta contendo o
access token que será devolvida para a aplicação cliente (a livraria).

A geração do access token é feita utilizando-se uma implementação da interface OAuthIssuer do


Apache Oltu.
OAuthIssuer issuer = new OAuthIssuerImpl(new MD5Generator());

O token de acesso é gerado pelo método accessToken() :


String accessToken = issuer.accessToken();

242 9.5 RESOURCE OWNER PASSWORD CREDENTIAL GRANT


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Depois de gerar o token, o authorization server precisa de alguma forma armazená-lo e depois gerar
a resposta que será devolvida para o Client Application . Segundo a especificação do OAuth 2.0, a
resposta que devolve o token para o cliente deve ser um json com a seguinte forma:
{
access_token: <access_token>,
token_type: "Bearer",
expires_in: 3600,
refresh_token: <refresh_token>,
scope: permissões
}

Onde:

access_token (Obrigatório): é o access token que foi gerado pelo OAuthIssuer


token_type (Obrigatório): esse parâmetro define como o cliente deve enviar o access token
para o servidor que contém os recursos do usuário. O valor mais utilizado é bearer .
expires_in (Opcional): Tempo de duração do token em segundos
refresh_token (Opcional): Token que pode ser utilizado para criar um novo access token sem
que seja necessário fornecer novamente as credenciais
scope (Opcional): Permissões que foram concedidas pelo usuário para esse access token

Para construirmos o JSON, utilizaremos a classe OAuthASResponse :

OAuthResponse tokenResponse = OAuthASResponse


.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setTokenType("bearer")
.buildJSONMessage();

E agora, só precisamos devolver o JSON gerado para o cliente com o HttpServletResponse :


HttpServletResponse response = // pega a response
response.setHeader("Content-type", "application/json");
response.getWriter().println(tokenResponse.getBody());

9.6 EXERCÍCIOS
1. Para facilitar a implementação do código que gerará o access token dentro do projeto fj36-
webservice , vamos importar um molde para as classes que implementam as servlets que utilizam as
classes do Apache Oltu:

Dentro do eclipse, clique com o botão direito no projeto fj36-webservice e escolha a opção
Import > Import... .

Na nova janela, escolha a opção File System e clique em Next .

No campo From directory , clique no botão Browse e escolha o caminho


Caelum/cursos/36/fj36-webservice-oauth .

9.5 RESOURCE OWNER PASSWORD CREDENTIAL GRANT 243


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
2. Adicione os métodos adicionaAuthorizationCode e existeAuthorizationCode na classe
TokenDao do projeto fj36-webservice . Não se esqueça também da lista de Authorization Codes:

public class TokenDao {


private List<String> authorizationCodes = new ArrayList<>();
// outros atributos

public void adicionaAuthorizationCode(String code) {


authorizationCodes.add(code);
}

public boolean existeAuthorizationCode(String code) {


return authorizationCodes.contains(code);
}

// outros métodos
}

3. Abra a classe PasswordGrantTokenServlet do projeto fj36-webservice e dentro de seu método


doPost , esse método já contém todo o código para recuperar e validar os valores que foram
enviados na requisição OAuth.

Tudo o que precisamos fazer é gerar e enviar para o usuário o access token. Para isso, procure o
seguinte comentário dentro do método:
// Código para gerar o access token

No lugar desse comentário, coloque o código que gera o access token utilizando o OAuthIssuer do
apache Oltu:

OAuthIssuer issuer = new OAuthIssuerImpl(new MD5Generator());


accessToken = issuer.accessToken();
tokenDao.adicionaAccessToken(accessToken);
tokenResponse = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setTokenType("bearer")
.buildJSONMessage();

Nesse código a validação dos dados da aplicação cliente e do usuário foram feitas com constantes,
mas numa aplicação real, poderíamos verificar se os dados são válidos utilizando, por exemplo, um
banco de dados.

4. Para testarmos a geração do access token pelo Resource Owner Password Credential Grant, vamos
novamente utilizar o curl para enviar uma requisição para a Servlet do OAuth.

Abra o terminal do sistema e digite o comando abaixo:

curl -X POST -d 'grant_type=password'


-d 'client_id=livraria_id&client_secret=livraria_secret'
-d 'username=usuario&password=senha'
http://localhost:8080/fj36-webservice/oauth/password/token

O resultado devolvido pelo comando será um json contendo o access token parecido com o abaixo:

244 9.5 RESOURCE OWNER PASSWORD CREDENTIAL GRANT


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
{
"token_type": "bearer",
"access_token": <token de acesso gerado pelo servidor>
}

9.7 OBTENDO UM ACCESS TOKEN NA APLICAÇÃO CLIENTE


Como vimos em exercícios anteriores, o payfast exige que um access token válido seja enviado na
criação do pagamento, a livraria precisa, portanto, obter o access token (através da servlet que acabamos
de criar) antes de fazer a chamada para a criação de pagamentos.

A requisição que recupera o access token, podem ser feita utilizando-se a API cliente do Apache Oltu
com a classe OAuthClientRequest , passando qual é o tipo de grant que queremos utilizar (Resource
Owner Password Credential Grant, GrantType.PASSWORD ), o clientId e clientSecret (credenciais do
client application) e, para finalizar, o username e password que serão pedidos para o usuário da livraria,
essas credenciais do usuário são as informações de autenticação para o payfast.
OAuthClientRequest request = OAuthClientRequest
.tokenLocation(url para a servlet que gera o token)
.setGrantType(GrantType.PASSWORD)
.setClientId("livraria_id")
.setClientSecret("livraria_secret")
.setPassword("senha do usuário")
.setUsername("login do usuário")
.buildBodyMessage();

Depois de configurarmos a requisição, precisamos enviá-la ao servidor utilizando a classe


OAuthClient :

OAuthClient client = new OAuthClient(new URLConnectionClient());


OAuthJsonAccessTokenResponse response = client
.accessToken(request, OAuth.HttpMethod.POST);
String accessToken = response.getAccessToken();

Com o token obtido, podemos utilizar o cliente do JAX-RS para enviar uma requisição autenticada
pelo OAuth para o servidor de pagamentos.

9.8 IMPLEMENTANDO E CONSUMINDO UM SERVIÇO PROTEGIDO


Agora que sabemos como podemos obter o access token do payfast, podemos utilizar o cliente do
JAX-RS para enviar o token junto com a chamada REST. Pela especificação OAuth, o Access Token
deve ser passado em um cabeçalho chamado Authorization da requisição HTTP:
Authorization: Bearer <access token>

Esse cabeçalho pode ser colocado na requisição utilizando-se o método header do cliente do JAX-
RS. Então a requisição para cadastrar um novo pagamento, por exemplo, ficaria da seguinte forma:
Client cliente = ClientBuilder.newClient();
Pagamento resposta = cliente.target("url do serviço")
.request()

9.7 OBTENDO UM ACCESS TOKEN NA APLICAÇÃO CLIENTE 245


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
.header("Authorization", "Bearer " + accessToken)
.buildPost(Entity.json(transacao))
.invoke(Pagamento.class);

9.9 EXERCÍCIOS: CONSUMINDO UM SERVIDOR PROTEGIDO


1. Para conseguirmos recuperar um access token do servidor de autorização, precisaremos utilizar a
parte cliente do Apache Oltu dentro do projeto fj36-livraria .

Entre na pasta Caelum/cursos/36/jars/lib-oltu-client e copie todos os jars dessa pasta para a


pasta WebContent/WEB-INF/lib do projeto fj36-livraria .

2. Crie a classe AccessToken no pacote br.com.caelum.livraria.rest.oauth2 do projeto fj36-


livraria :

@Component
@Scope("session")
public class AccessToken implements Serializable{

private transient String token;

public String getToken() {


return token;
}

public void setToken(String token) {


this.token = token;
}

public boolean isPreenchido(){


return token != null;
}
}

3. Ainda no projeto fj36-livraria , faça com que a classe CarrinhoController tenha um atributo
gerenciado pelo Spring do tipo AccessToken :
public class CarrinhoController {

@Autowired
private AccessToken accessToken;
}

4. Ainda dentro da mesma classe, procure o método criarPagamento . Nesse método, só podemos
chamar o serviço de criação de pagamentos se já tivermos o access token. Então se o método
isPreenchido da classe AccessToken devolver o valor false , precisamos redirecionar o
usuário para a lógica de obtenção do access token (controller OAuthPasswordController ).

Então logo após o código que valida se o cartão de crédito foi preenchido, vamos colocar a
verificação do access token:

public String criarPagamento(


String numeroCartao,
String titularCartao,

246 9.9 EXERCÍCIOS: CONSUMINDO UM SERVIDOR PROTEGIDO


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
RedirectAttributes modelo) {
// verificação do cartão de crédito

// Aqui fica o código de verificação do access token


if(!accessToken.isPreenchido()){
return "redirect:/oauth/password/form";
}

// resto do código
}

5. Na classe OAuthPasswordController faça a injeção do AccessToken :


public class OAuthPasswordController {

@Autowired
private AccessToken accessToken;

// resto do código da classe


}

6. Ainda dentro do controller, no método token , coloque o código para recuperar o access token
utilizando a estratégia Resource Owner Password Credential Grant:
public String token(String username, String password) throws Exception{
OAuthClientRequest request = OAuthClientRequest
.tokenLocation(PASSWORD_GRANT_TOKEN_URL)
.setGrantType(GrantType.PASSWORD)
.setClientId("livraria_id")
.setClientSecret("livraria_secret")
.setUsername(username)
.setPassword(password).buildBodyMessage();
OAuthClient client = new OAuthClient(new URLConnectionClient());
OAuthJSONAccessTokenResponse tokenResponse = client.accessToken(request);
String token = tokenResponse.getAccessToken();
accessToken.setToken(token);
System.out.println("Token recebido " + token);
return "redirect:/carrinho/criarPagamento";
}

7. Para finalizar, precisamos modificar os métodos da classe ClienteRest para que eles incluam o
cabeçalho Authorization com o access token nas requisições que são enviadas para o
PagamentoResource .

Então vamos injetar o AccessToken no ClienteRest e utilizar o método header do cliente do


JAX-RS para incluir o access token na requisição.
public class ClienteRest implements Serializable {
@Autowired
private AccessToken accessToken;

public Pagamento criarPagamento(Transacao transacao) {


Client cliente = ClientBuilder.newClient();
Pagamento resposta = cliente.target(SERVER_URI + ENTRY_POINT)
.request()
.header("Authorization", "Bearer " + accessToken.getToken())
.buildPost(Entity.json(transacao))
.invoke(Pagamento.class);
System.out.println("Pagamento criado, id: " + resposta.getId());

9.9 EXERCÍCIOS: CONSUMINDO UM SERVIDOR PROTEGIDO 247


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
return resposta;
}

public Pagamento confirmarPagamento(Pagamento pagamento) {


Link linkConfirmar = pagamento.getLinkPeloRel("confirmar");
Client cliente = ClientBuilder.newClient();
Pagamento resposta = cliente
.target(SERVER_URI + linkConfirmar.getUri()).request()
.header("Authorization", "Bearer " + accessToken.getToken())
.build(linkConfirmar.getMethod()).invoke(Pagamento.class);
System.out.println("Pagamento confirmado, id: " + resposta.getId());
return resposta;
}

8. Depois de fazer essas modificações no código da livraria, tente finalizar novamente uma compra.
Dessa vez, como ainda não temos um access token, o navegador será redirecionado para uma página
de login onde precisamos colocar as informações de autenticação do usuário do payfast. Lembre-se
que o login e a senha do payfast são usuario e senha respectivamente.

Quando o formulário for enviado, o servidor da livraria fará uma requisição para o payfast incluindo
o login, a senha, o client_id e o client_secret (autenticação da aplicação e do usuário) para o
servidor de autenticação que foi implementado através da classe PasswordGrantTokenServlet . Se
as informações estiverem corretas, o servidor devolverá o access token, senão o código 401 será
devolvido para o cliente do Apache Oltu.

O access token devolvido pelo Authorization Server é guardado na sessão do usuário através da
classe AccessToken (que é session scoped). Em seguida o usuário é enviado novamente para a
lógica de criação de pagamentos (método criarPagamento do CarrinhoController ) onde, após
as validações, o ClienteRest será chamado para fazer a requisição para o PagamentoResource
incluindo o access token que acabou de ser gerado.

CÓDIGOS DE ERRO DO OAUTH

Além de definir o fluxo padrão que deve ser implementado, a especificação OAuth 2.0 também
define quais são as respostas que devem ser devolvidas caso aconteça algum erro. Um resumo dos
códigos pode ser encontrado na página de desenvolvedores do Yahoo:

https://developer.yahoo.com/oauth/guide/oauth-errors.html

Ou na própria página da especificação:

https://tools.ietf.org/html/rfc6749#page-45

9.10 AUTHORIZATION CODE GRANT


248 9.10 AUTHORIZATION CODE GRANT
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
No exercício anterior implementamos o Resource Owner Password Credential Grant dentro da
livraria e utilizando o token gerado, conseguimos acessar o sistema de pagamentos. Mas com essa
estratégia, o cliente foi obrigado a passar suas credenciais (usuário e senha do payfast) para a livraria
conseguir o access token, o que pode gerar um problema de segurança.

O Resource Owner Password Credential Grant é utilizado quando o resource owner tem uma alta
confiança no client application (afinal ele precisa passar suas credencias) mas geralmente não
conseguimos garantir essa confiança. Nesses casos, as credenciais não devem ser passadas para a
aplicação cliente, elas devem ser passadas diretamente ao servidor de autenticação. Para resolvermos esse
problema, a especificação OAuth nos fornece uma segunda estratégia de aquisição de tokens chamada de
Authorization Code Grant.

Nesse segundo grant, quando o usuário fecha a compra na livraria e tenta gerar o pagamento, a
livraria fará um um redirect para o formulário de login do payfast enviando nessa requisição o seu
client_id. Depois que o usuário se autentica com sucesso, o servidor de autorização gera um token que
representa as credencias do usuário. Esse token é chamado de authorization code (auth code).

Após gerar o auth code, o servidor de autorização fará um novo redirect, contendo o código gerado
como parâmetro, de volta para uma URL de callback definida pelo Client Application.

Ao receber o código de autorização, a livraria precisa fazer uma nova requisição ao servidor de
autorização para trocar o authorization code pelo access token. Nessa requisição ela precisa enviar o
authorization code (representa as credenciais do usuário), o clientid e o client_secret (credenciais da
livraria). Se as informações estiverem corretas, o access token será devolvido novamente no _json.

9.10 AUTHORIZATION CODE GRANT 249


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
9.11 IMPLEMENTANDO O AUTHORIZATION CODE GRANT COM O
APACHE OLTU
Agora que já entendemos qual é o fluxo que será implementado no Authorization Code Grant,
vamos implementá-lo dentro dos projetos do curso. Na implementação precisaremos de 5 novos
endpoints: 2 na livraria (um que faz o redirect para o payfast enviando o client_id e outro que troca o
authorization code pelo access token) e 3 no payfast(o que recebe o client_id e mostra o formulário de
login, o que valida os dados do formulário de login e gera o authorization code e o que troca o
authorization code, client_id e client_secret pelo access token).

Vamos começar pelo endpoint da livraria que faz o redirect para o formulário de login do payfast.
Para montarmos a url de redirect com o client_id passado corretamente, podemos utilizar novamente a
classe OAuthClientRequest passando as informações necessárias:
OAuthClientRequest oauthRequest = OAuthClientRequest
.authorizationLocation(url do form de login do payfast)
.setClientId("livraria_id")
.setResponseType(OAuth.OAUTH_CODE) // indica code grant
.buildQueryMessage();

No endereço que será construído, precisamos adicionar mais um parâmetro exigido pela
especificação do OAuth: o endereço do endpoint da livraria que fará a troca do authorization code pelo
access token (o redirect_url da figura anterior).

250 9.11 IMPLEMENTANDO O AUTHORIZATION CODE GRANT COM O APACHE OLTU


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
OAuthClientRequest oauthRequest = OAuthClientRequest
.authorizationLocation(url do form de login do payfast)
.setClientId("livraria_id")
.setResponseType(OAuth.OAUTH_CODE) // indica code grant
.setRedirectURI(REDIRECT_URL)
.buildQueryMessage();

String urlDoRedirectParaOPayfast = oauthRequest.getLocationUri();

Depois que a url do redirect foi calculada, podemos simplesmente utilizar o Spring para enviar a
resposta redirect para o navegador do usuário.

Quando o navegador receber o redirect ele acessará o endpoint que mostrará o formulário de login
do payfast. Dentro desse endpoint, podemos recuperar os parâmetros do OAuth enviados utilizando a
classe OAuthAuthzRequest do Oltu:
HttpServletRequest request = // pega a requisição

OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);

String clientId = oauthReq.getClientId();


String redirectURI = oauthReq.getRedirectURI();

// Esse parâmetro deve ser a String "code" no Authorization Code Grant


String responseType = oauthReq.getResponseType();

Se o client_id enviado na requisição for válido, podemos simplesmente redirecionar o usuário


para a camada de visualização que gerará o código HTML do formulário de login que será devolvido
para o navegador.

Os dados do formulário de login serão enviados para o próximo endpoint: O que valida os dados do
usuário e gera o Authorization Code. Nesse endpoint, supondo que o usuário se autenticou com sucesso,
precisamos gerar o authorization code utilizando o método authorizationCode() da interface
OAuthIssuer do Oltu:

OAuthIssuer issuer = new OAuthIssuerImpl(new MD5Generator());


String code = issuer.authorizationCode();

Depois que o código foi gerado, precisamos montar o redirect que levará o usuário ao próximo
endpoint da livraria. A url desse redirect pode ser construída utilizando-se a classe OAuthASResponse :
String redirectURI = // uri que foi passada na requisição do
// formulário de login do payfast

OAuthAuthorizationResponseBuilder builder = OAuthASResponse


.authorizationResponse(req, 302);

OAuthResponse oAuthResponse = builder.location(redirectURI)


.setCode(code)
.buildQueryMessage();

String redirectParaLivraria = oAuthResponse.getLocationUri();

Quando o navegador receber o redirect com o authorization code, ele será novamente levado para a

9.11 IMPLEMENTANDO O AUTHORIZATION CODE GRANT COM O APACHE OLTU 251


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
livraria, onde precisamos implementar o quarto endpoint, o que troca o authorization code pelo access
token.

Nesse endpoint, a extração do authorization code da requisição pode ser feita utilizando-se a classe
OAuthAuthzResponse :

HttpServletRequest request = // pega a requisição web

OAuthAuthzResponse oauthResponse = OAuthAuthzResponse


.oauthCodeAuthzResponse(request);

// A variável code representa o Authorization code gerado.


String code = oauthResponse.getCode();

A troca do authorization code pelo access token é feita utilizando-se novamente a classe
OAuthClientRequest . Na requisição de troca, passaremos o authorization code, o client_id, o
client_secret e, além disso, a especificação exige que seja passado o mesmo redirect uri utilizado na
requisição para gerar o authorization code:
OAuthClientRequest oauthRequest = OAuthClientRequest
.tokenLocation(AUTH_CODE_TOKEN_URL)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setClientId("livraria_id")
.setClientSecret("livraria_secret")
.setCode(code)
.setRedirectURI(REDIRECT_URL)
.buildBodyMessage();

A requisição preparada pode ser enviada para o authorization server utilizando-se a classe
OAuthClient :

OAuthClient client = new OAuthClient(new URLConnectionClient());

OAuthJSONAccessTokenResponse tokenResponse = client


.accessToken(oauthRequest, OAuth.HttpMethod.POST);

// Esse é o access token que foi gerado pelo authorization server


String token = tokenResponse.getAccessToken();

Para finalizarmos, resta apenas implementar o código do endpoint que troca o authorization code
pelo access token. Nesse endpoint, as informações da requisição OAuth podem ser recuperadas através
da classe OAuthTokenRequest :
OAuthTokenRequest oauthRequest = new OAuthTokenRequest(req);
String clientId = oauthRequest.getClientId();
String secret = oauthRequest.getClientSecret();
String code = oauthRequest.getCode();

Depois de recuperar as informações, o servidor deve verificar se as credenciais do client application


( client_id e client_secret ) e o authorization code são válidas e gerar o access token que será
novamente devolvido através de uma resposta do tipo json .

Assim como no Resource Owner Password Credential Grant, a geração do access token é feita

252 9.11 IMPLEMENTANDO O AUTHORIZATION CODE GRANT COM O APACHE OLTU


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
através de uma implementação do OAuthIssuer :
OAuthIssuer issuer = new OAuthIssuerImpl(new MD5Generator());
String accessToken = issuer.accessToken();

E a resposta para o cliente pode, novamente, ser gerada com o OAuthASResponse :


HttpServletResponse resp = // pega a response

AuthResponse oAuthResponse = OAuthASResponse


.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setTokenType("Bearer")
.buildJSONMessage();

resp.setHeader("Content-type", "application/json");
resp.getWriter().print(oAuthResponse.getBody());

9.12 EXERCÍCIO AUTHORIZATION CODE GRANT


1. Para modificarmos o grant que será utilizado na aplicação da livraria para o Authorization Code
Grant, abra a classe CarrinhoController do projeto fj36-webservice para fazer com que ele
redirecione para a url /oauth/code (que leva para a classe OAuthCodeController ). Essa é a classe
responsável por redirecionar o usuário para a página de login do payfast.

2. Abra o método redirectToPayfast da classe OAuthCodeController e dentro dele, monte a url


de redirecionamento para o payfast utilizando a classe OAuthClientRequest do Apache Oltu:
public String redirectToPayfast() throws Exception {
OAuthClientRequest message = OAuthClientRequest
.authorizationLocation(AUTH_CODE_FORM_URL)
.setClientId("livraria_id")
.setResponseType(OAuth.OAUTH_CODE)
.setRedirectURI(REDIRECT_URL)
.buildQueryMessage();
String oauthURI = message.getLocationUri();
return "redirect:" + oauthURI;
}

3. Agora vamos implementar o código da servlet que valida os dados enviados pelo formulário de login
da aplicação Payfast. Nessa servlet, se as credenciais do usuário forem válidas, precisamos utilizar
novamente o OAuthIssuer para gerar o authorizationCode que será devolvido ao usuário.

Abra a classe CodeGrantAuthorizationServlet e dentro do if que verifica as credencias do


usuário, coloque o código que gera e armazena o authorization code:

protected void doPost(HttpServletRequest req, HttpServletResponse resp)


throws ServletException, IOException {
// pega as informações da request

if("usuario".equals(login) && "senha".equals(senha)) {


OAuthResponse oAuthResponse = null;

// código para gerar o authorization code


OAuthIssuer issuer = new OAuthIssuerImpl(new MD5Generator());

9.12 EXERCÍCIO AUTHORIZATION CODE GRANT 253


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
String code = issuer.authorizationCode();
tokenDao.adicionaAuthorizationCode(code);

OAuthAuthorizationResponseBuilder builder = OAuthASResponse


.authorizationResponse(req, 302);

oAuthResponse = builder.location(redirectURI)
.setCode(code)
.buildQueryMessage();
resp.sendRedirect(oAuthResponse.getLocationUri());
}

// código de tratamento de erros


}

4. Depois que o código de autorização for gerado é devolvido para a livraria, ela precisa enviar o
authorization code junto com suas credenciais para o payfast. Isso é feito no método oauthReturn
da classe OAuthCodeController do projeto fj36-livraria .

Como esse método precisará armazenar o access token do usuário, vamos injetar no controller o
AccessToken que está sendo gerenciado pelo Spring:

public class OAuthCodeController {


@Autowired
private AccessToken accessToken;

// resto do código da classe


}

Na implementação do método, utilize o cliente do Apache Oltu para enviar a requisição que troca o
authorization_code pelo access_code no servidor do Payfast:

public String oauthReturn(HttpServletRequest request) throws Exception{


OAuthAuthzResponse oauthResponse = OAuthAuthzResponse
.oauthCodeAuthzResponse(request);

String code = oauthResponse.getCode();

OAuthClientRequest oauthRequest = OAuthClientRequest


.tokenLocation(AUTH_CODE_TOKEN_URL)
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setClientId("livraria_id")
.setClientSecret("livraria_secret")
.setCode(code)
.setRedirectURI(REDIRECT_URL)
.buildQueryMessage();

OAuthClient client = new OAuthClient(new URLConnectionClient());


OAuthJSONAccessTokenResponse tokenResponse =
client.accessToken(oauthRequest, OAuth.HttpMethod.POST);

String token = tokenResponse.getAccessToken();

accessToken.setToken(token);
return "redirect:/carrinho/criarPagamento";
}

5. Para finalizar, precisamos programar o endpoint do Payfast que valida o authorization code e gera o

254 9.12 EXERCÍCIO AUTHORIZATION CODE GRANT


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
access token que é devolvido ao cliente.

Faremos isso na class CodeGrantTokenServlet , dentro de seu método doPost :


// Código para gerar o access token
OAuthIssuer issuer = new OAuthIssuerImpl(new MD5Generator());
String accessToken = issuer.accessToken();
tokenDao.adicionaAccessToken(accessToken);
oAuthResponse = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setTokenType("Bearer")
.buildJSONMessage();

6. Depois de fazer essas modificações no projeto, tente criar novamente o pagamento para um pedido
finalizado na aplicação da livraria, dessa vez o navegador deve ser redirecionado para o formulário
de login da aplicação fj36-webservice (você pode verificar isso através da url que é exibida na
barra de endereços do navegador), repare que o client_id , redirect_url e grant_type são
passados como parâmetros na URL de redirecionamento.

Dentro desse formulário, podemos colocar as credenciais do usuário (login: usuario e senha: senha).
Repare que as informações são enviadas diretamente à aplicação de pagamento, a livraria não tem
acesso a essas informações. Depois que o usuário se autentica com sucesso, o authorization code é
gerado e é enviado à livraria através de um parâmetro do redirect.

A livraria, ao receber o authorization code no método oauthReturn do OAuthCodeController ,


faz uma nova requisição ao servidor de autorização do payfast para trocar o authorization code pelo
access token. Nessa requisição, a livraria envia o authorization code, o client_id e o
client_secret . Como a requisição é feita diretamente pela livraria para o servidor de pagamento,
sem um redirect do navegador, o usuário também não tem acesso às credenciais da livraria.

Após validar as informações enviadas, o servidor de autorização pode gerar e devolver o access token
à aplicação cliente (livraria). Depois de receber o access token, a livraria simplesmente armazena o
token recebido na sessão do usuário e depois faz o redirecionamento para a lógica de criação de
pagamentos, que utilizará o token gerado para acessar o PagamentoResource do fj36-
webservice .

9.13 CONSIDERAÇÕES SOBRE A IMPLEMENTAÇÃO


Agora que já temos um entendimento maior sobre como implementar as duas estratégias mais
utilizadas para aquisição do access token, vamos discutir algumas melhorias e considerações sobre a
segurança do código implementado.

9.14 CREDENCIAS DO USUÁRIO E DO CLIENT APPLICATION


No payfast as credencias, tanto do usuário (Resource Owner) quanto do Client Application, são

9.13 CONSIDERAÇÕES SOBRE A IMPLEMENTAÇÃO 255


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
constantes programadas no código, porém em uma aplicação real, essas são informações que são
geralmente armazenadas em um banco de dados ou em algum outro repositório de dados do servidor.

9.15 AUTHORIZATION CODE


No código desenvolvido, simplesmente armazenamos os authorization codes dentro de uma lista em
memória, mas em uma aplicação real, os códigos de autorização gerados precisam ser associados com
um client application e um usuário válido da aplicação, pois não queremos permitir que uma aplicação
utilize um authorization code gerado por outra aplicação.

Além disso, depois de obtermos o access token, o authorization code ainda é mantido em uma lista
na memória do servidor, porém isso permite que o mesmo authorization code gere diversos access
tokens diferentes, o que facilita a execução de um ataque man-in-the-middle que simplesmente repete a
requisição que troca o authorization code pelo access token.

9.16 ACCESS TOKEN


Além do authorization code, um outro aspecto que devemos cuidar ao utilizar o OAuth é o
gerenciamento dos access tokens gerados. Na implementação feita, os tokens são simplesmente
armazenados na memória em um componente Application scoped, porém isso pode representar uma
falha de segurança, pois se o token for descoberto ele pode ser utilizado por outras aplicações para
acessar os recursos de um usuário. Para diminuir esse problema, o servidor de autorização pode,
opcionalmente, colocar um tempo de expiração para o token, assim que o token expirar, ele não poderá
mais ser utilizado pelo client application. A invalidação dos tokens de acesso deve ser feita pelo código
do Authorization Server, a biblioteca Apache Oltu não gerencia o tempo de vida dos tokens gerados.

Quando um token expira, a especificação OAuth 2.0 oferece um mecanismo para que o Client
Application possa renová-lo sem ter que pedir novamente as credenciais do usuário, esse mecanismo é o
refresh token. Na resposta da requisição para adquirir o access token, o servidor de autorização pode
devolver, opcionalmente, um refresh token para o usuário, esse token funciona como se fosse um
authorization code que pode ser utilizado para renovar um access token expirado.

Além da expiração, ainda temos mais uma característica que não foi implementada em nossos
códigos de exemplo: access tokens são normalmente associados à conta de um usuário cadastrado e
permitem o acesso a informações apenas dessa conta. No entanto, para aumentar a segurança, podemos
restringir as autorizações dadas à um determinado token, para isso, a especificação OAuth 2.0 define os
escopos (scopes) que podem ser associados aos tokens gerados.

O scope é um parâmetro adicional que pode ser passado na requisição para o authorization code
(caso o grant type seja o Authorization Code Grant) ou na requisição para o access token (no caso do
Resource Owner Password Credential Grant). Em ambos os casos, o escopo pode ser passado na request

256 9.2 ACCESS TOKEN


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
utilizando-se o método setScope da classe OAuthClientRequest .

9.17 CROSS SITE REQUEST FORGERY


Com o aumento do número de serviços utilizando-se da especificação OAuth 2.0 para cuidar da
segurança, o número de possíveis ataques a um servidor protegido também tende a aumentar.
Considere, por exemplo, o caso da nossa aplicação de pagamento. Nesse tipo de aplicação, o usuário
normalmente cria uma conta e depois é capaz de cadastrar seus cartões de crédito na aplicação. Dessa
forma as informações do cartão podem ficar armazenadas em apenas um lugar.

Imagine que no payfast disponibilizamos um serviço em que o usuário pode associar um novo cartão
de crédito à sua conta. Para fazer a segurança desse serviço utilizamos o OAuth com o Authorization
Code Grant, então a livraria pode, por exemplo, associar o cartão de crédito à conta do usuário
utilizando esse serviço.

Agora imagine que um hacker quer roubar o cartão de crédito de um dos usuários da livraria, nesse
caso, ele poderia utilizar um cliente http para adquirir um authorization code para sua conta. Depois de
adquirir o código de autorização para sua própria conta, o hacker pode então fazer com que o navegador
de algum usuário da livraria troque o authorization code por um access token, para isso, ele poderia por
exemplo publicar um link ou uma imagem em uma página bastante visitada com a seguinte url:
http://<url da livraria>/oauth/code/returnURL
?code=<auth code>&client_id=<client_id>&client_secret=<client_secret>

Essa é a URL em que a aplicação da livraria troca o authorization code por um access token e associa
o token gerado com a sessão do usuário que fez a requisição. Dessa forma, o access token gravado na
sessão de um usuário autêntico fica associado com a conta do payfast de um hacker, quando o usuário
tentar fazer o próximo pagamento e, consequentemente associar um novo cartão à conta do payfast, esse
cartão será associado a conta do hacker.

Esse ataque é o Cross site request forgery (CSRF) adaptado para o mundo dos serviços. Para
protegermos nossos serviços contra o CSRF, podemos utilizar um outro mecanismo definido através de
uma extensão do OAuth 2.0: o state.

Quando fazemos as requisições para o servidor de autorização utilizando o Authorization Code


Grant, podemos adicionar um parâmetro extra na requisição chamado state. Esse parâmetro é gerado e
validado pelo client application e será simplesmente devolvido pelo servidor de autorização.

Utilizando o state, conseguimos nos proteger contra o CSRF da seguinte forma:

Antes de fazer o redirect para o authorization server, o client application deve gerar um token
que será associado com a sessão do usuário. Esse token será enviado como state junto com os
outros parâmetros do redirect para adquirir o authorization code.

9.17 CROSS SITE REQUEST FORGERY 257


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
O servidor de autorização deve autenticar o usuário e gerar o authorization code. Junto com a
resposta contendo o authorization code, o servidor deve devolver também o state que foi
enviado na requisição.

O client application, ao receber o authorization code, deve validar se o state enviado na


requisição é o mesmo que foi gerado para o usuário atual da sessão. Se o state for válido,
podemos trocar fazer a troca do authorization code pelo access token, senão a aplicação deve
enviar um erro ao usuário.

Com essa validação simples, conseguimos nos proteger contra o CSRF.

9.18 FILTROS DO JAX-RS


Para implementarmos a verificação dos métodos do resource server, tivemos que replicar o trecho de
código que extrai e verifica a validade do access token enviado na request, porém para uma aplicação
maior, onde queremos expor diversos serviços diferentes protegidos por OAuth, essa replicação
prejudica muito a manutenção do código.

Podemos evitar a replicação do código de validação do accesss token utilizando um novo


componente do Jax-RS chamado filter. Existem dois tipos de filtro definidos na especificação, o
ContainerRequestFilter (executado antes da chamada do serviço) e o ContainerResponseFilter
(executado depois da chamada do serviço). Como a validação do access token precisa ser feita antes do
serviço ser executado, criaremos um novo ContainerRequestFilter chamado OAuthFilter para a
aplicação:
public class OAuthFilter implements ContainerRequestFilter {
// implementação do filtro
}

Dentro do OAuthFilter implementaremos a lógica de validação do token dentro do método


filter da interface ContainerRequestFilter :

public class OAuthFilter implements ContainerRequestFilter {


public void filter(ContainerRequestContext ctx){

}
}

Dentro do filter precisaremos do HttpServletRequest (para instanciar o request do OAuth) e


do TokenDao para verificar se o access token enviado é válido. Portanto injetaremos as instâncias desses
objetos utilizando o CDI:
public class OAuthFilter implements ContainerRequestFilter {
@Inject
private HttpServletRequest request;

@Inject
private TokenDao dao;

258 9.18 FILTROS DO JAX-RS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
// implementação do método filter

Dentro do filter , se a validação do access token for bem sucedida, queremos deixar a requisição
acontecer normalmente, senão abortaremos a execução enviando o código http 401 para o usuário. O
cancelamento da execução pode ser feito através do método abortWith do
ContainerRequestContext que recebe como argumento qual é a resposta do Jax-RS que será
devolvida:

public void filter(ContainerRequestContext ctx){


Response unauthorized = Response.status(Status.UNAUTHORIZED).build();

OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);

String accessToken = oauthRequest.getAccessToken();


if(!tokenDao.existeAccessToken(accessToken)){
ctx.abortWith(unauthorized);
}
}

Mas se o usuário enviar uma requisição sem o access token, o apache Oltu lança uma exceção que
precisa ser tratada pelo código:
public void filter(ContainerRequestContext ctx){
Response unauthorized = Response.status(Status.UNAUTHORIZED).build();

try {
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);

String accessToken = oauthRequest.getAccessToken();


if(!tokenDao.existeAccessToken(accessToken)){
ctx.abortWith(unauthorized);
}
} catch (Exception e) {
ctx.abortWith(unauthorized);
}
}

Agora que o código do filtro está pronto, precisamos indicar para o Jax-RS que queremos utilizá-lo
na aplicação e também quais são os resources protegidos pelo filtro.

A ativação doo filtro é feita através da anotação @Provider que é colocada sobre a declaração da
classe:
@Provider
public class OAuthFilter implements ContainerRequestFilter {
// implementação
}

E agora para indicarmos quais são os resources protegidos, precisamos criar uma nova anotação
configurada com o @NameBinding do Jax-RS:

9.18 FILTROS DO JAX-RS 259


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
@NameBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProtegidoPorOAuth {

Depois de criarmos a anotação, precisamos associá-la com o filtro que acabamos de criar. Isso é feito
anotando a classe do filtro com @ProtegidoPorOAuth :

@ProtegidoPorOAuth
@Provider
public class OAuthFilter implements ContainerRequestFilter {
// implementação
}

E agora podemos utilizar a anotação @ProtegidoPorOAuth nas classes ou métodos dos resources
onde precisamos da validação implementada no filtro:
@ProtegidoPorOAuth
@Path("/pagamento")
public class PagamentoResource {

Com essa modificação, o Jax-RS executará o código do filtro antes de chamar qualquer serviço
implementado na classe PagamentoResource .

9.19 EXERCÍCIOS
1. No projeto fj36-webservice , dentro do pacote br.com.caelum.payfast.oauth2 crie uma nova
anotação chamada ProtegidoPorOAuth :
@NameBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProtegidoPorOAuth {

2. Dentro do pacote br.com.caelum.payfast.oauth2 crie o filtro OAuthFilter que deve ser um


ContainerRequestFilter que faz a validação do access token do OAuth 2:

@Provider
@ProtegidoPorOAuth
public class OAuthFilter implements ContainerRequestFilter {
@Inject
private HttpServletRequest request;

@Inject
private TokenDao dao;

public void filter(ContainerRequestContext ctx) throws IOException {


System.out.println("validando o access token");

Response unauthorized =

260 9.6 EXERCÍCIOS


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Response.status(Status.UNAUTHORIZED).build();

try {
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);

String accessToken = oauthRequest.getAccessToken();


if(!dao.existeAccessToken(accessToken)){
ctx.abortWith(unauthorized);
}
} catch (Exception e) {
ctx.abortWith(unauthorized);
}
}
}

3. Abra a classe PagamentoResource e dentro dela, remova todos os trechos de código de validação
do access token e depois anote a classe com @ProtegidoPorOAuth , isso fará com que a validação do
access token do oauth seja feita através do filtro.

Depois de fazer as modificações na classe, tente abrir o navegador e entrar na url


http://localhost:8080/fj36-webservice/pagamento/1 . O navegador deverá mostrar uma
página em branco pois a requisição enviada não possui um access token válido. Além disso, o
terminal da aplicação deve mostrar a mensagem validando o access token .

9.20 PARA SABER MAIS: PROVEDORES OAUTH 2.0


Existe uma serie de provedores disponíveis onde podemos cadastrar a nossa aplicação e configurar o
acesso. A partir disso podemos usar este provedor para acessar a conta do usuário. O que podemos fazer
com essa conta depende do provedor e das permissões (scope). Por exemplo, usando Facebook como
provedor podemos acessar o perfil do usuário, acessar a lista de amigos etc.

Alguns dos provedores mais famosos são:

Google
Facebook
Twitter
Github
LinkedIn
Yahoo
entre outros...

Uma lista mais completa se encontra no link: https://oauth.io/providers

9.21 PARA SABER MAIS: OUTROS CLIENTES OAUTH 2.0


Como o OAuth 2.0 é uma especificação aberta de segurança, existem diversas implementações

9.20 PARA SABER MAIS: PROVEDORES OAUTH 2.0 261


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
diferentes tanto para a parte cliente quanto para a servidor. Uma das implementações de cliente mais
populares do OAuth 2.0 é o Scribe.

Assim como no Oltu, com o Scribe conseguimos consumir serviços protegidos independentemente
do provedor (Authorization Server e Resource Server) utilizado. Com ele podemos usar Facebook ou
Google, sempre usando as mesmas classes da biblioteca.

No Scribe, as configurações do servidor de segurança são feitas através da classe OAuthService .


Essa classe é instanciada através de um builder que recebe as configurações principais como client_id e
client_secret.
String apiKey = "clienteId";
String apiSecret = "clienteSecret";
OAuthService service = new ServiceBuilder()
.provider(FacebookApi.class)
.apiKey(apiKey)
.apiSecret(apiSecret)
.callback("http://localhost:8080/fj36-livraria/oauth/callback")
.build();

A classe FacebookApi já vem com Scribe e encapsula a URI que é utilizada para receber o
authorization code e o access token.

Como o Facebook utiliza o Authorization Code Grant, precisamos redirecionar o usuário para a
página em que ele fará o login no facebook e autorizará que a aplicação acesse sua conta. Para
conseguirmos o endereço de redirecionamento, utilizamos o método getAuthorizationUrl() :

String authorizationUrl = service.getAuthorizationUrl(EMPTY_TOKEN);

Após autorização recebemos um código de autorização como um parâmetro chamado code do


redirect feito pelo endpoint de autorização do facebook. Esse código pode ser extraído da request, por
exemplo, utilizando-se diretamente o HttpServletRequest . Depois de adquirirmos o authorization
code, podemos adquirir o access token utilizando o método getAccessToken() da classe
OAuthService :

HttpServletRequest request = // pega a request

String authCode = request.getParameter("code");


Verifier verifier = new Verifier(code);
Token accessToken = service.getAccessToken(null, verifier);

Com o access token em mãos podemos enviar um request para acessar a conta do usuário, utilizando
o novamente a classe OAuthService :

OAuthRequest oauthRequest = new OAuthRequest(Verb.GET,


"https://graph.facebook.com/me");
service.signRequest(accessToken, request);

Mais exemplos e documentação no site da biblioteca:


http://tinyurl.com/o7pqe2k

262 9.21 PARA SABER MAIS: OUTROS CLIENTES OAUTH 2.0


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
e

http://tinyurl.com/osozn2v

9.22 EXERCÍCIO OPCIONAL: OAUTH2 COM GITHUB


Vamos testar autenticação e autorização com OAuth2 usando Github como provedor. Para tal você
precisa ter uma conta no Github.

1. O primeiro passo é registrar a aplicação no Github para receber o client_id e client_secret .

Após ter feito login no Github acesse a URI:

https://github.com/settings/applications

Clique no botão Register new Application. No formulário use os dados seguintes:

fj36-livraria como Application Name.


No item Homepage URL coloque http://localhost:8088/fj36-livraria

Authorization callback URL coloque http://localhost:8088/fj36-livraria/oauth/callback

Confirme o cadastro e guarde o client id e client Secret

2. Para simplificar a comunicação da nossa aplicação com Github usaremos a biblioteca Scribe-Java.

Entre na pasta caelum/36/oauth e copie o jar scribe-1.x.x.jar para a pasta fj36-livraria/WEB-


INF/lib.

3. No projeto fj36-livraria cria uma classe nova GithubApi.

A classe é responsável por configurar duas URI_s: a primeira para obter o _access_token e a segunda
para efetuar o login.

Crie a classe dentro do pacote br.com.caelum.oauth:


public class GithubApi extends DefaultApi20 {

@Override
public String getAccessTokenEndpoint() {
return "https://github.com/login/oauth/access_token";
}

@Override
public String getAuthorizationUrl(OAuthConfig config) {
//o param scope define o que queremos acessar
return String.format("https://github.com/login/oauth/authorize?" +
"scope=user:email&client_id=%s", config.getApiKey());
}
}

4. No pacote br.com.caelum.livraria.controller crie uma nova classe que representa o controller das

9.22 EXERCÍCIO OPCIONAL: OAUTH2 COM GITHUB 263


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
requisições com Github.

No método prepareOAuthService é preciso inserir o client id e client secret:

@Controller
@RequestMapping("/oauth")
public class OAuthController {

private OAuthService service;

@PostConstruct
public void prepareOAuthService() {
this.service = new ServiceBuilder()
.provider(GithubApi.class)
.apiKey("seuClientIdAqui")
.apiSecret("seuClientSecretAqui")
.callback("http://localhost:8088/fj36-livraria/oauth/callback")
.build();
}

@RequestMapping("/github-login")
public String redirectToGithub() {
final Token EMPTY_TOKEN = null
return "redirect:" + service.getAuthorizationUrl(EMPTY_TOKEN);
}

@RequestMapping("/callback")
public String callback(@RequestParam("code") String authToken,
Model model) {

Verifier verifier = new Verifier(authToken);


Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier);

model.addAttribute("accessToken", accessToken.getToken());
model.addAttribute("authToken", authToken);

return "redirect:github-logado";
}

@RequestMapping("/github-emails")
public String githubRequest(
@RequestParam("accessToken") String token,
RedirectAttributes redirectAttributes) {

OAuthRequest request =
new OAuthRequest(Verb.GET, "https://api.github.com/user/emails");
request.addBodyParameter("access_token", token.trim());

service.signRequest(new Token(token, ""), request);


Response response = request.send();

redirectAttributes.addFlashAttribute("responseBody", response.getBody());

return "redirect:github-logado";
}

@RequestMapping("/github-logado")
public String logado() {
return "github-logado";
}
}

264 9.22 EXERCÍCIO OPCIONAL: OAUTH2 COM GITHUB


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
5. Crie o arquivo github-logado.jsp dentro da pasta WebContent/WEB-INF/views/

Você pode copiar o arquivo já pronto da pasta caelum/36/oauth:


<html>
<body>
Código de autenticaçao: ${authToken}

<br>
AccessToken (Authorization:Bearer): ${accessToken}
<br><br>

Cole no campo abaixo o AccessToken para testar o request autorizado.


<br><br>
<form action="github-emails">
<label for="token">AccessToken:</label>
<input type="text" name="accessToken">
<input type="submit" value="Enviar Request ao Github">
</form>
<br><br>
${responseBody}
</body>
</html>

6. Antes de testar o OAuth deslogue-se no Github.

Reinicie o Tomcat pelo Eclipse e acesse a URI:

http://localhost:8088/fj36-livraria/oauth/github-login

A aplicação deve executar um redirecionamento para a página de login do Github e em seguida você
deve autorizar a aplicação (scopes).

9.22 EXERCÍCIO OPCIONAL: OAUTH2 COM GITHUB 265


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
CAPÍTULO 10

APÊNDICE: SWAGGER

Atualmente é bem comum que empresas utilizem APIs REST para a integração de aplicações, seja para
consumir serviços de terceiros ou prover novos serviços.

Ao consumir uma API existente, precisamos conhecer as funcionalidades disponíveis e detalhes de


como invocá-las: recursos, URIs, métodos, Content-Types e outras informações.

Ao prover uma nova API REST, além da implementação, há outras duas preocupações comuns:
como modelar e documentar a API?

10.1 FERRAMENTAS PARA MODELAGEM E DOCUMENTAÇÃO DE APIS


REST
Em um Web Service do estilo SOAP temos o WSDL, que funciona como uma documentação (para
máquinas) do serviço, facilitando a geração automatizada dos clientes que vão consumi-lo. Além disso,
podemos modelar nosso serviço escrevendo o WSDL, em uma abordagem conhecida como Contract-
First. Não é nada legível nem fácil de escrever, mas funciona. Só que no mundo dos Web Services REST
não temos o WSDL. E agora?

Algumas ferramentas para nos auxiliar nessa questão foram criadas, e dentre elas temos: WSDL 2.0,
WADL, API Blueprint, RAML e Swagger.

Vamos abordar o Swagger, que é uma das principais ferramentas utilizadas para modelagem,
documentação e geração de código para APIs do estilo REST.

10.2 MAS O QUE EXATAMENTE É O SWAGGER?


O Swagger é um projeto composto por algumas ferramentas que auxiliam o desenvolvedor de APIs
REST em algumas tarefas como:

Modelagem da API
Geração de documentação (legível) da API
Geração de códigos do Cliente e do Servidor, com suporte a várias linguagens de programação

Para isso, o Swagger especifica a OpenAPI, uma linguagem para descrição de contratos de APIs
REST. A OpenAPI define um formato JSON com campos padronizados (através de um JSON Schema)

266 10 APÊNDICE: SWAGGER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
para que você descreva recursos, modelo de dados, URIs, Content-Types, métodos HTTP aceitos e
códigos de resposta. Também pode ser utilizado o formato YAML, que é um pouco mais legível.

Além da OpenAPI, o Swagger provê um ecossistema de ferramentas. As principais são:

Swagger Editor – para a criação do contrato


Swagger UI – para a publicação da documentação
Swagger Codegen – para geração de “esqueletos” de servidores em mais de 10 tecnologias e de
clientes em mais de 25 tecnologias diferentes

10.3 MODELANDO A API DA PAYFAST


Para modelar nossa nova API, utilizaremos o Swagger Editor. Você pode instalá-lo localmente,
executando uma aplicação NodeJS, ou utilizar a versão online em: http://editor.swagger.io/ .

Pra começar, devemos definir algumas informações iniciais, como a versão do Swagger que estamos
usando:

swagger: '2.0'

O título, descrição e versão da API devem ser definidos:


info:
title: Payfast API
description: Pagamentos rápidos
version: 1.0.0

Em host , inserimos o endereço do servidor da API, em basePath colocamos o contexto da


aplicação e em schemes informamos se a aplicação aceita HTTP e/ou HTTPS.

host: localhost:8080
basePath: /fj36-webservice/v1
schemes:
- http
- https

10.4 DEFINININDO O MODELO DE DADOS


De alguma forma, precisamos definir quais dados são recebidos e retornados pela API.

Na nossa API, recebemos dados de uma Transação, que tem um código, titular, data e valor. A partir
disso, geramos um Pagamento com id, status e valor.

Em um WSDL, esse modelo de dados é definido através de um XML Schema (XSD). No caso do
Swagger, o modelo de dados fica em um JSON Schema na seção definitions do contrato.

De acordo com o JSON Schema, em type podemos usar tipos primitivos de dados para números
inteiros ( integer ), números decimais ( number ), textos ( string ) e booleanos ( boolean ).

10.3 MODELANDO A API DA PAYFAST 267


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Esses tipos primitivos podem ser modificados com a propriedade format . Para o tipo integer
temos os formatos int32 (32 bits) e int64 (64 bits, ou long). Para o number, temos os formatos
float e double . Não há um tipo específico para datas, então temos que utilizar uma string com o
formato date (só data) ou date-time (data e hora).

Além dos tipos primitivos, podemos definir objetos com um type igual a object . Esses objetos
são compostos por várias outras propriedades, que ficam em properties .

No nosso caso, o modelo de dados com os objetos Transacao e Pagamento ficaria algo como:
definitions:
Transacao:
type: object
properties:
codigo:
type: string
titular:
type: string
data:
type: string
format: date
valor:
type: number
format: double
Pagamento:
type: object
properties:
id:
type: integer
format: int32
status:
type: string
valor:
type: number
format: double

10.5 DEFINININDO OS RECURSOS DA API


Com o modelo de dados pronto, precisamos modelar os recursos da nossa API e as respectivas URIs.
No Payfast, teremos o recurso Pagamento acessível pela URI /pagamentos .

Um POST em /pagamentos cria um novo pagamento. Se o pagamento criado tiver o id 1, por


exemplo, as informações estariam acessíveis na URI /pagamentos/1 .

Podemos fazer duas coisas com o nosso pagamento: para confirmá-lo, devemos enviar um PUT para
/pagamentos/1 ; para cancelá-lo, enviamos um DELETE.

No Swagger, as URIs devem ser descritas na seção paths :


paths:
/pagamentos:
post:
summary: Cria novo pagamento

268 10.5 DEFINININDO OS RECURSOS DA API


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
/pagamentos/{id}:
put:
summary: Confirma um pagamento
delete:
summary: Cancela um pagamento

10.6 DEFININDO OS PARÂMETROS DE REQUEST


Para a URI /pagamentos , que recebe um POST, é enviada uma transação no corpo da requisição,
que deve estar no formato JSON. É feita uma referência ao modelo Transacao definido anteriormente.
paths:
/pagamentos:
post:
summary: Cria novo pagamento
consumes:
- application/json
parameters:
- in: body
name: transacao
required: true
schema:
$ref: '#/definitions/Transacao'

Já para a URI /pagamentos/{id} , é definido um path parameter com o id do pagamento. Esse


parâmetro pode ser descrito na seção parameters , logo acima da seção paths , e depois referenciado
nos métodos.
parameters:
pagamento-id:
name: id
in: path
description: id do pagamento
type: integer
format: int32
required: true
paths:
/pagamentos:
#código omitido...

/pagamentos/{id}:
put:
summary: Confirma um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'
delete:
summary: Cancela um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'

10.7 DEFININDO OS TIPOS DE RESPONSE


Definidos os parâmetros de request, precisamos modelar o response.

Depois da criação do pagamento, deve ser retornado um response com o status 201 (Created)

10.6 DEFININDO OS PARÂMETROS DE REQUEST 269


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
juntamente com a URI do novo pagamento no header Location e uma representação em JSON no
corpo da resposta.
paths:
/pagamentos:
post:
summary: Cria novo pagamento
consumes:
- application/json
produces:
- application/json
#código omitido...
responses:
'201':
description: Novo pagamento criado
schema:
$ref: '#/definitions/Pagamento'
headers:
Location:
description: uri do novo pagamento
type: string

Após a confirmação de um pagamento, é simplesmente retornado o status 200 (OK) . O mesmo


retorno acontece após um cancelamento.
/pagamentos/{id}:
put:
summary: Confirma um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'
responses:
'200':
description: 'Pagamento confirmado'
delete:
summary: Cancela um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'
responses:
'200':
description: 'Pagamento cancelado'

10.8 EXERCÍCIO: MODELANDO UMA API COM SWAGGER


1. Acesse o Swagger Editor: http://editor.swagger.io/
2. Na parte da esquerda, insira o contrato a seguir:
swagger: '2.0'
info:
title: Payfast API
description: Pagamentos rápidos
version: 1.0.0
host: localhost:8080
basePath: /fj36-webservice/v1
schemes:
- http
- https
definitions:
Transacao:

270 10.8 EXERCÍCIO: MODELANDO UMA API COM SWAGGER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
type: object
properties:
codigo:
type: string
titular:
type: string
data:
type: string
format: date
valor:
type: number
format: double
Pagamento:
type: object
properties:
id:
type: integer
format: int32
status:
type: string
valor:
type: number
format: double
parameters:
pagamento-id:
name: id
in: path
description: id do pagamento
type: integer
format: int32
required: true
paths:
/pagamentos:
post:
summary: Cria novo pagamento
consumes:
- application/json
produces:
- application/json
parameters:
- in: body
name: transacao
required: true
schema:
$ref: '#/definitions/Transacao'
responses:
'201':
description: Novo pagamento criado
schema:
$ref: '#/definitions/Pagamento'
headers:
Location:
description: uri do novo pagamento
type: string
/pagamentos/{id}:
put:
summary: Confirma um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'
responses:
'200':
description: 'Pagamento cancelado'
delete:

10.8 EXERCÍCIO: MODELANDO UMA API COM SWAGGER 271


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
summary: Cancela um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'
responses:
'200':
description: 'Pagamento cancelado'

Um arquivo YAML depende muito de uma identação correta. Tome cuidado!


3. Perceba, à direita, que é mostrada uma pré-visualização da documentação.

4. Perceba que no menu superior temos a opção Generate Server para gerar um esqueleto do servidor
em Java (JAX-RS e Spring-MVC), PHP (Slim e Silex), Python (Flask), entre outras tecnologias.

Há também a opção Generate Client, que gera clientes nessas tecnologias e em diversas outras.

10.9 DOCUMENTANDO UMA API JÁ EXISTENTE


Para usar o Swagger para documentar nossa API, precisamos de seus jars.

Por meio da classe BeanConfig , do pacote io.swagger.jaxrs.config , fazemos configurações


básicas como título, descrição e versão da API, endereço do servidor e contexto da aplicação, se é usado
HTTP ou HTTPS e pacotes cujos recursos REST devem ser escaneados. Criamos um objeto dessa classe
no construtor de PagamentoService e ao invocar o método setScan, as configurações são realizadas.

@ApplicationPath("/v1")
public class PagamentoService extends Application {
public PagamentoService() {
BeanConfig conf = new BeanConfig();
conf.setTitle("Payfast API");
conf.setDescription("Pagamentos rápidos");
conf.setVersion("1.0.0");
conf.setHost("localhost:8080");
conf.setBasePath("/fj36-webservice/v1");
conf.setSchemes(new String[] { "http" });
conf.setResourcePackage("br.com.caelum.payfast.rest");
conf.setScan(true);
}
}

Além disso, precisamos carregar as classes ApiListingResource e SwaggerSerializers do


Swagger. Para isso, sobrescrevemos o método getClasses de Application , em
PagamentoService . Uma coisa chata é que, ao fazermos isso, precisamos adicionar manualmente o
recurso PagamentoResource .
@ApplicationPath("/")
public class PagamentoService extends Application {
public PagamentoService() {
//código omitido...
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
resources.add(PagamentoResource.class);

272 10.9 DOCUMENTANDO UMA API JÁ EXISTENTE


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
//classes do swagger...
resources.add(ApiListingResource.class);
resources.add(SwaggerSerializers.class);
return resources;
}
}

Para que o Swagger gere a documentação para o nosso recurso, devemos anotar a classe
PagamentoResource com @Api :

@Api
@Path("/pagamentos")
@Singleton
public class PagamentoResource {
//restante do código...
}

10.10 OBTENDO A DOCUMENTAÇÃO GERADA PELO SWAGGER


Podemos obter o JSON e YAML que descrevem nossa API nas seguintes URLs, respectivamente:

http://localhost:8080/fj36-webservice/v1/swagger.json

http://localhost:8080/fj36-webservice/v1/swagger.yaml

O JSON e o YAML gerados pelo Swagger a partir do nosso recurso PagamentoResource usam a
linguagem OpenAPI para detalhar URIs, Content-Types, métodos HTTP aceitos, códigos de resposta e
modelos de dados.

O YAML gerado ficaria assim:

---
swagger: "2.0"
info:
description: "Pagamentos rápidos"
version: "1.0.0"
title: "Payfast API"
host: "localhost:8080"
basePath: "/fj36-webservice/v1"
schemes:
- "http"
paths:
/pagamentos:
post:
operationId: "criarPagamento"
parameters:
- in: "body"
name: "body"
required: false
schema:
$ref: "#/definitions/Transacao"
responses:
default:
description: "successful operation"

10.10 OBTENDO A DOCUMENTAÇÃO GERADA PELO SWAGGER 273


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
/pagamentos/{id}:
get:
operationId: "buscaPagamento"
parameters:
- name: "id"
in: "path"
required: true
type: "integer"
format: "int32"
responses:
200:
description: "successful operation"
schema:
$ref: "#/definitions/Pagamento"
headers: {}
put:
operationId: "confirmarPagamento"
parameters:
- name: "id"
in: "path"
required: true
type: "integer"
format: "int32"
responses:
200:
description: "successful operation"
schema:
$ref: "#/definitions/Pagamento"
headers: {}
delete:
operationId: "cancelarPagamento"
parameters:
- name: "id"
in: "path"
required: true
type: "integer"
format: "int32"
responses:
200:
description: "successful operation"
schema:
$ref: "#/definitions/Pagamento"
headers: {}
definitions:
Transacao:
type: "object"
properties:
numero:
type: "string"
titular:
type: "string"
data:
type: "string"
valor:
type: "number"
Pagamento:
type: "object"
properties:
id:
type: "integer"
format: "int32"
xml:
attribute: true

274 10.10 OBTENDO A DOCUMENTAÇÃO GERADA PELO SWAGGER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
status:
type: "string"
valor:
type: "number"
links:
type: "array"
items:
$ref: "#/definitions/Link"
Link:
type: "object"
properties:
rel:
type: "string"
uri:
type: "string"
method:
type: "string"

10.11 CORRIGINDO ALGUNS DETALHES


Há algumas diferenças entre o YAML gerado pelo Swagger e a nossa API.

Para o POST em /pagamentos , as diferenças mais relevantes são:

Não há uma descrição (summary)


Sumiram os content-types recebidos e enviados
O response ficou com uma descrição genérica, sem o cabeçalho de Location nem o status
201
O parâmetro está não obrigatório e com um nome ruim (body)

Para definir uma descrição e content-types para o POST em /pagamentos , devemos utilizar a
anotação @ApiOperation . Para o status e cabeçalho no response, usamos as anotações
@ApiResponses , @ApiResponse e @ResponseHeader . Para modificar a obrigatoriedade e nome do
parâmetro, usamos @ApiParam .

@ApiOperation(
value = "Cria novo pagamento",
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON)
@ApiResponses(
@ApiResponse(
code=201,
message="Novo pagamento criado",
response = Pagamento.class,
responseHeaders=
@ResponseHeader(
name="Location",
description="uri do novo pagamento",
response=String.class)))
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response criarPagamento(
@ApiParam(
value="Transação",
name="transacao",

10.11 CORRIGINDO ALGUNS DETALHES 275


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
required=true)
Transacao transacao) throws URISyntaxException {
//código omitido...
}

Após essas configurações, o trecho do POST no YAML passaria a ser gerado assim:

/pagamentos:
post:
summary: "Cria novo pagamento"
description: ""
operationId: "criarPagamento"
consumes:
- "application/json"
produces:
- "application/json"
parameters:
- in: "body"
name: "transacao"
description: "Transação"
required: true
schema:
$ref: "#/definitions/Transacao"
responses:
201:
description: "Novo pagamento criado"
schema:
$ref: "#/definitions/Pagamento"
headers:
Location:
type: "string"
description: "uri do novo pagamento"

Bem parecido com o que tinhamos antes!

As diferenças mais importantes para o PUT e o DELETE são as seguintes:

Não há o status 200 no response


O path parameter está duplicado

Podemos usar a anotação @ApiResponse para melhorar as informações do response. Infelizmente,


não há como evitar a duplicação do parâmetro id .

@ApiResponses(
@ApiResponse(
code=200,
message="Pagamento confirmado",
response = Pagamento.class))
@PUT
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON) // cuidado javax.ws.rs
public Pagamento confirmarPagamento(@PathParam("id") Integer pagamentoId) {
//código omitido...
}

@ApiResponses(
@ApiResponse(
code=200,
message="Pagamento cancelado",

276 10.11 CORRIGINDO ALGUNS DETALHES


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
response = Pagamento.class))
@DELETE
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON) // cuidado javax.ws.rs
public Pagamento cancelarPagamento(@PathParam("id") Integer pagamentoId) {
//código omitido...
}

O trecho correspondente do YAML ficaria:


/pagamentos/{id}:
put:
operationId: "confirmarPagamento"
parameters:
- name: "id"
in: "path"
required: true
type: "integer"
format: "int32"
responses:
200:
description: "Pagamento confirmado"
schema:
$ref: "#/definitions/Pagamento"
delete:
operationId: "cancelarPagamento"
parameters:
- name: "id"
in: "path"
required: true
type: "integer"
format: "int32"
responses:
200:
description: "Pagamento cancelado"
schema:
$ref: "#/definitions/Pagamento"

10.12 DOCUMENTAÇÃO PARA HUMANOS


Temos o JSON e o YAML, mas esses formatos não são legíveis para pessoas. São quase tão difíceis de
ler quanto um WSDL.

Em Web Services do estilo SOAP, é bastante comum que sejam disponibilizados PDFs que focam em
descrever a API para humanos. É o caso dos Correios e da Nota Fiscal Paulista, por exemplo.

Será que precisamos escrever um PDF manualmente? Seria interessante alguma maneira de
transformar esse JSON ou YAML em uma página HTML com boa usabilidade.

Para isso, há o projeto Swagger UI!

Na página da ferramenta, podemos baixar um zip com o último release: https://github.com/swagger-


api/swagger-ui/releases

Depois de extrair, basta copiar o conteúdo da pasta dist para uma pasta doc no WebContent do

10.12 DOCUMENTAÇÃO PARA HUMANOS 277


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
nosso projeto.

Então, a página da documentação estaria acessível em: http://localhost:8080/fj36-webservice/v1/doc/

O estranho é que a documentação exibida não seria da nossa API, mas de uma tal de PetStore. É um
exemplo que vem configurado no Swagger UI.

Precisamos modificar o seguinte trecho de JavaScript da página index.html, corrigindo a URL.


url = "http://petstore.swagger.io/v2/swagger.json";

Devemos mudar para:

url = "../v1/swagger.json";

Ao acessar novamente a página, teríamos uma documentação razoavelmente legível:

O Swagger UI, através de um código JavaScript, transforma o swagger.json nessa documentação. É


possível disparar requisições de teste com diferentes content-type. É possível utilizar autorização, oauth,
entre várias outras funcionalidades.

10.13 EXERCÍCIO: DOCUMENTANDO A API DO PAYFAST COM


SWAGGER
1. Precisamos copiar os jars do Swagger, e de suas dependências, para o nosso projeto.

Vá ao diretório caelum/36/jars/lib-swagger .

Selecione todos os JARs, clique com o botão direito e escolha Copy (ou CTRL+C ).

278 10.13 EXERCÍCIO: DOCUMENTANDO A API DO PAYFAST COM SWAGGER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Cole todos os JARs na pasta WebContent/WEB-INF/lib do projeto fj36-webservice
( CTRL+V ).

Caso o Eclipse acuse a existência de algum JAR, clique em Yes To All.

2. Vamos habilitar o uso do Swagger no nosso projeto. Para isso, temos que inserir algumas
configurações na classe PagamentoService :
@ApplicationPath("/v1")
public class PagamentoService extends Application {
public PagamentoService() {
BeanConfig conf = new BeanConfig();
conf.setTitle("Payfast API");
conf.setDescription("Pagamentos rápidos");
conf.setVersion("1.0.0");
conf.setHost("localhost:8080");
conf.setBasePath("/fj36-webservice/v1");
conf.setSchemes(new String[] { "http" });
conf.setResourcePackage("br.com.caelum.payfast.rest");
conf.setScan(true);
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
resources.add(PagamentoResource.class);

//classes do swagger...
resources.add(ApiListingResource.class);
resources.add(SwaggerSerializers.class);
return resources;
}
}

Note que alteramos o conteúdo da anotação @ApplicationPath para /v1 .

3. Temos que anotar a classe PagamentoResource com @Api .


@Api
@Path("/pagamentos")
@Singleton
public class PagamentoResource {
//restante do código...
}

4. O método de criarPagamento de PagamentoResource precisa ser anotado com


@ApiOperation , @ApiResponses e @ApiParam .

@ApiOperation(
value = "Cria novo pagamento",
consumes = MediaType.APPLICATION_JSON,
produces = MediaType.APPLICATION_JSON)
@ApiResponses(
@ApiResponse(
code=201,
message="Novo pagamento criado",
response = Pagamento.class,
responseHeaders=
@ResponseHeader(
name="Location",

10.13 EXERCÍCIO: DOCUMENTANDO A API DO PAYFAST COM SWAGGER 279


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
description="uri do novo pagamento",
response=String.class)))
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response criarPagamento(
@ApiParam(
value="Transação",
name="transacao",
required=true)
Transacao transacao) throws URISyntaxException {
//código omitido...
}

5. O método de confirmarPagamento e cancelarPagamento de PagamentoResource tem que ser


anotados com @ApiResponses .
@ApiResponses(
@ApiResponse(
code=200,
message="Pagamento confirmado",
response = Pagamento.class))
@PUT
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON) // cuidado javax.ws.rs
public Pagamento confirmarPagamento(@PathParam("id") Integer pagamentoId) {
//código omitido...
}

@ApiResponses(
@ApiResponse(
code=200,
message="Pagamento cancelado",
response = Pagamento.class))
@DELETE
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON) // cuidado javax.ws.rs
public Pagamento cancelarPagamento(@PathParam("id") Integer pagamentoId) {
//código omitido...
}

6. Reinicie o Wildfly e acesse pelo navegador o endereço:

http://localhost:8080/fj36-webservice/v1/swagger.yaml

O arquivo deverá ser baixado. Abra-o com um editor de texto (p.ex., GEdit) e analise o resultado.

7. O swagger.yaml não é um documento legível. Para gerar uma documentação para pessoas, vamos
utilizar o Swagger UI.

Copie o conteúdo do diretório caelum/36/swagger-ui/ para dentro do WebContent da aplicação


fj36-webservice .

8. Reinicie o Wildfly e acesse pelo navegador o endereço:

http://localhost:8080/fj36-webservice/doc/

Clique em Expand Operations para visualizar todas as operações.

280 10.13 EXERCÍCIO: DOCUMENTANDO A API DO PAYFAST COM SWAGGER


Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
10.13 EXERCÍCIO: DOCUMENTANDO A API DO PAYFAST COM SWAGGER 281

S-ar putea să vă placă și