001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.jms.pool;
018
019import java.io.Serializable;
020import java.util.Iterator;
021import java.util.concurrent.CopyOnWriteArrayList;
022import java.util.concurrent.atomic.AtomicBoolean;
023
024import javax.jms.BytesMessage;
025import javax.jms.Destination;
026import javax.jms.JMSException;
027import javax.jms.MapMessage;
028import javax.jms.Message;
029import javax.jms.MessageConsumer;
030import javax.jms.MessageListener;
031import javax.jms.MessageProducer;
032import javax.jms.ObjectMessage;
033import javax.jms.Queue;
034import javax.jms.QueueBrowser;
035import javax.jms.QueueReceiver;
036import javax.jms.QueueSender;
037import javax.jms.QueueSession;
038import javax.jms.Session;
039import javax.jms.StreamMessage;
040import javax.jms.TemporaryQueue;
041import javax.jms.TemporaryTopic;
042import javax.jms.TextMessage;
043import javax.jms.Topic;
044import javax.jms.TopicPublisher;
045import javax.jms.TopicSession;
046import javax.jms.TopicSubscriber;
047import javax.jms.XASession;
048import javax.transaction.xa.XAResource;
049
050import org.apache.commons.pool.KeyedObjectPool;
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054public class PooledSession implements Session, TopicSession, QueueSession, XASession {
055    private static final transient Logger LOG = LoggerFactory.getLogger(PooledSession.class);
056
057    private final SessionKey key;
058    private final KeyedObjectPool<SessionKey, Session> sessionPool;
059    private final CopyOnWriteArrayList<MessageConsumer> consumers = new CopyOnWriteArrayList<MessageConsumer>();
060    private final CopyOnWriteArrayList<QueueBrowser> browsers = new CopyOnWriteArrayList<QueueBrowser>();
061    private final CopyOnWriteArrayList<PooledSessionEventListener> sessionEventListeners = new CopyOnWriteArrayList<PooledSessionEventListener>();
062    private final AtomicBoolean closed = new AtomicBoolean();
063
064    private MessageProducer producer;
065    private TopicPublisher publisher;
066    private QueueSender sender;
067
068    private Session session;
069    private boolean transactional = true;
070    private boolean ignoreClose;
071    private boolean isXa;
072    private boolean useAnonymousProducers = true;
073
074    public PooledSession(SessionKey key, Session session, KeyedObjectPool<SessionKey, Session> sessionPool, boolean transactional, boolean anonymous) {
075        this.key = key;
076        this.session = session;
077        this.sessionPool = sessionPool;
078        this.transactional = transactional;
079        this.useAnonymousProducers = anonymous;
080    }
081
082    public void addSessionEventListener(PooledSessionEventListener listener) {
083        // only add if really needed
084        if (!sessionEventListeners.contains(listener)) {
085            this.sessionEventListeners.add(listener);
086        }
087    }
088
089    protected boolean isIgnoreClose() {
090        return ignoreClose;
091    }
092
093    protected void setIgnoreClose(boolean ignoreClose) {
094        this.ignoreClose = ignoreClose;
095    }
096
097    @Override
098    public void close() throws JMSException {
099        if (ignoreClose) {
100            return;
101        }
102
103        if (closed.compareAndSet(false, true)) {
104            boolean invalidate = false;
105            try {
106                // lets reset the session
107                getInternalSession().setMessageListener(null);
108
109                // Close any consumers and browsers that may have been created.
110                for (Iterator<MessageConsumer> iter = consumers.iterator(); iter.hasNext();) {
111                    MessageConsumer consumer = iter.next();
112                    consumer.close();
113                }
114
115                for (Iterator<QueueBrowser> iter = browsers.iterator(); iter.hasNext();) {
116                    QueueBrowser browser = iter.next();
117                    browser.close();
118                }
119
120                if (transactional && !isXa) {
121                    try {
122                        getInternalSession().rollback();
123                    } catch (JMSException e) {
124                        invalidate = true;
125                        LOG.warn("Caught exception trying rollback() when putting session back into the pool, will invalidate. " + e, e);
126                    }
127                }
128            } catch (JMSException ex) {
129                invalidate = true;
130                LOG.warn("Caught exception trying close() when putting session back into the pool, will invalidate. " + ex, ex);
131            } finally {
132                consumers.clear();
133                browsers.clear();
134                for (PooledSessionEventListener listener : this.sessionEventListeners) {
135                    listener.onSessionClosed(this);
136                }
137                sessionEventListeners.clear();
138            }
139
140            if (invalidate) {
141                // lets close the session and not put the session back into the pool
142                // instead invalidate it so the pool can create a new one on demand.
143                if (session != null) {
144                    try {
145                        session.close();
146                    } catch (JMSException e1) {
147                        LOG.trace("Ignoring exception on close as discarding session: " + e1, e1);
148                    }
149                }
150                try {
151                    sessionPool.invalidateObject(key, session);
152                } catch (Exception e) {
153                    LOG.trace("Ignoring exception on invalidateObject as discarding session: " + e, e);
154                }
155            } else {
156                try {
157                    sessionPool.returnObject(key, session);
158                } catch (Exception e) {
159                    javax.jms.IllegalStateException illegalStateException = new javax.jms.IllegalStateException(e.toString());
160                    illegalStateException.initCause(e);
161                    throw illegalStateException;
162                }
163            }
164
165            session = null;
166        }
167    }
168
169    @Override
170    public void commit() throws JMSException {
171        getInternalSession().commit();
172    }
173
174    @Override
175    public BytesMessage createBytesMessage() throws JMSException {
176        return getInternalSession().createBytesMessage();
177    }
178
179    @Override
180    public MapMessage createMapMessage() throws JMSException {
181        return getInternalSession().createMapMessage();
182    }
183
184    @Override
185    public Message createMessage() throws JMSException {
186        return getInternalSession().createMessage();
187    }
188
189    @Override
190    public ObjectMessage createObjectMessage() throws JMSException {
191        return getInternalSession().createObjectMessage();
192    }
193
194    @Override
195    public ObjectMessage createObjectMessage(Serializable serializable) throws JMSException {
196        return getInternalSession().createObjectMessage(serializable);
197    }
198
199    @Override
200    public Queue createQueue(String s) throws JMSException {
201        return getInternalSession().createQueue(s);
202    }
203
204    @Override
205    public StreamMessage createStreamMessage() throws JMSException {
206        return getInternalSession().createStreamMessage();
207    }
208
209    @Override
210    public TemporaryQueue createTemporaryQueue() throws JMSException {
211        TemporaryQueue result;
212
213        result = getInternalSession().createTemporaryQueue();
214
215        // Notify all of the listeners of the created temporary Queue.
216        for (PooledSessionEventListener listener : this.sessionEventListeners) {
217            listener.onTemporaryQueueCreate(result);
218        }
219
220        return result;
221    }
222
223    @Override
224    public TemporaryTopic createTemporaryTopic() throws JMSException {
225        TemporaryTopic result;
226
227        result = getInternalSession().createTemporaryTopic();
228
229        // Notify all of the listeners of the created temporary Topic.
230        for (PooledSessionEventListener listener : this.sessionEventListeners) {
231            listener.onTemporaryTopicCreate(result);
232        }
233
234        return result;
235    }
236
237    @Override
238    public void unsubscribe(String s) throws JMSException {
239        getInternalSession().unsubscribe(s);
240    }
241
242    @Override
243    public TextMessage createTextMessage() throws JMSException {
244        return getInternalSession().createTextMessage();
245    }
246
247    @Override
248    public TextMessage createTextMessage(String s) throws JMSException {
249        return getInternalSession().createTextMessage(s);
250    }
251
252    @Override
253    public Topic createTopic(String s) throws JMSException {
254        return getInternalSession().createTopic(s);
255    }
256
257    @Override
258    public int getAcknowledgeMode() throws JMSException {
259        return getInternalSession().getAcknowledgeMode();
260    }
261
262    @Override
263    public boolean getTransacted() throws JMSException {
264        return getInternalSession().getTransacted();
265    }
266
267    @Override
268    public void recover() throws JMSException {
269        getInternalSession().recover();
270    }
271
272    @Override
273    public void rollback() throws JMSException {
274        getInternalSession().rollback();
275    }
276
277    @Override
278    public XAResource getXAResource() {
279        if (session instanceof XASession) {
280            return ((XASession) session).getXAResource();
281        }
282        return null;
283    }
284
285    @Override
286    public Session getSession() {
287        return this;
288    }
289
290    @Override
291    public void run() {
292        if (session != null) {
293            session.run();
294        }
295    }
296
297    // Consumer related methods
298    // -------------------------------------------------------------------------
299    @Override
300    public QueueBrowser createBrowser(Queue queue) throws JMSException {
301        return addQueueBrowser(getInternalSession().createBrowser(queue));
302    }
303
304    @Override
305    public QueueBrowser createBrowser(Queue queue, String selector) throws JMSException {
306        return addQueueBrowser(getInternalSession().createBrowser(queue, selector));
307    }
308
309    @Override
310    public MessageConsumer createConsumer(Destination destination) throws JMSException {
311        return addConsumer(getInternalSession().createConsumer(destination));
312    }
313
314    @Override
315    public MessageConsumer createConsumer(Destination destination, String selector) throws JMSException {
316        return addConsumer(getInternalSession().createConsumer(destination, selector));
317    }
318
319    @Override
320    public MessageConsumer createConsumer(Destination destination, String selector, boolean noLocal) throws JMSException {
321        return addConsumer(getInternalSession().createConsumer(destination, selector, noLocal));
322    }
323
324    @Override
325    public TopicSubscriber createDurableSubscriber(Topic topic, String selector) throws JMSException {
326        return addTopicSubscriber(getInternalSession().createDurableSubscriber(topic, selector));
327    }
328
329    @Override
330    public TopicSubscriber createDurableSubscriber(Topic topic, String name, String selector, boolean noLocal) throws JMSException {
331        return addTopicSubscriber(getInternalSession().createDurableSubscriber(topic, name, selector, noLocal));
332    }
333
334    @Override
335    public MessageListener getMessageListener() throws JMSException {
336        return getInternalSession().getMessageListener();
337    }
338
339    @Override
340    public void setMessageListener(MessageListener messageListener) throws JMSException {
341        getInternalSession().setMessageListener(messageListener);
342    }
343
344    @Override
345    public TopicSubscriber createSubscriber(Topic topic) throws JMSException {
346        return addTopicSubscriber(((TopicSession) getInternalSession()).createSubscriber(topic));
347    }
348
349    @Override
350    public TopicSubscriber createSubscriber(Topic topic, String selector, boolean local) throws JMSException {
351        return addTopicSubscriber(((TopicSession) getInternalSession()).createSubscriber(topic, selector, local));
352    }
353
354    @Override
355    public QueueReceiver createReceiver(Queue queue) throws JMSException {
356        return addQueueReceiver(((QueueSession) getInternalSession()).createReceiver(queue));
357    }
358
359    @Override
360    public QueueReceiver createReceiver(Queue queue, String selector) throws JMSException {
361        return addQueueReceiver(((QueueSession) getInternalSession()).createReceiver(queue, selector));
362    }
363
364    // Producer related methods
365    // -------------------------------------------------------------------------
366    @Override
367    public MessageProducer createProducer(Destination destination) throws JMSException {
368        return new PooledProducer(getMessageProducer(destination), destination);
369    }
370
371    @Override
372    public QueueSender createSender(Queue queue) throws JMSException {
373        return new PooledQueueSender(getQueueSender(queue), queue);
374    }
375
376    @Override
377    public TopicPublisher createPublisher(Topic topic) throws JMSException {
378        return new PooledTopicPublisher(getTopicPublisher(topic), topic);
379    }
380
381    public Session getInternalSession() throws IllegalStateException {
382        if (session == null) {
383            throw new IllegalStateException("The session has already been closed");
384        }
385        return session;
386    }
387
388    public MessageProducer getMessageProducer() throws JMSException {
389        return getMessageProducer(null);
390    }
391
392    public MessageProducer getMessageProducer(Destination destination) throws JMSException {
393        MessageProducer result = null;
394
395        if (useAnonymousProducers) {
396            if (producer == null) {
397                // Don't allow for duplicate anonymous producers.
398                synchronized (this) {
399                    if (producer == null) {
400                        producer = getInternalSession().createProducer(null);
401                    }
402                }
403            }
404
405            result = producer;
406        } else {
407            result = getInternalSession().createProducer(destination);
408        }
409
410        return result;
411    }
412
413    public QueueSender getQueueSender() throws JMSException {
414        return getQueueSender(null);
415    }
416
417    public QueueSender getQueueSender(Queue destination) throws JMSException {
418        QueueSender result = null;
419
420        if (useAnonymousProducers) {
421            if (sender == null) {
422                // Don't allow for duplicate anonymous producers.
423                synchronized (this) {
424                    if (sender == null) {
425                        sender = ((QueueSession) getInternalSession()).createSender(null);
426                    }
427                }
428            }
429
430            result = sender;
431        } else {
432            result = ((QueueSession) getInternalSession()).createSender(destination);
433        }
434
435        return result;
436    }
437
438    public TopicPublisher getTopicPublisher() throws JMSException {
439        return getTopicPublisher(null);
440    }
441
442    public TopicPublisher getTopicPublisher(Topic destination) throws JMSException {
443        TopicPublisher result = null;
444
445        if (useAnonymousProducers) {
446            if (publisher == null) {
447                // Don't allow for duplicate anonymous producers.
448                synchronized (this) {
449                    if (publisher == null) {
450                        publisher = ((TopicSession) getInternalSession()).createPublisher(null);
451                    }
452                }
453            }
454
455            result = publisher;
456        } else {
457            result = ((TopicSession) getInternalSession()).createPublisher(destination);
458        }
459
460        return result;
461    }
462
463    private QueueBrowser addQueueBrowser(QueueBrowser browser) {
464        browsers.add(browser);
465        return browser;
466    }
467
468    private MessageConsumer addConsumer(MessageConsumer consumer) {
469        consumers.add(consumer);
470        // must wrap in PooledMessageConsumer to ensure the onConsumerClose
471        // method is invoked when the returned consumer is closed, to avoid memory
472        // leak in this session class in case many consumers is created
473        return new PooledMessageConsumer(this, consumer);
474    }
475
476    private TopicSubscriber addTopicSubscriber(TopicSubscriber subscriber) {
477        consumers.add(subscriber);
478        return subscriber;
479    }
480
481    private QueueReceiver addQueueReceiver(QueueReceiver receiver) {
482        consumers.add(receiver);
483        return receiver;
484    }
485
486    public void setIsXa(boolean isXa) {
487        this.isXa = isXa;
488    }
489
490    @Override
491    public String toString() {
492        return "PooledSession { " + session + " }";
493    }
494
495    /**
496     * Callback invoked when the consumer is closed.
497     * <p/>
498     * This is used to keep track of an explicit closed consumer created by this
499     * session, by which we know do not need to keep track of the consumer, as
500     * its already closed.
501     *
502     * @param consumer
503     *            the consumer which is being closed
504     */
505    protected void onConsumerClose(MessageConsumer consumer) {
506        consumers.remove(consumer);
507    }
508}