UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas Aplicações distribuídas em Java Parte I: RPC e Messaging 4 1 argonavis.com.br Objetivos •Explorar as APIs do Java que implementam RPC, troca de mensagens e serviços de nomes •Java Remote Method Invocation (Java RMI) •Java RMI sobre IIOP •Java Naming and Directory Interface (JNDI) •Java Message Service (JMS) •JAX-RPC (Web Services) •Este capítulo ilustra as principais tecnologias de sistemas distribuídos aplicadas a Java argonavis.com.br © 2003 Helder L. S. da Rocha 2 4-1 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Resumo: objetos distribuídos em Java Interface de programação • Java RMI sobre JRMP • Protocolo Java nativo (Java Remote Method Protocol) - Java-to-Java • Serviço de nomes não-hierárquico e centralizado • Modelo de programação Java: interfaces Java OMG IDL Java IDL (CORBA) RMI-IIOP IIOP RMI-JRMP JRMP • Java IDL: mapeamento OMG IDL-Java • Protocolo OMG IIOP (Internet Inter-ORB Interface de comunicação Protocol) independente de plataforma/linguagem em rede • Serviço de nomes (COS Naming) hierárquico, distribuído e transparente quanto à localização dos objetos • Modelo de programação CORBA: OMG IDL (language-neutral) • Java RMI sobre IIOP • Protocolo OMG IIOP, COS Naming, transparência de localidade, etc. • Modelo de programação Java com geração automática de OMG IDL argonavis.com.br 3 Como criar uma aplicação RMI 1. Criar subinterface de java.rmi.Remote para cada objeto remoto • Interface deve declarar todos os métodos visíveis remotamente • Todos os métodos devem declarar java.rmi.RemoteException 2. Implementar e compilar os objetos remotos • Criar classes que implementem a interface criada em (1) e estendam java.rmi.server.UnicastRemoteObject (ou Activatable*) • Todos os métodos (inclusive construtor) provocam RemoteException 3. Gerar os stubs e skeletons • Rodar a ferramenta rmic sobre a classe de cada objeto remoto 4. Implementar a aplicação servidora • Registrar os objetos remotos no RMIRegistry usando Naming.bind() • Definir um SecurityManager (obrigatório p/ download de cód. remoto) 5. Implementar o cliente • Definir um SecurityManager e conectar-se ao codebase do servidor • Recuperar os objetos remotos usando (Tipo)Naming.lookup() 6. Compilar servidor e cliente argonavis.com.br © 2003 Helder L. S. da Rocha 4 4-2 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Construção de aplicação RMI extends java.rmi.Remote Hello.java interface Hello extends Remote{ void sayHello() throws RemoteException; 1 } java.rmi.server.UnicastRemoteObject implements extends 5 4 HelloClient.java HelloServer.java Hello h =(Hello) HelloImpl rf = Naming.lookup("hello"); new HelloImpl(); h.sayHello(); Naming.bind(rf, "hello"); 2 HelloImpl.java ... void sayHello throws RemoteException { System.out.println("Hello"); } HelloImpl_Skel HelloImpl_Stub Stub Skel 3 6 HelloClient lookup() Java compiler RMIRegistry RMIC 2 bind() HelloServer HelloImpl HelloImpl_Stub argonavis.com.br JRMP HelloImpl_Skel (obj remoto) 5 Interface Remote •Interface de comunicação entre cliente e servidor •Stub e objeto remoto implementam esta interface de forma que cliente e esqueleto enxergam a mesma fachada •Declara os métodos que serão visíveis remotamente •Todos devem declarar provocar RemoteException package hello.rmi; import java.rmi.*; public interface HelloRemote extends Remote { public String sayHello() throws RemoteException ; public void sayThis(String toSay) throws RemoteException; } argonavis.com.br © 2003 Helder L. S. da Rocha 6 4-3 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Implementação do objeto remoto package hello.rmi; Principal classe base para objetos remotos RMI (outra opção é Activatable*) import java.rmi.*; public class HelloImpl extends java.rmi.server.UnicastRemoteObject implements HelloRemote { private String message = "Hello, World!"; public HelloImpl() throws RemoteException { } public String sayHello() throws RemoteException { return message; } public void sayThis(String toSay) throws RemoteException { message = toSay; } Construtor declara que pode provocar RemoteException! } * javax.rmi.activatable.Activatable (modelo de implementação não abordado neste curso) argonavis.com.br 7 Geração de Stubs e Skeletons •Tendo-se a implementação de um objeto remoto, pode-se gerar os stubs e esqueletos > rmic hello.rmi.HelloImpl •As classes são automaticamente compiladas (e as fontes são descartadas) •Resultados •_HelloImpl_Stub.class •_HelloImpl_Skel.class •Para preservar (e visualizar) o código-fonte gerado (.java) use a opção -keep: > rmic -keep hello.rmi.HelloImpl argonavis.com.br © 2003 Helder L. S. da Rocha 8 4-4 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Servidor e rmi.policy package hello.rmi; import java.rmi.*; import javax.naming.*; SecurityManager viabiliza download de código Associando o objeto com public class HelloServer { um nome no RmiRegistry public static void main (String[] args) { if (System.getSecurityManager() == null) System.setSecurityManager(new RMISecurityManager()); try { HelloRemote hello = new HelloImpl(); Naming.rebind("hellormi", hello); System.out.println("Remote object bound to 'hellormi'."); } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException)e; System.out.println("" + e); } } } Arquivo de politicas de segurança para uso pelo SecurityManager rmi.policy grant { permission java.net.SocketPermission "*:1024-65535", "connect, accept, resolve"; permission java.net.SocketPermission "*:80", "connect, accept, resolve"; permission java.util.PropertyPermission "*", "read, write"; }; argonavis.com.br 9 Cliente package hello.rmi; import java.rmi.*; import javax.naming.*; public class HelloClient { public static void main (String[] args) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { HelloRemote hello = (HelloRemote) Naming.lookup("hellormi"); System.out.println(hello.sayHello()); hello.sayThis("Goodbye, Fred!"); System.out.println(hello.sayHello()); } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException)e; System.out.println("" + e); } Obtenção do objeto com associado a"hellormi" no serviço de nomes Chamadas de métodos remotos } } argonavis.com.br © 2003 Helder L. S. da Rocha 10 4-5 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Execução • Inicie o RMIRegistry O diretório onde você instalou os exemplos > rmiregistry & • Rode o servidor > java hello.rmi.HelloServer -Djava.rmi.server.codebase=file:///webmidia/exemplos/build/ -Djava.security.policy=../lib/rmi.policy Remote object bound to 'hellormi'. • Rode o cliente > java hello.rmiop.HelloClient -Djava.rmi.server.codebase=file:///webmidia/exemplos/build/ -Djava.security.policy=../lib/rmi.policy Hello, World! Goodbye! • Você pode rodar esta aplicação no usando o Ant. Mude para o subdiretório* exemplos/ e monte uma aplicação semelhante a esta com > ant buildrmi • Depois, em janelas diferentes, inicie os servidores e cliente digitando > ant runrmiserver > ant runrmiclient * informe antes no arquivo build.properties o caminho completo para este diretório argonavis.com.br 11 Necessidade de RMI-IIOP • Queremos programar em Java • Não queremos aprender outra linguagem (IDL) • Não queremos escrever linhas e linhas de código para transformar, registrar e localizar objetos CORBA (queremos transparência) • Queremos comunicação IIOP por causa de • integração com objetos em outras linguagens • vantagens do modelo ORB (transparência de localidade, escalabilidade, serviços, etc.) • comunicação com clientes escritos em outras linguagens • Solução: RMI sobre IIOP • Principal benefício: comunicação com mundo CORBA • Desvantagens: limitações de CORBA • Não faz download de bytecode • Não faz Distributed Garbage Collection - DGC argonavis.com.br © 2003 Helder L. S. da Rocha 12 4-6 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Como criar uma aplicação RMI-IIOP 1. Criar subinterface de java.rmi.Remote para cada objeto remoto • Interface deve declarar todos os métodos visíveis remotamente • Todos os métodos devem declarar java.rmi.RemoteException 2. Implementar e compilar os objetos remotos • Criar classes que implementem a interface criada em (1) e estendam javax.rmi.PortableRemoteObject • Todos os métodos (inclusive construtor) provocam RemoteException 3. Gerar os stubs e skeletons (e opcionalmente, os IDLs) • Rodar a ferramenta rmic -iiop sobre a classe de cada objeto remoto 4. Implementar a aplicação servidora • Registrar os objetos remotos no COSNaming usando JNDI • Definir um SecurityManager 5. Implementar o cliente • Definir um SecurityManager e conectar-se ao codebase do servidor • Recuperar objetos usando JNDI e PortableRemoteObject.narrow() 6. Compilar 13 argonavis.com.br Construção de aplicação RMI-IIOP Hello.java interface Hello extends Remote{ void sayHello() throws RemoteException; 1 } java.rmi.Remote javax.rmi.PortableRemoteObject implements 2 HelloImpl.java 5 4 HelloClient.java Hello h =(Hello) ctx.lookup("hello"); h.sayHello(); HelloServer.java HelloImpl rf = new HelloImpl(); ctx.bind(rf, "hello"); ... void sayHello throws RemoteException { System.out.println("Hello"); } HelloImpl_Tie _HelloImpl_Stub Stub Skel 3 HelloClient 6 Java compiler lookup() JNDI (CosNaming) argonavis.com.br © 2003 Helder L. S. da Rocha 2 bind() IIOP RMIC HelloServer ORB ORB _HelloImpl_Stub IDL HelloImpl_Tie HelloImpl (obj remoto) 14 4-7 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Interface Remote •Declara os métodos que serão visíveis remotamente •Todos devem declarar provocar RemoteException •Indêntica à interface usada em RMI sobre JRMP package hello.rmiop; import java.rmi.*; public interface HelloRemote extends Remote { public String sayHello() throws RemoteException ; public void sayThis(String toSay) throws RemoteException; } 15 argonavis.com.br Implementação do objeto remoto package hello.rmiop; Classe base para todos os objetos remotos RMI-IIOP import java.rmi.*; public class HelloImpl extends javax.rmi.PortableRemoteObject implements HelloRemote { private String message = "Hello, World!"; public HelloImpl() throws RemoteException { } public String sayHello() throws RemoteException { return message; } public void sayThis(String toSay) throws RemoteException { message = toSay; } Construtor declara que pode provocar RemoteException! } argonavis.com.br © 2003 Helder L. S. da Rocha 16 4-8 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Geração de Stubs e Skeletons •Tendo-se a implementação de um objeto remoto, pode-se gerar os stubs e esqueletos > rmic -iiop hello.rmiop.HelloImpl •Resultados • _HelloRemote_Stub.class • _HelloImpl_Tie.class - este é o esqueleto! •Para gerar, opcionalmente, uma (ou mais) interface IDL compatível use a opção -idl > rmic -iiop -idl hello.rmiop.HelloImpl •Resultado HelloRemote.idl argonavis.com.br #include "orb.idl" Tipos definidos em orb.idl module hello { (equivalem a wstring) module rmiop { interface HelloRemote { ::CORBA::WStringValue sayHello( ); void sayThis(in ::CORBA::WStringValue arg0 ); }; }; }; 17 JNDI • RMI-IIOP usa JNDI como interface para o serviço de nomes CORBA (COS Naming) • Java Naming and Directory Interface é uma ponte sobre os diversos serviços de nomes e diretórios diferentes • Vantagens •Isola a aplicação dos detalhes específicos do protocolo •Pode ser usada para ler objetos Java (serializados) que estejam armazenados em um diretório •Pode combinar diferentes tipos de diretório (federação) e tratá-los como um diretório único • Componentes • API - Application Programming Interface • SPI - Service Provider Interface que permite que novos serviços sejam plugados transparentemente argonavis.com.br © 2003 Helder L. S. da Rocha 18 4-9 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Arquitetura JNDI Aplicação Java JNDI API Pacote javax.naming Naming Manager JNDI Service Provider Interface (SPI) NDS SP FileSystem SP LDAP SP DNS SP RMI SP NDS Sistema de Arquivos LDAP DNS RMI Registry Corba SP WinReg SP COS Windows Naming Registry Service Providers Sistemas de Nomes e Diretórios 19 Fonte: argonavis.com.br JNDI Tutorial Contexto inicial • Precisa ser obtido antes de qualquer operação. Passos: • 1: selecionar o provedor de serviços Properties env = new Properties(); env.put(Context.INITIAL_CONTEXT_FACTORY, "classe.do.ProvedorDeServicos"); • 2: configurar o acesso ao serviço env.put(Context.PROVIDER_URL, "ldap://xyz.com:389"); env.put(Context.OUTRA_PROPRIEDADE, "valor"); (...) • 3: criar um objeto para representar o contexto Context ctx = new InitialContext(env); • A configuração (1, 2) pode ser feita via propriedades do sistema • Passadas em linha de comando via argumento -Dprop=valor • Carregados via arquivos de propriedades • Principais propriedades java.naming.factory.initial: java.naming.provider.url: argonavis.com.br © 2003 Helder L. S. da Rocha Context.INITIAL_CONTEXT_FACTORY Context.PROVIDER_URL 20 4 - 10 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Arquivo jndi.properties • Uma outra forma de definir propriedades usadas pelos clientes JNDI, é através de um arquivo jndi.properties • Um arquivo com este nome deve ser colocado no CLASSPATH da aplicação cliente • Ao inicializar o ambiente com InitialContext(), não passe nenhum parâmetro no construtor. O class loader irá procurar um arquivo jndi.properties no CLASSPATH e carregará as propriedades que estiverem definidas dentro dele • Por exemplo, um arquivo pode conter java.naming.factory.initial=\ com.sun.jndi.fscontext.RefFSContextFactory java.naming.provider.url=file:/cap02/lab/filesys • Para inicializar o sistema com essas propriedades, use Context ctx = new InitialContext(); 21 argonavis.com.br Recuperação de objetos (lookup) • Para obter a referência para um objeto de um contexto usa-se o método lookup() • Para usar o objeto retornado é preciso conhecer o seu tipo e fazer o cast (ou narrow, se objeto remoto) para promover a referência • Se o objeto for um contexto, lookup() age como um método para mudar de contexto (como o chdir, em Unix) • Exemplo • O método lookup() usando com o provedor de serviço fscontext retorna um java.io.File pelo nome de arquivo Serviço de nomes para sistema de arquivos env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory"); env.put(Context.PROVIDER_URL, Diretório raiz do serviço "file:/cap02/lab/filesys"); Context ctx = new InitialContext(env); File f = (File)ctx.lookup("report.txt"); argonavis.com.br © 2003 Helder L. S. da Rocha Arquivo localizado na raiz do serviço 22 4 - 11 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Provedores de serviços para objetos •Pode-se usar JNDI para mapear nomes a objetos •Objetos localizáveis por nome podem ser abstraídos do contexto ou até linguagem em que são usados •Aplicações diferentes podem compartilhar objetos •Dois drivers JNDI estão disponíveis para acesso a objetos distribuídos no J2SDK •SPI CORBA (COS Naming): permite localização de objetos CORBA (serializados em um formato independende de linguagem) - usado em RMI-IIOP (EJB) com.sun.jndi.cosnaming.CNCtxFactory •SPI RMI: permite a localização de objetos Java serializados (objetos pode ser usados por outras aplicações Java) com.sun.jndi.rmi.registry.RegistryContextFactory 23 argonavis.com.br Objetos RMI • Pode-se usar JNDI para acesso a Java RMI. Para isto é preciso antes configurar o contexto inicial com suas propriedades • Propriedades a definir java.naming.factory.initial ou Context.INITIAL_CONTEXT_FACTORY com.sun.jndi.rmi.registry.RegistryContextFactory java.naming.provider.url ou Context.PROVIDER_URL rmi://nome_do_host:1099 (endereço e porta do RMI Registry) • Como fazer mapeamento Context ctx = new InitialContext(); Fruit fruit = new Fruit("orange"); ctx.bind("favorite", fruit); • Como localizar Context ctx = new InitialContext(); Fruit fruit = (Fruit) ctx.lookup("favorite"); argonavis.com.br © 2003 Helder L. S. da Rocha 24 4 - 12 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Objetos CORBA (RMI sobre IIOP) • Usado em RMI-IIOP. Proprieades a definir java.naming.factory.initial ou Context.INITIAL_CONTEXT_FACTORY com.sun.jndi.cosnaming.CNCtxFactory java.naming.provider.url ou Context.PROVIDER_URL iiop://nome_do_host:1900 (endereço e porta do ORB) • Mapeamento Context ctx = new InitialContext(); Fruit fruit = new Fruit("orange"); ctx.bind("favorite", fruit); • Localização Converte objeto CORBA em objeto Java Context ctx = new InitialContext(); Object corbaFruit = ctx.lookup("favorite"); Object javaFruit = javax.rmi.PortableRemoteObject.narrow(corbaFruit, Fruit.class); Fruit fruit = (Fruit)javaFruit; 25 argonavis.com.br Servidor e rmi.policy package hello.rmiop; import java.rmi.*; import javax.naming.*; SecurityManager viabiliza download de código public class HelloServer { public static void main (String[] args) { if (System.getSecurityManager() == null) System.setSecurityManager(new RMISecurityManager()); try { HelloRemote hello = new HelloImpl(); Context initCtx = new InitialContext(); initCtx.rebind("hellormiop", hello); System.out.println("Remote object bound to 'hellormiop'."); } catch (Exception e) { if (e instanceof RuntimeException) Associando o objeto com throw (RuntimeException)e; System.out.println("" + e); um nome no serviço de nomes } } } Arquivo de politicas de segurança para uso pelo SecurityManager rmi.policy grant { permission java.net.SocketPermission "*:1024-65535", "connect, accept, resolve"; permission java.net.SocketPermission "*:80", "connect, accept, resolve"; permission java.util.PropertyPermission "*", "read, write"; }; argonavis.com.br 26 © 2003 Helder L. S. da Rocha 4 - 13 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Cliente package hello.rmiop; import java.rmi.*; import javax.naming.*; Obtenção do objeto com associado a"hellormiop" no contexto inicial do serviço de nomes public class HelloClient { public static void main (String[] args) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { Context initCtx = new InitialContext(); Object obj = initCtx.lookup("hellormiop"); HelloRemote hello = (HelloRemote) javax.rmi.PortableRemoteObject.narrow(obj, hello.HelloRemote.class); System.out.println(hello.sayHello()); hello.sayThis("Goodbye, Helder!"); System.out.println(hello.sayHello()); } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException)e; Não basta fazer cast! O objeto retornado System.out.println("" + e); é um objeto CORBA (e não Java) cuja raiz } } } argonavis.com.br não é java.lang.Object mas org.omg.CORBA.Object narrow transforma a referência no tipo correto 27 Execução • Inicie o ORB > orbd -ORBInitialPort 1900 -ORBInitialHost localhost & • Rode o servidor O diretório onde você instalou os exemplos > java hello.rmiop.HelloServer -Djava.rmi.server.codebase=file:///webmidia/exemplos/build/ -Djava.security.policy=../lib/rmi.policy -cp ../lib Remote object bound to 'hellormiop'. • Rode o cliente > java hello.rmiop.HelloClient <mesmas propriedades do servidor> Hello, World! Goodbye! • Alternativas para reduzir a quantidade de parâmetros de execução • Defina as propridades no código ou crie um arquivo rmi.properties (melhor!) • Use um script shell, .bat ou o Ant • Ant: no subdiretório* exemplos/, monte aplicação semelhante a esta com > ant buildrmiop • Depois, em janelas diferentes, inicie os servidores e cliente digitando “ant orbd", "ant runrmiopserver" e "ant runrmiopclient" * informe antes no arquivo build.properties o caminho completo para este diretório argonavis.com.br © 2003 Helder L. S. da Rocha 28 4 - 14 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Java Message Service •Interface Java única para unir as MOMs incompatíveis •API que permite que aplicações criem, enviem, recebam e leiam mensagens através de um MOM •API consiste principalmente de interfaces (implementadas pelo fabricante do MOM) •Parte integral da plataforma J2EE (acrescenta possibilidade de comunicação assíncrona a EJBs) 29 argonavis.com.br Arquitetura JMS Cliente JMS Cliente JMS Mensagens: objetos que Mensagem passam informações Mensagem entre clientes JMS Clientes: produzem e consomem mensagens JNDI lookup() JMS API Destinos tipo Canal Destinos tipo Fila Destinos tipo Canal Provedor JMS oferece: - Sistema de mensagens - Serviços administrativos - Sistemas de controle argonavis.com.br © 2003 Helder L. S. da Rocha Ferramentas de administração do provedor Espaço de nomes JNDI Destinos tipo Fila Fábricas de Conexões JNDI bind() Toda a comunicação com o provedor JMS é realizada através de JNDI e da API JMS Objetos gerenciados acessíveis via JNDI Fábricas de Conexões 30 4 - 15 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Domínio ptp (ponto-a-ponto) •Baseado no conceito de filas, remetentes e destinatários •Um ou muitos para um: cada mensagem é enviada para uma fila específica e é consumida por um destinatário (que pode ou não estar disponível no momento) •Destinatário confirma que a mensagem foi recebida e processada corretamente (acknowledgement) •Filas retém mensagens até que sejam QueueReceiver r; consumidas (ou expirem) Provedor JMS Message m = Remetente s m1 Consome Destinatário r Confirma Recebimento m4 Envia argonavis.com.br r.receive(); FIFO QueueSender s; s.send(m1); ... s.send(m5); m5 Fila (queue) Message m5 31 Domínio pub/sub (publica/inscreve) • Baseado em canais (tópicos) • Muitos para muitos: mensagens são enviadas a um canal onde todos os assinantes do canal podem retirá-la Canal Fila TopicPublisher p; p.publish(m1); ... p.publish(m5); m2 m3 TopicSubscriber s1; MessageListener k = new MessageListener() { public void onMessage(Message m){...} }; s1.setMessageListener(k); Inscreve-se em Tópico m1 Fornece Assinante s1 s1.onMessage(m1); s2.setMessageListener(k); m4 m5 Publicador p Publica argonavis.com.br © 2003 Helder L. S. da Rocha Inscreve-se em Tópico m1 Provedor JMS Fornece Assinante s2 s2.onMessage(m1); 32 4 - 16 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Consumo síncrono e assíncrono •Mensagens podem ser retiradas do destino através de uma chamada receive() •Esta é a forma síncrona de retirar mensagens •Cliente bloqueia enquanto mensagem não chega •Mensagens também podem ser enviadas ao destinatário quando chegarem •Esta é a forma assíncrona de retirar mensagens •Para ser notificado, destinatário precisa ser implementado como um ouvinte e cadastrado para receber notificações •Nos exemplos anteriores, o modelo P2P foi implementado com destinatário síncrono e o modelo publish-subscribe com destinatário assíncrono 33 argonavis.com.br Desenvolvimento de aplicações JMS • Para escrever aplicações que enviam ou recebem mensagens é preciso realizar uma série de etapas • Obter um destino e uma fábrica de conexões via JNDI • Usar a fábrica para obter uma conexão • Usar a conexão para obter uma ou mais sessões • Usar a sessão para criar uma mensagem • Iniciar a sessão • Depois, pode-se • Enviar mensagens • Receber mensagens • Cadastrar ouvintes para receber mensagens automaticamente Fonte da ilustração: JMS Tutorial argonavis.com.br © 2003 Helder L. S. da Rocha Message Producer Connection Factory ctx.lookup(nome) createConnection() Connection createSession() Session create() Message Consumer create() receive() send() createMessage() Message Destination Destination ctx.lookup(nome) 34 4 - 17 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Interfaces: dois domínios • É preciso escolher um domínio • Para cada domínio há um destino, fábrica de conexões, conexão, sessão, produtor e consumidor • Interfaces são semelhantes Destination Objetos gerenciados ConnectionFactory MessageProducer Topic TopicConnectionFactory TopicPublisher Queue QueueConnectionFactory QueueSender Connection Session MessageConsumer TopicConnection TopicSession TopicSubscriber QueueConnection QueueSession QueueReceiver ?Domínio pub/sub ?Domínio ptp argonavis.com.br 35 Há dois tipos de destinos JMS • Filas (Queue) • Retêm todas as mensagens que recebem até que sejam retiradas ou expirem • Para cada mensagem enviada, apenas um cliente pode retirá-la Queue fila = (Queue) ctx.lookup("jms/Queue"); • Canais (Topic) • Cada canal pode ter vários clientes assinantes • Cada assinante recebe uma cópia das mensagens enviadas • Para receber uma mensagem publicada em um canal, clientes precisam já ser assinantes dele antes do envio. • Em canais "duráveis", assinantes não precisam estar ativos no momento do envio. Retornando, receberão mensagens nao lidas. Topic canal = (Topic) ctx.lookup("jms/Topic"); argonavis.com.br © 2003 Helder L. S. da Rocha 36 4 - 18 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Fábricas de conexões • Antes que se possa • enviar uma mensagem para uma fila, • publicar uma mensagem em um canal, • consumir uma mensagem de uma fila ou • fazer uma assinatura de um canal é preciso obter uma conexão ao provedor JMS • Isto é feito através de uma fábrica de conexões. Há duas: • TopicConnectionFactory - para conexões no domínio Topic • QueueConnectionFactory - para conexões no domínio Queue • É preciso conhecer o nome JNDI String nomeJRI = "TopicConnectionFactory"; //default J2EE-RI String nomeJBoss = "ConnectionFactory"; // default JBossMQ Precisa ser definido (geralmente Context ctx = new InitialContext(); arquivo jndi.properties no CPATH) TopicConnectionFactory factory = (TopicConnectionFactory) ctx.lookup(nomeJBoss); 37 argonavis.com.br Conexões •Encapsulam uma conexão virtual com o provedor JMS •Suportam múltiplas sessões (threads) •Uma vez obtida uma fábrica de conexões, pode-se obter uma conexão QueueConnection queueCon = queueConnectionFactory.createQueueConnection(); TopicConnection topicCon = topicConnectionFactory.createTopicConnection(); •Quando a conexão terminar, é preciso fechá-la •O fechamento fecha todas as seções, produtores e consumidores associados à conexão queueCon.close(); topicCon.close(); argonavis.com.br © 2003 Helder L. S. da Rocha 38 4 - 19 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Sessões • Contexto onde se produz e se consome mensagens • Criam produtores, consumidores e mensagens • Processam a execução de ouvintes • Single-threaded • Podem ser configuradas para definir • forma de acknowledgement • uso ou não de transações (tratar uma série de envios/recebimentos como unidade atômica e controlá-la via commit e rollback) • Exemplos Sem transações Confirmação automática após recebimento correto TopicSession topicSession = topicCon.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); Sessão controlada por transações QueueSession queueSession = queueCon.createQueueSession(true, 0); Tratamento de confirmações não especificado 39 argonavis.com.br Produtores de mensagens • Objeto criado pela sessão e usado para enviar mensagens para um destino • QueueSender: domínio ponto-a-ponto • TopicPublisher: domínio pub/sub QueueSender sender = queueSession.createSender(fila); TopicPublisher publisher = topicSession.createPublisher(canal); • Uma vez criado o produtor, ele pode ser usado para enviar mensagens Durante o envio cliente pode definir qualidade do serviço como modo (persistente ou sender.send( message ); não), prioridade (sobrepõe • publish() no domínio pub/sub comportamento FIFO) e publisher.publish( message ); tempo de vida (TTL) • send() no domínio ponto-a-ponto argonavis.com.br © 2003 Helder L. S. da Rocha 40 4 - 20 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Consumidores de mensagens • Objeto criado pela sessão e usado para receber mensagens • QueueReceiver e QueueBrowser: domínio ponto-a-ponto • TopicSubscriber: domínio pub/sub QueueReceiver receiver = queueSession.createReceiver(fila); TopicSubscriber subscriber = topicSession.createSubscriber(canal); • É preciso iniciar a conexão antes de começar a consumir: topicCon.start(); • Depois, pode consumir mensagens de forma síncrona (método é o mesmo para domínios PTP e pub/sub Message queueMsg = receiver.receive(); Message topicMsg = subscriber.receive(1000); • Para consumir mensagens de forma assíncrona é preciso criar um MessageListener argonavis.com.br 41 MessageListener • Event handler que detecta o recebimento de mensagens • Para usar, implemente MessageListener e seu método onMessage(): public class MyListener implements MessageListener { public void onMessage(Message msg) { TextMessage txtMsg = (TextMessage) msg; System.out.println( "Mensagem recebida: " + txtMsg.getText() ) } } • Método onMessage() não deve deixar escapar exceções • Código acima deve estar em um bloco try-catch • Para que objeto seja notificado, é preciso registrá-lo em um QueueReceiver ou TopicSubscriber subscriber.setMessageListener( new MyListener() ); topicCon.start(); // iniciar a conexão argonavis.com.br © 2003 Helder L. S. da Rocha 42 4 - 21 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Mensagens •Mensagens são compostas de três partes •Propriedades (opcionais): pares nome/valor (nomes e valores arbitrários definidos pela aplicação); contém tipos primitivos Java (int, boolean, etc.) ou String. •Cabeçalhos: propriedades com nomes e tipos de valores padrão definidos na especificação JMS •Corpo: conteúdo que não pode ser representado através de propriedades •Os tipos de mensagem correspondem a formatos de dados armazenados no corpo de mensagem •Texto, objetos serializados, bytes, valores primitivos, etc. •Mensagens são criadas a partir de uma Session 43 argonavis.com.br Seis tipos de mensagens • Message • Mensagem genérica sem corpo (contendo apenas cabeçalho e possíveis propriedades) • TextMessage • Objeto do tipo String (ex: conteúdo XML) • MapMessage • Conjunto de pares nome/valor onde nomes são Strings e valores são tipos primitivos • BytesMessage • Stream de bytes não interpretados • StreamMessage • Seqüência de tipos primitivos Java • ObjectMessage Message TextMessage MapMessage BytesMessage StreamMessage ObjectMessage • Objeto Java serializado argonavis.com.br © 2003 Helder L. S. da Rocha 44 4 - 22 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Criação de mensagens • Para cada tipo de mensagem, Session fornece método create() • createMessage(), createTextMessage(), createBytesMessage(), createObjectMessage(), createMapMessage(), createStreamMessage() TextMessage message = queueSession.createTextMessage(); message.setText(msg_text); // msg_text é String sender.send(message); • Após receber uma mensagem, via receive() ou onMessage(), é preciso fazer o cast para ter acesso aos métodos específicos de cada tipo de mensagem Message m = receiver.receive(); if (m instanceof TextMessage) { TextMessage message = (TextMessage) m; System.out.println("Recebido: " + message.getText()); } 45 argonavis.com.br Exemplos de aplicações •Os exemplos são do capítulo 4 do JMS Tutorial (Sun) •As versões em exemplos/jms foram alteradas •Todas estão nos pacotes jmstut.topic ou jmstut.queue •Há alvos prontos no Ant. Rode-os em duas ou três janelas e mude a ordem (rode o produtor antes e depois inverta) •Aplicação ponto-a-ponto (rode em janelas diferentes) > ant create-messages-queue > ant receive-messages-queue (leitura síncrona) •Aplicação pub/sub > ant create-messages-topic > ant receive-messages-topic (leitura assíncrona) argonavis.com.br © 2003 Helder L. S. da Rocha 46 4 - 23 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I JAX-RPC (Web Services) • Implementação de SOAP-RPC em Java •SOAP: protocolo de comunicação baseado em XML que usa HTTP ou SMTP como transporte •SOAP: padrão da indústria - permite a comunicação entre plataformas diferentes (ex: J2EE e .NET) • Parte integrante do Java Web Services Development Pack • Principais componentes do JWSDP •API SOAP-RPC: javax.xml.rpc.* e javax.xml.soap •API SOAP Messaging (JAXM): javax.xml.messaging •Registro UDDI: javax.xml.registry •Servlet que funciona como servidor UDDI 2 •Servlets que controlam serviços JAX-RPC e JAXM 47 argonavis.com.br WSDL •Web Service Description Language •É um documento XML que descreve o serviço •É possível criar um serviço a partir de um WSDL. •É possível criar um cliente a partir de um WSDL •Exemplo de WSDL <description> <types> ... XML Schema ... </types> <message name="..."> ... </message> <message name="..."> ... </message> ... <portType name="..."> ... </portType> <binding ...> ...</binding> <service name="..."> ... </description> argonavis.com.br © 2003 Helder L. S. da Rocha </service> 48 4 - 24 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Mapeamento WSDL •WSDL serve apenas para descrever interfaces •Não serve para ser executada •Nenhuma aplicação precisa da WSDL (não faz parte da implementação - é só descrição de interface) •WSDL pode ser mapeada a linguagens (binding) •Mapeamento: tipos de dados, estruturas, etc. •Compiladores SOAP geram código de cliente e servidor a partir de documentos WSDL (stubs para o cliente, handlers para o servidor) •Pode-se gerar a parte do cliente em uma plataforma (ex: .NET) e a parte do servidor em outra (ex: J2EE), viabilizando a comunicação entre arquiteturas diferentes. 49 argonavis.com.br Do WSDL a um Web Service 1 É preciso implementar ... Hello.wsdl <operation name="sayHello"> ... wsdl to java É preciso implementar wsdl to C# 3 5 HelloClient.java Hello h =(Hello) ctx.lookup("hello"); h.sayHello(); 2 Stub Stub Stubs Java compiler HelloClient lookup() Stub Stub Handlers 6 UDDI HelloImpl.cpp void sayHello { print("Hello"); } C++ compiler & linker register() Servlet WSDL stub argonavis.com.br © 2003 Helder L. S. da Rocha SOAP HelloImpl handler (serv remoto) 50 4 - 25 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Compilação do WSDL (JWSDP 1.0) ? O WSDL pode ser compilado para gerar todas as classes necessárias (cliente e servidor) ou apenas um dos lados > xrpcc -client hello.wdsl > xrpcc -server hello.wsdl <?xml <?xml version="1.0" version="1.0" encoding="UTF-8"?> encoding="UTF-8"?> (só classes essenciais para o cliente) (só classes essenciais para o servidor) ? Ex: Classes geradas no cliente <definitions <definitions name="HelloService" name="HelloService" ... ... >> <types/> <types/> <message <message name="HelloIF_sayHello"> name="HelloIF_sayHello"> <part <part name="String_1" name="String_1" type="xsd:string"/> type="xsd:string"/> </message> </message> <message <message name="HelloIF_sayHelloResponse"> name="HelloIF_sayHelloResponse"> <part <part name="result" name="result" type="xsd:string"/> type="xsd:string"/> </message> </message> <portType <portType name="HelloIF"> name="HelloIF"> <operation <operation name="sayHello" name="sayHello" ... ... >> <input <input message="tns:HelloIF_sayHello"/> message="tns:HelloIF_sayHello"/> <output message="tns:HelloIF_sayHelloResponse"/> <output message="tns:HelloIF_sayHelloResponse"/> </operation> </operation> </portType> </portType> <binding <binding ...> ...> ...</binding> ...</binding> example/ service/ HelloIFStub.class HelloIF.class HelloIF_Impl.class HelloService.class HelloService_Impl.class HelloService_SerializerRegistry.class Hello_sayHello_RequestStruct.class Hello_sayHello_ResponseStruct.class Hello_sayHello_RequestStruct _SOAPSerializer.class Hello_sayHello_ResponseStruct _SOAPSerializer.class <service <service name="HelloService"> name="HelloService"> <port <port name="HelloIFPort" name="HelloIFPort" ...> ...> <soap:address <soap:address location="http://localhost:8080/jaxrpc-hello/hellopoint/HelloIF"/> location="http://localhost:8080/jaxrpc-hello/hellopoint/HelloIF"/> </port> </port> </service> </service> </definitions> hello.wsdl </definitions> argonavis.com.br 51 Criação de um Web Service com JAX-RPC 1. Escrever uma interface RMI para o serviço package package example.service; example.service; public interface public interface BookstoreIF BookstoreIF extends extends java.rmi.Remote java.rmi.Remote {{ public public BigDecimal BigDecimal getPrice(String getPrice(String isbn) isbn) throws throws java.rmi.RemoteException; java.rmi.RemoteException; }} 2. Implementar a interface package package example.service; example.service; public public class class BookstoreImpl BookstoreImpl implements implements BookstoreIF BookstoreIF {{ private private BookstoreDB BookstoreDB database database == DB.getInstance(); DB.getInstance(); public BigDecimal getPrice(String public BigDecimal getPrice(String isbn) isbn) {{ return database.selectPrice(isbn); return database.selectPrice(isbn); }} }} argonavis.com.br © 2003 Helder L. S. da Rocha 52 4 - 26 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Criação de um Web Service com JAX-RPC 3. Escrever arquivo de configuração* <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <service name="BookstoreService" targetNamespace="http://mybooks.org/wsdl" typeNamespace="http://mybooks.org/types" packageName="example.service"> <interface name="example.service.BookstoreIF" servantName="example.service.BookstoreImpl"/> </service> </configuration> config_rmi.xml 4. Compilar classes e interfaces RMI > javac -d mydir BookstoreIF.java BookstoreImpl.java 5. Gerar código do servidor gendir/ > xrpcc -classpath mydir -server -keep -d gendir config_rmi.xml * Não faz parte da especificação - procedimento pode mudar no futuro argonavis.com.br 53 Criação de um Web Service com JAX-RPC •6. Criar web deployment descriptor web.xml <web-app> <servlet> Nosso <servlet-name>JAXRPCEndpoint</servlet-name> "container" <servlet-class> com.sun.xml.rpc.server.http.JAXRPCServlet </servlet-class> <init-param> <param-name>configuration.file</param-name> <param-value> /WEB-INF/BookstoreService_Config.properties </param-value> </init-param> <load-on-startup>0</load-on-startup> Nome do arquivo </servlet> <servlet-mapping> gerado pelo xrpcc <servlet-name>JAXRPCEndpoint</servlet-name> <url-pattern>/bookpoint/*</url-pattern> </servlet-mapping> subcontexto que será </web-app> o endpoint do serviço argonavis.com.br © 2003 Helder L. S. da Rocha 54 4 - 27 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Criação de um Web Service com JAX-RPC •7. Colocar tudo em um WAR 8. Deployment no servidor Copiar arquivo para diretório webapps do Tomcat webapps/ jaxrpc-bookstore.war argonavis.com.br 55 Construção e instalação do serviço com o Ant •Script do Ant para compilar as classes RMI, compilálas com xrpcc, gerar o WSDL, empacotar no WAR e copiar para o diretório webapps/ do Tomcat > ant BUILD.ALL.and.DEPLOY •Teste para saber se o serviço está no ar •Inicie o Tomcat do JWSDP •Acesse: http://localhost:8080/jaxrpc-bookstore/bookpoint argonavis.com.br © 2003 Helder L. S. da Rocha 56 4 - 28 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Clientes JAX-RPC Cliente de implementação estática + performance, + acoplamento 2. Chama o serviço Service endpoint stub (1) WSDL Clientes de implementação dinâmica proxy (3) 1. Obtém informações sobre o serviço dynamic (2) - performance, - acoplamento argonavis.com.br 57 Conclusão (sobre soluções Java) • Use RMI sobre JRMP se • Sua aplicação é 100% Java e sempre vai ser 100% Java • Você não precisa de um sistema de nomes distribuído e hierárquico • Os seus objetos nunca mudam de servidor • Quiser usar recursos exclusivos do RMI (activation, DGC, download) • Use RMI sobre IIOP se você • Prefere o modelo de desenvolvimento Java RMI • Quer usufruir da escalabilidade e interoperabilidade do CORBA • Use Java IDL se você • Já tem um sistema especificado através de IDLs e precisa criar clientes ou servidores que se comuniquem via IIOP • Use JMS (Messaging) para • Serviços assíncronos e pouco acoplados • Use JAX-RPC se você • Precisa de integração dinâmica com outras plataformas (ex: .NET) argonavis.com.br © 2003 Helder L. S. da Rocha 58 4 - 29 UNIFACS Pós-graduação em Sistemas e Aplicações Web Plataformas para Aplicações Distribuídas 4 - Aplicações Distribuídas em Java: Parte I Fontes [1] Sun Microsystems. RMI-IIOP Tutorial. http://java.sun.com/j2se/1.4/docs/guide/rmi-iiop/ Ponto de partida para tutoriais e especificações sobre RMI-IIOP (documentação do J2SDK 1.4) [2] Ed Roman et al. Mastering EJB 2, Appendixes A and B: RMI-IIOP, JNDI and Java IDL http://www.theserverside.com/books/masteringEJB/index.jsp [3] Jim Farley. Java Distributed Computing. O'Reilly and Associates, 1998. Esta é a primeira edição Contém um breve e objetivo tutorial sobre os três assuntos (procure a segunda). Compara várias estratégias de computação distribuída em Java. Helder da Rocha, Análise Comparativa de Dempenho entre Tecnologias Distribuídas Java. UFPB, 1999, Tese de Mestrado. [5] Qusay H. Mahmoud Distributed Java Programming with RMI and CORBA http://developer.java.sun.com/developer/technicalArticles/RMI/rmi_corba/ Sun, Maio 2002. Artigo que [4] compara RMI com CORBA e desenvolve uma aplicação que transfere arquivos remotos em RMI e CORBA [6] David Flanagan et al. Java Enterprise in a Nutshell, 2nd. edition. Abril 2002 (O'Reilly). Oferece tutoriais sobre as tecnologias Java para ambientes distribuídos, incluindo RMI, RMI-IIOP e Java IDL Kim Haase. The JMS Tutorial. Sun Microsystems, 2002 http://java.sun.com/products/jms/tutorial/ [8] Sun Microsystems. The Java Message Service Specification [7] http://www.javasoft.com/products/jms/docs.html [9] JSR-101 Expert Group. Java™ API for XML-based RPC: JAX-RPC 1.0 Specification. Java Community Process: www.jcp.org. [10] Sun Microsystems. Java™ Web Services Tutorial. java.sun.com/webservices/. Coleção de tutoriais sobre XML, JSP, servlets, Tomcat, SOAP, JAX-RPC, JAXM, etc. argonavis.com.br 59 Plataformas para Aplicações Distribuídas Versão 1.0 © 2003, Helder da Rocha www.argonavis.com.br argonavis.com.br © 2003 Helder L. S. da Rocha 60 4 - 30