Chat JMS com ActiveMQ

Algum tempo atrás postei no blog um tutorial sobre como criar um chat muilti usuários com a utilização da classe Socket e o uso de threads, agora irei mostrar uma outra forma de criar um chat multi usuários, sem a utilização de Sockets e nem de threads e sim utilizando a API JMS.

Java Message Service, ou simplesmente JMS, é uma API da linguagem Java para middleware orientado a mensagens. atraves da API JMS, uma ou mais aplicações podem se comunicar trocando mensagens atraves de um provider. O provider é um servidor que gerencia e armazena essas mensagens.

Vários servidores Java como, JOnASGlassFishJBoss , entre outros, possuem suporte a JMS. Mas existe também alguns servidores JMS que nao estão vinculados a um servidor web, entre eles temos o JoramOpenJMS e o ActiveMQ, o qual sera o foco deste artigo para a criação do chat atraves do conceito de topicos.

1. Arquitetura JMS

A arquitetura JMS disponibiliza dois tipos de troca de mensagens, o Queue (modelo ponto a ponto ou modelo de filas) e o modelo Topic ou Publish/Subscribe.

Com o modelo de troca de mensagens por filas, poderíamos ter um ou vários produtores enviando mensagens para uma mesma fila, mas teríamos apenas um único consumidor. Neste modelo, cada vez que uma mensagem é consumida, ela deixa de existir na fila, por isso, podemos ter apenas um único consumidor.

O modelo Topic, permite uma situação em que teríamos de um a vários consumidores para o mesmo topico. Funciona como um sistema de assinaturas, cada cliente assina um topico e a mensagem fica armazenada no servidor até todos assinantes a tenham consumido. Este sera o modelo usado neste artigo.

2. Elementos da API JMS

  • Cliente JMS: Uma aplicação ou objeto Java que produz (Programas Java que criam e enviam mensagens) e que consome (Programas Java que recebem mensagens) as mensagens. As mensagens são objetos que contém os dados que são transferidos entre os clientes JMS. Essas transferências são feitas a partir de um Provedor JMS.
  • conexao JMS: A partir do momento que uma factory é obtida, conexões para o provedor JMS podem ser criadas. Uma conexao representa a ligação entre a aplicação cliente e a aplicação servidora. Dependendo do tipo da conexao, ela permitirá que os clientes criem sessões para o envio e recebimento de mensagens de filas ou topicos.
  • Factory de conexões JMS: É um objeto administrado que a aplicação cliente utiliza para a criação de conexões para o provedor JMS. Normalmente o cliente obtém as factories atraves de interfaces portável, desta forma, mesmo se as configurações do provedor JMS mudar, o código do cliente permanece inalterado. Os administradores mantêm as configurações em objetos (objetos da classe factory), que são obtidos atraves de buscas (lookup) na JNDI. Dependendo do tipo da mensagem, o cliente obterá uma factory para topico ou para fila.
  • Destino: É um objeto administrado que encapsula a identidade do destino das mensagens, que é para onde as mensagens são enviadas e consumidas, são uma fila ou um topico. O administrador JMS cria estes objetos, e os usuários os obtém atraves de buscas na JNDI. Da mesma forma que as factories de conexões, o administrador pode criar dois tipos de classe de destino, fila e topico. O destino recebe um nome, que é pelo qual o produtor e o consumidor se comunicam com o provedor.
  • Consumidor: Um objeto criado atraves de uma sessao JMS. Ele recebe mensagens de um destino. O consumidor pode receber mensagens de maneira síncrona ou assíncrona de filas ou topicos.
  • Produtor: Um objeto criado atraves de uma sessao JMS. Ele envia mensagens para um destino.
  • Mensagens: São objetos enviados entre consumidores e produtores de mensagens. Eles contêm um objeto que encapsula os dados que serão trafegados pelas mensagens. As mensagens são pacotes independentes de dados de negócios. Uma mensagem possui três partes principais:

    • Cabeçalho (Contém informações de roteamento de rede e identificadores de mensagens), propriedades (Contém metadados para a mensagem. O JMS dita algumas das propriedades, mas programadores também podem incluir suas próprias propriedades) e carga útil (Contém os verdadeiros dados do negócio. A carga útil é totalmente controlada pelo programador).
    • Corpo da mensagem pode conter seis tipos de mensagens: TextMessage (padrão para textos e xml), ObjectMessageByteMessageMapMessageStreamMessage. Todos esses tipos são subtipos da interface javax.jms.Message, mensagem genérica sem corpo, que contém apenas informações como cabeçalho e propriedades.
    • A interface de uma mensagem é extremamente flexível e permite várias formas de customização de conteúdo.
  • Provedor JMS: Representa uma interface para um software de middleware orientado à mensagens. Ele suporta a interface JMS que é especificada pela Sun MicroSystems, recentemente adquirida pela Oracle. Ele é basicamente um adaptador de um middleware.

Tipos de Mensagens JMS

3. Apache ActiveMQ

O ActiveMQ é um provedor open source(licence Apache 2.0). O ActiveMQ pode ser utilizado por linguagens como Java, .NET, C/C++, Delphi, Perl, entre outras, atraves do chamado “Cross Language Clients”.

Para obter o ActiveMQ devemos acessar o endereço http://activemq.apache.org/activemq-542-release.html e fazer o download da versão apache-activemq-5.4.2-bin.zip. apos baixar o arquivo, vamos descompactá-lo no diretório “c:\”. Para nosso projeto vamos precisar adicionar a biblioteca activemq-all-5.4.2.jar, encontrada no diretório raiz do pacote.

Para ativar o ActiveMQ, entre no diretório bin e execute o arquivo activemq.bat. nao é necessária nenhuma alteração em configuração do ActiveMQ para executar o exemplo deste artigo, você deve ter apenas o Java nas variáveis de ambiente do Windows.

Você pode fazer um teste pelo browser, procure no log lançado no console por uma linha tipo esta: INFO | ActiveMQ Console at http://0.0.0.0:8161/admin. O importante aqui é o número da porta, se a sua nao for 8161, nao tem problema algum. Digite no browser este endereço e substitua os zeros por localhost, ficando assim: http://localhost:8161/admin, e caso sua porta seja outra, também substitua o número dela. Você deverá ter uma página como a da figura 2.

Painel de Administração ActiveMQ

Painel de Administração ActiveMQ

4. Entendendo o JMS

Para escrever aplicações que enviam ou recebem mensagens é preciso realizar uma serie de etapas:

  1. Obter um destino e uma fábrica de conexões via JNDI
  2. Usar a fábrica para obter uma conexao
  3. Usar a conexao para obter uma ou mais sessões
  4. Usar a sessao para criar uma mensagem
  5. Iniciar a sessao
  6. Enviar e/ou receber mensagens
  7. Cadastrar ouvintes para receber mensagens

Diagrama JMS

Diagrama JMS

Um topico JMS pode ser de dois tipos, durável e nao durável. Quando um topico é configurado como durável o assinante nao precisa estar conectado no momento em que o produtor envia a mensagem, ela permanece no provedor até que o assinante (consumidor) esteja ativo e faça o consumo dela. O topico nao durável o assinante deve estar ativo para receber a mensagem, caso nao esteja a mensagem nao sera entregue.

Cada topico pode ter vários assinantes, e para um assinante receber as mensagens enviadas para o topico, eles já devem ser assinantes antes da mensagem ser enviada. Isto significa que se dois assinantes receberem a mensagem “Java com JMS”, e um terceiro assinante se inscrever no topico apos o envio desta mensagem, este terceiro assinante nao irá receber a mensagem.

O contexto utilizado para a conexao JMS é feito atraves de uma conexao JNDI que pode ser efetuada programaticamente ou por um arquivo de configurações do tipo properties. Os elementos desta configuração são o endereço e a porta do provedor, o nome da fabrica de conexao, o nome do topico e a classe JNDI de criação do objeto do contexto. Essa classe nao é uma classe padrão para qualquer provedor, cada um possui a sua própria classe como pode diferenciar também a maneira de configurar as demais propriedades.

5. Configurando um topico

Antes de tudo precisamos configurar um topico no provedor, para isso, inicie o provedor como mostrado na sessao 3 e acesse a url do administrador.

Quando a página abrir, como a figura 2, clique em Topics e no campo TopicName insira topicChat e clique em create. Você verá em seguida na tabela Topics, que sera criado um topico com este nome.

Feito isto, vamos criar o arquivo jndi.properties para depois adicioná-lo no classpath da aplicação, conforme listagem 1.

Listagem 1. Arquivo jndi.properties
#classe jndi para o ActiveMQ
java.naming.factory.initial = org.apache.activemq.jndi.ActiveMQInitialContextFactory
#url para conexao com o provedor
java.naming.provider.url = tcp://localhost:61616
#nome da conexao
connectionFactoryNames = TopicCF
#nome do topico jms
topic.topicChat = topicChat

6.  Classe Chat

Crie a classe Chat conforme a listagem 1. nao se esqueça de adicionar ao projeto a biblioteca activemq-all-5.4.2.jar, ela contém todas as classes necessárias para a criação dos serviços JMS.

Os comentários estão linha a linha no código, para indicar o que cada linha faz durante o processo de criação do chat.

Listagem 2. Classe de conexao com banco de dados
package br.mb.tutorialActiveMQ.chat;

import javax.jms.JMSException;
import javax.jms.TopicConnection;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicConnectionFactory;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;
import javax.jms.Message;
import javax.jms.TextMessage;
import javax.jms.MessageListener;
import javax.naming.InitialContext;
import java.util.Scanner;

public class Chat implements MessageListener {
    private TopicSession pubSession;
    private TopicPublisher publisher;
    private TopicConnection connection;
    private String username;

    /* Construtor usado para inicializar o cliente JMS do Chat */
    public Chat(String topicFactory, String topicName, String username) throws Exception {
        // Obtem os dados da conexao JNDI atraves 
		// do arquivo jndi.properties
        InitialContext ctx = new InitialContext();
        // O cliente utiliza o TopicConnectionFactory 
		// para criar um objeto do tipo
		// TopicConnection com o provedor JMS
        TopicConnectionFactory conFactory = (TopicConnectionFactory) ctx.lookup(topicFactory);
        // Utiliza o TopicConnectionFactory para criar 
		//a conexao com o provedor JMS
        TopicConnection connection = conFactory.createTopicConnection();
        // Utiliza o TopicConnection para criar a sessao para o produtor
        // Atributo false -> uso ou nao de transacoes (tratar uma serie de
		// envios/recebimentos como unidade atomica e controla-la via commit e rollback)
        // Atributo AUTO_ACKNOWLEDGE -> Exige confirmacao automatica apos recebimento correto
        TopicSession pubSession = 
			connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
			
        // Utiliza o TopicConnection para criar a sessao para o consumidor
        TopicSession subSession = 
			connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
			
        // Pesquisa o destino do topico via JNDI
        Topic chatTopic = (Topic) ctx.lookup(topicName);
        // Cria o topico JMS do produtor das mensagens 
		// atraves da sessao e o nome do topico
        TopicPublisher publisher = 
			pubSession.createPublisher(chatTopic);
			
        // Cria (Assina) o topico JMS do consumidor das 
		// mensagens atraves da sessao e o nome do topico
        TopicSubscriber subscriber = 
			subSession.createSubscriber(chatTopic);
        // Escuta o topico para receber as mensagens 
		// atraves do metodo onMessage()
        subscriber.setMessageListener(this);
        // Inicializa as variaveis do Chat
        this.connection = connection;
        this.pubSession = pubSession;
        this.publisher = publisher;
        this.username = username;
        // Inicia a conexao JMS, permite que 
		// mensagens sejam entregues
        connection.start();
    }

    // Recebe as mensagens do topico assinado
    public void onMessage(Message message) {
        try {
            // As mensagens criadas como TextMessage 
			// e devem ser recebidas como TextMessage
            TextMessage textMessage = (TextMessage) message;
            System.out.println(textMessage.getText());
        } catch (JMSException jmse) {
            jmse.printStackTrace();
        }
    }

    // Cria a mensagem de texto e a publica no topico. Evento referente ao
	produtor
    public void writeMessage(String text) throws JMSException {
        // Recebe um objeto da sessao para criar 
		// uma mensagem do tipo TextMessage
        TextMessage message = pubSession.createTextMessage();
        // Seta no objeto a mensagem que sera enviada
        message.setText(username + ": " + text);
        // Publica a mensagem no topico
        publisher.publish(message);
    }

    // Fecha a conexao JMS
    public void close() throws JMSException {
        connection.close();
    }

    // Roda o Chat
    public static void main(String[] args) {
        try {
            //Habilita o envio de mensagens por linha de comando
            Scanner commandLine = new Scanner(System.in);

            System.out.print("Digite seu nome: ");
            String name = commandLine.nextLine();
            // Faz uma chamada ao construtor da classe para iniciar o chat
            Chat chat = new Chat("TopicCF", "topicChat", name);
            // Depois da conexao criada, 
			// faz um loop para enviar mensagens
            while (true) {
                //captura a mensagem digitada no console
                String s = commandLine.nextLine();
                //para sair digite exit
                if (s.equalsIgnoreCase("exit")) {
                    //fecha a conexao com o provedor
                    chat.close();
                    //sai do sistema
                    System.exit(0);
                } else {
                    //envia a mensagem digitada no
					// console para o metodo writeMessage que vai publica-la
                    chat.writeMessage(s);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

nao se esqueça de adicionar no projeto, dentro do diretório src (root dos fontes), o arquivo jndi.properties para ser possível a conexao com o provedor. Quando quiser para o provedor utilize “ctrl+c” e na próxima pergunta confirme.

Conclusão

Este artigo exemplificou como instalar e configurar um provedor do tipo ActiveMQ utilizando a API JMS. Foi criado um chat com os recursos do tipo Topic, onde a mesma aplicação envia e recebe mensagens. O exemplo do chat foi retirado da referência O’REILLY com algumas poucas modificações.

Referências

download project 

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.