/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_jms;

import java.util.LinkedList;

import javax.jms.Connection;
import javax.jms.ConnectionConsumer;
import javax.jms.ConnectionMetaData;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.InvalidClientIDException;
import javax.jms.JMSException;
import javax.jms.ServerSessionPool;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.XAConnection;
import javax.jms.XAConnectionFactory;

import org.objectweb.transaction.jta.TransactionManager;
import org.objectweb.util.monolog.api.BasicLevel;

/**
 * Common methods used in JQueueConnection and JTopicConnection.
 *
 * @author Laurent Chauvirey, Frederic Maistre, Nicolas Tachker
 * Contributor(s):
 * Philippe Durieux
 * Jeff Mesnil connection anonymous
 * Philippe Coq JMS 1.1 integration
 */

public class JConnection implements Connection {

    // The XAConnection used in the MOM
    protected XAConnection xac;

    protected boolean closed;
    protected String user;
    protected boolean globaltx = false;
    protected static TransactionManager tm;
    protected JConnectionFactory jcf;
    protected LinkedList sessionlist = new LinkedList();

    // This constant is used to determine connection
    // with an anonymous user in the pool of JConnection.
    protected static final String INTERNAL_USER_NAME = "anInternalNameUsedOnlyByJOnAS";

    /**
     * Prepares the construction of a JConnection.
     */
    protected JConnection(JConnectionFactory jcf, String user)  throws JMSException {
        this.user = user;
        this.jcf = jcf;
        closed = false;
        if (tm == null) {
            tm = JmsManagerImpl.getTransactionManager();
        }
        // Remember if we are inside a global transaction
        // This is to handle a case that is not expected in JMS specs.
        try {
            globaltx = (tm.getTransaction() != null);
        } catch (Exception e) {
        }
    }


    /**
     * Constructor of a JConnection for a specified user.
     *
     * @param user user's name
     * @param passwd user's password
     */
    public JConnection(JConnectionFactory jcf, XAConnectionFactory xacf, String user, String passwd)
        throws JMSException {
        this(jcf, user);
        // Create the underlaying XAConnection
        xac = xacf.createXAConnection(user, passwd);
    }

    /**
     * Constructor of a JConnection for an anonymous user.
     */
    public JConnection(JConnectionFactory jcf, XAConnectionFactory xacf) throws JMSException {
        this(jcf, INTERNAL_USER_NAME);
        // Create the underlaying XAConnection
        xac = xacf.createXAConnection();
    }

    // -----------------------------------------------------------------------
    // internal methods
    // -----------------------------------------------------------------------

    /**
     * A new non transacted session has been opened
     */
    protected synchronized boolean sessionOpen(Session s) {
        if (!closed) {
            sessionlist.add(s);
            return true;
        } else {
            return false;
        }
    }

    /**
     * A non transacted session has beem closed
     */
    protected synchronized void sessionClose(Session s) {
        sessionlist.remove(s);
        if (sessionlist.size() == 0 && closed) {
            notify();
        }
    }

    /**
     * Return the user associated to this connection
     */
    public String getUser() {
        return user;
    }

    // -----------------------------------------------------------------------
    // Connection implementation
    // -----------------------------------------------------------------------

    /**
     * When this method is invoked it should not return until message processing
     * has been orderly shut down. This means that all message listeners that may
     * have been running have returned and that all pending receives have returned.
     * A close terminates all pending message receives on the connection's sessions'
     * consumers.
     * @throws JMSException - if JMS implementation fails to return the client ID for this
     * Connection due to some internal
     */
    public void close() throws JMSException {
        TraceJms.logger.log(BasicLevel.DEBUG, "");
        if (globaltx) {
            // Connection that was open inside a global transaction.
            // Don't wait (to avoid deadlocks) and don't close it now
            // Since this situation is not expected by the specs, we just
            // pool this connection for now, waiting better...
            jcf.freeJConnection(this);
        } else {
            // Wait for all NON transacted sessions to be finished.
            // LATER: Should rollback first all transacted sessions still running.
            synchronized(this) {
                while (sessionlist.size() > 0) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        TraceJms.logger.log(BasicLevel.ERROR, "interrupted");
                    }
                }
            }
            closed = true;
            xac.close();
        }
    }
    public void finalClose() throws JMSException {
        if (!closed) {
            xac.close();
        }
    }

    /**
     * Creates a connection consumer for this connection (optional operation)
     * @param destination - the destination to access
     * @param messageSelector - only messages with properties matching
     *                          the message selector expression are delivered.
     *                          A value of null or an empty string indicates that
     *                          there is no message selector for the message consumer.
     * @param sessionPool - the server session pool to associate with this connection consumer
     * @param maxMessages - the maximum number of messages that can be assigned to a server
     *                      session at one time
     * @return the connection consumer
     */

    public ConnectionConsumer createConnectionConsumer(Destination destination,
                                                       java.lang.String messageSelector,
                                                       ServerSessionPool sessionPool,
                                                       int maxMessages)
        throws JMSException {
        TraceJms.logger.log(BasicLevel.DEBUG, "");
        return xac.createConnectionConsumer(destination,
                                            messageSelector,
                                            sessionPool,
                                            maxMessages);
    }

    /**
     * Creates a connection consumer for this connection (optional operation)
     * @param topic - the topic to access
     * @param subscriptionName - durable subscription name
     * @param messageSelector - only messages with properties matching
     *                          the message selector expression are delivered.
     *                          A value of null or an empty string indicates that
     *                          there is no message selector for the message consumer.
     * @param sessionPool - the server session pool to associate with this connection consumer
     * @param maxMessages - the maximum number of messages that can be assigned to a server
     *                      session at one time
     * @return the durable connection consumer
     */
    public ConnectionConsumer createDurableConnectionConsumer(Topic topic,
                                                          java.lang.String subscriptionName,
                                                          java.lang.String messageSelector,
                                                          ServerSessionPool sessionPool,
                                                          int maxMessages)
        throws JMSException {
        TraceJms.logger.log(BasicLevel.DEBUG, "");
        return xac.createDurableConnectionConsumer(topic,
                                                   subscriptionName,
                                                   messageSelector,
                                                   sessionPool,
                                                   maxMessages);
    }


    /**
     * Creates a Session object.
     * @param transacted - indicates whether the session is transacted
     * @param acknowledgeMode indicates whether the consumer or the client
     *                        will acknowledge any messages it receives;
     *                        ignored if the session is transacted.
     *        Legal values are Session.AUTO_ACKNOWLEDGE, Session.CLIENT_ACKNOWLEDGE,
     *        and Session.DUPS_OK_ACKNOWLEDGE.
     */

    public Session createSession(boolean transacted,int acknowledgeMode) throws JMSException {
        TraceJms.logger.log(BasicLevel.DEBUG, "");
        return new JSession(this, xac);
    }


    /**
     * Get the client identifier for this connection. This value is JMS Provider specific.
     * Either pre-configured by an administrator in a ConnectionFactory or assigned dynamically
     * by the application by calling setClientID method.
     * @return the unique client identifier.
     * @throws JMSException - if JMS implementation fails to return the client ID for this
     * Connection due to some internal
     */
    public String getClientID() throws JMSException {
    TraceJms.logger.log(BasicLevel.DEBUG, "");
    return xac.getClientID();
    }

    /**
     * Set the client identifier for this connection.
     * If another connection with clientID is already running when this method is called,
     * the JMS Provider should detect the duplicate id and throw InvalidClientIDException.
     * @param clientID - the unique client identifier
     * @throws JMSException - general exception if JMS implementation fails to set the client
     * ID for this Connection due to some internal error.
     * @throws InvalidClientIDException - if JMS client specifies an invalid or duplicate client id.
     * @throws IllegalStateException - if attempting to set a connection's client identifier at
     * the wrong time or when it has been administratively configured.
     */
    public void setClientID(String clientID) throws JMSException {
    TraceJms.logger.log(BasicLevel.DEBUG, "");
    xac.setClientID(clientID);
    }

    /**
     * Get the meta data for this connection.
     * @return the connection meta data.
     * @throws JMSException - general exception if JMS implementation fails to get the Connection
     * meta-data for this Connection.
     */
    public ConnectionMetaData getMetaData() throws JMSException {
    TraceJms.logger.log(BasicLevel.DEBUG, "");
    return xac.getMetaData();
    }

    /**
     * Get the ExceptionListener for this Connection.
     * @return the ExceptionListener for this Connection.
     * @throws JMSException - general exception if JMS implementation fails to get
     * the Exception listener for this Connection.
     */
    public ExceptionListener getExceptionListener() throws JMSException {
    TraceJms.logger.log(BasicLevel.DEBUG, "");
    return xac.getExceptionListener();
    }

    /**
     * Set an exception listener for this connection.
     * @param listener - the exception listener.
     * @throws JMSException - general exception if JMS implementation fails to set
     * the Exception listener for this Connection.
     */
    public void setExceptionListener(ExceptionListener listener) throws	JMSException {
    TraceJms.logger.log(BasicLevel.DEBUG, "");
    xac.setExceptionListener(listener);
    }

    /**
     * Start (or restart) a Connection's delivery of incoming messages.
     * @throws JMSException - if JMS implementation fails to start the message
     * delivery due to some internal error.
     */
    public void start() throws JMSException {
    TraceJms.logger.log(BasicLevel.DEBUG, "");
    xac.start();
    }

    /**
     * Used to temporarily stop a Connection's delivery of incoming messages.
     * It can be restarted using its start method.
     * When stopped, delivery to all the Connection's message consumers is inhibited:
     * synchronous receive's block and messages are not delivered to message listeners.
     * @throws JMSException - if JMS implementation fails to start the message
     * delivery due to some internal error.
     */
    public void stop() throws JMSException {
    TraceJms.logger.log(BasicLevel.DEBUG, "");
    xac.stop();
    }
}
