Documente Academic
Documente Profesional
Documente Cultură
com
Caelum Sumário
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
Versão: 20.6.10
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.
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.
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!
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:
2 1.2 O CURSO
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
REST: Construa API's inteligentes de maneira simples, Alexandre Saudate
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.
ARQUITETURA DE SISTEMAS
DISTRIBUÍDOS
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.
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.
Clique em Extract;
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:
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:
9. Ainda no Eclipse vá em File > Import e selecione General e clique Existing projects into workspace
e Next:
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...:
Explore a loja, escolha alguns livros, no entanto nem todas as funcionalidades estarão prontas, pois
ainda falta implementá-las.
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.
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.
package br.com.caelum;
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.
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();
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 .
$ 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.
@Override
public String toString() {
return "livro";
}
}
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 .
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.
Suponha que um cliente deseja consultar todos os livros de uma determinada categoria. Então, seria
interessante que a JVM servidora serializasse o objeto Categoria desejado juntamente com os livros.
Para isso, basta que tanto a classe Categoria quanto a classe Livro sejam Serializable :
public class Categoria implements Serializable {
private String nome;
private List<Livro> livros = new ArrayList<Livro>();
// outros atributos
// métodos
}
Mas, se a classe Livro não implementa Serializable , mesmo que a classe Categoria
implemente, o processo de serialização vai falhar.
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 .
A partir da versão 5, o compilador do Java gera um warning se uma classe Serializable não
define o serialVersionUID .
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
INCOMPATIBILIDADE
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) .
oos.writeObject(itens);
}
}
}
7. Vamos criar a classe que testa o ObjectInputStream para ler os itens serializados. Crie uma nova
classe TestaLeituraDeObjetos no mesmo package main . Use o método main com o código
seguinte:
try(ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("itens.bin"))){
List<ItemEstoque> itens = (List<ItemEstoque>) ois.readObject();
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?
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?
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.
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?
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 {
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));
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.
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 .
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;
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 {
// ...
Nossa próxima questão: quem vai criar a classe do objeto de mentira? Temos três alternativas:
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.
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:
//...
}
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.
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
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.
Subindo o catálogo:
linha de comando:
$ rmiregistry
programaticamente:
LocateRegistry.createRegistry(1099);
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.
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.
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;
28 2.13 O CLIENTE
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
public static void main(String[] args) throws Exception {
EstoqueRmi estoque =
(EstoqueRmi) Naming.lookup("rmi://localhost:1099/estoque");
ItemEstoque item = estoque.getItemEstoque("SOA");
System.out.println("Quantidade disponível: " + item.getItemEstoque());
}
}
Um cliente RMI pode ser qualquer classe Java, como uma action do Spring MVC, um managed bean
do JSF, um ActionListener do Swing ou mesmo um applet.
2.14 E OS STUBS?
Os stubs que precisamos são realmente classes. Essas classes não serão escritas por nós (até poderiam
ser), pois são geradas pelo Java. Para gerar um stub do nosso EstoqueService :
$ rmic br.com.caelum.estoque.rmi
Você 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.
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.
2.14 E OS STUBS? 29
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
1099 é a porta default usada pelo rmiregistry .
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.
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
Quando estender de UnicastRemoteObject , o quickfix pode ser utilizado para gerar o construtor
da classe filha que lança RemoteException .
package br.com.caelum.estoque.rmi;
@Override
public ItemEstoque getItemEstoque(String codigoProduto)
throws RemoteException {
package br.com.caelum.estoque.main;
LocateRegistry.createRegistry(1099);
Naming.rebind("/estoque", new EstoqueService());
System.out.println("Estoque registrado e rodando");
}
package br.com.caelum.estoque.cliente;
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?
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á?
System.out.println(estoque.toString());
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.
Copie para este pacote a interface EstoqueRmi e a classe ItemEstoque do projeto fj36-
estoque .
ATENÇÃO: Não copie a classe EstoqueService . Ela não é necessária para executar o código do
cliente.
Nela abra o método listar(). Vamos delegar a verificação de cada livro para o carrinho de compra.
Adicione:
if (itemCompra.isImpresso()) {
itemCompra.setQuantidadeNoEstoque(itemEstoque
.getQuantidade());
}
}
}
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.
"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.
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.
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.
Troca de arquivos
Banco de dados compartilhado
RPC
Mensageria
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:
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.
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.
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.
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.
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:
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 :
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.
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.
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:
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] .
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 :
http://localhost:9990/
Será necessário que você se autentique com os dados de usuário e senha que você acabou de criar:
Caso não possua a opção JBoss Community -> WildFly 8.x , clique em Download
additional server adapters :
Ao final do procedimento, será solicitado que você reinicie o Eclipse. Faça isso e volte
novamente para a aba Servers .
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.
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.
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 {
message.writeTo(System.out);
2. Execute o método main pelo Eclipse. O Tomcat não precisa estar rodando.
WSDL (Web Service Description Language) é uma linguagem também baseada em XML,que
descreve serviços na Web. Em um WSDL temos:
<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">
</definitions>
Em definitions adicionamos uma seção chamada types, para definimos os tipos que o serviço usará.
Para descrever os campos usamos um XML Schema (ou XSD). Confira o exemplo abaixo:
<types>
<xs:schema xmlns:tns="http://webservices.caelum.com.br/"
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0"
targetNamespace="http://webservices.caelum.com.br/">
<xs:complexType name="calculaParcela">
<xs:sequence>
<xs:element name="valorTotal" type="xs:double"></xs:element>
<xs:element name="quantidade" type="xs:int"></xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="calculaParcelaResponse">
<xs:sequence>
<xs:element name="return" type="xs:double"></xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
</types>
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.
A diferença entre literal e encoded resume-se a transportar ou não o tipo de cada parâmetro.
Exemplo de uma mensagem SOAP Literal:
<soap:envelope>
<soap:body>
<!-- Nome da operação -->
<multiply>
<!-- primeira mensagem(input)/parametro -->
<a>2.0</a>
<!-- segunda mensagem(input)/parametro -->
<b>7</b>
</multiply>
</soap:body>
</soap:envelope>
Como os tipos não estão explícitos na mensagem, eles ficarão no schema do WSDL.
Ao usarmos Document-Style podemos usar SOAP para trabalharmos de forma mais genérica,
por exemplo, usando JMS. Mas se precisarmos usar Document com RPC, usamos um padrão
conhecido como Document/Literal Wrapped. Nele, os elementos do nosso serviço farão parte do
schema do WSDL (já que estamos usando literal). E com isso conseguiremos validar a mensagem
SOAP. Iremos envolver os parâmetros com o nome do elemento definido no schema. Exemplo:
SOAP:
<soap:envelope>
<soap:body>
<multiply>
<a>2.0</a>
<b>7</b>
</multiply>
</soap:body>
</soap:envelope>
WSDL:
<types>
<schema>
<element name="multiply">
<complexType>
<sequence>
<element name="a" type="xsd:float"/>
<element name="b" type="xsd:float"/>
</sequence>
</complexType>
</element>
</schema>
</types>
Document
- permite construir a mensagem de qq forma, nao só para RPC
- SoapAction fica vazio
Encoded:
``` xml
<soma>
<a xsi:type="xsd:int">10</a>
<b xsi:type="xsd:int">10</b>
</soma>
<a>10</a>
<b>10</b>
O próximo item do definition é a parte de mensagem. Nessa seção definimos todas as mensagens que
serão enviadas e recebidas pelos serviços. Nas mensagens, usamos os tipos declarados na seção type para
definir seus parâmetros. Exemplo de uma mensagem de envio e uma de recebimento:
<message name="calculaParcela">
<part name="parameters" element="tns:calculaParcela"></part>
</message>
<message name="calculaParcelaResponse">
<part name="parameters" element="tns:calculaParcelaResponse"></part>
</message>
Depois das mensagens, temos que escrever quais serviços receberão e responderão cada mensagem.
Essa configuração é feita na seção portType. Aqui, além de juntar as mensagens com os serviços, ainda
agrupamos logicamente as operações disponíveis.
<portType name="CalculadorDeParcela">
<operation name="calculaParcela">
<input message="tns:calculaParcela"></input>
<output message="tns:calculaParcelaResponse"></output>
</operation>
</portType>
Falta ainda definir pelo menos o protocolo (HTTP ou outro) de comunicação e o endereço a ser
acessado. O protocolo é definido na tag binding. Além do protocolo, especificamos como será o estilo da
mensagem a ser enviada e como a aplicação que requisitou o serviço deve enviar a mensagem, de forma
literal (usando os tipos do schema do WSDL) ou encoding (definição padrão SOAP).
Por fim, definimos o endereço de acesso do serviço, fazendo o vínculo com os serviços disponíveis
no binding
<service name="CalculadorDeParcelaService">
<port name="CalculadorDeParcelaPort" binding="tns:CalculadorDeParcelaPortBinding">
<soap:address location="http://localhost:8080/webservice/oi"></soap:address>
</port>
</service>
A principal implementação do JAX-WS é a que vem junto da JDK (desde a versão 6), ou seja, não
precisamos baixar nenhuma biblioteca adicional.
Antes disso, era necessário baixar os JARs de uma implementação como o Metro, o Apache Axis e o
Apache CXF. Hoje em dia esses e outros implementam a especificação JAX-WS.
Tecnicamente, para expor um serviço, deveríamos começar escrevendo o WSDL, porém com o JAX-
WS não precisamos dessa abordagem top-down. Podemos configurar nosso serviço usando anotações, e
o JAX-WS gerará o WSDL.
Temos duas anotações importantes, @WebService e @WebMethod . A primeira configura uma classe
para ser exposta como um Web Service, a segunda define que um método será exposto como uma
operação do serviço.
@WebService
public class CalculadorDeParcela {
@WebMethod
public double calculaParcela(double valorTotal, int quantidade) {
if (quantidade < 0) {
return 0;
}
double oValorTotal = valorTotal * (1 + (quantidade / 100.0));
return oValorTotal / quantidade;
}
}
Bastam essas duas anotações para expor esse método como um serviço web. O único problema são
os nomes dos parâmetros. Como o Java não consegue descobrir nomes de parâmetros de métodos por
reflection, o WSDL será gerado para receber arg0 e arg1 , dificultando o entendimento para os nossos
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
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 .
Assim que executarmos esse código, temos nosso serviço exposto, pronto para ser acessado.
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.
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.
1. No Eclipse crie um novo projeto do tipo Dynamic Web Project chamado fj36-webservice e escolha
o servidor Wildfly como Target Runtime.
package br.com.caelum.estoque.ws;
ItemEstoque() {
}
//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
@Stateless
public class EstoqueWS {
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));
}
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
Repare que o WSDL que foi gerado automaticamente baseado na classe EstoqueWS .
7. Analise WSDL.
<!--@note
port é o stub/proxy
fault a exceção,
message os dados que são trocados (paramentros/retorno)
operation é o método
Em algum momento deixe claro a tradução das palavras Java <-> WS (fazendo RPC):
-->
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/
Após ter baixado e instalado o SoapUI podemos criar um novo projeto do tipo SOAP pela interface
gráfica:
Após confirmação já podemos testar o serviço. O SoapUI gera automaticamente uma requisição
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
5. Dentro do XML SOAP, no elemento arg0 , no lugar de ? coloque o código do livro, por exemplo:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.estoque.caelum.com.br/">
<soapenv:Header/>
<soapenv:Body>
<ws:getQuantidade>
<arg0>ARQ</arg0>
</ws:getQuantidade>
</soapenv:Body>
</soapenv:Envelope>
6. Verifique a resposta no outro lado da janela. Deve aparecer a mensagem SOAP com as informações
de quantidade no estoque:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:getQuantidadeResponse xmlns:ns2="http://ws.estoque.caelum.com.br/">
<return>
<codigo>ARQ</codigo>
<quantidade>2</quantidade>
</return>
</ns2:getQuantidadeResponse>
</soap:Body>
</soap:Envelope>
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:
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.
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
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.
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.
Repare que agora na seção wsdl:types do novo contrato do serviço, temos o seguinte código:
<xs:complexType name="getQuantidade">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="arg0"
type="xs:string">
</xs:element>
</xs:sequence>
</xs:complexType>
Esse trecho indica que o método recebe uma lista com uma quantidade ilimitada de xs:string s
<xs:complexType name="getQuantidadeResponse">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="return"
type="tns:itemEstoque">
</xs:element>
</xs:sequence>
</xs:complexType>
Por esse trecho de código, sabemos que o tipo getQuantidadeResponse representa uma lista de
elementos do tipo tns:itemEstoque .
3. Agora vamos testar novamente o serviço implementado com o SoapUI. Como o contrato do serviço
foi alterado, precisamos atualizar a requisição preparada pelo SoapUI.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.estoque.caelum.com.br/">
<soapenv:Header/>
<soapenv:Body>
<ws:getQuantidade>
<arg0>ARQ</arg0>
</ws:getQuantidade>
</soapenv:Body>
</soapenv:Envelope>
4. Submeta uma nova requisição passando dois códigos diferentes, por exemplo:
<arg0>ARQ</arg0>
<arg0>SOA</arg0>
Verifique a resposta.
5. (conceitual) Como consumidor de um Web Service, o que você acha de um elemento no XML que
possui o nome arg0 ou return ?
Como criamos o serviço a partir de uma classe Java, o Wildfly (nesse caso a implementação JAX-WS
CXF) leu a assinatura dela e gerou o WSDL. O problema é que não há todas as informações na classe
para gerar um WSDL expressivo.
Para resolver este problema o JAX-WS oferece uma serie de anotações para definir essas meta-
informações que ajudam a deixar a mensagem SOAP mais legível. As anotações @WebMethod ,
@WebResult e @WebParam possuem atributos para alterar o nome dos elementos na mensagem SOAP:
@WebMethod(operationName="ItensPeloCodigo")
@WebResult(name="ItemEstoque")
public List<ItemEstoque> getQuantidade(
@WebParam(name = "codigo") List<String> codigos) {
@WebMethod(operationName="ItensPeloCodigo")
@WebResult(name="ItemEstoque")
public List<ItemEstoque> getQuantidade(
@WebParam(name = "codigo") List<String> codigos) {
Confirme o dialogo e depois escolha o novo método ItensPeloCodigo. É gerado um novo elemento
Request 1 já com a mensagem SOAP atualizada.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ws="http://ws.estoque.caelum.com.br/">
<soapenv:Header/>
<soapenv:Body>
<ws:ItensPeloCodigo>
<codigo>?</codigo>
</ws:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>
4. Submeta uma vez a mensagem SOAP. Não esqueça de substituir ? por um código do livro.
Repare que a resposta SOAP também está mais expressiva. So invés do elemento <return> aparece
<ItemEstoque> :
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns2:ItensPeloCodigoResponse
xmlns:ns2="http://ws.estoque.caelum.com.br/">
<ItemEstoque>
<codigo>ARQ</codigo>
<quantidade>2</quantidade>
</ItemEstoque>
</ns2:ItensPeloCodigoResponse>
</soap:Body>
</soap:Envelope>
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
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.
@WebService(targetNamespace="http://caelum.com.br/estoquews/v1")
v1 é a versão do contrato. Qualquer alteração que quebraria a compatibilidade (maior change) deve
causar a alteração da versão. Para alterações que são compatíveis (minor change) deveríamos usar a tag
<documentation> no WSDL. Segue um exemplo:
<wsdl:definitions ....
targetNamespace="http://caelum.com.br/estoquews/v1">
<documentation>Version 1.1</documentation>
Infelizmente o JAX-WS não especificou uma forma padrão de definir o conteúdo da tag
<documentation> .
```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.
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
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.
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.
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.
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.
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.
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!
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
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
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
Ao acessar o Tomcat o navegador reclamará do nosso certificado. Isto por que o nosso certificado
não foi emitido por uma autoridade de certificação, e sim gerado manualmente pelo keytool .
Por fim, falta habilitar a segurança de transporte na aplicação web. No arquivo de configuração da
aplicação, ou seja no web.xml , é preciso adicionar o elemento security-constraint :
<security-constraint>
<web-resource-collection>
<web-resource-name>aplicação protegida</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
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.
WS-Policy: para expressar restrições e requisitos, por exemplo uma politica de segurança
WS-Security: Para definir como aplicar segurança a mensagem SOAP
WS-Addressing: para definir informações sobre destinatário, remetente e roteamento da
mensagem SOAP, é útil para comunicação assíncrona
entre vários outros
As extensões normalmente são chamadas de WS-* por causa da grande quantidade de especificações
e documentos relacionadas. A complexidade e má fama dos Web Services se origina dessas extensões.
Como o SOAP é extensível podemos adicionar diferentes extensões a uma mensagem, sempre dentro
de elemento <header> . Várias extensões podem ser combinadas para conseguir a qualidade de serviço
desejada.
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.
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.
Adicione apenas o segundo parâmetro. Repare que a anotação @WebParam usa o atributo
header=true:
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 {
4. Antes de publicar o Web Service vamos introduzir o versionamento pelo namespace para facilitar a
sua evolução.
@Stateless
@WebService(targetNamespace="http://caelum.com.br/estoquews/v1")
public class EstoqueWS {
Verifique o namespace novo no início do WSDL. Também procure o elemento tokenUsuario que
faz parte do soap:header .
Abra o XML SOAP e coloque o token e o codigo no lugar das ? . Por exemplo:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v1="http://caelum.com.br/estoquews/v1">
<soapenv:Header>
<v1:tokenUsuario>TOKEN123</v1:tokenUsuario>
</soapenv:Header>
<soapenv:Body>
<v1:ItensPeloCodigo>
<codigo>ARQ</codigo>
</v1:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>
7. Altere a requisição e submeta um token inválido. Teste com soapUI. Qual é a resposta?
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.
As classes geradas:
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();
A partir do WSDL publicado gere as classes clientes do serviço EstoqueWS . Para tal:
Digite:
EstoqueWS.java
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 :
ItensPeloCodigoResponse resposta =
estoqueWS.itensPeloCodigo(parameter, "TOKEN123");
List<ItemEstoque> itensNoEstoque = resposta.getItemEstoque();
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.
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
/**
* @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);
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.
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.
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.
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.
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.
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
Acesse esse endereço e clique no link para fazer o download da especificação do serviço.
3. Para consumirmos esse novo serviço, vá ao Terminal e entre no diretório raiz do projeto fj36-
livraria , nesse diretório realize o wsimport para gerarmos o Stub e as classes necessárias para
acessarmos o serviço dos correios:
wsimport -s src/ -d build/ -p br.com.caelum.correios.soap
http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx?WSDL
Vá ao seu projeto no Eclipse, dê um F5 e veja as novas classes geradas pelo wsimport . Todas,
menos ConsumidorServicoCorreios foram geradas.
4. No projeto já tem uma classe preparada para encapsular a chamada do Web Service. Abra a classe
ConsumidorServicoCorreios e procure o método calculaFrete . Nele adicione:
CalcPrecoPrazoWSSoap servico =
new CalcPrecoPrazoWS().getCalcPrecoPrazoWSSoap();
5. Nessa variável servico , possuímos o método calcPrecoPrazo , que nos retorna um objeto
contendo os dados referentes à nossa pesquisa, como o valor, prazo de entrega e assim por diante.
No entanto, esse método recebe 15 parâmetros, o que fará com que a chamada ao método fique com
um código extenso. Para simplificar, já existem alguns atributos na classe.
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);
http://localhost:8088/fj36-livraria
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?
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.
http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx?WSDL
"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.
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).
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.
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.
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.
<livro>
<nome>RESTful Web Services</nome>
<preco>60.0</autor>
</livro>
JSON
{"livro": {
"nome":"RESTful Web Services"
"preco":"60.0"
}
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.
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.
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
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.
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.
Para trabalhar com a arquitetura REST foi criada uma especificação chamada JAX-RS (JSR-311).
Essa especificação garante diversas vantagens:
XML
Objeto Java
JSON
Texto puro
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.
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:
<servlet-mapping>
<servlet-name>RestJersey</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Com RestEasy temos uma configuração parecida, novamente só necessária para servlet containers
2.5 ou mais antigo:
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
A 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 {
//...
Neste caso, qualquer requisição que comece com /loja , será a classe LojaResource que cuidará.
Ainda não definimos nenhum recurso para nenhum método desta classe. Para isso, criamos um
método que retorna algum objeto Java, por exemplo Livro e nele anotamos a qual método HTTP ele
responderá.
@Path("/loja")
public class LojaResource {
@GET
public List<Livro> getLivros() {
//código omitido
return livros;
}
}
Para definir o restante da URI deste recurso, basta usarmos a mesma anotação @Path , agora para o
método.
@GET
@Path("/livros")
public List<Livro> getLivros() {
//código omitido
return livros;
}
Finalmente, temos que definir qual será o tipo de recurso devolvido, no geral temos que definir o
content-type da resposta HTTP. A anotação usada aqui é a @Produces , passando como parâmetro
qual o mime-type do retorno.
@GET
@Path("/livros")
@Produces("text/plain")
public List<Livro> getLivrosTexto() {
return getLista();
}
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{
//....
}
@Path("/loja")
public class LojaResource {
@GET
@Path("/livros")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Livro> getLivrosXmlOuJson() {
return //busca livros em algum lugar;
}
http://blog.caelum.com.br/2009/12/15/arquitetura-rest-com-java-jax-rs/
@GET
@Path("/livros/{id}")
@Produces(MediaType.APPLICATION_XML)
public Livro getLivro(@PathParam("id") Long id) {
//código omitido
}
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
}
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 {
...
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.
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
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.
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/
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);
}
http://localhost:8080/fj36-webservice/pagamentos/1
@Produces({MediaType.APPLICATION_XML})
por
@Produces({MediaType.APPLICATION_JSON})
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":[]}
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) {
//....
}
}
System.out.println(response.getStatusLine());
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.
@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.
repositorio.put(pagamento.getId(), 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();
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 ).
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ó):
{"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).
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"}
]
}
{"id":3,"status":"CONFIRMADO","valor":29.9,
"links":[
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.
@POST
@Consumes({MediaType.APPLICATION_JSON})
public Response criarPagamento(Transacao transacao)
throws URISyntaxException {
Pagamento pagamento = new Pagamento();
pagamento.setId(idPagamento++);
pagamento.setValor(transacao.getValor());
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:
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
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
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") .
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 {
@RestController
public class PagamentoController {
Uma vez definido como RestController podemos devolver um pagamento envolvendo-o usando a
classe Resource. Ela permitirá adicionarmos os próximos passos (links) na entidade.
Queremos que ao ser criado um novo pagamento, recebemos o recurso com o link de outra action
do nosso Controller. Como por exemplo, a do método cancelarPagamento.
@RestController
public class PagamentoController {
@RequestMapping(value="/pagamentos", method=RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,
produces=MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<Resource<Pagamento>> criarPagamento(
@RequestBody Pagamento pagamento) {
System.out.println("Pagamento "
+ pagamento.getId()
+ " criado com sucesso!");
resource.add(
linkTo(
methodOn(PagamentoController.class)
.cancelarPagamento(pagamento.getId()))
.withRel("cancelar"));
@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.
Como podemos ver, o XML gerado não possui a lista de links que podem ser utilizados para operar
nos recursos do servidor. Para mudarmos esse comportamento, precisamos configurar a biblioteca que é
utilizada pelo JAX-RS para gerar o XML dos objetos que são devolvidos pelo serviço, o JAX-B.
No JAX-B, por padrão, apenas atributos que possuem tanto getters quanto setters são mapeados para
o XML e, por isso, como a classe Pagamento não possui um setter para a lista de links, essa propriedade
não é colocada no xml gerado. Para mudar esse comportamento, precisamos configurar o JAX-B através
de anotações.
A anotação que informa o JAX-B que ele deve mapear atributos ao invés de getters e setters no
formulário gerado é a @XmlAccessorType :
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pagamento {
Depois dessa configuração, se testarmos novamente o serviço, veremos que o xml gerado contém a
lista de links do pagamento:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pagamento>
<id>2</id>
<status>CRIADO</status>
<valor>39.9</valor>
<links>
</links>
</pagamento>
Repare que a lista de links está vazia, isso acontece por que a classe link também não possui setters
para seus atributos, para corrigirmos o problema, utilizaremos novamente a anotação
@XmlAccessorType(XmlAccessType.FIELD) :
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 :
@XmlElement(name="estadoAtual")
private String status;
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Pagamento {
@XmlAttribute
private int id;
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();
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);
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.
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
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 ).
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.
return resposta;
}
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.
http://localhost:8080/fj36-webservice/pagamentos/{idPagamento}
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
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.
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:
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.
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
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();
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.
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
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.
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.
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á
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).
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.
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.
Use Messaging to transfer packets of data frequently, immediately, reliably, and asynchronously,
using customizable formats.
http://www.eaipatterns.com/Messaging.html
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
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.
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
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
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.
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);
É possível enviar diversos conteúdos em uma mensagem, como texto puro, objetos, mapas, streams,
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:
ConnectionFactory connectionFactory =
(ConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
Queue queue = (Queue) ic.lookup("jms/FILA.GERADOR");
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();
}
}
}
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);
context.start();
Quando não quisermos mais tratar mensagens, precisamos invocar o método stop do
JMSContext para pararmos de receber as mensagens.
http://blog.caelum.com.br/a-nova-api-do-jms-2-0-no-java-ee-7/
Create multiple Competing Consumers on a single channel so that the consumers can process
multiple messages concurrently.
http://www.eaipatterns.com/CompetingConsumers.html
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
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 .
6. Faça uma classe para registrar o tratador de mensagens na fila gerador, também dentro do pacote
br.com.caelum.jms :
ConnectionFactory factory =
(ConnectionFactory) ic.lookup("jms/RemoteConnectionFactory");
Queue queue = (Queue) ic.lookup("jms/FILA.GERADOR");
try(JMSContext context = factory.createContext("jms", "jms2")) {
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.
Para enviarmos uma mensagem tinhamos que abrir uma conexão para a fila de mensagens
utilizando a ConnectionFactory:
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.
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.
Além disso, para que as mensagens passem através da Connection , é necessário dar o start :
connection.start();
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.
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 :
}
}
}
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.
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 .
Clique na role guest que é exibida na tabela e em seguida clique no link Edit :
Depois de fazer essas modificações, execute novamente o programa. Dessa vez a criação da
assinatura durável deve acontecer sem problemas.
producer.send(topico, line);
}
scanner.close();
}
}
}
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 :
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.
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.
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
teclado.nextLine();
teclado.close();
context.close();
}
}
}
3. Modifique o código da classe EnviaMensagemParaOTopico e faça com que ela envie mensagens
com o cabeçalho formato=ebook :
producer.send(topico, line);
}
scanner.close();
}
}
}
destinationLookup: Define qual é o nome JNDI que deve ser utilizado para fazer o lookup da
fila/tópico que será utilizado.
subscriptionName: Nome que será utilizado para a criação da subscription no caso de utilizarmos
um durable subscription
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName = "destinationLookup",
propertyValue = "jms/TOPICO.LIVRARIA"),
@ActivationConfigProperty(propertyName = "destinationType",
propertyValue = "javax.jms.Topic"),
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();
}
}
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.
Mas com o Spring MVC, podemos utilizar a estrutura do próprio framework para fazer a injeção de
dependências de recursos disponibilizados pelo JNDI. Para isso, precisamos configurar o componente
do spring chamado JndiTemplate dentro do arquivo de configuração do spring, o spring-
context.xml :
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");
Também podemos configurar o lookup dos tópicos e filas que foram criados no HornetQ:
<bean id="topicoLivraria"
class="org.springframework.jndi.JndiObjectFactoryBean">
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;
}
@Qualifier("topicoLivraria")
@Autowired
private Topic topico;
<bean id="hornetQConnectionFactory"
class="org.springframework.jndi.JndiObjectFactoryBean">
<bean id="topicoLivraria"
class="org.springframework.jndi.JndiObjectFactoryBean">
2. Copie o JAR do cliente do Wildfly para a pasta WEB-INF/lib do seu projeto fj36-livraria dessa
forma:
Selecione o JAR jboss-client.jar, clique com o botão direito e escolha Copy (ou CTRL+C ).
@Autowired
private ConnectionFactory factory;
@Autowired
private Topic topico;
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.
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 mais informações sobre o shared subscriber, leia o post no blog da Oracle:
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.
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/
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).
Amazon SQS
IronIO/MQ
Oracle Messaging Cloud Service
entre outros
http://blog.caelum.com.br/mensageria-com-amazon-sqs/
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.
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!
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:
A resposta do HornetQ devolve o código 200, caso exista a fila, e alguns cabeçalhos personalizados.
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
Para criar uma nova mensagem basta então enviar um HTTP POST com os dados da mensagem no
corpo da requisição usando a URI definido no msg-create :
POST /queues/jms.queue.FILA.GERADOR/create
Host: example.com
Content-Type: application/xml
<pedido>
<data>2013-12-05</data>
<itens>
...
</itens>
</pedido>
Como resposta recebemos o código 201 e novamente um cabeçalho para criar a próxima mensagem:
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:
"Se nós não tivéssemos defeitos, não teríamos tanto prazer em notá-los nos outros." -- François de La
Rochefoucauld
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.
XML (eXtensible Markup Language) é um formato de texto com diversos usos e, em especial, pode
guardar dados, configurações e informações com metainformações hierarquizadas. Metainformação
nada mais é do que uma informação sobre a informação, assim como uma anotação permite adicionar
uma metainformação a classes, métodos e atributos. Por exemplo, se é necessário armazenar o título de
um livro, o formato XML permite que tanto o valor do campo quanto o nome do campo que guarda esse
valor sejam explicitamente definidos e agrupados.
<titulo>Arquitetura Java</titulo>
O formato XML permite também que dados compostos sejam armazenados de maneira hierárquica.
Por exemplo, suponha que seja necessário armazenar um livro com seu título e valor. Nesse caso, basta
encadear tags, como mostra o exemplo abaixo:
<livro>
<titulo>Arquitetura Java</titulo>
É 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.
Exemplo:
<nome>José</nome>
Possui fins gerais e extensíveis: Não temos limitação sobre o tipo de informação que podemos
guardar em arquivo XML, basta a aplicação que for ler o XML saber o que procurar. Há
diversos exemplos de XML que guardam informações (usados para troca de dados),
documentos (como arquivos do OpenOffice), configurações (veja a configuração do Tomcat) e
até imagens (por exemplo, arquivos *.svg).
Hierárquico: Um XML é uma estrutura intrinsecamente hierárquica, como uma árvore, com
campos dentro de outros campos.
Exemplo:
<empresa>
<nome>Pamonha de Piracicaba Ltda.</nome>
<cnpj>00.000.000/0001-00</cnpj>
<funcionarios>
<funcionario>
<nome>João da Silva</nome>
</funcionario>
<funcionario>
<nome>Maria da Silva</nome>
</funcionario>
</funcionarios>
</empresa>
LINGUAGEM DE MARCAÇÃO
Uma linguagem de marcação não necessariamente precisa ser delimitada por tags (\), ela pode
ser definida de qualquer maneira que demonstre onde um campo começa e onde ele termina:
João da Silva|39|Maria da Silva|37
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.
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
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{
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.
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:
@XmlRootElement
public class Livro {
// GETTERS E SETTERS
}
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 {
2. Crie um Java Bean para modelar livros no pacote br.com.caelum.jaxb e anote-o com
@XmlRootElement
public class Livro {
3. Faça uma classe para testar o processo de marshal de um livro no pacote br.com.caelum.jaxb.
public class TesteMarshal {
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.
Adicione um atributo na classe Livro, para associar os livros às categorias. Adicione também os
respectivos get e set .
@XmlRootElement
public class Livro {
//...
// 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);
(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 .
<xs:complexType name="livro">
<xs:sequence>
<xs:element name="valor" type="xs:decimal" minOccurs="0"/>
<xs:element name="titulo" type="xs:string" minOccurs="0"/>
<xs:element name="nomeAutor" type="xs:string" minOccurs="0"/>
<xs:element name="codigo" type="xs:string" minOccurs="0"/>
<xs:element name="categoria" type="categoria" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="categoria">
<xs:sequence>
<xs:element name="nome" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Um detalhe que normalmente pode dar um nó na cabeça é o fato dos Schemas, que servem para
definir XML, serem definidos em XML. Isso é possível porque há um Schema de Schemas. O leitor pode
notar que, na segunda linha do XML acima, há a indicação do Schema para definir outros Schemas.
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
Além dos tipos simples que já existem no XML e que podem ser utilizados dentro de um Schema, há
a possibilidade de definir novos tipos, que são os chamados tipos complexos. Um tipo complexo pode
ser baseado em outros tipos complexos e/ou em tipos simples. Vejamos um exemplo que define um tipo
complexo, chamado categoria:
<xs:complexType name="categoria">
<xs:sequence>
<xs:element name="nome" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
No Schema do tipo complexo categoria, está definido que toda categoria é composta de um elemento
chamado nome, do tipo simples string. Inclusive, é definida a quantidade mínima de vezes que o
elemento nome deve aparecer em uma categoria. Um outro exemplo de definição de um tipo complexo é
o Schema para os livros.
<xs:complexType name="livro">
<xs:sequence>
<xs:element name="valor" type="xs:decimal" minOccurs="0"/>
<xs:element name="titulo" type="xs:string" minOccurs="0"/>
<xs:element name="nomeAutor" type="xs:string" minOccurs="0"/>
<xs:element name="codigo" type="xs:string" minOccurs="0"/>
<xs:element name="categoria" type="categoria" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
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:
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.
Algumas customizações podem ser feitas no XSD gerado. Por exemplo, o nome de um tipo
complexo.
@XmlType(name="cat")
public class Categoria ...
A definição do namespace para os elementos que queremos criar deve ficar no XSD. Essa informação
é inserida com o uso do atributo targetNamespace. O targetNamespace associa um namespace com o
Schema que está sendo craido:
<xs:schema
version="1.0"
targetNamespace="http://www.caelum.com.br/fj36"
...
Para utilizar os elementos definidos nesse Schema, é necessário adicionar o namespace no XML por
meio do atributo xmlns.
<caelum:livro xmlns:caelum="http://www.caelum.com.br/fj36">
Perceba, inclusive, que foi dado um "apelido" para o namespace, com o intuito de não usar o nome
verdadeiro dele toda vez que um elemento desse namespace for utilizado (xmlns:caelum).
Novamente, o JAXB oferece uma maneira de definir um targetNamespace de um XSD. Para definir
um targetNamespace, é necessário criar um arquivo chamado package-info.java, com um conteúdo
parecido com o do código abaixo.
@XmlSchema(namespace="http://www.caelum.com.br/fj36")
package br.com.caelum.jaxb;
import javax.xml.bind.annotation.XmlSchema;
@javax.xml.bind.annotation.XmlSchema (
xmlns = {
@javax.xml.bind.annotation.XmlNs(prefix = "caelum",
namespaceURI="http://www.caelum.com.br/fj36"),
@javax.xml.bind.annotation.XmlNs(prefix="xs",
namespaceURI="http://www.w3.org/2001/XMLSchema")
}
)
package br.com.caelum.jaxb;
Agora, ao gerar o XSD com os exercícios feitos nas seções anteriores, um código parecido com o que
segue abaixo seria criado. Perceba a utilização do apelido para o targetNamespace (tns).
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0"
targetNamespace="http://www.caelum.com.br/fj36"
xmlns:tns="http://www.caelum.com.br/fj36"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="livro">
<xs:sequence>
<xs:complexType name="categoria">
<xs:sequence>
...
</xs:sequence>
</xs:complexType>
</xs:schema>
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.
Repare, quando geramos o cliente de um Webservice através do wsimport , essa fábrica foi
criada automaticamente e foi chamada de ObjectFactory .
3. (Opcional) Utilize a anotação @XmlType para mudar o nome do tipo complexo que representa a
Categoria .
@XmlType(name="CAT")
public class Categoria {
....
}
7. (Opcional) Alternativamente, existe o comando xjc para gerar classes Java a partir de um arquivo
XSD.
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.
Repare que criamos um novo tipo com o nome codigo . Trata-se de uma string de três
Dessa forma, o elemento codigo é obrigatório e se baseia nas restrições do tipo tns:codigo .
@Override
public void error(SAXParseException exception) throws SAXException {
System.out.println(exception.getMessage());
}
4. Crie uma nova classe TesteValidacao , no pacote br.com.caelum.jaxb , e gere o método main .
Faça a validação:
SchemaFactory sf = SchemaFactory
.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("schema.xsd"));
}
}
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.
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")));
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);
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);
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.
}
}
3. Execute o método main e verifique se o arquivo livro.json foi gerado na raíz do projeto (F5).
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
}
},
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);
https://github.com/fge/json-schema-validator
http://spacetelescope.github.io/understanding-json-schema/index.html
http://www.eaipatterns.com/DocumentMessage.html
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) {
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 .
Acesse a aplicação:
http://localhost:8088/fj36-livraria
Deixe os atributos seguintes como transient, usando a anotação @XmlTransient em cada atributo:
Vamos deixar o XML ainda mais expressivo. Repare que cada item é apresentado pela tag itens
(no plural). Isto é porque o atributo da coleção Java na classe Pedido possui este nome:
private Set<ItemCompra> itens;
Assim, será serializado um elemento <itens> e, para cada ItemCompra , um elemento <item> .
Reinicie o Tomcat e finalize uma compra. Verifique o console e perceba as diferenças no XML
gerado.
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.
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
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!
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.
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.
Com o context em mãos podemos adicionar uma nova rota. Uma rota é configurada usando o
RouteBuilder :
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");
Veremos mais para frente outros componentes para processar e transformar a mensagem.
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
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
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 ).
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.
@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.
context.start();
Thread.sleep(30 * 1000);
context.stop();
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 .
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
Para transformações mais complexas o Camel oferece o componente Velocity. Velocity é um template
engine bem parecido com o JSP. Aliás, JSP foi criado baseado nas ideias do Velocity. Como o JSP,
Velocity oferece uma linguagem de expressões que usamos dentro dos templates para gerar HTML,
JSON ou XML de maneira simples:
from("file:entrada?delay=5s").
log(LoggingLevel.INFO, "Processando mensagem ${id}").
transform(body(String.class).regexReplaceAll("nomeAutor", "autor")).
bean(TransformerBean.class, "transform").
setHeader("dataAtual",
constant(
new SimpleDateFormat("dd/MM/yyyy").format(new Date())
)
).
to("velocity:NotaFiscal.vm").
to("file:saida");
O template define o novo conteúdo aproveitando objetos já disponíveis para a expression language
como ${headers} e ${body} :
<!-- Arquivo NotaFiscal.vm-->
<nota data="${headers.dataAtual}">
${body}
</nota>
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
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.
@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");
}
}
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.
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");
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
from("file:entrada?delay=5s").
...
to("validator:file:xsd/pedido.xsd");
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:
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.
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.
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.
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.
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.
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
Se quisermos usar o JAX-B é preciso criar o contexto antes. Baseado no contexto JAX-B é criado um
DataFormat , aqui o JaxbDataFormat :
Uma vez criado podemos repassar o formato para os métodos unmarshal(..) e marshal(..) :
...
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.
http://camel.apache.org/data-format.html
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.
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()
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())
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
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.
XStream xstream =
new XStream(new JettisonMappedXmlDriver());
http://localhost:8088/fj36-livraria/loja/livros/mais-vendidos
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
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() {
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();
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.
new Scanner(System.in).nextLine();
ctx.stop();
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
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.
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.
Integrate applications by having them store their data in a single Shared Database.
http://www.eaipatterns.com/SharedDataBaseIntegration.html
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:
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();
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")
2. Vamos criar uma nova rota com o nome livros que grava os dados no banco.
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).
.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();
.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.
mysqlDs.setDatabaseName("fj36_camel");
mysqlDs.setServerName("localhost");
mysqlDs.setPort(3306);
mysqlDs.setUser("root");
mysqlDs.setPassword("");
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 .
http://camel.apache.org/spring.html
Para usar o Camel com Spring basta cadastrar o CamelContext no XML de configuração do Spring:
<camel:camelContext />
Como usamos o prefixo camel devemos registrar o namespace correto no início do XML do Spring:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
xmlns:camel="http://camel.apache.org/schema/spring"
xsi:schemaLocation="...
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">
<camel:camelContext />
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:
Uma vez configurado o Spring podemos deixar toda configuração da rota dentro do XML do Spring:
<camel:route>
<camel:from uri="file:entrada?delay=5s"/>
<camel:bean ref="validadorPedido" method="validar"/>
<camel:to uri="file:saida"/>
</camel:route>
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 .
<bean id="camelJMSConfig"
class="org.apache.camel.component.jms.JmsConfiguration">
Veja que essa é a mesma ConnectionFactory que foi configurada no capítulo sobre JMS. Além
disso, precisamos informar ao Camel como ele fará a busca pelos Destinations (Queue e Topic) do
JMS. Na busca dos destinations, o JmsComponent do Camel utiliza internamente o
DestinationResolver
do spring. Para buscarmos os destinations no JNDI, configuraremos o
JndiDestinationResolver informando qual é o JndiTemplate do spring que será utilizado na busca:
<bean id="camelDestinationResolver"
class="org.springframework.jms.support.destination.JndiDestinationResolver">
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);
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:
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.
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
3. Agora vamos configurar o JmsComponent que será utilizado pela aplicação da livraria. Dentro do
spring-context.xml , descomente o seguinte trecho de código:
<bean id="camelDestinationResolver"
class="org.springframework.jms.support.destination.JndiDestinationResolver">
<bean id="camelJMSConfig"
class="org.apache.camel.component.jms.JmsConfiguration">
<property name="connectionFactory" ref="hornetQConnectionFactory"/>
<property name="destinationResolver" ref="camelDestinationResolver"/>
</bean>
@Autowired
private CamelContext context;
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
http://localhost:8088/fj36-livraria
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.
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
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.
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
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
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:
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 .
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.
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
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:
Para enviar uma mensagem ao serviço de EstoqueWS basta enviar o XML abaixo:
<soapenv:Envelope ...>
<soapenv:Header>
<v1:tokenUsuario>TOKEN123</v1:tokenUsuario>
</soapenv:Header>
<soapenv:Body>
<v1:ItensPeloCodigo>
<codigo>ARQ</codigo>
</v1:ItensPeloCodigo>
</soapenv:Body>
</soapenv:Envelope>
Assim, podemos configurar a rota completa. Ela lê as mensagens SOAP de uma pasta e envia para o
serviço de estoque:
A resposta SOAP aparece no console pois logamos o resultado pelo método log(..) :
<soap:Envelope ...>
<soap:Body>
<ns2:ItensPeloCodigoResponse ...>
<ItemEstoque>
<codigo>ARQ</codigo>
<quantidade>2</quantidade>
</ItemEstoque>
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 {
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} ):
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")).
A chamada síncrona pode ser feita pelo ProducerTemplate que envia o corpo da mensagem para a
rota direct:start :
Como resposta recebemos a mensagem SOAP com a quantidade dos livros disponíveis.
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.
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("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.
if (primeiraMsg == null) {
return segundaMsg;
}
primeiraMsg.getIn().setBody(primeiroBody + segundoBody);
return primeiraMsg;
}
}
O problema, ainda, é que aceitamos qualquer mensagem para agregar. Pode ser que o Camel
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.
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");
http://camel.apache.org/aggregator2.html
2. No projeto fj36-livraria , crie um novo arquivo nota.vm na raíz da pasta src . Nele, adicione
o template de transformação:
<nota data="${headers.data}">
${body}
</nota>
3. Abra a classe ConfiguracaoCamel e adicione dentro do método configure() , mais uma rota:
from("direct:notas").
setHeader("data",
constant(new SimpleDateFormat("dd/MM/yyyy").format(new Date()))).
split().
http://localhost:8088/fj36-livraria
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.
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.
"Para obtermos êxito no mundo temos de parecer idiotas mas sermos espertos." -- Baron de Montesquieu
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]
Abaixo está uma lista de verificação para descobrir se um ESB realmente é preciso:
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/
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
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.
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:
Não é preciso alterar nenhuma outra configuração para criar o projeto. O AnypointStudio já vem
com o Mule ESB integrado.
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.
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.
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:
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.
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.
<flow name="fj36-estoque-proxyFlow1">
<http:listener config-ref="HTTP_Listener_Configuration"
path="/EstoqueWS" doc:name="EstoqueWS Listener"
allowedMethods="POST"/>
<cxf:proxy-service doc:name="CXF Proxy" payload="envelope"/>
<logger message="#[message.payloadAs(java.lang.String)]"
level="INFO" doc:name="Logger"/>
</flow>
</mule>
Podemos configurar o fluxo usando o XML que é a forma mais rápida, mas para tal é preciso
conhecer bem os componentes. A interface gráfica facilita a descoberta de componentes e dá uma
visão geral do fluxo.
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 //*
//*: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']
//*[local-name()='Envelope' and
namespace-uri()='http://schemas.xmlsoap.org/soap/envelope/']
Dessa maneira podemos verificar a existência de um namespace no XML. Falta agora tomar uma
decisão baseado no resultado. Para isso o Mule oferece o componente Choice que recebe a expressão
XPath e faz o roteamento do fluxo se for verdadeira.
<choice doc:name="Choice">
</when>
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']")]
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.
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.
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')]",
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:
Confirme o diálogo, adicione ainda o PATH como /v1/EstoqueWS e o método HTTP como POST
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).
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.
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
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 .
10. (opcional) Altere uma vez o namespace para uma versão inexistente, por exemplo:
http://caelum.com.br/estoquews/v3
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.
Mais para frente veremos mecanismos mais sofisticado para lidar com erros.
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.
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 .
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.9 EXERCÍCIOS OPCIONAL: APLICANDO XSLT PARA ADICIONAR NOVOS HEADERS SOAP 211
Apostila gerada especialmente para Walmir Bellani - wbellani@gmail.com
Ao fazer uma compra na livraria é gerado um XML que possui os dados de pagamentos e os itens de
compra. Nos capítulos anteriores vimos como usar JMS para enviar esse XML e consumir
assincronamente.
Nesse exercícios veremos como publicar um endpoint REST com Mule que pode ser utilizada pela
livraria para enviar o XML do pedido. Vamos criar um fluxo que trata o XML e se integra com outros
sistemas, como o sistema financeiro e o gerador de EBook. Além disso, vamos separar os dados do XML
e transformá-lo para JSON e SOAP, algo muito comum no mundo das integrações.
Novamente utilizaremos o SoapUI para testar e enviar o request HTTP com a diferença que o
request não usa SOAP e sim um POX (plain old xml) com os dados do pedido.
2. O primeiro passo nesse projeto é criar o endpoint HTTP. Dentro do AnypointStudio, na paleta de
componentes, procure o componente HTTP:
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.
O connector HTTP é configurado para receber requisições do tipo HTTP POST para
http://localhost:8082/fj36-pedidos .
Primeiro procure o componente Logger e arraste ao lado do Pedido Listener (dentro do process).
5. Agora vamos configurar a resposta. Procure na paleta pelo componente Set Payload e arraste-o
abaixo do Logger.
6. Ainda relacionado com a resposta, procure na palete pelo componente Property e arraste-o ao lado
do componente Set Payload (dentro do response):
7. Garanta que tudo foi salvo e rode o fluxo pelo AnypointStudio. Para isso, selecione o projeto fj36-
pedidos e depois Run As -> Mule Application
<http:listener-config name="HTTP_Listener_Configuration"
host="0.0.0.0" port="8082" doc:name="HTTP Listener Configuration"
basePath="/fj36-pedidos"/>
<flow name="fj36-pedidosFlow">
<logger message="#[message.payloadAs(java.lang.String)]"
level="INFO" doc:name="Logger"/>
<response>
<set-payload value="<resposta>ok</resposta>"
doc:name="Set Property"/>
<set-property propertyName="http.status" value="202"
doc:name="Property"/>
</response>
</flow>
8. Se o fluxo estiver na ar podemos testá-lo. Abra o SoapUI e crie um novo Rest Project com a URI:
http://localhost:8082/fj36-pedidos
9. Na janela de configuração do request, defina o método HTTP POST e altere o Media Type para
application/xml.
Observação: O XML utilizado é igual aos outros gerados pela livraria. Possui os dados do pagamento
e os itens comprados.
10. Submeta a resposta pelo SoapUI. Você deve receber uma resposta 202 com um
<resposta>ok</resposta> .
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.
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.
#[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.
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.
Vá ao SoapUI e envie novamente um request HTTP POST com o XML do pedido. A resposta deve
ser a mesma: <resposta>ok</resposta>
Depois verifique o console, além do XML do pedido deveria aparecer um JSON para cada item do
tipo ebook.
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.
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.
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 .
6. Procure na paleta pelo Flow Reference e arraste o componente para dentro do elemento Scatter-
Gather. No Flow Reference selecione o Flow Name financeiro_flow .
8. Vá ao SoapUI e envie novamente um request HTTP POST com o XML do pedido. A resposta deve
ser a mesma: <resposta>ok</resposta>
Depois verifique o console, além do XML do pedido deveria aparecer um JSON para cada item do
tipo ebook e a mensagem SOAP.
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.
@Override
public MuleEvent aggregate(AggregationContext context) throws MuleException {
//criado um payload
DefaultMuleEvent evento = new DefaultMuleEvent(
context.getOriginalEvent(),
context.getOriginalEvent().getFlowConstruct()
);
evento.getMessage().setPayload("<resposta>ok</resposta>");
return evento;
}
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.
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.
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.
Além disso, no elemento Nested Filter adicione um Core-Filter. Para isso, clique no botão +, selecione
Core-Filter, e a Schema_Validation:
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 .
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:
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.
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.
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.
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.
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:
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;
try {
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);
String accessToken = oauthRequest.getAccessToken();
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.
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;
9.3 EXERCÍCIO
1. Precisamos inicialmente colocar os jars para o servidor do apache Oltu no projeto fj36-
webservice .
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class TokenDao {
private List<String> accessTokens = new ArrayList<>();
@Inject
private TokenDao tokenDao;
@Inject
private HttpServletRequest request;
try {
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);
String accessToken = oauthRequest.getAccessToken();
if(tokenDao.existeAccessToken(accessToken)) {
// aqui fica o código de cadastro do pagamento
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.
Onde:
9.6 EXERCÍCIOS
1. Para facilitar a implementação do código que gerará o access token dentro do projeto fj36-
webservice , vamos importar um molde para as classes que implementam as servlets que utilizam as
classes do Apache Oltu:
Dentro do eclipse, clique com o botão direito no projeto fj36-webservice e escolha a opção
Import > Import... .
// outros métodos
}
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:
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.
O resultado devolvido pelo comando será um json contendo o access token parecido com o abaixo:
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();
Com o token obtido, podemos utilizar o cliente do JAX-RS para enviar uma requisição autenticada
pelo OAuth para o servidor de pagamentos.
Esse cabeçalho pode ser colocado na requisição utilizando-se o método header do cliente do JAX-
RS. Então a requisição para cadastrar um novo pagamento, por exemplo, ficaria da seguinte forma:
Client cliente = ClientBuilder.newClient();
Pagamento resposta = cliente.target("url do serviço")
.request()
@Component
@Scope("session")
public class AccessToken implements Serializable{
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:
// resto do código
}
@Autowired
private AccessToken accessToken;
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 .
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.
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
https://tools.ietf.org/html/rfc6749#page-45
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.
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).
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
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:
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
Quando o navegador receber o redirect com o authorization code, ele será novamente levado para a
Nesse endpoint, a extração do authorization code da requisição pode ser feita utilizando-se a classe
OAuthAuthzResponse :
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 :
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();
Assim como no Resource Owner Password Credential Grant, a geração do access token é feita
resp.setHeader("Content-type", "application/json");
resp.getWriter().print(oAuthResponse.getBody());
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.
oAuthResponse = builder.location(redirectURI)
.setCode(code)
.buildQueryMessage();
resp.sendRedirect(oAuthResponse.getLocationUri());
}
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:
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:
accessToken.setToken(token);
return "redirect:/carrinho/criarPagamento";
}
5. Para finalizar, precisamos programar o endpoint do Payfast que valida o authorization code e gera o
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.
Após validar as informações enviadas, o servidor de autorização pode gerar e devolver o access token
à aplicação cliente (livraria). Depois de receber o access token, a livraria simplesmente armazena o
token recebido na sessão do usuário e depois faz o redirecionamento para a lógica de criação de
pagamentos, que utilizará o token gerado para acessar o PagamentoResource do fj36-
webservice .
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.
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
Imagine que no payfast disponibilizamos um serviço em que o usuário pode associar um novo cartão
de crédito à sua conta. Para fazer a segurança desse serviço utilizamos o OAuth com o Authorization
Code Grant, então a livraria pode, por exemplo, associar o cartão de crédito à conta do usuário
utilizando esse serviço.
Agora imagine que um hacker quer roubar o cartão de crédito de um dos usuários da livraria, nesse
caso, ele poderia utilizar um cliente http para adquirir um authorization code para sua conta. Depois de
adquirir o código de autorização para sua própria conta, o hacker pode então fazer com que o navegador
de algum usuário da livraria troque o authorization code por um access token, para isso, ele poderia por
exemplo publicar um link ou uma imagem em uma página bastante visitada com a seguinte url:
http://<url da livraria>/oauth/code/returnURL
?code=<auth code>&client_id=<client_id>&client_secret=<client_secret>
Essa é a URL em que a aplicação da livraria troca o authorization code por um access token e associa
o token gerado com a sessão do usuário que fez a requisição. Dessa forma, o access token gravado na
sessão de um usuário autêntico fica associado com a conta do payfast de um hacker, quando o usuário
tentar fazer o próximo pagamento e, consequentemente associar um novo cartão à conta do payfast, esse
cartão será associado a conta do hacker.
Esse ataque é o Cross site request forgery (CSRF) adaptado para o mundo dos serviços. Para
protegermos nossos serviços contra o CSRF, podemos utilizar um outro mecanismo definido através de
uma extensão do OAuth 2.0: o state.
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.
}
}
@Inject
private TokenDao dao;
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:
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);
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);
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:
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 {
@Provider
@ProtegidoPorOAuth
public class OAuthFilter implements ContainerRequestFilter {
@Inject
private HttpServletRequest request;
@Inject
private TokenDao dao;
Response unauthorized =
try {
OAuthAccessResourceRequest oauthRequest =
new OAuthAccessResourceRequest(request);
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.
Google
Facebook
Twitter
Github
LinkedIn
Yahoo
entre outros...
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.
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() :
Com o access token em mãos podemos enviar um request para acessar a conta do usuário, utilizando
o novamente a classe OAuthService :
http://tinyurl.com/osozn2v
https://github.com/settings/applications
2. Para simplificar a comunicação da nossa aplicação com Github usaremos a biblioteca Scribe-Java.
A classe é responsável por configurar duas URI_s: a primeira para obter o _access_token e a segunda
para efetuar o login.
@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
@Controller
@RequestMapping("/oauth")
public class OAuthController {
@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) {
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());
redirectAttributes.addFlashAttribute("responseBody", response.getBody());
return "redirect:github-logado";
}
@RequestMapping("/github-logado")
public String logado() {
return "github-logado";
}
}
<br>
AccessToken (Authorization:Bearer): ${accessToken}
<br><br>
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).
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 prover uma nova API REST, além da implementação, há outras duas preocupações comuns:
como modelar e documentar a API?
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.
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)
Pra começar, devemos definir algumas informações iniciais, como a versão do Swagger que estamos
usando:
swagger: '2.0'
host: localhost:8080
basePath: /fj36-webservice/v1
schemes:
- http
- https
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 ).
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
Podemos fazer duas coisas com o nosso pagamento: para confirmá-lo, devemos enviar um PUT para
/pagamentos/1 ; para cancelá-lo, enviamos um DELETE.
/pagamentos/{id}:
put:
summary: Confirma um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'
delete:
summary: Cancela um pagamento
parameters:
- $ref: '#/parameters/pagamento-id'
Depois da criação do pagamento, deve ser retornado um response com o status 201 (Created)
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.
@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);
}
}
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...
}
http://localhost:8080/fj36-webservice/v1/swagger.json
http://localhost:8080/fj36-webservice/v1/swagger.yaml
O JSON e o YAML gerados pelo Swagger a partir do nosso recurso PagamentoResource usam a
linguagem OpenAPI para detalhar URIs, Content-Types, métodos HTTP aceitos, códigos de resposta e
modelos de dados.
---
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"
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",
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"
@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",
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.
Depois de extrair, basta copiar o conteúdo da pasta dist para uma pasta doc no WebContent do
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.
url = "../v1/swagger.json";
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 ).
2. Vamos habilitar o uso do Swagger no nosso projeto. Para isso, temos que inserir algumas
configurações na classe PagamentoService :
@ApplicationPath("/v1")
public class PagamentoService extends Application {
public PagamentoService() {
BeanConfig conf = new BeanConfig();
conf.setTitle("Payfast API");
conf.setDescription("Pagamentos rápidos");
conf.setVersion("1.0.0");
conf.setHost("localhost:8080");
conf.setBasePath("/fj36-webservice/v1");
conf.setSchemes(new String[] { "http" });
conf.setResourcePackage("br.com.caelum.payfast.rest");
conf.setScan(true);
}
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> resources = new HashSet<>();
resources.add(PagamentoResource.class);
//classes do swagger...
resources.add(ApiListingResource.class);
resources.add(SwaggerSerializers.class);
return resources;
}
}
@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",
@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...
}
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.
http://localhost:8080/fj36-webservice/doc/