Fj-36.pdf

  • Uploaded by: Luciano de Oliveira
  • 0
  • 0
  • April 2020
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Fj-36.pdf as PDF for free.

More details

  • Words: 62,412
  • Pages: 288
Apostila gerada especialmente para Walmir Bellani - [email protected]

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 3.1 Integração hetereogênea

Apostila gerada especialmente para Walmir Bellani - [email protected]

34 34

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 - [email protected]

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 - [email protected]

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 - [email protected]

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 - [email protected]

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

1

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

3

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 - [email protected]

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. Apagueo 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

5

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

7

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 - [email protected]

Na opção Select root directoy escolha Browse. Vá na pasta caelum/36 e selecione o diretório fj36livraria. 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

9

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

11

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 - [email protected]

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)

Apostila gerada especialmente para Walmir Bellani - [email protected]

13

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 - [email protected]

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 livros = new ArrayList(); // 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

15

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

17

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 - [email protected]

List itens = new ArrayList(); 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 itens = (List) 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

19

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

21

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

23

}

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

25

"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 - [email protected]

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?

Apostila gerada especialmente para Walmir Bellani - [email protected]

27

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 - [email protected]

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?

Apostila gerada especialmente para Walmir Bellani - [email protected]

29

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

31

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 fj36estoque . 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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

33

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

35

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 - [email protected]

É 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

37

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

39

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

41

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

43

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: <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Header> <ns2:senhaDoOperador xmlns:ns2="http://webservices.caelum.com.br/"> 123456 <S:Body> <ns2:calculaParcela xmlns:ns2="http://webservices.caelum.com.br/"> 100.0 2

44

3.8 SOAP - SIMPLE OBJECT ACCESS PROTOCOL

Apostila gerada especialmente para Walmir Bellani - [email protected]



Exemplo da mensagem de resposta: <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <ns2:calculaParcelaResponse xmlns:ns2="http://webservices.caelum.com.br/"> 51.0

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

45

} }

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 - [email protected]

3.10 WSDL: O CONTRATO DO SEU WEB SERVICE

Apostila gerada especialmente para Walmir Bellani - [email protected]

47

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">

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: <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 name="calculaParcelaResponse" type="tns:calculaParcelaResponse"> <xs:complexType name="calculaParcela"> <xs:sequence> <xs:element name="valorTotal" type="xs:double"> <xs:element name="quantidade" type="xs:int"> <xs:complexType name="calculaParcelaResponse"> <xs:sequence> <xs:element name="return" type="xs:double">

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 - [email protected]

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> <multiply> 2.0 7

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> <multiply> 2.0 7

3.10 WSDL: O CONTRATO DO SEU WEB SERVICE

Apostila gerada especialmente para Walmir Bellani - [email protected]

49

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> 2.0 7

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

2.0 7 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> 10 10

50

3.10 WSDL: O CONTRATO DO SEU WEB SERVICE

Apostila gerada especialmente para Walmir Bellani - [email protected]

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

No literal dá pra fazer um wrapper (para os elementos) e assim também passa a aparecer o nome do método <soma> 10 10

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"> <message name="calculaParcelaResponse"> <part name="parameters" element="tns:calculaParcelaResponse">

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">

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). <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"> <soap:operation soapAction=""> <soap:body use="literal">

3.10 WSDL: O CONTRATO DO SEU WEB SERVICE

Apostila gerada especialmente para Walmir Bellani - [email protected]

51

<soap:body use="literal">


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">

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 JAXWS 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 - [email protected]

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 subpacote 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

53

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

55

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 - [email protected]

Como saber quais são os tipos válidos?

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

57

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

59

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

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/"> ARQ 2

60

3.14 EXERCÍCIOS: CONSUMINDO O WEB SERVICE

Apostila gerada especialmente para Walmir Bellani - [email protected]

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 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

61

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 fj36webservice para que ele receba como argumento a lista de todos os códigos que serão consultados: public List getQuantidade(List<String> codigos) { List 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 - [email protected]

}

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">

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">

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

63

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

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

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 metainformaçõ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 getQuantidade( @WebParam(name = "codigo") List<String> codigos) {

O @WebMethod e @WebParam mudam a mensagem SOAP de ida (request): <soapenv:Envelope ....> <soapenv:Body> ARQ

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

64

3.18 MAIS RECURSOS DO JAX-WS

Apostila gerada especialmente para Walmir Bellani - [email protected]

<soap:Envelope ...> <soap:Body> <ns2:ItensPeloCodigoResponse ...> ARQ 2

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 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

65

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> ?

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 aparece : <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <ns2:ItensPeloCodigoResponse xmlns:ns2="http://ws.estoque.caelum.com.br/"> ARQ 2

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 - [email protected]

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

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

67

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

69

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

71

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 (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 - [email protected]

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:

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 /* <user-data-constraint> CONFIDENTIAL

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-*

Apostila gerada especialmente para Walmir Bellani - [email protected]

73

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
. 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 - [email protected]

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 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

75

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> TOKEN123 <soapenv:Body> ARQ

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

77

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 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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

79

}) 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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

81

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

do

serviço, que é: 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 fj36livraria , 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 - [email protected]

semEntregueEmMaos, semValorDeclarado, semAvisoRecebimento); List 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

83

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 - [email protected]

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 <nome>RESTful Web Services <preco>60.0

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

4.2 ORIENTADO AO RECURSO

Apostila gerada especialmente para Walmir Bellani - [email protected]

85

}

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

87

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 - [email protected]

<servlet> <servlet-name>RestJersey <servlet-class>org.glassfish.jersey.servlet.ServletContainer <param-name>jersey.config.server.provider.packages <param-value> br.com.caelum.loja.recursos <servlet-mapping> <servlet-name>RestJersey /*

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-class> org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher <servlet-mapping> <servlet-name>Resteasy /*

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

89

}

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 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 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 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 - [email protected]

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 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

91

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 - [email protected]

@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 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

93

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> 1 10

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 - [email protected]

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( ""+ "Jim Webber"+ "66.9"+ ""); 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

95

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

97

{"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 caelum/36/etc/exemplos-curl.txt. Basta copiar e colar no terminal quando necessário.

em

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 - [email protected]

{"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

Apostila gerada especialmente para Walmir Bellani - [email protected]

99

@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> 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, 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 - [email protected]

{"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: <pagamento> 2 <status>CRIADO 39.9

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: <pagamento> 2 <status>CRIADO 39.9

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

101

@XmlAccessorType(XmlAccessType.FIELD) public class Link { // código da classe }

O xml depois dessa segunda modificação fica da seguinte forma: <pagamento> 2 <status>CRIADO 39.9 confirmar /pagamentos/1 <method>PUT cancelar /pagamentos/1 <method>DELETE

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 : <pagamento> 2 <estadoAtual>CRIADO 39.9 confirmar /pagamentos/1 <method>PUT cancelar /pagamentos/1 <method>DELETE

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 - [email protected]

@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: <pagamento id="1"> <estadoAtual>CRIADO 39.9 confirmar /pagamentos/1 <method>PUT cancelar /pagamentos/1 <method>DELETE

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

103

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

105

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 - [email protected]

(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

Apostila gerada especialmente para Walmir Bellani - [email protected]

107

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

109

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 - [email protected]

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)

Apostila gerada especialmente para Walmir Bellani - [email protected]

111

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 - [email protected]

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?

Apostila gerada especialmente para Walmir Bellani - [email protected]

113

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

115

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

117

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

119

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

121

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

123

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

125

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

127

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

129

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 standalonefull.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"> true

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

131

@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 - [email protected]

}

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 springcontext.xml : <property name="environment"> <props> <prop key="java.naming.factory.initial"> org.jboss.naming.remote.client.InitialContextFactory <prop key="java.naming.provider.url"> http-remoting://localhost:8080

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

133

spring chamado JndiObjectFactoryBean para automatizar a busca: <property name="jndiTemplate" ref="jmsJndiTemplate"/> <property name="jndiName" value="jms/RemoteConnectionFactory"/>

Também podemos configurar o lookup dos tópicos e filas que foram criados no HornetQ: <property name="jndiTemplate" ref="jmsJndiTemplate"/> <property name="jndiName" value="jms/TOPICO.LIVRARIA"/>

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 springcontext.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 - [email protected]

seguinte trecho de código que configura o lookup no JNDI do spring: <property name="environment"> <props> <prop key="java.naming.factory.initial"> org.jboss.naming.remote.client.InitialContextFactory <prop key="java.naming.provider.url"> http-remoting://localhost:8080 <property name="jndiTemplate" ref="jmsJndiTemplate"/> <property name="jndiName" value="jms/RemoteConnectionFactory"/> <property name="jndiTemplate" ref="jmsJndiTemplate"/> <property name="jndiName" value="jms/TOPICO.LIVRARIA"/>

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

135

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

137

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

139

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 2013-12-05 ...

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 - [email protected]

5.26 MENSAGENS INDEPENDENTES DE PLATAFORMA: AMQP, STOMP E REST

Apostila gerada especialmente para Walmir Bellani - [email protected]

141

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. Arquitetura Java

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: Arquitetura Java

142

6 CRIAÇÃO DO MODELO CANONICAL

Apostila gerada especialmente para Walmir Bellani - [email protected]

29.90


É 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é

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. 00.000.000/0001-00 <nome>João da Silva <nome>Maria da Silva

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

143

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 - [email protected]

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: ARQ <nomeAutor>Paulo Silveira Arquitetura Java 29.90

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

145

//... 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: ARQ <nomeAutor>Paulo Silveira Arquitetura Java 29.90 <nome>TI

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 - [email protected]

@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

Apostila gerada especialmente para Walmir Bellani - [email protected]

147

// 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 - [email protected]

Vejamos um exemplo concreto de um Schema XML: <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:complexType name="categoria"> <xs:sequence> <xs:element name="nome" type="xs:string" minOccurs="0"/>

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"/>

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"/>

6.7 DEFINIÇÃO DE TIPOS - XML SCHEMA DEFINITION (XSD)

Apostila gerada especialmente para Walmir Bellani - [email protected]

149

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:

150

6.8 IDENTIFICAÇÃO PELO NAMESPACE

Apostila gerada especialmente para Walmir Bellani - [email protected]

José da Silva 1234-1234 FJ-36


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.

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). <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

Apostila gerada especialmente para Walmir Bellani - [email protected]

151

... <xs:complexType name="categoria"> <xs:sequence> ...

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 - [email protected]

} }); } }

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:simpleType name="codigo"> <xs:restriction base="xs:string"> <xs:pattern value="[A-Z]{3}" />

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

153

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 - [email protected]

<xs:simpleType name="valor"> <xs:restriction base="xs:decimal"> <xs:minExclusive value="0"/> <xs:fractionDigits value="2"/>

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

155

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

157

"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 - [email protected]

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 itens;

Para melhorar use no atributo as seguintes anotações: @XmlElementWrapper(name="itens") @XmlElement(name="item") private Set itens;

Assim, será serializado um elemento e, para cada ItemCompra , um elemento . 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

159

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

161

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

163

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 fj36camel . 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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

165

mensagem. Exchange é nada mais do que um container para a mensagem que possui algumas metainformaçõ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} : <nota data="${headers.dataAtual}"> ${body}

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

167

);

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

169

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
o seguinte trecho de código: <pagamento> <status>CONFIRMADO 29.90

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 - [email protected]

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 longlive 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

171

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

173

@ResponseBody @RequestMapping(value="/livros/mais-vendidos", method=RequestMethod.GET, produces=MediaType.APPLICATION_JSON_VALUE) public String livrosMaisVendidos() { List 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 - [email protected]

@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 livros = (ArrayList) 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

175

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

177

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 - [email protected]

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:

Como usamos o prefixo camel devemos registrar o namespace correto no início do XML do Spring:

7.17 INTEGRAÇÃO DO CAMEL COM SPRING

Apostila gerada especialmente para Walmir Bellani - [email protected]

179

...

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:

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:

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 , dentro da configuração desse bean, precisamos informar qual é a ConnectionFactory que será utilizada: JmsConfiguration

<property name="connectionFactory" ref="hornetQConnectionFactory"/>

180

7.18 ENVIANDO E CONSUMINDO MENSAGENS JMS

Apostila gerada especialmente para Walmir Bellani - [email protected]



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: <property name="jndiTemplate" ref="jmsJndiTemplate"/>

E agora precisamos passar o DestinationResolver para o JmsConfiguration : <property name="connectionFactory" ref="hornetQConnectionFactory"/> <property name="destinationResolver" ref="camelDestinationResolver"/>

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

181

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 :

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: <property name="jndiTemplate" ref="jmsJndiTemplate"/> <property name="connectionFactory" ref="hornetQConnectionFactory"/> <property name="destinationResolver" ref="camelDestinationResolver"/>

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

183

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

185

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> TOKEN123 <soapenv:Body> ARQ

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 ...> ARQ 2

186

7.22 INTEGRAR SERVIÇOS SOAP E REST

Apostila gerada especialmente para Walmir Bellani - [email protected]



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} ): <soapenv:Envelope ...> <soapenv:Header> ${headers.token} <soapenv:Body> ${body}

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

187

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

189

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}

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 - [email protected]

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> 14 <status>CONFIRMADO 29.90

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?

Apostila gerada especialmente para Walmir Bellani - [email protected]

191

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

193

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 - [email protected]

- 0..1 MessageSource - 1..n MessageProcessor(s) - 0..1 ExceptionStrategy

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

195

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 - [email protected]

8.5 EXERCÍCIOS: CRIAÇÃO DO PRIMEIRO FLUXO

Apostila gerada especialmente para Walmir Bellani - [email protected]

197

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

199

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

201

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 ...>

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 - [email protected]

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. <when expression="#[xpath3( ... )]"> <when expression="#[xpath3( ... )]">

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

203

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 - [email protected]

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("//*[contains(namespace-uri(), 'http://caelum.com.br/estoquews/v1')]",

8.7 EXERCÍCIOS: ROTEANDO ENTRE VERSÕES DO SERVIÇO

Apostila gerada especialmente para Walmir Bellani - [email protected]

205

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

207

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 - [email protected]

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-mulemocks-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

Apostila gerada especialmente para Walmir Bellani - [email protected]

209

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

211

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

213

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

215

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 - [email protected]

Através desse componente vamos definir a resposta HTTP. Nas propriedades coloque no elemento Value e valor: ok

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

217

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 - [email protected]

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 fj36pedidos e depois Run As -> Mule Application Fique atento ao console para descobrir possíveis erros. Segue o XML do fluxo para comparação:

8.11 EXERCÍCIOS: DESENHANDO O FLUXO DE PEDIDOS

Apostila gerada especialmente para Walmir Bellani - [email protected]

219

<set-payload value="<resposta>ok</resposta>" doc:name="Set Property"/> <set-property propertyName="http.status" value="202" doc:name="Property"/>


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 ok .

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

221

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

223

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: ok 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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

225

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 - [email protected]

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 ScatterGather. 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

227

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: ok 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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

229

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("ok"); 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 - [email protected]

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/mulemocks.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

Apostila gerada especialmente para Walmir Bellani - [email protected]

231

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

233

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

235

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

237

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

239

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 fj36webservice .

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

241

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 - [email protected]

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: , token_type: "Bearer", expires_in: 3600, 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 fj36webservice , 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

243

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 - [email protected]

{ "token_type": "bearer", "access_token": }

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

Esse cabeçalho pode ser colocado na requisição utilizando-se o método header do cliente do JAXRS. 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

245

.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 fj36livraria : @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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

247

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

249

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

251

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

253

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 - [email protected]

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 fj36webservice .

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

255

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 - [email protected]

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:///oauth/code/returnURL ?code=&client_id=&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

Apostila gerada especialmente para Walmir Bellani - [email protected]

257

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 - [email protected]

// 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

259

@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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

261

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 - [email protected]

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/WEBINF/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

Apostila gerada especialmente para Walmir Bellani - [email protected]

263

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 - [email protected]

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: Código de autenticaçao: ${authToken}
AccessToken (Authorization:Bearer): ${accessToken}

Cole no campo abaixo o AccessToken para testar o request autorizado.



${responseBody}

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

265

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 ContractFirst. 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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

267

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 - [email protected]

/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

Apostila gerada especialmente para Walmir Bellani - [email protected]

269

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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

271

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> getClasses() { Set> resources = new HashSet<>(); resources.add(PagamentoResource.class);

272

10.9 DOCUMENTANDO UMA API JÁ EXISTENTE

Apostila gerada especialmente para Walmir Bellani - [email protected]

//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 e 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

273

/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 - [email protected]

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

Apostila gerada especialmente para Walmir Bellani - [email protected]

275

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 - [email protected]

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/swaggerapi/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

Apostila gerada especialmente para Walmir Bellani - [email protected]

277

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 - [email protected]

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> getClasses() { Set> 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

Apostila gerada especialmente para Walmir Bellani - [email protected]

279

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 - [email protected]

10.13 EXERCÍCIO: DOCUMENTANDO A API DO PAYFAST COM SWAGGER

281

More Documents from "Luciano de Oliveira"