/*
 * JORAM: Java(TM) Open Reliable Asynchronous Messaging
 * Copyright (C) 2004 - 2025 ScalAgent Distributed Technologies
 *
 * 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.
 */
package org.ow2.joram.jakarta.connector;

import java.util.Vector;

import jakarta.jms.Connection;
import jakarta.jms.ConnectionConsumer;
import jakarta.jms.ConnectionMetaData;
import jakarta.jms.Destination;
import jakarta.jms.ExceptionListener;
import jakarta.jms.IllegalStateException;
import jakarta.jms.JMSException;
import jakarta.jms.ServerSessionPool;
import jakarta.jms.Session;
import jakarta.jms.Topic;
import jakarta.jms.XAConnection;
import jakarta.resource.ResourceException;
import jakarta.resource.spi.ManagedConnection;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.common.Debug;
import fr.dyade.aaa.util.management.MXWrapper;

/**
 * An <code>OutboundConnection</code> instance is a handler for a physical
 * connection to an underlying JORAM server, allowing a component to
 * transparently use this physical connection possibly within a transaction
 * (local or global).
 */
public class OutboundConnection implements Connection, OutboundConnectionMBean {
  private static final Logger logger = Debug.getLogger(OutboundConnection.class.getName());
  private static final boolean DEBUG = logger.isLoggable(BasicLevel.DEBUG);
  
  /** The managed connection this "handle" belongs to. */
  ManagedConnectionImpl managedCx;
  /** The physical connection this "handle" handles. */
  XAConnection xac;
  /** <code>true</code> if this "handle" is valid. */
  boolean valid = true;
  /** Vector of the connection's sessions. */
  Vector<OutboundSession> sessions;
 
  /**
   * Constructs an <code>OutboundConnection</code> instance.
   *
   * @param managedCx  The managed connection building the handle.
   * @param xac        The underlying physical connection to handle.
   */
  OutboundConnection(ManagedConnectionImpl managedCx, XAConnection xac) {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "OutboundConnection(" + managedCx + ", " + xac + ")");

    this.managedCx = managedCx;
    this.xac = xac;
    sessions = new Vector<OutboundSession>();
    
    registerMBean();
  }

  /**
   * Returns <code>true</code> if the parameter is a <code>Connection</code> instance sharing the same
   * proxy identifier and connection key.
   */
  public boolean cnxEquals(Object obj) {
    return (obj instanceof Connection) && xac.equals(obj);
  }

  public String toString() {
    return "OutboundConnection[" + xac.toString() + "]@" + hashCode();
  }

  /**
   * Close all sessions.
   */
  public void cleanup() {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, this + " cleanup()");
    
    // JORAM-335: Don't close underlying session => cleanup(false)
    ((org.ow2.joram.jakarta.jms.Connection) xac).cleanup(true);
    
    // unregister the OutboundConnection mbean created by ManagedConnectionImpl.getConnection(null,null).
    unregisterMBean();
  }

  // ************************************************************
  // Implementation of Connection
  // ************************************************************

  @Override
  public Session createSession() throws JMSException {
    return createSession(false, Session.AUTO_ACKNOWLEDGE);
  }

  @Override
  public Session createSession(int sessionMode) throws JMSException {
    if (sessionMode == Session.SESSION_TRANSACTED) {
      return createSession(true, Session.SESSION_TRANSACTED);
    } else if(sessionMode == Session.CLIENT_ACKNOWLEDGE || 
        sessionMode == Session.AUTO_ACKNOWLEDGE || 
        sessionMode == Session.DUPS_OK_ACKNOWLEDGE) {
      return createSession(false, sessionMode);
    } else {
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, "Unrecognised sessionMode given as parameter: " + sessionMode);
      throw new JMSException("Error occured in session creation");
    }
  }
 
  /**
   * Returns the unique authorized JMS session per connection wrapped
   * in an <code>OutboundSession</code> instance.
   *
   * @exception IllegalStateException  If the handle is invalid.
   * @exception JMSException           Generic exception.
   */
  @Override
  public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, this + " createSession(" + transacted + ", " + acknowledgeMode + ")");

    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    Session sess = createManagedSession(acknowledgeMode);
    return new OutboundSession(sess, this, transacted);
  }

  Session createManagedSession(int acknowledgeMode) throws JMSException {
    // TODO (AF): JORAM-335, if needed the creation of the effective session should be done by the ManagedConnection,
    // so the getSession should never return null (and it shall be synchronized).
    Session sess = managedCx.getSession();
    if (DEBUG)
    	logger.log(BasicLevel.DEBUG, this + " createSession managedCx.session = " + sess);

    if (sess == null) {
    	try {
    		sess = xac.createSession(false, acknowledgeMode);
    		// JORAM-327: managedCx.session should be set with the created session. May be, we should deal
    		// with potential synchronization issues.
    		managedCx.setSession(sess);
    	} catch (IllegalStateException e) {
        if (DEBUG)
          logger.log(BasicLevel.WARN, this + " createSession reconnection in progress...", e);
        else
          logger.log(BasicLevel.WARN, this + " createSession reconnection in progress...");
    		
    		try {
    			if (managedCx.isReconnected()) {
    				ManagedConnection mc = managedCx.mcf.createManagedConnection(managedCx.subject, managedCx.cxRequest);
    				OutboundConnection outboundConnection = (OutboundConnection) mc.getConnection(managedCx.subject, managedCx.cxRequest);
    				outboundConnection.managedCx.associateConnection(this);
    			} else {
            if (DEBUG)
    					logger.log(BasicLevel.DEBUG, this + " createSession : managed connection is not reconnected.");
    				throw new JMSException(this + " createSession : managed connection is not reconnected.");
    			}
    			sess = xac.createSession(false, acknowledgeMode);
          // Joram-327: managedCx.session should be set with the created session. May be, we should deal
          // with potential synchronization issues.
    			managedCx.setSession(sess);
    		} catch (ResourceException exc) {
    			if (logger.isLoggable(BasicLevel.WARN))
    				logger.log(BasicLevel.WARN, this + " createSession (ResourceException)", exc);
    		}
    	}
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, this + " createSession new session = " + sess);
    } else {
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, this + " createSession retrieve session = " + sess);
    }
    
    return sess;
  }

  /**
   * Forbidden call on an application or component's outbound connection,
   * throws a <code>IllegalStateException</code> instance.
   */
  @Override
  public String getClientID() throws JMSException {
    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    throw new IllegalStateException("Forbidden call on a component's connection.");
  }

  /**
   * Forbidden call on an application or component's outbound connection,
   * throws a <code>IllegalStateException</code> instance.
   */
  @Override
  public void setClientID(String clientID) throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, this + " setClientID(" + clientID + ")");
    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    throw new IllegalStateException("Forbidden call on a component's connection.");
  }
  
  /**
   * Delegates the call to the wrapped JMS connection.
   *
   * @exception IllegalStateException  If the handle is invalid.
   * @exception JMSException           Generic exception.
   */
  @Override
  public ConnectionMetaData getMetaData() throws JMSException {
    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    return xac.getMetaData();
  }

  /**
   * Forbidden call on an application or component's outbound connection,
   * throws a <code>IllegalStateException</code> instance.
   */
  @Override
  public ExceptionListener getExceptionListener() throws JMSException {
    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    throw new IllegalStateException("Forbidden call on a component's connection.");
  }

  /**
   * Forbidden call on an application or component's outbound connection,
   * throws a <code>IllegalStateException</code> instance.
   */
  @Override
  public void setExceptionListener(ExceptionListener listener) throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, this + " setExceptionListener(" + listener + ")");

    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    throw new IllegalStateException("Forbidden call on a component's connection.");
  }

  /**
   * Delegates the call to the wrapped JMS connection.
   *
   * @exception IllegalStateException  If the handle is invalid.
   * @exception JMSException           Generic exception.
   */
  @Override
  public void start() throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, this + " start()");

    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    xac.start();

    for (int i = 0; i < sessions.size(); i++) {
      OutboundSession session = sessions.get(i);
      session.start();

      if (DEBUG)
        logger.log(BasicLevel.DEBUG, this + " start session = " + session);
    }
  }

  /**
   * Forbidden call on an application or component's outbound connection,
   * throws a <code>IllegalStateException</code> instance.
   */
  @Override
  public void stop() throws JMSException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, this + " stop()");

    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    throw new IllegalStateException("Forbidden call on a component's connection.");
  }

  /**
   * Forbidden call on an application or component's outbound connection,
   * throws a <code>IllegalStateException</code> instance.
   */
  @Override
  public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector,
                               ServerSessionPool sessionPool, int maxMessages) throws JMSException {
    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    throw new IllegalStateException("Forbidden call on a component's connection.");
  }

  /**
   * Forbidden call on an application or component's outbound connection,
   * throws a <code>IllegalStateException</code> instance.
   */
  @Override
  public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector,
                                         ServerSessionPool sessionPool, int maxMessages) throws JMSException {
    if (! valid)
      throw new IllegalStateException("Invalid connection handle.");

    throw new IllegalStateException("Forbidden call on a component's connection.");
  }

  /**
   * Requests to close the physical connection.
   *
   * @exception IllegalStateException  If the handle is invalid.
   * @exception JMSException           Generic exception.
   */
  @Override
  public synchronized void close() throws JMSException {
    valid = false;

    if (DEBUG)
      logger.log(BasicLevel.DEBUG, this + " close()");

    for (int i = 0; i < sessions.size(); i++) {
      OutboundSession session = sessions.get(i);
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, this + " close() session = " + session);

      session.close();
      // TODO (AF): Should remove session from vector or set sessions to null
    }

    managedCx.closeHandle(this);
    
    unregisterMBean();
  }

  @Override
  public int getNumberOfSession() {
    return sessions.size();
  }

  @Override
  public String[] getSessions() {
    String[] sessTab = new String[sessions.size()];
    for (int i = 0; i < sessions.size(); i++) {
      OutboundSession outboundSess = sessions.get(i);
      try {
        sessTab[i] = outboundSess.sess.toString();
      } catch (Exception e) {
        sessTab[i] = "unknown";
      }
    }
    return sessTab;
  }

  public ConnectionConsumer createSharedConnectionConsumer(Topic topic,
      String subscriptionName, String messageSelector,
      ServerSessionPool sessionPool, int maxMessages) throws JMSException {
    // TODO ? Should be forbidden on OutboundConnection ?
    throw new JMSException("not yet implemented.");
  }

  public ConnectionConsumer createSharedDurableConnectionConsumer(Topic topic,
      String subscriptionName, String messageSelector,
      ServerSessionPool sessionPool, int maxMessages) throws JMSException {
    // TODO ? Should be forbidden on OutboundConnection ?
    throw new JMSException("not yet implemented.");
  }

  // ************************************************************
  // Handling of JMX
  // ************************************************************
  
  public String getJMXBeanName(XAConnection cnx) {
    if (! (cnx instanceof org.ow2.joram.jakarta.jms.Connection)) return null;
    StringBuffer buf = new StringBuffer();
    buf.append(((org.ow2.joram.jakarta.jms.Connection) cnx).getJMXBeanName());
    buf.append(",location=OutboundConnection");
    buf.append(",OutboundConnection=").append("OutboundConnection@").append(hashCode());
    return buf.toString();
  }

  public String registerMBean() {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "OutboundConnection.registerMBean: " + this);
    String JMXBeanName = getJMXBeanName(xac);
    try {
      if (JMXBeanName != null)
        MXWrapper.registerMBean(this, JMXBeanName);
    } catch (Exception e) {
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, "OutboundConnection.registerMBean: " + JMXBeanName, e);
    }

    return JMXBeanName;
  }

  public void unregisterMBean() {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, "OutboundConnection.unregisterMBean: " + this);
    try {
      MXWrapper.unregisterMBean(getJMXBeanName(xac));
    } catch (Exception e) {
      if (DEBUG)
        logger.log(BasicLevel.DEBUG, "OutboundConnection.unregisterMBean: " + getJMXBeanName(xac), e);
    }
  }
}
