/*
 * JORAM: Java(TM) Open Reliable Asynchronous Messaging
 * Copyright (C) 2004 - 2026 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.io.PrintWriter;
import java.util.Iterator;
import java.util.Set;

import jakarta.jms.IllegalStateException;
import jakarta.jms.JMSException;
import jakarta.jms.JMSSecurityException;
import jakarta.jms.XAConnection;
import jakarta.jms.XAConnectionFactory;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import jakarta.resource.ResourceException;
import jakarta.resource.spi.CommException;
import jakarta.resource.spi.ConnectionManager;
import jakarta.resource.spi.ConnectionRequestInfo;
import jakarta.resource.spi.ManagedConnection;
import jakarta.resource.spi.ManagedConnectionFactory;
import jakarta.resource.spi.ResourceAdapter;
import jakarta.resource.spi.ResourceAdapterAssociation;
import jakarta.resource.spi.SecurityException;
import jakarta.resource.spi.ValidatingManagedConnectionFactory;

import javax.security.auth.Subject;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import org.ow2.joram.jakarta.jms.admin.AbstractConnectionFactory;
import org.ow2.joram.jakarta.jms.local.LocalConnectionFactory;
import org.ow2.joram.jakarta.jms.tcp.TcpConnectionFactory;

import fr.dyade.aaa.common.Debug;

/**
 * A <code>ManagedConnectionFactoryImpl</code> instance manages
 * outbound connectivity to a given JORAM server.
 */
public class ManagedConnectionFactoryImpl extends ManagedConnectionFactoryConfig
                                          implements ManagedConnectionFactory, ResourceAdapterAssociation, ValidatingManagedConnectionFactory, java.io.Serializable {
  /** Define serialVersionUID for interoperability. */
  private static final long serialVersionUID = 1L;
  
  private static final Logger logger = Debug.getLogger(ManagedConnectionFactoryImpl.class.getName());
  private static final boolean DEBUG = logger.isLoggable(BasicLevel.DEBUG);

  /** Out stream for error logging and tracing. */
  protected transient PrintWriter out = null;

  /** Resource adapter central authority. */
  transient JoramResourceAdapter ra = null;

  /**
   * Constructs a <code>ManagedConnectionFactoryImpl</code> instance.
   */
  public ManagedConnectionFactoryImpl() {}

  /**
   * Method called by an application server (managed case) for creating an
   * <code>OutboundConnectionFactory</code> instance.
   *
   * @param cxManager  Application server's connections pooling manager.
   *
   * @exception ResourceException  Never thrown.
   */
  public Object createConnectionFactory(ConnectionManager cxManager)
  throws ResourceException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " createConnectionFactory(" + cxManager + ")");

    return new OutboundConnectionFactory(this, cxManager);
  }

  /**
   * Method called in the non managed case for creating an
   * <code>OutboundConnectionFactory</code> instance.
   *
   * @exception ResourceException  Never thrown.
   */
  public Object createConnectionFactory() throws ResourceException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " createConnectionFactory()");

    OutboundConnectionFactory factory = new OutboundConnectionFactory(this, null);
    
    Reference ref = new Reference(factory.getClass().getName(), "org.ow2.joram.jakarta.connector.ObjectFactoryImpl", null);
    ref.add(new StringRefAddr("hostName", getHostName()));
    ref.add(new StringRefAddr("serverPort", "" + getServerPort()));
    ref.add(new StringRefAddr("userName", getUserName()));
    ref.add(new StringRefAddr("password", getPassword()));
    ref.add(new StringRefAddr("identityClass", getIdentityClass()));

    factory.setReference(ref);
    return factory;
  }

  protected XAConnectionFactory createFactory(ConnectionRequestInfo cxRequest) throws ResourceException {
  	XAConnectionFactory factory = null;

  	String hostName = getHostName();
  	int serverPort = getServerPort();

  	if (DEBUG)
  	  logger.log(BasicLevel.DEBUG, getName() + " createFactory hostName = " + hostName + ", serverPort = " + serverPort);

  	if (isCollocated()) {
  	  factory = LocalConnectionFactory.create();
  	} else {
  	  factory = TcpConnectionFactory.create(hostName, serverPort);
  	}

  	if (ra == null) {
  	  setResourceAdapter(new JoramAdapter());
  	}
  	((AbstractConnectionFactory) factory).setCnxJMXBeanBaseName(ra.jmxRootName + '#' + ra.getName());

  	return factory;
  }
  
  protected XAConnection createXAConnection(XAConnectionFactory factory, String userName, String password) throws ResourceException {
  	XAConnection cnx = null;
  	 try {
       cnx = factory.createXAConnection(userName, password);

       if (DEBUG)
         logger.log(BasicLevel.DEBUG, getName() + " createManagedConnection cnx = " + cnx);
     } catch (IllegalStateException exc) {
       if (out != null)
         out.print("Could not access the JORAM server: " + exc);
       throw new CommException("Could not access the JORAM server: " + exc);
     } catch (JMSSecurityException exc) {
       if (out != null)
         out.print("Invalid user identification: " + exc);
       throw new SecurityException("Invalid user identification: " + exc);
     } catch (JMSException exc) {
       if (out != null)
         out.print("Failed connecting process: " + exc);
       throw new ResourceException("Failed connecting process: " + exc);
     }
     return cnx;
  }
  
  /**
   * Creates a new physical connection to the underlying JORAM server,
   * and returns a <code>ManagedConnectionImpl</code> instance for a
   * managed environment.
   *
   * @param subject        Security data, not taken into account.
   * @param cxRequest      User identification data, may be <code>null</code>.
   *
   * @exception CommException          If the JORAM server is not reachable.
   * @exception SecurityException      If the connecting is not allowed.
   * @exception IllegalStateException  If the central Joram adapter state is
   *                                    invalid.
   * @exception ResourceException      If the provided user info is invalid,
   *                                   or if connecting fails for any other
   *                                   reason.
   */
  public final ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo cxRequest) throws ResourceException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " createManagedConnection(" + subject + ", " + cxRequest + ")");

    String userName;
    String password;
    String identityClass;

    String hostName = getHostName();
    int serverPort = getServerPort();

    // For XA recovery, connecting to the JORAM server with the default user
    // identity.
    if (cxRequest == null) {
      userName = getUserName();
      password = getPassword();
      identityClass = getIdentityClass();
    } else {
      if (! (cxRequest instanceof ConnectionRequest)) {
        if (out != null)
          out.print("Provided ConnectionRequestInfo instance is not a JORAM object.");
        throw new ResourceException("Provided ConnectionRequestInfo instance is not a JORAM object.");
      }

      userName = ((ConnectionRequest) cxRequest).getUserName();
      password = ((ConnectionRequest) cxRequest).getPassword();
      identityClass = ((ConnectionRequest) cxRequest).getIdentityClass();
    }

    XAConnectionFactory factory = createFactory(cxRequest);
    setParameters(factory);
    ((AbstractConnectionFactory) factory).setIdentityClassName(identityClass);
    
    XAConnection cnx = createXAConnection(factory, userName, password);

    if (ra == null) {
      setResourceAdapter(new JoramAdapter());
    }
    
    ManagedConnectionImpl managedCx = new ManagedConnectionImpl(ra, this, cnx, hostName, serverPort, userName);
    managedCx.setLogWriter(out);
    // for reconnection 
    managedCx.subject = subject;
    managedCx.cxRequest = cxRequest;
    
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " createManagedConnection managedCx = " + managedCx);

    return managedCx;
  }

  /**
   * Finds a matching connection from the candidate set of connections and
   * returns a <code>ManagedConnectionImpl</code> instance.
   *
   * @param connectionSet  Set of connections to test.
   * @param subject        Security data, not taken into account.
   * @param cxRequest      User identification data, may be <code>null</code>.
   *
   * @exception ResourceException  If the provided connection request info is
   *                               invalid.
   */
  @Override
  public final ManagedConnection matchManagedConnections(Set connectionSet, Subject subject, ConnectionRequestInfo cxRequest) throws ResourceException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " matchManagedConnections(" + connectionSet + ", " + subject + ", " + cxRequest + ")");

    String userName;

    // No user identification provided, using the default one.
    String mode = ManagedConnectionFactoryConfig.MODE_UNIFIED;

    if (cxRequest == null)
      userName = getUserName();
    else {
      if (! (cxRequest instanceof ConnectionRequest)) {
        out.print("Provided ConnectionRequestInfo instance is not a JORAM object.");
        throw new ResourceException("Provided ConnectionRequestInfo instance is not a JORAM object.");
      }

      if (cxRequest instanceof QueueConnectionRequest)
        mode = ManagedConnectionFactoryConfig.MODE_PTP;
      else if (cxRequest instanceof TopicConnectionRequest)
        mode = ManagedConnectionFactoryConfig.MODE_PUBSUB;

      userName = ((ConnectionRequest) cxRequest).getUserName();
    }

    ManagedConnectionImpl managedCx = null;
    boolean matching = false;

    Iterator it = connectionSet.iterator();

    String hostName = getHostName();
    int serverPort = getServerPort();

    if (DEBUG)
      logger.log(BasicLevel.DEBUG,
                 getName() + " hostName = " +  hostName + ", serverPort = " + serverPort);
    if (isCollocated()) {
      hostName = "localhost";
      serverPort = -1;
    }

    while (! matching && it.hasNext()) {
      try {
        managedCx = (ManagedConnectionImpl) it.next();
        matching = managedCx.matches(hostName, serverPort, userName, mode);
      } catch (ClassCastException exc) {
        logger.log(BasicLevel.WARN,
                   getName() + " matchManagedConnections managedCx = " + managedCx, exc);
      }
    }

    if (matching) {
      if (DEBUG)
        logger.log(BasicLevel.DEBUG,
                   getName() + " matchManagedConnections managedCx = " + managedCx);
      managedCx.setLogWriter(out);
      return managedCx;
    }

    return null;
  }



  /**
   * Sets the log writer for this <code>ManagedConnectionFactoryImpl</code>
   * instance.
   */
  public void setLogWriter(PrintWriter out) throws ResourceException {
    this.out = out;
  }

  /**
   * Gets the log writer of this <code>ManagedConnectionFactoryImpl</code>
   * instance.
   */
  public PrintWriter getLogWriter() throws ResourceException {
    return out;
  }

  public String getName() {
    return "Unified:" + getHostName() + ':' + getServerPort() + '-' + getUserName();
  }
  
  /** Returns a code depending on the managed factory configuration. */
  public int hashCode() {
    return getName().hashCode();
  }

  /** Compares managed factories according to their configuration. */
  public boolean equals(Object o) {
    if (! (o instanceof ManagedConnectionFactoryImpl)
        || o instanceof ManagedQueueConnectionFactoryImpl
        || o instanceof ManagedTopicConnectionFactoryImpl)
      return false;

    ManagedConnectionFactoryImpl other = (ManagedConnectionFactoryImpl) o;

    boolean res = getHostName().equals(other.getHostName()) && getServerPort().equals(other.getServerPort()) && getUserName().equals(other.getUserName());

    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " equals " + res);
    return res;
  }

  /** Returns the resource adapter central authority instance. */
  public ResourceAdapter getResourceAdapter() {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " getResourceAdapter() = " + ra);
    return ra;
  }

  /**
   * Sets the resource adapter central authority.
   *
   * @exception ResourceException  If the adapter could not be set.
   */
  public void setResourceAdapter(ResourceAdapter ra) throws ResourceException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " setResourceAdapter(" + ra + ")");

    if (this.ra != null) {
      String error = "ResourceAdapter instance already associated.";
      out.print(error);
      throw new jakarta.resource.spi.IllegalStateException(error);
    }

    if (! (ra instanceof JoramAdapter)) {
      String error = "Provided ResourceAdapter is not a JORAM ResourceAdapter object: " + ra.getClass().getName();
      out.print(error);
      throw new ResourceException(error);
    }

    this.ra = (JoramAdapter) ra;
    
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, 
                 getName() + " setResourceAdapter isCollocated = " + ((JoramAdapter) ra).getCollocated() + ", serverPort = " + ((JoramAdapter) ra).getServerPort());
    
    if (((JoramAdapter) ra).getCollocated()) {
      if (getServerPort() < 0) {
        setServerPort(((JoramAdapter) ra).getServerPort());
      }
    }
  }

  /**
   * From a set of managed connections, returns the set of invalid ones.
   */
  @Override
  public Set<ManagedConnectionImpl> getInvalidConnections(Set connectionSet) throws ResourceException {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG, getName() + " getInvalidConnections(" + connectionSet + ")");

    Iterator it = connectionSet.iterator();
    ManagedConnectionImpl managedCx;

    java.util.HashSet<ManagedConnectionImpl> invalidConnections = new java.util.HashSet<ManagedConnectionImpl>();
    while (it.hasNext()) {
      try {
        managedCx = (ManagedConnectionImpl) it.next();
        if (!managedCx.isValid())
          invalidConnections.add(managedCx);
      }
      catch (ClassCastException exc) {}
    }

    return invalidConnections; 
  }
}
