/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/

package org.jboss.resource.adapter.jms;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;

import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.IllegalStateException;
import javax.jms.JMSException;
import javax.jms.MessageListener;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.StreamMessage;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import javax.resource.spi.ConnectionEvent;

import org.jboss.logging.Logger;

// make sure we throw the jmx variety
import javax.jms.IllegalStateException;

/**
 * Adapts the JMS QueueSession and TopicSession API to a JmsManagedConnection.
 * 
 * <p>Created: Tue Apr 17 22:39:45 2001
 * 
 * @author <a href="mailto:peter.antman@tim.se">Peter Antman </a>.
 * @author <a href="mailto:jason@planet57.com">Jason Dillon </a>.
 * @version $Revision: 1.1.4.2 $
 */
public class JmsSession implements Session, QueueSession, TopicSession
{
   private static final Logger log = Logger.getLogger(JmsSession.class);

   /** The managed connection for this session. */
   private JmsManagedConnection mc; // = null;

   /** The connection request info */
   private JmsConnectionRequestInfo info;
   
   /** The session factory for this session */
   private JmsSessionFactory sf;
   
   /** The message consumers */
   private HashSet consumers = new HashSet();
   
   /** The message producers */
   private HashSet producers = new HashSet();
   
   /**
    * Construct a <tt>JmsSession</tt>.
    * 
    * @param mc The managed connection for this session.
    */
   public JmsSession(final JmsManagedConnection mc, final JmsConnectionRequestInfo info)
   {
      this.mc = mc;
      this.info = info;
   }

   public void setJmsSessionFactory(JmsSessionFactory sf)
   {
      this.sf = sf;
   }
   
   /**
    * Ensure that the session is opened.
    * 
    * @return The session
    * 
    * @throws IllegalStateException The session is closed
    */
   Session getSession() throws JMSException
   {
      // ensure that the connection is opened
      if (mc == null)
         throw new IllegalStateException("The session is closed");

      return mc.getSession();
   }

   /**
    * Ensure we have a queue session
    * 
    * @return a queue session
    * @throws IllegalStateException for a non topic session
    */
   QueueSession getQueueSession() throws JMSException
   {
      Session session = getSession();
      if (session instanceof QueueSession)
         return (QueueSession) session;
      else
         throw new IllegalStateException("Not a queue session");
   }

   /**
    * Ensure we have a topic session
    * 
    * @return a topic session
    * @throws IllegalStateException for a non topic session
    */
   TopicSession getTopicSession() throws JMSException
   {
      Session session = getSession();
      if (session instanceof TopicSession)
         return (TopicSession) session;
      else
         throw new IllegalStateException("Not a topic session");
   }
   
   // ---- Session API

   public BytesMessage createBytesMessage() throws JMSException
   {
      return getSession().createBytesMessage();
   }

   public MapMessage createMapMessage() throws JMSException
   {
      return getSession().createMapMessage();
   }

   public Message createMessage() throws JMSException
   {
      return getSession().createMessage();
   }

   public ObjectMessage createObjectMessage() throws JMSException
   {
      return getSession().createObjectMessage();
   }

   public ObjectMessage createObjectMessage(Serializable object) throws JMSException
   {
      return getSession().createObjectMessage(object);
   }

   public StreamMessage createStreamMessage() throws JMSException
   {
      return getSession().createStreamMessage();
   }

   public TextMessage createTextMessage() throws JMSException
   {
      return getSession().createTextMessage();
   }

   public TextMessage createTextMessage(String string) throws JMSException
   {
      return getSession().createTextMessage(string);
   }

   public boolean getTransacted() throws JMSException
   {
      getSession(); // check closed
      return info.isTransacted();
   }

   /**
    * Always throws an Exception.
    * 
    * @throws IllegalStateException Method not allowed.
    */
   public MessageListener getMessageListener() throws JMSException
   {
      throw new IllegalStateException("Method not allowed");
   }

   /**
    * Always throws an Exception.
    * 
    * @throws IllegalStateException Method not allowed.
    */
   public void setMessageListener(MessageListener listener) throws JMSException
   {
      throw new IllegalStateException("Method not allowed");
   }

   /**
    * Always throws an Error.
    * 
    * @throws Error Method not allowed.
    */
   public void run()
   {
      // should this really throw an Error?
      throw new Error("Method not allowed");
   }

   /**
    * Closes the session. Sends a ConnectionEvent.CONNECTION_CLOSED to the
    * managed connection.
    * 
    * @throws JMSException Failed to close session.
    */
   public void close() throws JMSException
   {
      sf.closeSession(this);
      closeSession();
   }

   // FIXME - is this really OK, probably not
   public void commit() throws JMSException
   {
      Session session = getSession();
      if (info.isTransacted() == false)
         throw new IllegalStateException("Session is not transacted");
      session.commit();
   }

   public void rollback() throws JMSException
   {
      Session session = getSession();
      if (info.isTransacted() == false)
         throw new IllegalStateException("Session is not transacted");
      session.rollback();
   }

   public void recover() throws JMSException
   {
      Session session = getSession();
      if (info.isTransacted())
         throw new IllegalStateException("Session is transacted");
      session.recover();
   }

   // --- TopicSession API

   public Topic createTopic(String topicName) throws JMSException
   {
      return getTopicSession().createTopic(topicName);
   }

   public TopicSubscriber createSubscriber(Topic topic) throws JMSException
   {
      TopicSubscriber result = ((TopicSession) getSession()).createSubscriber(topic);
      result = new JmsTopicSubscriber(result, this);
      addConsumer(result);
      return result;
   }

   public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) throws JMSException
   {
      TopicSubscriber result = ((TopicSession) getSession()).createSubscriber(topic, messageSelector, noLocal);
      result = new JmsTopicSubscriber(result, this);
      addConsumer(result);
      return result;
   }

   public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException
   {
      TopicSubscriber result = getTopicSession().createDurableSubscriber(topic, name);
      result = new JmsTopicSubscriber(result, this);
      addConsumer(result);
      return result;
   }

   public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal)
         throws JMSException
   {
      TopicSubscriber result = getTopicSession().createDurableSubscriber(topic, name, messageSelector, noLocal);
      result = new JmsTopicSubscriber(result, this);
      addConsumer(result);
      return result;
   }

   public TopicPublisher createPublisher(Topic topic) throws JMSException
   {
      TopicPublisher result = getTopicSession().createPublisher(topic);
      addProducer(result);
      return result;
   }

   public TemporaryTopic createTemporaryTopic() throws JMSException
   {
      TemporaryTopic temp = getTopicSession().createTemporaryTopic();
      sf.addTemporaryTopic(temp);
      return temp;
   }

   public void unsubscribe(String name) throws JMSException
   {
      getTopicSession().unsubscribe(name);
   }

   //--- QueueSession API

   public QueueBrowser createBrowser(Queue queue) throws JMSException
   {
      return getQueueSession().createBrowser(queue);
   }

   public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException
   {
      return getQueueSession().createBrowser(queue, messageSelector);
   }

   public Queue createQueue(String queueName) throws JMSException
   {
      return getQueueSession().createQueue(queueName);
   }

   public QueueReceiver createReceiver(Queue queue) throws JMSException
   {
      QueueReceiver result = getQueueSession().createReceiver(queue);
      result = new JmsQueueReceiver(result, this);
      addConsumer(result);
      return result;
   }

   public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException
   {
      QueueReceiver result = getQueueSession().createReceiver(queue, messageSelector);
      result = new JmsQueueReceiver(result, this);
      addConsumer(result);
      return result;
   }

   public QueueSender createSender(Queue queue) throws JMSException
   {
      QueueSender result = getQueueSession().createSender(queue);
      addProducer(result);
      return result;
   }

   public TemporaryQueue createTemporaryQueue() throws JMSException
   {
      TemporaryQueue temp = getQueueSession().createTemporaryQueue();
      sf.addTemporaryQueue(temp);
      return temp;
   }

   // --- JmsManagedConnection api

   void setManagedConnection(final JmsManagedConnection mc)
   {
      if (this.mc != null)
         this.mc.removeHandle(this);
      this.mc = mc;
   }

   void destroy()
   {
      mc = null;
   }

   void start() throws JMSException
   {
      if (mc != null)
         mc.start();
   }

   void stop() throws JMSException
   {
      if (mc != null)
         mc.stop();
   }

   void checkStrict() throws JMSException
   {
      if (mc != null && mc.getManagedConnectionFactory().isStrict())
         throw new IllegalStateException("Method not allowed");
   }
   
   void closeSession() throws JMSException
   {
      if (mc != null)
      {
         log.trace("Closing session");

         try
         {
            mc.stop();
         }
         catch (Throwable t)
         {
            log.trace("Error stopping managed connection", t);
         }
         
         synchronized (consumers)
         {
            for (Iterator i = consumers.iterator(); i.hasNext();)
            {
               JmsMessageConsumer consumer = (JmsMessageConsumer) i.next();
               try
               {
                  consumer.closeConsumer();
               }
               catch (Throwable t)
               {
                  log.trace("Error closing consumer", t);
               }
               i.remove();
            }
         }

         synchronized (producers)
         {
            for (Iterator i = producers.iterator(); i.hasNext();)
            {
               MessageProducer producer = (MessageProducer) i.next();
               try
               {
                  producer.close();
               }
               catch (Throwable t)
               {
                  log.trace("Error closing producer", t);
               }
               i.remove();
            }
         }
         
         mc.removeHandle(this);
         ConnectionEvent ev = new ConnectionEvent(mc, ConnectionEvent.CONNECTION_CLOSED);
         ev.setConnectionHandle(this);
         mc.sendEvent(ev);
         mc = null;
      }
   }
   
   void addConsumer(MessageConsumer consumer)
   {
      synchronized (consumers)
      {
         consumers.add(consumer);
      }
   }
   
   void removeConsumer(MessageConsumer consumer)
   {
      synchronized (consumers)
      {
         consumers.remove(consumer);
      }
   }
   
   void addProducer(MessageProducer producer)
   {
      synchronized (producers)
      {
         producers.add(producer);
      }
   }
   
   void removeProducer(MessageProducer producer)
   {
      synchronized (producers)
      {
         producers.remove(producer);
      }
   }
}
