All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.activemq.ActiveMQMessageConsumer Maven / Gradle / Ivy

There is a newer version: 5.7.0
Show newest version
/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.activemq;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import javax.jms.IllegalStateException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;

import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.ActiveMQMessage;
import org.apache.activemq.command.ActiveMQTempDestination;
import org.apache.activemq.command.ConsumerId;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.MessageAck;
import org.apache.activemq.command.MessageDispatch;
import org.apache.activemq.command.MessagePull;
import org.apache.activemq.management.JMSConsumerStatsImpl;
import org.apache.activemq.management.StatsCapable;
import org.apache.activemq.management.StatsImpl;
import org.apache.activemq.selector.SelectorParser;
import org.apache.activemq.thread.Scheduler;
import org.apache.activemq.transaction.Synchronization;
import org.apache.activemq.util.Callback;
import org.apache.activemq.util.IntrospectionSupport;
import org.apache.activemq.util.JMSExceptionSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
import edu.emory.mathcs.backport.java.util.concurrent.Executors;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;

/**
 * A client uses a MessageConsumer object to receive messages
 * from a destination. A  MessageConsumer object is created by
 * passing a Destination object to a message-consumer creation
 * method supplied by a session.
 * 

* MessageConsumer is the parent interface for all message * consumers. *

* A message consumer can be created with a message selector. A message selector * allows the client to restrict the messages delivered to the message consumer * to those that match the selector. *

* A client may either synchronously receive a message consumer's messages or * have the consumer asynchronously deliver them as they arrive. *

* For synchronous receipt, a client can request the next message from a message * consumer using one of its receive methods. There are several * variations of receive that allow a client to poll or wait for * the next message. *

* For asynchronous delivery, a client can register a MessageListener * object with a message consumer. As messages arrive at the message consumer, * it delivers them by calling the MessageListener's * onMessage method. *

* It is a client programming error for a MessageListener to * throw an exception. * * @version $Revision: 1.22 $ * @see javax.jms.MessageConsumer * @see javax.jms.QueueReceiver * @see javax.jms.TopicSubscriber * @see javax.jms.Session */ public class ActiveMQMessageConsumer implements MessageAvailableConsumer, StatsCapable, ActiveMQDispatcher { private static final Log log = LogFactory.getLog(ActiveMQMessageConsumer.class); protected final ActiveMQSession session; protected final ConsumerInfo info; // These are the messages waiting to be delivered to the client private final MessageDispatchChannel unconsumedMessages = new MessageDispatchChannel(); // The are the messages that were delivered to the consumer but that have // not been acknowledged. It's kept in reverse order since we // Always walk list in reverse order. Only used when session is client ack. private final LinkedList deliveredMessages = new LinkedList(); private int deliveredCounter = 0; private int additionalWindowSize = 0; private int rollbackCounter = 0; private long redeliveryDelay = 0; private int ackCounter = 0; private MessageListener messageListener; private JMSConsumerStatsImpl stats; private final String selector; private boolean synchronizationRegistered = false; private AtomicBoolean started = new AtomicBoolean(false); private MessageAvailableListener availableListener; private RedeliveryPolicy redeliveryPolicy; private boolean optimizeAcknowledge; private AtomicBoolean deliveryingAcknowledgements = new AtomicBoolean(); private ExecutorService executorService = null; /** * Create a MessageConsumer * * @param session * @param dest * @param name * @param selector * @param prefetch * @param maximumPendingMessageCount TODO * @param noLocal * @param browser * @param dispatchAsync * @throws JMSException */ public ActiveMQMessageConsumer(ActiveMQSession session, ConsumerId consumerId, ActiveMQDestination dest, String name, String selector, int prefetch, int maximumPendingMessageCount, boolean noLocal, boolean browser, boolean dispatchAsync) throws JMSException { if (dest == null) { throw new InvalidDestinationException("Don't understand null destinations"); } else if (dest.getPhysicalName() == null) { throw new InvalidDestinationException("The destination object was not given a physical name."); } else if (dest.isTemporary()) { String physicalName = dest.getPhysicalName(); if (physicalName == null) { throw new IllegalArgumentException("Physical name of Destination should be valid: " + dest); } String connectionID = session.connection.getConnectionInfo().getConnectionId().getValue(); if (physicalName.indexOf(connectionID) < 0) { throw new InvalidDestinationException("Cannot use a Temporary destination from another Connection"); } if (session.connection.isDeleted(dest)) { throw new InvalidDestinationException("Cannot use a Temporary destination that has been deleted"); } } this.session = session; this.selector = selector; this.redeliveryPolicy = session.connection.getRedeliveryPolicy(); this.info = new ConsumerInfo(consumerId); this.info.setSubscriptionName(name); this.info.setPrefetchSize(prefetch); this.info.setCurrentPrefetchSize(prefetch); this.info.setMaximumPendingMessageLimit(maximumPendingMessageCount); this.info.setNoLocal(noLocal); this.info.setDispatchAsync(dispatchAsync); this.info.setRetroactive(this.session.connection.isUseRetroactiveConsumer()); // Allows the options on the destination to configure the consumerInfo if (dest.getOptions() != null) { HashMap options = new HashMap(dest.getOptions()); IntrospectionSupport.setProperties(this.info, options, "consumer."); } this.info.setDestination(dest); this.info.setBrowser(browser); if (selector != null && selector.trim().length() != 0) { // Validate that the selector new SelectorParser().parse(selector); this.info.setSelector(selector); } else { this.info.setSelector(null); } this.stats = new JMSConsumerStatsImpl(session.getSessionStats(), dest); this.optimizeAcknowledge=session.connection.isOptimizeAcknowledge()&&session.isAutoAcknowledge() &&!info.isBrowser(); this.info.setOptimizedAcknowledge(this.optimizeAcknowledge); try { this.session.addConsumer(this); this.session.syncSendPacket(info); } catch (JMSException e) { this.session.removeConsumer(this); throw e; } if(session.connection.isStarted()) start(); } public StatsImpl getStats() { return stats; } public JMSConsumerStatsImpl getConsumerStats() { return stats; } public RedeliveryPolicy getRedeliveryPolicy() { return redeliveryPolicy; } /** * Sets the redelivery policy used when messages are redelivered */ public void setRedeliveryPolicy(RedeliveryPolicy redeliveryPolicy) { this.redeliveryPolicy = redeliveryPolicy; } /** * @return Returns the value. */ protected ConsumerId getConsumerId() { return info.getConsumerId(); } /** * @return the consumer name - used for durable consumers */ protected String getConsumerName() { return this.info.getSubscriptionName(); } /** * @return true if this consumer does not accept locally produced messages */ protected boolean isNoLocal() { return info.isNoLocal(); } /** * Retrieve is a browser * * @return true if a browser */ protected boolean isBrowser() { return info.isBrowser(); } /** * @return ActiveMQDestination */ protected ActiveMQDestination getDestination() { return info.getDestination(); } /** * @return Returns the prefetchNumber. */ public int getPrefetchNumber() { return info.getPrefetchSize(); } /** * @return true if this is a durable topic subscriber */ public boolean isDurableSubscriber() { return info.getSubscriptionName()!=null && info.getDestination().isTopic(); } /** * Gets this message consumer's message selector expression. * * @return this message consumer's message selector, or null if no message * selector exists for the message consumer (that is, if the message * selector was not set or was set to null or the empty string) * @throws JMSException * if the JMS provider fails to receive the next message due to * some internal error. */ public String getMessageSelector() throws JMSException { checkClosed(); return selector; } /** * Gets the message consumer's MessageListener. * * @return the listener for the message consumer, or null if no listener is * set * @throws JMSException * if the JMS provider fails to get the message listener due to * some internal error. * @see javax.jms.MessageConsumer#setMessageListener(javax.jms.MessageListener) */ public MessageListener getMessageListener() throws JMSException { checkClosed(); return this.messageListener; } /** * Sets the message consumer's MessageListener. *

* Setting the message listener to null is the equivalent of unsetting the * message listener for the message consumer. *

* The effect of calling MessageConsumer.setMessageListener * while messages are being consumed by an existing listener or the consumer * is being used to consume messages synchronously is undefined. * * @param listener * the listener to which the messages are to be delivered * @throws JMSException * if the JMS provider fails to receive the next message due to * some internal error. * @see javax.jms.MessageConsumer#getMessageListener */ public void setMessageListener(MessageListener listener) throws JMSException { checkClosed(); if (info.getPrefetchSize() == 0) { throw new JMSException("Illegal prefetch size of zero. This setting is not supported for asynchronous consumers please set a value of at least 1"); } this.messageListener = listener; if (listener != null) { boolean wasRunning = session.isRunning(); if (wasRunning) session.stop(); session.redispatch(unconsumedMessages); if (wasRunning) session.start(); } } public MessageAvailableListener getAvailableListener() { return availableListener; } /** * Sets the listener used to notify synchronous consumers that there is a message * available so that the {@link MessageConsumer#receiveNoWait()} can be called. */ public void setAvailableListener(MessageAvailableListener availableListener) { this.availableListener = availableListener; } /** * Used to get an enqueued message from the unconsumedMessages list. The * amount of time this method blocks is based on the timeout value. - if * timeout==-1 then it blocks until a message is received. - if timeout==0 * then it it tries to not block at all, it returns a message if it is * available - if timeout>0 then it blocks up to timeout amount of time. * * Expired messages will consumed by this method. * * @throws JMSException * * @return null if we timeout or if the consumer is closed. */ private MessageDispatch dequeue(long timeout) throws JMSException { try { long deadline = 0; if (timeout > 0) { deadline = System.currentTimeMillis() + timeout; } while (true) { MessageDispatch md = unconsumedMessages.dequeue(timeout); if (md == null) { if (timeout > 0 && !unconsumedMessages.isClosed()) { timeout = Math.max(deadline - System.currentTimeMillis(), 0); } else { return null; } } else if ( md.getMessage()==null ) { return null; } else if (md.getMessage().isExpired()) { if (log.isDebugEnabled()) { log.debug("Received expired message: " + md); } beforeMessageIsConsumed(md); afterMessageIsConsumed(md, true); if (timeout > 0) { timeout = Math.max(deadline - System.currentTimeMillis(), 0); } } else { if (log.isDebugEnabled()) { log.debug("Received message: " + md); } return md; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw JMSExceptionSupport.create(e); } } /** * Receives the next message produced for this message consumer. *

* This call blocks indefinitely until a message is produced or until this * message consumer is closed. *

* If this receive is done within a transaction, the consumer * retains the message until the transaction commits. * * @return the next message produced for this message consumer, or null if * this message consumer is concurrently closed */ public Message receive() throws JMSException { checkClosed(); checkMessageListener(); sendPullCommand(0); MessageDispatch md = dequeue(-1); if (md == null) return null; beforeMessageIsConsumed(md); afterMessageIsConsumed(md, false); return createActiveMQMessage(md); } /** * @param md * @return */ private ActiveMQMessage createActiveMQMessage(final MessageDispatch md) { ActiveMQMessage m = (ActiveMQMessage) md.getMessage().copy(); if (session.isClientAcknowledge()) { m.setAcknowledgeCallback(new Callback() { public void execute() throws Exception { session.checkClosed(); session.acknowledge(); } }); } return m; } /** * Receives the next message that arrives within the specified timeout * interval. *

* This call blocks until a message arrives, the timeout expires, or this * message consumer is closed. A timeout of zero never * expires, and the call blocks indefinitely. * * @param timeout * the timeout value (in milliseconds), a time out of zero never expires. * @return the next message produced for this message consumer, or null if * the timeout expires or this message consumer is concurrently * closed */ public Message receive(long timeout) throws JMSException { checkClosed(); checkMessageListener(); if (timeout == 0) { return this.receive(); } sendPullCommand(timeout); while (timeout > 0) { MessageDispatch md; if (info.getPrefetchSize() == 0) { md = dequeue(-1); // We let the broker let us know when we timeout. } else { md = dequeue(timeout); } if (md == null) return null; beforeMessageIsConsumed(md); afterMessageIsConsumed(md, false); return createActiveMQMessage(md); } return null; } /** * Receives the next message if one is immediately available. * * @return the next message produced for this message consumer, or null if * one is not available * @throws JMSException * if the JMS provider fails to receive the next message due to * some internal error. */ public Message receiveNoWait() throws JMSException { checkClosed(); checkMessageListener(); sendPullCommand(-1); MessageDispatch md; if (info.getPrefetchSize() == 0) { md = dequeue(-1); // We let the broker let us know when we timeout. } else { md = dequeue(0); } if (md == null) return null; beforeMessageIsConsumed(md); afterMessageIsConsumed(md, false); return createActiveMQMessage(md); } /** * Closes the message consumer. *

* Since a provider may allocate some resources on behalf of a * MessageConsumer outside the Java virtual machine, clients should * close them when they are not needed. Relying on garbage collection to * eventually reclaim these resources may not be timely enough. *

* This call blocks until a receive or message listener in * progress has completed. A blocked message consumer receive * call returns null when this message consumer is closed. * * @throws JMSException * if the JMS provider fails to close the consumer due to some * internal error. */ public void close() throws JMSException { if (!unconsumedMessages.isClosed()) { dispose(); this.session.syncSendPacket(info.createRemoveCommand()); } } void clearMessagesInProgress(){ unconsumedMessages.clear(); } void deliverAcks(){ MessageAck ack=null; if(deliveryingAcknowledgements.compareAndSet(false,true)){ if(this.optimizeAcknowledge){ if(!deliveredMessages.isEmpty()){ MessageDispatch md=(MessageDispatch) deliveredMessages.getFirst(); ack=new MessageAck(md,MessageAck.STANDARD_ACK_TYPE,deliveredMessages.size()); deliveredMessages.clear(); ackCounter=0; } } if(ack!=null){ final MessageAck ackToSend=ack; if(executorService==null){ executorService=Executors.newSingleThreadExecutor(); } executorService.submit(new Runnable(){ public void run(){ try{ session.asyncSendPacket(ackToSend); }catch(JMSException e){ log.error("Failed to delivered acknowledgements",e); }finally{ deliveryingAcknowledgements.set(false); } } }); }else{ deliveryingAcknowledgements.set(false); } } } public void dispose() throws JMSException { if (!unconsumedMessages.isClosed()) { // Do we have any acks we need to send out before closing? // Ack any delivered messages now. (session may still // commit/rollback the acks). deliverAcks();//only processes optimized acknowledgements if (executorService!=null){ executorService.shutdown(); try { executorService.awaitTermination(60, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } if ((session.isTransacted() || session.isDupsOkAcknowledge())) { acknowledge(); } deliveredMessages.clear(); unconsumedMessages.close(); this.session.removeConsumer(this); } } /** * @throws IllegalStateException */ protected void checkClosed() throws IllegalStateException { if (unconsumedMessages.isClosed()) { throw new IllegalStateException("The Consumer is closed"); } } /** * If we have a zero prefetch specified then send a pull command to the broker to pull a message * we are about to receive * */ protected void sendPullCommand(long timeout) throws JMSException { if (info.getPrefetchSize() == 0 && unconsumedMessages.isEmpty()) { MessagePull messagePull = new MessagePull(); messagePull.configure(info); messagePull.setTimeout(timeout); session.asyncSendPacket(messagePull); } } protected void checkMessageListener() throws JMSException { session.checkMessageListener(); } protected void setOptimizeAcknowledge(boolean value){ if (optimizeAcknowledge && !value){ deliverAcks(); } optimizeAcknowledge=value; } protected void setPrefetchSize(int prefetch){ deliverAcks(); this.info.setCurrentPrefetchSize(prefetch); } private void beforeMessageIsConsumed(MessageDispatch md) throws JMSException { md.setDeliverySequenceId(session.getNextDeliveryId()); if (!session.isDupsOkAcknowledge()) { deliveredMessages.addFirst(md); if( session.isTransacted() ) { ackLater(md,MessageAck.DELIVERED_ACK_TYPE); } } } private void afterMessageIsConsumed(MessageDispatch md,boolean messageExpired) throws JMSException{ if(unconsumedMessages.isClosed()) return; if(messageExpired){ ackLater(md,MessageAck.DELIVERED_ACK_TYPE); }else{ stats.onMessage(); if( session.isTransacted() ) { } else if(session.isAutoAcknowledge()) { if(!deliveredMessages.isEmpty()){ if(optimizeAcknowledge){ if(deliveryingAcknowledgements.compareAndSet(false,true)){ ackCounter++; if(ackCounter>=(info.getCurrentPrefetchSize()*.65)){ MessageAck ack=new MessageAck(md,MessageAck.STANDARD_ACK_TYPE,deliveredMessages.size()); session.asyncSendPacket(ack); ackCounter=0; deliveredMessages.clear(); } deliveryingAcknowledgements.set(false); } }else{ MessageAck ack=new MessageAck(md,MessageAck.STANDARD_ACK_TYPE,deliveredMessages.size()); session.asyncSendPacket(ack); deliveredMessages.clear(); } } } else if(session.isDupsOkAcknowledge()){ ackLater(md,MessageAck.STANDARD_ACK_TYPE); } else if(session.isClientAcknowledge()){ ackLater(md,MessageAck.DELIVERED_ACK_TYPE); } else{ throw new IllegalStateException("Invalid session state."); } } } private void ackLater(MessageDispatch md, byte ackType) throws JMSException { // Don't acknowledge now, but we may need to let the broker know the // consumer got the message // to expand the pre-fetch window if (session.isTransacted()) { session.doStartTransaction(); if (!synchronizationRegistered) { synchronizationRegistered = true; session.getTransactionContext().addSynchronization(new Synchronization() { public void beforeEnd() throws Exception { acknowledge(); synchronizationRegistered = false; } public void afterCommit() throws Exception { commit(); synchronizationRegistered = false; } public void afterRollback() throws Exception { rollback(); synchronizationRegistered = false; } }); } } // The delivered message list is only needed for the recover method // which is only used with client ack. deliveredCounter++; if ((0.5 * info.getPrefetchSize()) <= (deliveredCounter - additionalWindowSize)) { MessageAck ack = new MessageAck(md, ackType, deliveredCounter); ack.setTransactionId(session.getTransactionContext().getTransactionId()); session.asyncSendPacket(ack); additionalWindowSize = deliveredCounter; // When using DUPS ok, we do a real ack. if (ackType == MessageAck.STANDARD_ACK_TYPE) { deliveredCounter = additionalWindowSize = 0; } } } /** * Acknowledge all the messages that have been delivered to the client upto * this point. * * @throws JMSException */ public void acknowledge() throws JMSException { if (deliveredMessages.isEmpty()) return; // Acknowledge the last message. MessageDispatch lastMd = (MessageDispatch) deliveredMessages.get(0); MessageAck ack = new MessageAck(lastMd, MessageAck.STANDARD_ACK_TYPE, deliveredMessages.size()); if (session.isTransacted()) { session.doStartTransaction(); ack.setTransactionId(session.getTransactionContext().getTransactionId()); } session.asyncSendPacket(ack); // Adjust the counters deliveredCounter -= deliveredMessages.size(); additionalWindowSize = Math.max(0, additionalWindowSize - deliveredMessages.size()); if (!session.isTransacted()) { deliveredMessages.clear(); } } public void commit() throws JMSException { deliveredMessages.clear(); rollbackCounter = 0; redeliveryDelay = 0; } public void rollback() throws JMSException{ synchronized(unconsumedMessages.getMutex()){ if(optimizeAcknowledge){ // remove messages read but not acked at the broker yet through optimizeAcknowledge for(int i=0;(i 0 ) redeliveryDelay = redeliveryPolicy.getRedeliveryDelay(redeliveryDelay); rollbackCounter++; if(redeliveryPolicy.getMaximumRedeliveries() != RedeliveryPolicy.NO_MAXIMUM_REDELIVERIES && rollbackCounter>redeliveryPolicy.getMaximumRedeliveries()){ // We need to NACK the messages so that they get sent to the // DLQ. // Acknowledge the last message. MessageDispatch lastMd=(MessageDispatch) deliveredMessages.get(0); MessageAck ack=new MessageAck(lastMd,MessageAck.POSION_ACK_TYPE,deliveredMessages.size()); session.asyncSendPacket(ack); // Adjust the window size. additionalWindowSize=Math.max(0,additionalWindowSize-deliveredMessages.size()); rollbackCounter=0; redeliveryDelay=0; }else{ // stop the delivery of messages. unconsumedMessages.stop(); for(Iterator iter=deliveredMessages.iterator();iter.hasNext();){ MessageDispatch md=(MessageDispatch) iter.next(); md.getMessage().onMessageRolledBack(); unconsumedMessages.enqueueFirst(md); } if( redeliveryDelay > 0 ) { // Start up the delivery again a little later. Scheduler.executeAfterDelay(new Runnable(){ public void run(){ try{ if(started.get()) start(); }catch(JMSException e){ session.connection.onAsyncException(e); } } },redeliveryDelay); } else { start(); } } deliveredCounter-=deliveredMessages.size(); deliveredMessages.clear(); } if(messageListener!=null){ session.redispatch(unconsumedMessages); } } public void dispatch(MessageDispatch md) { MessageListener listener = this.messageListener; try { synchronized(unconsumedMessages.getMutex()){ if (unconsumedMessages.isClosed()) { return; } if (listener == null || !unconsumedMessages.isRunning() ) { unconsumedMessages.enqueue(md); if (availableListener != null) { availableListener.onMessageAvailable(this); } return; } } ActiveMQMessage message = createActiveMQMessage(md); beforeMessageIsConsumed(md); try { listener.onMessage(message); afterMessageIsConsumed(md, false); } catch (RuntimeException e) { if ( session.isDupsOkAcknowledge() || session.isAutoAcknowledge() ) { // Redeliver the message } else { // Transacted or Client ack: Deliver the next message. afterMessageIsConsumed(md, false); } log.warn("Exception while processing message: " + e, e); } } catch (Exception e) { session.connection.onAsyncException(e); } } public int getMessageSize() { return unconsumedMessages.size(); } public void start() throws JMSException { if (unconsumedMessages.isClosed()) { return; } started.set(true); unconsumedMessages.start(); session.executor.wakeup(); } public void stop() { started.set(false); unconsumedMessages.stop(); } public String toString() { return "ActiveMQMessageConsumer { value=" +info.getConsumerId()+", started=" +started.get()+" }"; } /** * Delivers a message to the message listener. * @return * @throws JMSException */ public boolean iterate() { MessageListener listener = this.messageListener; if( listener!=null ) { MessageDispatch md = unconsumedMessages.dequeueNoWait(); if( md!=null ) { try { ActiveMQMessage message = createActiveMQMessage(md); beforeMessageIsConsumed(md); listener.onMessage(message); afterMessageIsConsumed(md, false); } catch (JMSException e) { session.connection.onAsyncException(e); } return true; } } return false; } public boolean isInUse(ActiveMQTempDestination destination) { return info.getDestination().equals(destination); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy