/** * 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.camel.component.sjms; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import javax.jms.Connection; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.Session; import org.apache.camel.Endpoint; import org.apache.camel.ExchangePattern; import org.apache.camel.Processor; import org.apache.camel.component.sjms.consumer.AbstractMessageHandler; import org.apache.camel.component.sjms.consumer.InOnlyMessageHandler; import org.apache.camel.component.sjms.consumer.InOutMessageHandler; import org.apache.camel.component.sjms.jms.ConnectionResource; import org.apache.camel.component.sjms.taskmanager.TimedTaskManager; import org.apache.camel.component.sjms.tx.BatchTransactionCommitStrategy; import org.apache.camel.component.sjms.tx.DefaultTransactionCommitStrategy; import org.apache.camel.component.sjms.tx.SessionBatchTransactionSynchronization; import org.apache.camel.component.sjms.tx.SessionTransactionSynchronization; import org.apache.camel.impl.DefaultConsumer; import org.apache.camel.spi.Synchronization; import org.apache.commons.pool.BasePoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; /** * The SjmsConsumer is the base class for the SJMS MessageListener pool. */ public class SjmsConsumer extends DefaultConsumer { protected GenericObjectPool<MessageConsumerResources> consumers; private ExecutorService executor; private Future<?> asyncStart; /** * A pool of MessageConsumerResources created at the initialization of the associated consumer. */ protected class MessageConsumerResourcesFactory extends BasePoolableObjectFactory<MessageConsumerResources> { /** * Creates a new MessageConsumerResources instance. * * @see org.apache.commons.pool.PoolableObjectFactory#makeObject() */ @Override public MessageConsumerResources makeObject() throws Exception { return createConsumer(); } /** * Cleans up the MessageConsumerResources. * * @see org.apache.commons.pool.PoolableObjectFactory#destroyObject(java.lang.Object) */ @Override public void destroyObject(MessageConsumerResources model) throws Exception { if (model != null) { // First clean up our message consumer if (model.getMessageConsumer() != null) { model.getMessageConsumer().close(); } // If the resource has a if (model.getSession() != null) { if (model.getSession().getTransacted()) { try { model.getSession().rollback(); } catch (Exception e) { // Do nothing. Just make sure we are cleaned up } } model.getSession().close(); } } } } public SjmsConsumer(Endpoint endpoint, Processor processor) { super(endpoint, processor); } @Override public SjmsEndpoint getEndpoint() { return (SjmsEndpoint) super.getEndpoint(); } @Override protected void doStart() throws Exception { super.doStart(); this.executor = getEndpoint().getCamelContext().getExecutorServiceManager().newDefaultThreadPool(this, "SjmsConsumer"); if (consumers == null) { consumers = new GenericObjectPool<MessageConsumerResources>(new MessageConsumerResourcesFactory()); consumers.setMaxActive(getConsumerCount()); consumers.setMaxIdle(getConsumerCount()); if (getEndpoint().isAsyncStartListener()) { asyncStart = getEndpoint().getComponent().getAsyncStartStopExecutorService().submit(new Runnable() { @Override public void run() { try { fillConsumersPool(); } catch (Throwable e) { log.warn("Error starting listener container on destination: " + getDestinationName() + ". This exception will be ignored.", e); } } @Override public String toString() { return "AsyncStartListenerTask[" + getDestinationName() + "]"; } }); } else { fillConsumersPool(); } } } private void fillConsumersPool() throws Exception { while (consumers.getNumIdle() < consumers.getMaxIdle()) { consumers.addObject(); } } @Override protected void doStop() throws Exception { super.doStop(); if (asyncStart != null && !asyncStart.isDone()) { asyncStart.cancel(true); } if (consumers != null) { if (getEndpoint().isAsyncStopListener()) { getEndpoint().getComponent().getAsyncStartStopExecutorService().submit(new Runnable() { @Override public void run() { try { consumers.close(); consumers = null; } catch (Throwable e) { log.warn("Error stopping listener container on destination: " + getDestinationName() + ". This exception will be ignored.", e); } } @Override public String toString() { return "AsyncStopListenerTask[" + getDestinationName() + "]"; } }); } else { consumers.close(); consumers = null; } } if (this.executor != null) { getEndpoint().getCamelContext().getExecutorServiceManager().shutdownGraceful(this.executor); } } /** * Creates a {@link MessageConsumerResources} with a dedicated * {@link Session} required for transacted and InOut consumers. */ private MessageConsumerResources createConsumer() throws Exception { MessageConsumerResources answer; ConnectionResource connectionResource = getOrCreateConnectionResource(); Connection conn = connectionResource.borrowConnection(); try { Session session = conn.createSession(isTransacted(), isTransacted() ? Session.SESSION_TRANSACTED : Session.AUTO_ACKNOWLEDGE); MessageConsumer messageConsumer = getEndpoint().getJmsObjectFactory().createMessageConsumer(session, getEndpoint()); MessageListener handler = createMessageHandler(session); messageConsumer.setMessageListener(handler); answer = new MessageConsumerResources(session, messageConsumer); } catch (Exception e) { log.error("Unable to create the MessageConsumer", e); throw e; } finally { connectionResource.returnConnection(conn); } return answer; } /** * Helper factory method used to create a MessageListener based on the MEP * * @param session a session is only required if we are a transacted consumer * @return the listener */ protected MessageListener createMessageHandler(Session session) { TransactionCommitStrategy commitStrategy; if (getTransactionCommitStrategy() != null) { commitStrategy = getTransactionCommitStrategy(); } else if (getTransactionBatchCount() > 0) { commitStrategy = new BatchTransactionCommitStrategy(getTransactionBatchCount()); } else { commitStrategy = new DefaultTransactionCommitStrategy(); } Synchronization synchronization; if (commitStrategy instanceof BatchTransactionCommitStrategy) { TimedTaskManager timedTaskManager = getEndpoint().getComponent().getTimedTaskManager(); synchronization = new SessionBatchTransactionSynchronization(timedTaskManager, session, commitStrategy, getTransactionBatchTimeout()); } else { synchronization = new SessionTransactionSynchronization(session, commitStrategy); } AbstractMessageHandler messageHandler; if (getEndpoint().getExchangePattern().equals(ExchangePattern.InOnly)) { if (isTransacted()) { messageHandler = new InOnlyMessageHandler(getEndpoint(), executor, synchronization); } else { messageHandler = new InOnlyMessageHandler(getEndpoint(), executor); } } else { if (isTransacted()) { messageHandler = new InOutMessageHandler(getEndpoint(), executor, synchronization); } else { messageHandler = new InOutMessageHandler(getEndpoint(), executor); } } messageHandler.setSession(session); messageHandler.setProcessor(getAsyncProcessor()); messageHandler.setSynchronous(isSynchronous()); messageHandler.setTransacted(isTransacted()); messageHandler.setSharedJMSSession(isSharedJMSSession()); messageHandler.setTopic(isTopic()); return messageHandler; } /** * @deprecated use {@link #getOrCreateConnectionResource()} */ @Deprecated protected ConnectionResource getConnectionResource() { return getEndpoint().getConnectionResource(); } protected ConnectionResource getOrCreateConnectionResource() { ConnectionResource answer = getEndpoint().getConnectionResource(); if (answer == null) { answer = getEndpoint().createConnectionResource(this); } return answer; } public int getAcknowledgementMode() { return getEndpoint().getAcknowledgementMode().intValue(); } /** * Use to determine if transactions are enabled or disabled. * * @return true if transacted, otherwise false */ public boolean isTransacted() { return getEndpoint().isTransacted(); } /** * Use to determine if JMS session should be propagated to share with other SJMS endpoints. * * @return true if shared, otherwise false */ public boolean isSharedJMSSession() { return getEndpoint().isSharedJMSSession(); } /** * Use to determine whether or not to process exchanges synchronously. * * @return true if synchronous */ public boolean isSynchronous() { return getEndpoint().isSynchronous(); } /** * The destination name for this consumer. * * @return String */ public String getDestinationName() { return getEndpoint().getDestinationName(); } /** * Returns the number of consumer listeners. * * @return the consumerCount */ public int getConsumerCount() { return getEndpoint().getConsumerCount(); } /** * Flag set by the endpoint used by consumers and producers to determine if * the consumer is a JMS Topic. * * @return the topic true if consumer is a JMS Topic, default is false */ public boolean isTopic() { return getEndpoint().isTopic(); } /** * Gets the JMS Message selector syntax. */ public String getMessageSelector() { return getEndpoint().getMessageSelector(); } /** * Gets the durable subscription Id. * * @return the durableSubscriptionId */ public String getDurableSubscriptionId() { return getEndpoint().getDurableSubscriptionId(); } /** * Gets the commit strategy. * * @return the transactionCommitStrategy */ public TransactionCommitStrategy getTransactionCommitStrategy() { return getEndpoint().getTransactionCommitStrategy(); } /** * If transacted, returns the nubmer of messages to be processed before * committing the transaction. * * @return the transactionBatchCount */ public int getTransactionBatchCount() { return getEndpoint().getTransactionBatchCount(); } /** * Returns the timeout value for batch transactions. * * @return long */ public long getTransactionBatchTimeout() { return getEndpoint().getTransactionBatchTimeout(); } }