Uma das coisas interessantes de se fazer com JMS, é a invocação síncrona. Por mais que pareça estranho, em muitas ocasiões é bastante interessante e razoável, que um cliente de uma fila possa obter a resposta de sua requisição, de alguma maneira.
Uma das maneiras mais comuns de se fazer isto, é utilizando uma classe da própria especificação chamada QueueRequestor, que facilita bastante a criação de um Request/Response utilizando as filas como meio. O que a classe faz, é basicamente criar uma fila temporária e adicionar seu nome no header replyTo do próprio JMS, na mensagem a ser enviada. Desta maneira, quem consome esta mensagem, sabe que após porcessá-la deve enviar uma resposta na fila especificada.
Um código bem simples para realizar esta tarefa, poderia ser por exemplo:
QueueConnection c = (QueueConnectionFactory) (new
InitialContext().lookup("qcf"));
QueueSession s = c.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
Queue q = (Queue) (new InitialContext().lookup("requestqueue"));
QueueRequestor r = new QueueRequestor(s, q);
Message reply = r.request(s.createTextMessage("1233442342"));
c.close();
Neste trecho de código, uma mensagem é enviada em uma fila chamada “requestqueue" e, como estamos utilizando o QueueRequestor, ele automaticamente cria uma fila temporária para o recebimento da resposta de sua requisição, que é armazenada na variável “reply”. Lógico, quem implementou o MessageListener que consome a fila “requestqueue" precisa obviamente implementar a lógica necessária para obter o header replyTo e publicar alguma possível resposta nesta fila. Isto pode ser feito deste jeito:
Destination destinationJMS = message.getJMSReplyTo();
TemporaryQueue replyQueue = (TemporaryQueue) destinationJMS;
Desta maneira, obtemos uma referência à fila temporária criada pelo QueueRequestor e poderemos escrever nossas repostas por lá.
O que é mais comum no entanto, é que a lógica de todo o processamento não resida no MessageListener, e sim em outro lugar qualquer. A partir daí, temos dois pontos a considerar, um relacionado ao EJB e outro ao Glassfish (não consgui testar em outro APP Sever).
Problema do Glassfish: Nome da fila não é o mesmo nome JNDI
Como disse, normalmente a lógica toda de uma requisição a uma fila não fica no MessageListener, mas sim em algum outro lugar qualquer que o próprio MessageListener irá delegar. Neste caso, precisamos de alguma maneira guardar o nome da fila temporária, para que em tempo apropriado, possamos novamente obter uma referência a este memso objeto e publicarmos nossa resposta. No Glassfish, o nome obtido através do método getQueueName() aparentemente não é o mesmo nome JNDI desta fila. Estranho, mas um NameNotFoundException explode na cara ao tentar realizar o lookup do retorno deste método. A solução aqui é simples, apesar de soar obviamente desnecessária. Basta que realizemos um bind no nome da fila apontando para o próprio objeto, mais ou menos assim:
...
Destination destinationJMS = message.getJMSReplyTo();
TemporaryQueue replyQueue = (TemporaryQueue) destinationJMS;
context = new InitialContext();
temporaryQueueName = replyQueue.getQueueName().replaceAll(”[/]“, “A”);
temporaryQueueName = temporaryQueueName.replaceAll(”[:]“, “A”);
context.bind(temporaryQueueName, replyQueue);
…
Fazendo isto, garantimos que o nome JNDI seja igual ao nome retornado pelo método getQueueName(). Basta que este nome seja entregue ao objeto na qual o delegate foi realizado, de maneira que ele o preserve para futuro lookup. Repare que adicionei alguns replaceAll.. Isto se deve ao fato de o Glassfish não publicar nomes JNDI contendo “/” ou “:”.. Neste caso, uma exceção do tipo “Cannot create empty subcontext” é atirada.
Problema do EJB: Transações
O código que crua o objeto QueueRequestor, como demonstrado acima, funciona perfeitamente em qualquer lugar, menos em objetos EJB (não em todos os casos, existe uma solução).
Isto ocorre porque por padrão, o método request deste objeto utiliza os recursos transacionais do EJB realizar algumas garantias, mas o que acaba acontecendo é um bug difícil de encontrar: A fila temporária é criada e a mensagem chega ao destino com transação, mas seu consumidor (MDB, MessgaeListener) não consegue retirá-la de lá. O travamento é certo, mesmo parando o servidor a coisa continua feia. A única solução para este caso, é anotar o método que realiza a lógica do QueueRequestor com instruções não transacionais, deste jeito:
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
Se o método em questão precisa obrigatóriamente de transações, então não será possível utilizar o QueueRequestor (repito, pelo menos no GlassFish V2), alguma outra API deve ser usada para continuar com o efeito Request/Response.
Para uma explicação mais detalhada, vale a pena dar uma olhada neste blog tmabém: http://blogs.sun.com/fkieviet/entry/request_reply_from_an_ejb
Isso aí, apesar de tudo o peixe de vidro continua sendo bacana… Vamos ver até quando!