Java tutorial
// Copyright 2012,2013 Vaughn Vernon // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.saasovation.common.port.adapter.messaging.rabbitmq; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import com.rabbitmq.client.AMQP.BasicProperties; import com.rabbitmq.client.Channel; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import com.rabbitmq.client.QueueingConsumer.Delivery; import com.rabbitmq.client.ShutdownSignalException; import com.saasovation.common.port.adapter.messaging.MessageException; /** * I am a message consumer, which facilitates receiving messages * from a Queue. A MessageListener or a client may close me, * terminating message consumption. * * @author Vaughn Vernon */ public class MessageConsumer { /** My autoAcknowledged property. */ private boolean autoAcknowledged; /** My closed property, which indicates I have been closed. */ private boolean closed; /** My messageTypes, which indicates the messages of types I accept. */ private Set<String> messageTypes; /** My queue, which is where my messages come from. */ private Queue queue; /** My tag, which is produced by the broker. */ private String tag; /** * Answers a new auto-acknowledged MessageConsumer, which means all * messages received are automatically considered acknowledged as * received from the broker. * @param aQueue the Queue from which messages are received * @return MessageConsumer */ public static MessageConsumer autoAcknowledgedInstance(Queue aQueue) { return MessageConsumer.instance(aQueue, true); } /** * Answers a new MessageConsumer with manual acknowledgment. * @param aQueue the Queue from which messages are received * @return MessageConsumer */ public static MessageConsumer instance(Queue aQueue) { return new MessageConsumer(aQueue, false); } /** * Answers a new MessageConsumer with acknowledgment managed per * isAutoAcknowledged. * @param aQueue the Queue from which messages are received * @param isAutoAcknowledged the boolean indicating whether or not auto-acknowledgment is used * @return MessageConsumer */ public static MessageConsumer instance(Queue aQueue, boolean isAutoAcknowledged) { return new MessageConsumer(aQueue, isAutoAcknowledged); } /** * Closes me, which closes my queue. */ public void close() { this.setClosed(true); this.queue().close(); } /** * Answers whether or not I have been closed. * @return boolean */ public boolean isClosed() { return this.closed; } /** * Ensure an equalization of message distribution * across all consumers of this queue. */ public void equalizeMessageDistribution() { try { this.queue().channel().basicQos(1); } catch (IOException e) { throw new MessageException("Cannot equalize distribution.", e); } } /** * Receives all messages on a separate thread and dispatches * them to aMessageListener until I am closed or until the * broker is shut down. * @param aMessageListener the MessageListener that handles messages */ public void receiveAll(final MessageListener aMessageListener) { this.receiveFor(aMessageListener); } /** * Receives only messages of types included in aMessageTypes * on a separate thread and dispatches them to aMessageListener * until I am closed or until the broker is shut down. The type * must be included in the message's basic properties. If the * message's type is null, the message is filtered out. * @param aMessageTypes the String[] indicating filtered message types * @param aMessageListener the MessageListener that handles messages */ public void receiveOnly(final String[] aMessageTypes, final MessageListener aMessageListener) { String[] filterOutAllBut = aMessageTypes; if (filterOutAllBut == null) { filterOutAllBut = new String[0]; } this.setMessageTypes(new HashSet<String>(Arrays.asList(filterOutAllBut))); this.receiveFor(aMessageListener); } /** * Answers my tag, which was produced by the broker. * @return String */ public String tag() { return this.tag; } /** * Constructs my default state. * @param aQueue the Queue from which I receive messages * @param isAutoAcknowledged the boolean indicating whether or not auto-acknowledgment is used */ protected MessageConsumer(Queue aQueue, boolean isAutoAcknowledged) { super(); this.setMessageTypes(new HashSet<String>(Arrays.asList(new String[0]))); this.setQueue(aQueue); this.setAutoAcknowledged(isAutoAcknowledged); } /** * Answers my autoAcknowledged. * @return boolean */ private boolean isAutoAcknowledged() { return this.autoAcknowledged; } /** * Sets my autoAcknowledged. * @param isAutoAcknowledged the boolean to set as my autoAcknowledged */ private void setAutoAcknowledged(boolean isAutoAcknowledged) { this.autoAcknowledged = isAutoAcknowledged; } /** * Sets my closed. * @param aClosed the boolean to set as my closed */ private void setClosed(boolean aClosed) { this.closed = aClosed; } /** * Answers my queue. * @return Queue */ protected Queue queue() { return this.queue; } /** * Answers my messageTypes. * @return Set<String> */ private Set<String> messageTypes() { return this.messageTypes; } /** * Registers aMessageListener with the channel indirectly using * a DispatchingConsumer. * @param aMessageListener the MessageListener */ private void receiveFor(MessageListener aMessageListener) { Queue queue = this.queue(); Channel channel = queue.channel(); try { String tag = channel.basicConsume(queue.name(), this.isAutoAcknowledged(), new DispatchingConsumer(channel, aMessageListener)); this.setTag(tag); } catch (IOException e) { throw new MessageException("Failed to initiate consumer.", e); } } /** * Sets my messageTypes. * @param aMessageTypes the Set<String> to set as my messageTypes */ private void setMessageTypes(Set<String> aMessageTypes) { this.messageTypes = aMessageTypes; } /** * Sets my queue. * @param aQueue the Queue to set as my queue */ private void setQueue(Queue aQueue) { this.queue = aQueue; } /** * Sets my tag. * @param aTag the String to set as my tag */ private void setTag(String aTag) { this.tag = aTag; } private class DispatchingConsumer extends DefaultConsumer { private MessageListener messageListener; public DispatchingConsumer(Channel aChannel, MessageListener aMessageListener) { super(aChannel); this.setMessageListener(aMessageListener); } @Override public void handleDelivery(String aConsumerTag, Envelope anEnvelope, BasicProperties aProperties, byte[] aBody) throws IOException { if (!isClosed()) { handle(this.messageListener(), new Delivery(anEnvelope, aProperties, aBody)); } if (isClosed()) { queue().close(); } } @Override public void handleShutdownSignal(String aConsumerTag, ShutdownSignalException aSignal) { close(); } private void handle(MessageListener aMessageListener, Delivery aDelivery) { try { if (this.filteredMessageType(aDelivery)) { ; } else if (aMessageListener.type().isBinaryListener()) { aMessageListener.handleMessage(aDelivery.getProperties().getType(), aDelivery.getProperties().getMessageId(), aDelivery.getProperties().getTimestamp(), aDelivery.getBody(), aDelivery.getEnvelope().getDeliveryTag(), aDelivery.getEnvelope().isRedeliver()); } else if (aMessageListener.type().isTextListener()) { aMessageListener.handleMessage(aDelivery.getProperties().getType(), aDelivery.getProperties().getMessageId(), aDelivery.getProperties().getTimestamp(), new String(aDelivery.getBody()), aDelivery.getEnvelope().getDeliveryTag(), aDelivery.getEnvelope().isRedeliver()); } this.ack(aDelivery); } catch (MessageException e) { // System.out.println("MESSAGE EXCEPTION (MessageConsumer): " + e.getMessage()); this.nack(aDelivery, e.isRetry()); } catch (Throwable t) { // System.out.println("EXCEPTION (MessageConsumer): " + t.getMessage()); this.nack(aDelivery, false); } } private void ack(Delivery aDelivery) { try { if (!isAutoAcknowledged()) { this.getChannel().basicAck(aDelivery.getEnvelope().getDeliveryTag(), false); } } catch (IOException ioe) { // fall through } } private void nack(Delivery aDelivery, boolean isRetry) { try { if (!isAutoAcknowledged()) { this.getChannel().basicNack(aDelivery.getEnvelope().getDeliveryTag(), false, isRetry); } } catch (IOException ioe) { // fall through } } private boolean filteredMessageType(Delivery aDelivery) { boolean filtered = false; Set<String> filteredMessageTypes = messageTypes(); if (!filteredMessageTypes.isEmpty()) { String messageType = aDelivery.getProperties().getType(); if (messageType == null || !filteredMessageTypes.contains(messageType)) { filtered = true; } } return filtered; } /** * Answers my messageListener. * @return MessageListener */ private MessageListener messageListener() { return messageListener; } /** * Sets my messageListener. * @param messageListener the MessageListener to set as my messageListener */ private void setMessageListener(MessageListener messageListener) { this.messageListener = messageListener; } } }