/* * Copyright 2003,2004 Colin Crist * * 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 hermes.impl.jms; import hermes.Domain; import hermes.HermesException; import hermes.config.DestinationConfig; import hermes.config.SessionConfig; import hermes.impl.ConnectionManager; import hermes.impl.DestinationManager; import hermes.util.JMSUtils; import java.io.EOFException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.jms.Connection; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.QueueSession; import javax.jms.Session; import javax.jms.Topic; import javax.jms.TopicSession; import javax.jms.TransactionRolledBackException; import org.apache.log4j.Logger; /** * Manager for sessions, holds the thread local producers, consumers, sessions * and (if configured), destinations. Implements reconnect via the connect() * method but does not in itself dictate the reconect policy, that is left to * the implementor of Hermes that calls this. Asynchronous message listeners are * <b>not</b> supported as this functionality is managed via the dispatchers at * the DefaultHermesImpl layer. * * @author colincrist@hermesjms.com * @version $Id: ThreadLocalSessionManager.java,v 1.1 2004/07/21 20:25:40 * colincrist Exp $ */ public class ThreadLocalSessionManager extends AbstractSessionManager { private static final Logger log = Logger.getLogger(ThreadLocalSessionManager.class); private int sessions = 0; private long asyncSessionCloseTimeout = 30 * 1000; private ThreadLocal sessionTL = new ThreadLocal(); // private ThreadLocal queueProducerTL = new ThreadLocal(); private ThreadLocal producerTL = new ThreadLocal(); private ThreadLocal consumersTL = new ThreadLocal(); private ThreadLocal consumersWithSelectorTL = new ThreadLocal(); private Thread dispatchThread; private boolean doReconnectConsumers = false; /** * SessionManager constructor */ public ThreadLocalSessionManager(SessionConfig config, DestinationManager destinationManager) { super(destinationManager, config); } public boolean isOpen() { return sessionTL.get() != null; } /** * Close the session, the session will be recreated on the next use of this * hermes in this thread */ public synchronized void close() throws JMSException { if (sessionTL.get() != null) { closeConsumers((Map) consumersTL.get()); closeConsumers((Map) consumersWithSelectorTL.get()); try { getSession().close(); } catch(JMSException ex) { log.info("closing session:" + ex.getMessage(), ex) ; } sessionTL.set(null); consumersTL.set(null); consumersWithSelectorTL.set(null); producerTL.set(null); sessions--; if (getConnectionManager().getType() == ConnectionManager.Policy.SHARED_CONNECTION) { if (sessions == 0) { log.debug("all sessions closed, closing Connection"); getConnectionManager().close(); getConnectionFactoryManager().close(); } } else { log.debug("session closed, closing its Connection"); getConnectionManager().close(); getConnectionFactoryManager().close(); } } } public void reconnect(String username, String password) throws JMSException { try { close() ; } catch (JMSException ex) { log.warn("when closing session: " + ex.getMessage(), ex) ; } getConnectionManager().reconnect(username, password) ; } /** * Reconect the consumers in the map, keyed on destination */ private void reconnect(Map consumers) throws JMSException { /* * for (Iterator iter = consumers.keySet().iterator(); iter.hasNext();) { * Destination dest = (Destination) iter.next(); String destName = * (isQueue(getConnection())) ? ((Queue) dest).getQueueName() : ((Topic) * dest).getTopicName(); DestinationConfig dConfig = * getDestinationConfig(destName); Domain domain; if ( dConfig != null) { * domain = Domain.getDomain(dConfig.getDomain()); } else { domain = * Domain.getDomain(dest); } try { MessageConsumer oldConsumer = * (MessageConsumer) consumers.get(dest); MessageConsumer newConsumer = * getConsumer(getDestination(destName, domain), * oldConsumer.getMessageSelector()); if ( * oldConsumer.getMessageListener() != null) { * newConsumer.setMessageListener(oldConsumer.getMessageListener()); } } * catch (NamingException ex) { cat.error("unable to locate destination " + * destName + " in JNDI"); } } */ } /** * Create the session for this thread. Not that if consumers exist (during a * reconnect for example) then they will be recreated and the * MessageListener's reconnected. Possibly a bit dodgy. */ public synchronized void connect() throws JMSException { boolean connected = false; boolean firstConnect = (sessionTL.get() == null) ? true : false; int attempts = 0; JMSException throwThis = null; // this goes against my better // judgements... while (!connected) { try { Connection conn = (Connection) parent.getObject(); Session session = createSession(); sessionTL.set(session); connected = true; if (doReconnectConsumers) { // // Tidy up and reconnect any MessageConsumers. Map consumers = (Map) consumersTL.get(); Map consumersWithSelector = (Map) consumersWithSelectorTL.get(); consumersTL.set(new HashMap()); if (consumers != null) { reconnect(consumers); } if (consumersWithSelector != null) { reconnect(consumersWithSelector); } if (!firstConnect && session.getTransacted()) { throwThis = new TransactionRolledBackException("reconnect has forced transaction rollback"); } } } catch (JMSException ex) { synchronized (this) { if (getReconnects() == -1 || attempts < getReconnects()) { attempts++; log.error("connect failed (" + attempts + "): " + ex.getMessage()); try { Thread.sleep(getReconnectTimeout()); } catch (InterruptedException ex2) { log.error("unexpected: " + ex2.getMessage(), ex); } } else { // // If the linked exception is an EOFException we've probably lost connection to the broker // so force a reconnect. if (ex.getLinkedException() != null && ex.getLinkedException() instanceof EOFException) { try { getParent().close() ; } catch (JMSException ex2) { // Ignore. } } else { throw ex; } } } } } if (throwThis != null) { throw throwThis; } } public void closeConsumers(final Map consumers) { if (consumers != null) { for (final Iterator iter = consumers.entrySet().iterator(); iter.hasNext();) { final Map.Entry entry = (Map.Entry) iter.next(); final Destination d = (Destination) entry.getKey(); final MessageConsumer consumer = (MessageConsumer) entry.getValue(); try { log.debug("closing consumer for " + JMSUtils.getDestinationName(d)); consumer.close(); } catch (JMSException e) { log.error("closing consumer: " + e.getMessage(), e); } } } } public void closeConsumer(final Destination d, String selector) throws JMSException { final Map map = selector == null ? (Map) consumersTL.get() : (Map) consumersWithSelectorTL.get() ; if (map != null) { final MessageConsumer consumer = (MessageConsumer) map.remove(d); if (consumer != null) { log.debug("closing consumer for " + JMSUtils.getDestinationName(d)); consumer.close(); } } else { log.debug("no consumer found to close for " + JMSUtils.getDestinationName(d)); } } /** * Get a consumer for a destination. The consumer is cached thread local. */ public MessageConsumer getConsumer(final Destination d) throws JMSException { return getConsumer(d, null); } /** * Get a consumer for a destination and a selector. The consumer is cached * thread local. */ public MessageConsumer getConsumer(final Destination d, final String selector) throws JMSException { MessageConsumer consumer = null; Map<Destination, MessageConsumer> map; if (selector != null) { map = (Map) consumersWithSelectorTL.get(); if (map != null) { if (map.get(d) != null) { if (!map.get(d).getMessageSelector().equals(selector)) { map.remove(d).close() ; ; } } } } else { map = (Map) consumersTL.get(); } if (map == null) { map = new HashMap(); if (selector != null) { consumersWithSelectorTL.set(map); } else { consumersTL.set(map); } } if (map.containsKey(d) ) { consumer = (MessageConsumer) map.get(d); } else { final DestinationConfig dConfig = getDestinationConfig(d); Domain domain; if (dConfig != null) { domain = Domain.getDomain(dConfig.getDomain()); } else { domain = Domain.getDomain(d); } if (domain == Domain.QUEUE) { try { if (selector == null) { consumer = getSession().createConsumer(d); } else { consumer = getSession().createConsumer(d, selector, true); } } catch (NoSuchMethodError ex) { log.debug("JMS 1.1 interface failed, trying 1.0.2b"); } catch (AbstractMethodError ex) { log.debug("JMS 1.1 interface failed, trying 1.0.2b"); } catch (JMSException t) { // // WebSphereMQ hack, it does not correctly support JMS 1.1 if (d.getClass().getName().equals("com.ibm.mq.jms.MQQueue")) { log.debug("createConsumer() failed with WMQ via JMS 1.1 call, falling back to 1.0.2b call") ; } else { throw t; } } if (consumer == null) { if (selector == null) { consumer = ((QueueSession) getSession()).createReceiver((Queue) d); } else { consumer = ((QueueSession) getSession()).createReceiver((Queue) d, selector); } } } else { if (dConfig != null && dConfig.isDurable()) { try { if (selector == null) { consumer = getSession().createDurableSubscriber((Topic) d, dConfig.getClientID()); } else { consumer = getSession().createDurableSubscriber((Topic) d, dConfig.getClientID(), selector, true); } } catch (NoSuchMethodError ex) { log.debug("JMS 1.1 interface failed, trying 1.0.2b"); } catch (AbstractMethodError ex) { log.debug("JMS 1.1 interface failed, trying 1.0.2b"); } if (consumer == null) { if (selector == null) { consumer = ((TopicSession) getSession()).createDurableSubscriber((Topic) d, dConfig.getClientID()); } else { consumer = ((TopicSession) getSession()).createDurableSubscriber((Topic) d, dConfig.getClientID(), selector, true); } } } else { try { if (selector == null) { consumer = getSession().createConsumer(d); } else { consumer = getSession().createConsumer(d, selector, true); } } catch (NoSuchMethodError ex) { log.debug("JMS 1.1 interface failed, trying 1.0.2b"); } catch (AbstractMethodError ex) { log.debug("JMS 1.1 interface failed, trying 1.0.2b"); } if (consumer == null) { if (selector == null) { consumer = ((TopicSession) getSession()).createSubscriber((Topic) d); } else { consumer = ((TopicSession) getSession()).createSubscriber((Topic) d, selector, true); } } } } map.put(d, consumer); } return consumer; } public MessageProducer getProducer() throws JMSException { MessageProducer producer = (MessageProducer) producerTL.get(); if (producer == null) { try { /** * Begin WebMethods Enterprise Hack. When using this provider with a * JMS 1.1 interface some of the 1.1 methods match and some do not. */ if (getSession().getClass().getName().equals("com.wm.broker.jms.QueueSession")) { log.debug("WebMethods session creation hack is active"); } else { producer = getSession().createProducer(null); log.debug("producer created using JMS 1.1 interface"); } } catch (NoSuchMethodError ex) { // NOP } catch (AbstractMethodError ex) { // NOP } finally { try { if (producer == null) { producer = createQueueProducer(); } } catch (Throwable t) { log.debug("cannot create a QueueSender: " + t.getMessage(), t); log.debug("trying a TopicPublisher"); producer = createTopicProducer(); } producerTL.set(producer); } } return producer; } /** * Get the session itself, note that if no session is available then * connect() is invoked to create one. */ public synchronized Session getSession() throws JMSException { Session rval = (Session) sessionTL.get(); if (rval == null) { connect(); synchronized (this) { sessions++; } rval = (Session) sessionTL.get(); } return rval; } public void unsubscribe(String name) throws JMSException { try { try { getSession().unsubscribe(name); } catch (NoSuchMethodError ex) { ((TopicSession) getSession()).unsubscribe(name); } catch (AbstractMethodError ex) { ((TopicSession) getSession()).unsubscribe(name); } } catch (Throwable ex) { log.error(ex.getMessage(), ex); throw new HermesException("Session " + getId() + " cannot unsubscribe: " + ex.getMessage()); } } }