User: allsopp 
  Date: 01/01/14 21:06:58

  Added:       src/main/org/jboss/resource ConnectionFactoryConfig.java
                        ConnectionFactoryLoader.java
                        ConnectionFactoryLoaderMBean.java
                        ConnectionManagerImpl.java RARDeployer.java
                        RARDeployerMBean.java RARMetaData.java
  Log:
  First cut at the J2EE Connector Architecture.
  
  Revision  Changes    Path
  1.1                  jboss/src/main/org/jboss/resource/ConnectionFactoryConfig.java
  
  Index: ConnectionFactoryConfig.java
  ===================================================================
  /*
   * JBoss, the OpenSource EJB server
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.resource;
  
  /**
   *   Provides access to configuration parameters for a JCA connection
   *   factory.
   *
   *   @author Toby Allsopp ([EMAIL PROTECTED])
   *   @version $Revision: 1.1 $
   */
  public interface ConnectionFactoryConfig
  {
     // Constants -----------------------------------------------------
  
     // Static --------------------------------------------------------
  
     // Public --------------------------------------------------------
  
     /**
      * The name of the class implementing {@link
      * org.jboss.resource.security.PrincipalMapping} that is to be used
      * for mapping caller principals to resource principals for this
      * connection factory.
      */
     String getPrincipalMappingClass();
     void setPrincipalMappingClass(String className);
  
     /**
      * A string in a format parseable by {@link
      * java.util.Properties#load} that defines the properties to set on
      * the <code>PrincipalMapping</code> for this connection factory
      * instance.
      */
     String getPrincipalMappingProperties();
     void setPrincipalMappingProperties(String properties);
      
     // Connection pooling parameters
  
     /**
      * The name of the pool strategy to use - if not set then an
      * automagic setting is chosen.
      *
      * @see org.jboss.resource.pool.PoolStrategyFactory
      */
     String getPoolStrategy();
     void setPoolStrategy(String strategyName);
  
     void setMinSize(int minSize);
     int getMinSize();
     void setMaxSize(int maxSize);
     int getMaxSize();
     void setBlocking(boolean blocking);
     boolean getBlocking();
     void setGCEnabled(boolean gcEnabled);
     boolean getGCEnabled();
     void setGCInterval(long interval);
     long getGCInterval();
     void setGCMinIdleTime(long idleMillis);
     long getGCMinIdleTime();
     void setIdleTimeoutEnabled(boolean enabled);
     boolean getIdleTimeoutEnabled();
     void setIdleTimeout(long idleMillis);
     long getIdleTimeout();
     void setMaxIdleTimeoutPercent(float percent);
     float getMaxIdleTimeoutPercent();
     void setInvalidateOnError(boolean invalidate);
     boolean getInvalidateOnError();
     void setTimestampUsed(boolean timestamp);
     boolean getTimestampUsed();
  }
  
  
  
  1.1                  jboss/src/main/org/jboss/resource/ConnectionFactoryLoader.java
  
  Index: ConnectionFactoryLoader.java
  ===================================================================
  /*
   * JBoss, the OpenSource EJB server
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.resource;
  
  import java.beans.PropertyEditor;
  import java.beans.PropertyEditorManager;
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.lang.NoSuchMethodException;
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Properties;
  import java.util.StringTokenizer;
  
  import javax.management.InstanceNotFoundException;
  import javax.management.MBeanServer;
  import javax.management.MalformedObjectNameException;
  import javax.management.Notification;
  import javax.management.NotificationFilter;
  import javax.management.NotificationListener;
  import javax.management.ObjectName;
  import javax.management.ReflectionException;
  
  import javax.naming.Context;
  import javax.naming.InitialContext;
  import javax.naming.Name;
  import javax.naming.NamingException;
  import javax.naming.Reference;
  import javax.naming.spi.ObjectFactory;
  
  import javax.resource.Referenceable;
  import javax.resource.ResourceException;
  import javax.resource.spi.ConnectionManager;
  import javax.resource.spi.ManagedConnectionFactory;
  
  import javax.transaction.TransactionManager;
  
  import org.jboss.logging.Log;
  import org.jboss.logging.LogWriter;
  import org.jboss.resource.security.PrincipalMapping;
  import org.jboss.util.ServiceMBeanSupport;
  
  /**
   *   Service that configures an instance of a deployed resource
   *   adapter and binds the resulting connection factory into JNDI.
   *
   *   <p> This service does nothing until it receives a notification
   *   from the RAR deployer that the resource adapter has been
   *   deployed.
   *      
   *   @see RARDeployer
   *   @author Toby Allsopp ([EMAIL PROTECTED])
   *   @version $Revision: 1.1 $
   */
  public class ConnectionFactoryLoader
     extends ServiceMBeanSupport
     implements ConnectionFactoryLoaderMBean, NotificationListener, ObjectFactory
  {
     // Constants -----------------------------------------------------
  
     // Attributes ----------------------------------------------------
  
     private MBeanServer server = null;
  
     private String resourceAdapterName = null;
     private String factoryName = null;
     private String properties = null;
     private String rarDeployerName = null;
     private String tmName = "java:/TransactionManager";
  
     // Principal mapping parameters
     private String princMapClass;
     private String princMapProps;
  
     // Pool strategy parameters
     private String poolStrategy;
  
     // ObjectPool configuration parameters
     private int minSize;
     private int maxSize;
     private boolean blocking;
     private boolean gcEnabled;
     private long gcInterval;
     private long gcMinIdleTime;
     private boolean idleTimeoutEnabled;
     private long idleTimeout;
     private float maxIdleTimeoutPercent;
     private boolean invalidateOnError;
     private boolean timestampUsed;
  
     private ObjectName rarDeployerObjectName = null;
  
     /** The JNDI name to which this connection factory is bound */
     private String bindName;
  
     private ConnectionManagerImpl cm = null;
  
     /** Maps factory name to <code>ConnectionFactory</code> instance
         for JNDI lookups */
     private static Map cfs = new HashMap();
      
     // Static --------------------------------------------------------
     
     // Constructors --------------------------------------------------
     
     // Public --------------------------------------------------------
  
     // ConnectionFactoryLoaderMBean implementation -------------------
  
     public String getResourceAdapterName() { return resourceAdapterName; }
     public void setResourceAdapterName(String resourceAdapterName) {
        this.resourceAdapterName = resourceAdapterName;
     }
  
     public String getFactoryName() { return factoryName; }
     public void setFactoryName(String name) { this.factoryName = name; }
  
     public String getProperties() { return properties; }
     public void setProperties(String p) { this.properties = p; }
  
     public String getRARDeployerName() { return rarDeployerName; }
     public void setRARDeployerName(String rarDeployerName)
     {
        this.rarDeployerName = rarDeployerName;
     }
  
     public String getTransactionManagerName() { return tmName; }
     public void setTransactionManagerName(String n) { tmName = n; }
  
     // Pincipal mapping settings
  
     public String getPrincipalMappingClass() { return princMapClass; }
     public void setPrincipalMappingClass(String c) { princMapClass = c; }
  
     public String getPrincipalMappingProperties() { return princMapProps; }
     public void setPrincipalMappingProperties(String p) { princMapProps = p; }
      
     // Object pool settings
  
     public String getPoolStrategy() { return poolStrategy; }
     public void setPoolStrategy(String strategy) { poolStrategy = strategy; }
  
     public int getMinSize() { return minSize; }
     public void setMinSize(int minSize) { this.minSize = minSize; }
     
     public int getMaxSize() { return maxSize; }
     public void setMaxSize(int maxSize) { this.maxSize = maxSize; }
     
     public boolean getBlocking() { return blocking; }
     public void setBlocking(boolean blocking) { this.blocking = blocking; }
     
     public boolean getGCEnabled() { return gcEnabled; }
     public void setGCEnabled(boolean gcEnabled) { this.gcEnabled = gcEnabled; }
     
     public long getGCInterval() { return gcInterval; }
     public void setGCInterval(long interval) { this.gcInterval = interval; }
     
     public long getGCMinIdleTime() { return gcMinIdleTime; }
     public void setGCMinIdleTime(long idleMillis) { gcMinIdleTime = idleMillis; }
     
     public boolean getIdleTimeoutEnabled() { return idleTimeoutEnabled; }
     public void setIdleTimeoutEnabled(boolean e) { idleTimeoutEnabled = e; }
     
     public long getIdleTimeout() { return idleTimeout; }
     public void setIdleTimeout(long idleMillis) { idleTimeout = idleMillis; }
     
     public float getMaxIdleTimeoutPercent() { return maxIdleTimeoutPercent; }
     public void setMaxIdleTimeoutPercent(float p) { maxIdleTimeoutPercent = p; }
     
     public boolean getInvalidateOnError() { return invalidateOnError; }
     public void setInvalidateOnError(boolean i) { invalidateOnError = i; }
     
     public boolean getTimestampUsed() { return timestampUsed; }
     public void setTimestampUsed(boolean tstamp) { timestampUsed = tstamp; }
  
     // ServiceMBeanSupport overrides ---------------------------------
  
     public String getName() { return "ConnectionFactoryLoader"; }
  
     protected ObjectName getObjectName(MBeanServer server, ObjectName name)
     {
        this.server = server;
        if (name == null)
        {
           String nameStr = OBJECT_NAME + ",name=" + factoryName;
           try
           {
              name = new ObjectName(nameStr);
           }
           catch (MalformedObjectNameException mone)
           {
              log.error("The name '" + nameStr + "' is malformed");
              log.exception(mone);
           }
        }
        return name;
     }
  
     protected void initService() throws Exception
     {
        rarDeployerObjectName = new ObjectName(rarDeployerName);
        server.addNotificationListener(rarDeployerObjectName, this,
                                       new RAFilter(log), null);
  
        log = Log.createLog(factoryName);
     }
  
     // NotificationListener implementation ---------------------------
  
     public void handleNotification(Notification n, Object handback)
     {
        log.debug("Received notification '" + n + "'");
  
        // We know that this is relevent to us because of the filter
        String type = n.getType();
        try
        {
           if (type.endsWith(DEPLOY_NOTIFICATION))
              loadConnectionFactory((RARMetaData) n.getUserData());
           else if (type.endsWith(UNDEPLOY_NOTIFICATION))
              unloadConnectionFactory((RARMetaData) n.getUserData());
           else
              log.error("Unknown notification type: " + type);
        }
        catch (Exception e)
        {
           log.exception(e);
        }
     }
     
     // ObjectFactory implementation ----------------------------------
  
     public Object getObjectInstance(Object obj, Name name, Context nameCtx,
                                     Hashtable environment)
     {
        // Return the connection factory with the requested name
        Log.getLog().debug("ConnectionFactoryLoader.getObjectInstance, name = '" +
                           name + "'");
        synchronized (cfs)
        {
           return cfs.get(name.toString());
        }
     }
  
     // Package protected ---------------------------------------------
      
     // Protected -----------------------------------------------------
      
     // Private -------------------------------------------------------
  
     /**
      * Does the actual work of configuring a connection factory.
      * Because this is invoked from a notification handler, it makes no
      * sense to propagate exceptions, so we handle all checked
      * exceptions in the body of this method.
      */
     private void loadConnectionFactory(RARMetaData metaData)
     {
        // This context is used in a few places. There is no point
        // continuing if JNDI isn't working.
  
        Context ctx;
        try
        {
           ctx = new InitialContext();
        }
        catch (NamingException ne)
        {
           log.error("Unable to obtain initial context");
           log.exception(ne);
           return;
        }
  
        // This is the class loader through which we should be able to
        // load the resource adapter's classes
  
        ClassLoader cl = metaData.getClassLoader();
  
        // Create the ManagedConnectionFactory instance
  
        Class mcfClass;
        ManagedConnectionFactory mcf;
  
        String mcfClassName = metaData.getManagedConnectionFactoryClass();
        try
        {
           mcfClass = cl.loadClass(mcfClassName);
        }
        catch (ClassNotFoundException cnfe)
        {
           log.error("Unable to load managed connection factory class '" +
                     mcfClassName + "'");
           log.exception(cnfe);
           return;
        }
        try
        {
           mcf = (ManagedConnectionFactory) mcfClass.newInstance();
        }
        catch (Exception e)
        {
           log.error("Unable to instantiate manageed connection factory class '" +
                     mcfClass + "'");
           log.exception(e);
           return;
        }
  
        // Set the properties on it
  
        Properties props = new Properties();
        try
        {
           props.load(
              new ByteArrayInputStream(properties.getBytes("ISO-8859-1")));
        }
        catch (IOException ioe)
        {
           // This shouldn't happen, so we try to carry on as if it didn't
           log.error("Problem converting properties string '" + properties +
                     "' to Properties");
           log.exception(ioe);
        }
  
        // the properties that the deployment descriptor says we need to
        // set
        Map ddProps = metaData.getProperties(); 
        for (Iterator i=ddProps.values().iterator(); i.hasNext(); )
        {
           RARMetaData.Property ddProp = (RARMetaData.Property) i.next();
           String value = (String) props.get(ddProp.name);
           if (value == null )
           {
              if (ddProp.value == null)
              {
                 log.warning("Not setting config property '" + ddProp.name + "'");
                 continue;
              }
              log.warning("Using default value '" + ddProp.value + "' for " +
                          "config property '" + ddProp.name + "'");
              value = ddProp.value;
           }
  
           Class clazz;
           Method setter;
  
           try {
              clazz = cl.loadClass(ddProp.type);
           }
           catch (ClassNotFoundException cnfe)
           {
              log.warning("Unable to find class '" + ddProp.type + "' for " +
                          "property '" + ddProp.name + "' - skipping property.");
              continue;
           }
           PropertyEditor pe = PropertyEditorManager.findEditor(clazz);
           if (pe == null)
           {
              log.warning("Unable to find a PropertyEditor for class '" +
                          clazz + "' of property '" + ddProp.name + "' - " +
                          "skipping property");
              continue;
           }
           try
           {
              pe.setAsText(value);
           }
           catch (IllegalArgumentException iae)
           {
              log.warning("Value '" + value + "' is not valid for property '" +
                          ddProp.name + "' of class '" + clazz + "' - skipping " +
                          "property");
              continue;
           }
           Object v = pe.getValue();
           try
           {
              setter = mcfClass.getMethod("set" + ddProp.name,
                                          new Class[] { clazz });
           }
           catch (NoSuchMethodException nsme)
           {
              log.warning("The class '" + mcfClass.toString() + "' has no " +
                          "setter for config property '" + ddProp.name + "'");
              continue;
           }
           try
           {
              setter.invoke(mcf, new Object[] { v });
           }
           catch (Exception e)
           {
              log.warning("Unable to invoke setter method '" + setter + "' " +
                          "on object '" + mcf + "'");
              log.exception(e);
           }
        }
  
        // Give it somewhere to tell people things
  
        PrintWriter logWriter = new LogWriter(log);
        try
        {
           mcf.setLogWriter(logWriter);
        }
        catch (ResourceException re)
        {
           log.warning("Unable to set log writer '" + logWriter + "' on " +
                       "managed connection factory");
           log.exception(re);
           log.exception(re.getLinkedException());
        }
  
        // Find the transaction manager
  
        TransactionManager tm = null;
        try
        {
           tm = (TransactionManager) ctx.lookup(tmName);
        }
        catch (NamingException ne)
        {
           log.error("Unable to locate the transaction manager at '" + tmName +
                     "'");
           log.exception(ne);
        }
  
        // Create the principal mapper
  
        PrincipalMapping principalMapping;
        try
        {
           principalMapping =
              (PrincipalMapping) Class.forName(princMapClass).newInstance();
        }
        catch (Exception e)
        {
           log.error("Unable to instantiate principal mapping class '" +
                     princMapClass + "'");
           log.exception(e);
           return;
        }
  
        principalMapping.setLog(log);
        principalMapping.setManagedConnectionFactory(mcf);
        principalMapping.setRARMetaData(metaData);
        principalMapping.setProperties(princMapProps);
  
        // Create the connection manager
  
        try
        {
           cm = new ConnectionManagerImpl(metaData, this, mcf, log, tm,
                                          principalMapping);
        }
        catch (Exception e)
        {
           log.error("Unable to create connection manager");
           log.exception(e);
           return;
        }
  
        // Create us a connection factory
  
        Object cf;
  
        try
        {
           cf = mcf.createConnectionFactory(cm);
        }
        catch (ResourceException re)
        {
           log.error("Unable to create connection factory");
           log.exception(re);
           return;
        }
  
        // Bind it into JNDI
  
        bindName = "java:/" + factoryName;
        log.debug("Binding object '" + cf + "' into JNDI at '" + bindName + "'");
        synchronized (cfs)
        {
           cfs.put(factoryName, cf);
        }
        ((Referenceable) cf).setReference(new Reference(cf.getClass().getName(),
                                                        getClass().getName(),
                                                        null));
        try
        {
           ctx.bind(bindName, cf);
           log.log("Bound connection factory for resource adapter '" +
                   resourceAdapterName + "' to JNDI name '" + bindName + "'");
        }
        catch (NamingException ne)
        {
           log.error("Unable to bind connection factory to JNDI name '" +
                     bindName + "'");
           log.exception(ne);
        }
     }
  
     /**
      * Does the actual work of tearing down a connection factory.
      */
     private void unloadConnectionFactory(RARMetaData metaData)
     {
        // Destroy any managed connections
  
        cm.shutdown();
        cfs.remove(factoryName);
        log.log("Connection factory '" + factoryName + "' shut down.");
  
        // Unbind from JNDI
  
        try
        {
           new InitialContext().unbind(bindName);
        }
        catch (NamingException ne)
        {
           log.error("Unable to unbind connection factory at JNDI location '" +
                     bindName + "'");
           log.exception(ne);
        }
     }
  
     // Inner classes -------------------------------------------------
  
     private class RAFilter implements NotificationFilter
     {
        private Log log;
        private RAFilter(Log log) { this.log = log; }
  
        public boolean isNotificationEnabled(Notification n)
        {
           log.debug("Evaluating notification type='" + n.getType() + "', " +
                     "message='" + n.getMessage() + "'");
           return
              n.getType().startsWith(DEPLOYMENT_NOTIFICATION) &&
              n.getMessage().equals(resourceAdapterName);
        }
     }
  }
  
  
  
  1.1                  
jboss/src/main/org/jboss/resource/ConnectionFactoryLoaderMBean.java
  
  Index: ConnectionFactoryLoaderMBean.java
  ===================================================================
  /*
   * JBoss, the OpenSource EJB server
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.resource;
  
  import org.jboss.util.ServiceMBean;
  
  /**
   *   MBean that binds a connection factory for a deployed resource
   *   adapter using a specific configuration. Note that it is entirely
   *   possible that the resource adapter this refers to doesn't exist
   *   until later (i.e. until it is deployed). If so, we wait until the
   *   resource adapter deployer notifies us of the deployment.
   *      
   *   @see RARDeployer
   *   @author Toby Allsopp ([EMAIL PROTECTED])
   *   @version $Revision: 1.1 $
   */
  public interface ConnectionFactoryLoaderMBean
     extends ServiceMBean, ConnectionFactoryConfig
  {
     String OBJECT_NAME = ":service=ConnectionFactoryLoader";
     String DEPLOYMENT_NOTIFICATION = "org.jboss.resource.deployment";
     String DEPLOY_NOTIFICATION = ".deploy";
     String UNDEPLOY_NOTIFICATION = ".undeploy";
  
     /**
      * The name of the resource adapter. This is the value from the
      * <code>display-name</code> element in its deployment descriptor
      * becase I can't see a better name to use.
      */
     String getResourceAdapterName();
     void setResourceAdapterName(String resourceAdapterName);
  
     /**
      * The name under which to bind this connection factory in
      * JNDI. The name will be prepended with "java:/" to ensure that it
      * is only visible in the local JVM.
      */
     String getFactoryName();
     void setFactoryName(String factoryName);
  
     /**
      * A string in a format parseable by {@link
      * java.util.Properties#load} that defines the properties to set on
      * the <code>ManagedConnectionFactory</code> for this connection
      * factory instance.
      */
     String getProperties();
     void setProperties(String properties);
  
     /**
      * The name of the MBean responsible for deploying the resource
      * adapter. This is so we can listen for notification of the
      * resource adapter being deployed.
      */
     String getRARDeployerName();
     void setRARDeployerName(String rarDeployerName);
  
     /**
      * The name under which the transaction manager is bound in
      * JNDI. This has a sensible default.
      */
     String getTransactionManagerName();
     void setTransactionManagerName(String transactionManagerName);
  }
  
  
  
  1.1                  jboss/src/main/org/jboss/resource/ConnectionManagerImpl.java
  
  Index: ConnectionManagerImpl.java
  ===================================================================
  /*
   * JBoss, the OpenSource EJB server
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.resource;
  
  import java.security.Principal;
  import java.util.HashMap;
  import java.util.Map;
  
  import javax.resource.ResourceException;
  import javax.resource.spi.ApplicationServerInternalException;
  import javax.resource.spi.ConnectionEvent;
  import javax.resource.spi.ConnectionEventListener;
  import javax.resource.spi.ConnectionManager;
  import javax.resource.spi.ConnectionRequestInfo;
  import javax.resource.spi.ManagedConnection;
  import javax.resource.spi.ManagedConnectionFactory;
  import javax.resource.spi.security.PasswordCredential;
  
  import javax.security.auth.Subject;
  
  import javax.transaction.RollbackException;
  import javax.transaction.Status;
  import javax.transaction.Synchronization;
  import javax.transaction.SystemException;
  import javax.transaction.Transaction;
  import javax.transaction.TransactionManager;
  import javax.transaction.xa.XAResource;
  
  import org.jboss.logging.Log;
  import org.jboss.minerva.pools.ObjectPool;
  import org.jboss.minerva.pools.PoolObjectFactory;
  import org.jboss.resource.pool.PoolStrategy;
  import org.jboss.resource.pool.PoolStrategyFactory;
  import org.jboss.resource.security.PrincipalMapping;
  import org.jboss.security.SecurityAssociation;
  
  /**
   *   Provides connection factories with a hook into the app server's
   *   "quality of services". There is one instance of this class for
   *   each connection factory and therefore for each managed connection
   *   factory instance.
   *
   *   @see <related>
   *   @author Toby Allsopp ([EMAIL PROTECTED])
   *   @version $Revision: 1.1 $
   */
  public class ConnectionManagerImpl
     implements ConnectionManager, ConnectionEventListener
  {
     // Constants -----------------------------------------------------
  
     // Attributes ----------------------------------------------------
  
     private Log log;
  
     private TransactionManager tm;
     private ManagedConnectionFactory mcf;
     private RARMetaData metadata;
  
     private PrincipalMapping principalMapping;
     private PoolStrategy poolStrategy;
  
     /** Maps Transaction to ManagedConnection */
     private Map tx2mc = new HashMap();
  
     // Static --------------------------------------------------------
  
     // Constructors --------------------------------------------------
  
     ConnectionManagerImpl(RARMetaData metadata, ConnectionFactoryConfig cfConfig,
                           ManagedConnectionFactory mcf, Log log,
                           TransactionManager tm,
                           PrincipalMapping principalMapping)
        throws SystemException
     {
        this.log = log;
        this.metadata = metadata;
        this.mcf = mcf;
        poolStrategy = PoolStrategyFactory.getStrategy(metadata, cfConfig,
                                                       mcf, log);
        //FIXME communicate pool settings to pool strategy, possibly
        //through metadata
        poolStrategy.setConnectionEventListener(this);
        this.tm = tm;
        this.principalMapping = principalMapping;
     }
  
     // Public --------------------------------------------------------
  
     // ConnectionManager implementation ------------------------------
  
     public Object allocateConnection(ManagedConnectionFactory mcf,
                                      ConnectionRequestInfo cxRequestInfo)
        throws ResourceException
     {
        if (!mcf.equals(this.mcf))
        {
           throw new ApplicationServerInternalException(
              "allocateConnection called for a managed connection factory that" +
              "is not the one this connection manager handles. this = '" + this +
              "', this.mcf = '" + this.mcf + "', mcf = '" + mcf + "', " +
              "cxRequestInfo = '" + cxRequestInfo + "'");
        }
  
        // Obtain a subject that identifies the resource principal and
        // its credentials. There are two possibilities for passing the
        // security information to the resource adapter:
  
        // 1) Component-managed sign-on - in this case cxRequestInfo
        // will contain the information that the resource adapter needs
        // to sign onto the EIS and we will leave subject null
  
        // 2) Container-managed sign-on - in this case cxRequestInfo is
        // null and we will create a Subject identifying the resource
        // principal and its credentials
  
        Subject subject;
  
        if (cxRequestInfo != null)
           // component-managed sign-on
           subject = null;
        else
        {
           // container-managed sign-on
           Principal callerPrincipal = SecurityAssociation.getPrincipal();
           subject = principalMapping.createSubject(callerPrincipal);
        }
  
  
        // Find an appropriate managed connection
        ManagedConnection mc;
  
        // Figure out if we need to associate this managed connection
        // with a transaction
        try
        {
           Transaction tx = tm.getTransaction();
           if (tx != null)
           {
              mc = (ManagedConnection) tx2mc.get(tx);
              if (mc != null)
              {
                 log.debug("Using connection '" + mc + "', which is already " +
                           "associated with transaction '" + tx + "'");
              }
              else
              {
                 log.debug("Finding an unused managed connection to handle " +
                           "transaction '" + tx + "'");
                 mc = poolStrategy.getManagedConnection(subject, cxRequestInfo);
                 log.debug("Enlisting connection '" + mc + "' with transaction " +
                           "'" + tx + "'");
                 try
                 {
                    tx.enlistResource(mc.getXAResource());
                    tx2mc.put(tx, mc);
                    TransactionSynchronization synch =
                       new TransactionSynchronization(tx, mc);
                    tx.registerSynchronization(synch);
                 }
                 catch (RollbackException rbe)
                 {
                    // Oops! The transaction has been marked for
                    // rollback. We can't give out a connection not
                    // enlisted in any transaction, so we must throw an
                    // exception.
                    log.debug(rbe);
                    
                    // Don't forget to put the connection back in the
                    // pool! The connection must have just come from the
                    // pool because otherwise we wouldn't be trying to
                    // enlist it.
                    log.debug("The current transaction is marked for rollback, " +
                              "returning connection '" + mc + "' to its pool");
                    try
                    {
                       poolStrategy.releaseManagedConnection(mc);
                    }
                    catch (ResourceException re)
                    {
                       log.exception(re);
                    }
                    
                    ResourceException re =
                       new ResourceException("Transaction marked for rollback");
                    re.setLinkedException(rbe);
                    throw re;
                 }
              }
           }
           else
           {
              mc = poolStrategy.getManagedConnection(subject, cxRequestInfo);
              log.debug("Not enlisting connection '" + mc + "' with a " +
                        "transaction");
           }
        }
        catch (SystemException se)
        {
           log.debug(se);
           ApplicationServerInternalException asie =
              new ApplicationServerInternalException("Transaction manager FUBAR");
           asie.setLinkedException(se);
           throw asie;
        }
  
        // Return an application-level connection handle
        return mc.getConnection(subject, cxRequestInfo);
     }
  
     // ConnectionEventListener implementation ------------------------
  
     public void connectionClosed(ConnectionEvent event)
     {
        ManagedConnection mc = (ManagedConnection) event.getSource();
        log.debug("connectionClosed for connection '" + mc + "'");
  
        try
        {
           Transaction tx = tm.getTransaction();
           if (tx != null)
           {
              // Sanity checks
              ManagedConnection txmc = (ManagedConnection) tx2mc.get(tx);
              if (txmc == null)
              {
                 log.error("The connection '" + mc + "' has been closed in the " +
                           "context of transaction '" + tx + "' but I have no " +
                           "record of a connection being enlisted with that " +
                           "transaction. Bad");
                 //FIXME what action to take?
              }
              else if (!txmc.equals(mc))
              {
                 log.error("Connection '" + mc + "' has been closed in the " +
                           "context of transaction '" + tx + "' but " +
                           "connection '" + txmc + "' was enlisted in that " +
                           "transaction. Bad!");
                 //FIXME what action to take?
              }
  
              // The connection will be returned to the pool when the
              // transaction completes. See TransactionSynchronization.
  
              /* delisting will only work right with a full-on
                 XA-compliant resource adapter. I have none to test
                 with.
  
              // We delist the connection's resource from the transaction (if
              // there is one) and return it to the pool so that it can be
              // used for other transactions or further work on this
              // transaction
              log.debug("delisting connection '" + mc + "' from transaction '" +
                        tx + "'");
              try
              {
                 XAResource res = mc.getXAResource();
                 tx.delistResource(res, XAResource.TMSUCCESS);
              }
              catch (ResourceException re)
              {
                 log.warning("Unable to get XAResource from managed connection");
                 log.exception(re);
              }
              */
           }
           else
           {
              log.debug("Connection '" + mc + "' not participating in a " +
                        "transaction, returning it to its pool");
              try
              {
                 poolStrategy.releaseManagedConnection(mc);
              }
              catch (ResourceException re)
              {
                 log.exception(re);
              }
           }
        }
        catch (SystemException se)
        {
           log.error("Transaction manager FUBAR");
           log.exception(se);
        }
     }
  
     public void connectionErrorOccurred(ConnectionEvent event)
     {
        ManagedConnection mc = (ManagedConnection) event.getSource();
        log.debug("connectionErrorOccurred for connection '" + mc + "', " +
                  "exception is '" + event.getException() + "'");
  
        try
        {
           Transaction tx = tm.getTransaction();
           if (tx != null)
           {
              // Sanity checks
              ManagedConnection txmc = (ManagedConnection) tx2mc.get(tx);
              if (txmc == null)
              {
                 log.error("An error has occurred for the connection '" + mc +
                           "' in the context of transaction '" + tx + "' but " +
                           "I have no record of a connection being enlisted " +
                           "with that transaction. Bad!");
                 //FIXME what action to take?
              }
              else if (!txmc.equals(mc))
              {
                 log.error("An error has occurred for the connection '" + mc +
                           "' in the context of transaction '" + tx + "' but " +
                           "connection '" + txmc + "' was enlisted in that " +
                           "transaction. Bad!");
                 //FIXME what action to take?
              }
  
              try
              {
                 // The managed connection will be destroyed by the pool
                 // when it is returned at the completion of the
                 // transaction.
                 poolStrategy.condemnManagedConnection(mc);
              }
              catch (ResourceException re)
              {
                 log.exception(re);
              }
  
              /* delisting will only work right with a full-on
                 XA-compliant resource adapter. I have none to test
                 with.
  
              // We delist the connection's resource from the transaction (if
              // there is one) and tell the transaction manager that the unit
              // of work has failed, which should mean that the transaction is
              // marked for rollback
              log.debug("Delisting connection '" + mc + "' from transaction " +
                        tx + "'");
              try
              {
                 XAResource res = mc.getXAResource();
                 tx.delistResource(res, XAResource.TMFAIL);
              }
              catch (ResourceException re)
              {
                 log.warning("Unable to get XAResource from managed connection");
                 log.exception(re);
              }
  
              log.debug("Destroying connection '" + mc + "'");
              try
              {
                 poolStrategy.destroyManagedConnection(mc);
              }
              catch (ResourceException re)
              {
                 log.exception(re);
              }
              */
           }
           else
           {
              log.debug("Connection '" + mc + "' not participating in a " +
                        "transaction, destroying immediately");
              try
              {
                 poolStrategy.condemnManagedConnection(mc);
                 poolStrategy.releaseManagedConnection(mc);
              }
              catch (ResourceException re)
              {
                 log.exception(re);
              }
           }
        }
        catch (SystemException se)
        {
           log.error("Transaction manager FUBAR");
           log.exception(se);
        }
     }
  
     // We don't handle local transactions at the moment.
     public void localTransactionStarted(ConnectionEvent event)
     {
        log.error("Local transaction optimisation not implemented");
        log.exception(new Exception());
     } 
     public void localTransactionCommitted(ConnectionEvent event)
     {
        log.error("Local transaction optimisation not implemented");
        log.exception(new Exception());
     } 
     public void localTransactionRolledback(ConnectionEvent event)
     {
        log.error("Local transaction optimisation not implemented");
        log.exception(new Exception());
     } 
  
     // Package protected ---------------------------------------------
  
     void shutdown()
     {
        // Empty the pool(s)
        poolStrategy.shutdown();
     }
  
     // Protected -----------------------------------------------------
  
     // Private -------------------------------------------------------
  
     // Inner classes -------------------------------------------------
  
     /**
      * An instance of this class is registered as a synchronisation
      * whenever a connection is given out as part of a transaction.
      */
     private class TransactionSynchronization
        implements Synchronization
     {
        // Attributes -------------------------------------------------
  
        private Transaction tx;
        private ManagedConnection mc;
  
        // Constructors -----------------------------------------------
  
        private TransactionSynchronization(Transaction tx, ManagedConnection mc)
        {
           this.tx = tx;
           this.mc = mc;
        }
  
        // Synchronization implementation -----------------------------
        
        public void afterCompletion(int status)
        {
           log.debug("afterCompletion for connection '" + mc + "', transaction " +
                     "'" + tx + ", status = " + statusName(status));
           
           // Remove the association between this transaction and
           // managed connection
           ManagedConnection txmc = (ManagedConnection) tx2mc.remove(tx);
  
           // Sanity check
           if (txmc == null || !txmc.equals(mc))
           {
              log.error("Inconsistent transaction assocation: this.tx = '" + tx +
                        "', this.mc = '" + mc + "', tx2mc(tx) = '" + txmc + "'");
              //FIXME what action to take?
           }
  
           log.debug("Returning connection '" + mc + "' to its pool");
           try
           {
              poolStrategy.releaseManagedConnection(mc);
           }
           catch (ResourceException re)
           {
              log.exception(re);
           }
        }
        
        public void beforeCompletion() { }
  
        // Private ----------------------------------------------------
  
        private final String statusName(int s)
        {
           switch (s) {
           case Status.STATUS_ACTIVE: return "STATUS_ACTIVE";
           case Status.STATUS_COMMITTED: return "STATUS_COMMITED"; 
           case Status.STATUS_COMMITTING: return "STATUS_COMMITTING"; 
           case Status.STATUS_MARKED_ROLLBACK: return "STATUS_MARKED_ROLLBACK"; 
           case Status.STATUS_NO_TRANSACTION: return "STATUS_NO_TRANSACTION"; 
           case Status.STATUS_PREPARED: return "STATUS_PREPARED"; 
           case Status.STATUS_PREPARING: return "STATUS_PREPARING"; 
           case Status.STATUS_ROLLEDBACK: return "STATUS_ROLLEDBACK"; 
           case Status.STATUS_ROLLING_BACK: return "STATUS_ROLLING_BACK"; 
           case Status.STATUS_UNKNOWN: return "STATUS_UNKNOWN"; 
           }
           return "REALLY_UNKNOWN";
        }   
     }
  }
  
  
  
  1.1                  jboss/src/main/org/jboss/resource/RARDeployer.java
  
  Index: RARDeployer.java
  ===================================================================
  /*
   * JBoss, the OpenSource EJB server
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.resource;
  
  import java.io.File;
  import java.io.FileFilter;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.net.JarURLConnection;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.jar.JarEntry;
  import java.util.jar.JarFile;
  
  import javax.management.Notification;
  
  import org.jboss.configuration.ConfigurationServiceMBean;
  import org.jboss.deployment.DeployerMBeanSupport;
  import org.jboss.deployment.DeploymentException;
  import org.jboss.logging.Log;
  import org.jboss.metadata.XmlFileLoader;
  import org.jboss.util.MBeanProxy;
  
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  
  /**
   *   Service that deploys ".rar" files containing resource
   *   adapters. Deploying the RAR file is the first step in making the
   *   resource adapter available to application components; once it is
   *   deployed, one or more connection factories must be configured and
   *   bound into JNDI, a task performed by the
   *   <code>ConnectionFactoryLoader</code> service.
   *
   *   @author Toby Allsopp ([EMAIL PROTECTED])
   *   @version $Revision: 1.1 $
   *
   *   @see org.jboss.resource.ConnectionFactoryLoader
   */
  public class RARDeployer
     extends DeployerMBeanSupport
     implements RARDeployerMBean
  {
     // Constants -----------------------------------------------------
      
     // Attributes ----------------------------------------------------
  
     /** The directory that will contain local copies of deployed RARs */
     private File rarTmpDir;
  
     /** The next sequence number to be used in notifications about
         (un)deployment */
     private int nextMessageNum = 0;
      
     // Static --------------------------------------------------------
     
     // Constructors --------------------------------------------------
     
     // Public --------------------------------------------------------
  
     // RARDeployerMBean implementation -------------------------------
  
     // DeployerMBeanSupport overrides ---------------------------------
  
     public String getName() { return "RARDeployer"; }
  
     public void initService() throws Exception
     {
        // find the temp directory - it contains the file
        // "tmp.properties"
        URL tmpPropURL = getClass().getResource("/tmp.properties");
        File tmpDir = new File(tmpPropURL.getFile()).getParentFile();
  
        // Create our temp directory
        File deployTmpDir = new File(tmpDir, "deploy");
        rarTmpDir = new File(deployTmpDir, getName());
        if (rarTmpDir.exists())
        {
           log.log("Found a temp directory left over from a previous run - " +
                   "deleting it.");
           // What could it mean?
           if (!recursiveDelete(rarTmpDir))
           {
              log.warning("Unable to recursively delete temp directory '" +
                          rarTmpDir + "' that appears to be left over from " +
                          "the previous run. This might cause problems.");
           }
        }
        if (!rarTmpDir.exists() && !rarTmpDir.mkdir())
        {
           throw new DeploymentException("Can't create temp directory '" +
                                         rarTmpDir + "'");
        }
     }
  
     public void destroyService()
     {
        // Remove our temp directory
        if (!recursiveDelete(rarTmpDir))
        {
           log.warning("Unable to recursively delete the temp directory '" + 
                       rarTmpDir + "' - it should be cleaned up when the " +
                       "server is next restarted.");
        }
     }
  
     protected Object deploy(URL url) throws IOException, DeploymentException
     {
        log.log("Attempting to deploy RAR at '" + url + "'");
  
        // We want to take a local copy of the RAR so that we don't run
        // into problems if the original is removed/replaced. We also
        // need the RAR in unpacked form so that we can get at the
        // included JARs for classloading (I don't think URLClassLoader
        // deals with JARs within JARs).
  
        File unpackedDir = new File(rarTmpDir, generateUniqueDirName(url));
        if (unpackedDir.exists())
        {
           throw new DeploymentException("The application at URL '" + url + "' " +
                                         "appears to already have been " +
                                         "deployed because the directory '" +
                                         unpackedDir + "' exists");
        }
        unpackedDir.mkdirs();
  
        if (url.getFile().endsWith("/"))
        {
           // this is a directory - we can only deal with directories in
           // the local filesystem (because we can't get a list of files
           // from a general URL)
           if (!url.getProtocol().equals("file"))
              throw new DeploymentException("Can only deploy directories " +
                                            "specified by 'file:' URLs");
           copyDirectory(new File(url.getFile()), unpackedDir);
        }
        else
        {
           // this is a .rar file somewhere
           inflateJar(url, unpackedDir);
        }
  
        // Right, now we can forget about URLs and just use the file
        // system.
  
        File ddFile = new File(unpackedDir, "META-INF/ra.xml");
        
        if (!ddFile.exists())
        {
           throw new DeploymentException("No deployment descriptor " +
                                         "('META-INF/ra.xml') found in alleged " +
                                         "resource adapter at '" + url + "'");
        }
  
        Document dd;
        try
        {
           dd = XmlFileLoader.getDocument(ddFile.toURL());
        }
        catch (org.jboss.ejb.DeploymentException de)
        {
           throw new DeploymentException(de.getMessage(), de.getCause());
        }
  
        Element root = dd.getDocumentElement();
  
        RARMetaData metadata = new RARMetaData();
        Log.setLog(log);
        try
        {
           metadata.importXml(root);
        }
        finally
        {
           Log.unsetLog();
        }
  
        // Create a class loader that can load classes from any JARs
        // inside the RAR
  
        // First, we need to find the JARs. The procedure for this
        // depends on whether the URL points to a RAR file or a
        // directory.
  
        Collection jars = new ArrayList();
  
        FileFilter filter = new FileFilter()
           {
              public boolean accept(File file)
              {
                 return file.getName().endsWith(".jar");
              }
           };
        Collection jarFiles = recursiveFind(unpackedDir, filter);
        for (Iterator i = jarFiles.iterator(); i.hasNext(); )
        {
           File file = (File) i.next();
           jars.add(file.toURL());
        }
  
        log.debug("Adding the following URLs to classpath:");
        for (Iterator i = jars.iterator(); i.hasNext(); )
           log.debug(((URL) i.next()).toString());
  
        // Ok, now we have the URLs of the JARs contained in the RAR we
        // can create a classloader that loads classes from them
  
        ClassLoader cl = new URLClassLoader(
           (URL[]) jars.toArray(new URL[0]),
           Thread.currentThread().getContextClassLoader());
  
        metadata.setClassLoader(cl);
  
        // Look for a META-INF/ra-jboss.xml file that defines connection
        // factories. My current idea is that this can define connection
        // factory loaders in exactly the same way as jboss.jcml.
  
        /* not quite implemented yet
        File jbossDDFile = new File(unpackedDir, "META-INF/ra-jboss.xml");
  
        if (jbossDDFile.exists())
        {
           log.log("Loading JBoss deployment descriptor at '" + jbossDDFile +
                   "'");
           try
           {
              Document jbossDD = XmlFileLoader.getDocument(jbossDDFile.toURL());
              ConfigurationServiceMBean cs = (ConfigurationServiceMBean)
                 MBeanProxy.create(ConfigurationServiceMBean.class,
                                   ConfigurationServiceMBean.OBJECT_NAME);
              cs.load(jbossDD);
              //FIXME need to get reference to newly created MBean so we
              //can (a) call init and start on it and (b) destroy it
              //when this RAR is undeployed
           }
           catch (Exception e)
           {
              log.warning("Problem occurred while loading '" + jbossDDFile + "'");
              log.exception(e);
           }
        }
        */
  
        // Let's tell the waiting hordes (of connection factory loaders)
        // that this resource adapter is available
  
        Notification notification = new Notification(
           ConnectionFactoryLoaderMBean.DEPLOYMENT_NOTIFICATION +
           ConnectionFactoryLoaderMBean.DEPLOY_NOTIFICATION, this,
           nextMessageNum++, metadata.getDisplayName());
        notification.setUserData(metadata);
        sendNotification(notification);
  
        DeploymentInfo info = new DeploymentInfo();
        info.metadata = metadata;
        info.unpackedDir = unpackedDir;
        return info;
     }
  
     protected void undeploy(URL url, Object o) throws DeploymentException
     {
        log.log("Undeploying RAR at '" + url + "'");
        
        DeploymentInfo info = (DeploymentInfo) o;
  
        if (info == null)
        {
           throw new DeploymentException("There doesn't appear to be a RAR " +
                                         "deployed at '" + url + "'");
        }
  
        // Tell the waiting hordes (of connection factory loaders)
        // that this resource adapter is no longer available
        
        RARMetaData metadata = info.metadata;
        Notification notification = new Notification(
           ConnectionFactoryLoaderMBean.DEPLOYMENT_NOTIFICATION +
           ConnectionFactoryLoaderMBean.UNDEPLOY_NOTIFICATION, this,
           nextMessageNum++, metadata.getDisplayName());
        sendNotification(notification);
  
        // Remove the temporary copy
  
        File unpackedDir = info.unpackedDir;
        if (!recursiveDelete(unpackedDir))
        {
           log.warning("Unable to recursively delete temp directory '" +
                       unpackedDir + "' - this should be cleaned up either " +
                       "when the server is shut down or when it restarts.");
        }
     }
     
     // Package protected ---------------------------------------------
      
     // Protected -----------------------------------------------------
      
     // Private -------------------------------------------------------
  
     private static int nextNum = 0;
  
     private static String generateUniqueDirName(URL u)
     {
        int thisNum = nextNum++;
        return "rar." + thisNum;
     }
  
     private Collection recursiveFind(File dir, FileFilter filter)
     {
        Collection files = new ArrayList();
        File[] candidates = dir.listFiles();
        if (candidates == null) return null;
  
        for (int i = 0; i < candidates.length; ++i)
        {
           File candidate = candidates[i];
           if (candidate.isDirectory())
              files.addAll(recursiveFind(candidate, filter));
           else if (filter.accept(candidate))
              files.add(candidate);
        }
  
        return files;
     }
  
     private void copyDirectory(File srcDir, File destDir)
        throws DeploymentException, IOException
     {
        File[] files = srcDir.listFiles();
        if (files == null) throw new DeploymentException("Not a directory: '" +
                                                         srcDir + "'");
  
        destDir.mkdirs();
        for (int i = 0; i < files.length; ++i)
        {
           File file = files[i];
           File dest = new File(destDir, file.getName());
           if (file.isDirectory())
              copyDirectory(file, dest);
           else
              copyFile(file, dest);
        }
     }
  
     private void copyFile(File src, File dest)
        throws IOException
     {
        InputStream in = new FileInputStream(src);
        try
        {
           OutputStream out = new FileOutputStream(dest);
           try
           {
              copy(in, out);
           }
           finally
           {
              out.close();
           }
        }
        finally
        {
           in.close();
        }
     }
  
     private void inflateJar(URL url, File destDir)
        throws DeploymentException, IOException
     {
        URL jarUrl;
        try
        {
           jarUrl = new URL("jar:" + url.toString() + "!/");
        }
        catch (MalformedURLException mfue)
        {
           throw new DeploymentException("Oops! Couldn't convert URL to a " +
                                         "jar URL", mfue);
        }
           
        JarURLConnection jarConnection =
           (JarURLConnection) jarUrl.openConnection();
        JarFile jarFile = jarConnection.getJarFile();
  
        for (Enumeration e = jarFile.entries(); e.hasMoreElements(); )
        {
           JarEntry entry = (JarEntry) e.nextElement();
           String name = entry.getName();
           File outFile = new File(destDir, name);
           if (entry.isDirectory())
           {
              outFile.mkdirs();
           }
           else
           {
              InputStream in = jarFile.getInputStream(entry);
              try
              {
                 OutputStream out = new FileOutputStream(outFile);
                 try
                 {
                    copy(in, out);
                 }
                 finally
                 {
                    out.close();
                 }
              }
              finally
              {
                 in.close();
  
              }
           }
        }
        
        jarFile.close();
     }
  
     private void copy(InputStream in, OutputStream out) throws IOException
     {
        byte[] buffer = new byte[1024];
        int read;
        while ((read = in.read(buffer)) > 0)
        {
           out.write(buffer, 0, read);
        }
     }
  
     private boolean recursiveDelete(File f)
     {
        if (f.isDirectory())
        {
           File[] files = f.listFiles();
           for (int i=0; i<files.length; ++i)
           {
              if (!recursiveDelete(files[i])) return false;
           }
        }
        return f.delete();
     }
  
     // Inner classes -------------------------------------------------
  
     private static class DeploymentInfo
     {
        public RARMetaData metadata;
        public File unpackedDir;
     }
  }
  
  
  
  1.1                  jboss/src/main/org/jboss/resource/RARDeployerMBean.java
  
  Index: RARDeployerMBean.java
  ===================================================================
  /*
   * JBoss, the OpenSource EJB server
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.resource;
  
  import org.jboss.deployment.DeployerMBean;
  
  /**
   *   <description> 
   *
   *   @see <related>
   *   @author Toby Allsopp ([EMAIL PROTECTED])
   *   @version $Revision: 1.1 $
   */
  public interface RARDeployerMBean
     extends DeployerMBean
  {
     // Constants -----------------------------------------------------
  
     String OBJECT_NAME = ":service=RARDeployer";
      
     // Public --------------------------------------------------------
  
  //     public RARMetaData getMetaData(String resourceAdapterName);
  }
  
  
  
  1.1                  jboss/src/main/org/jboss/resource/RARMetaData.java
  
  Index: RARMetaData.java
  ===================================================================
  /*
   * JBoss, the OpenSource EJB server
   *
   * Distributable under LGPL license.
   * See terms of license at gnu.org.
   */
  package org.jboss.resource;
  
  import java.lang.StringBuffer;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.Map;
  
  import org.jboss.deployment.DeploymentException;
  import org.jboss.logging.Log;
  import org.jboss.metadata.MetaData;
  import org.jboss.metadata.XmlLoadable;
  import org.w3c.dom.Element;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  /**
   *   Represents the metadata present in a resource adapter deployment
   *   descriptor.
   *
   *   @see RARDeployer
   *   @author Toby Allsopp ([EMAIL PROTECTED])
   *   @version $Revision: 1.1 $
   */
  public class RARMetaData
     //   extends Y
     implements XmlLoadable
  {
     // Constants -----------------------------------------------------
      
     // Attributes ----------------------------------------------------
  
     private Log log;
  
     private ClassLoader classLoader;
  
     private String displayName;
      
     private String managedConnectionFactoryClass;
     private String connectionFactoryInterface;
     private String connectionFactoryImplClass;
     private String connectionInterface;
     private String connectionImplClass;
  
     private int transactionSupport;
     public static final int TX_SUPPORT_NO    = 0;
     public static final int TX_SUPPORT_LOCAL = 1;
     public static final int TX_SUPPORT_XA    = 2;
  
     private Map properties = new HashMap();
  
     private String authMechType;
     private String credentialInterface;
  
     private boolean reauthenticationSupport;
  
     // Static --------------------------------------------------------
     
     // Constructors --------------------------------------------------
     
     // Public --------------------------------------------------------
  
     /**
      * The class loader to use for the resource adapter's classes
      */
     public ClassLoader getClassLoader() { return classLoader; }
     public void setClassLoader(ClassLoader cl) { classLoader = cl; }
  
     public String getDisplayName() { return displayName; }
  
     public String getManagedConnectionFactoryClass()
     {
        return managedConnectionFactoryClass;
     }
  
     /**
      * Gets the type of transactions supported by the resource adapter.
      *
      * @return one of the <code>TX_SUPPORT_*</code> constants
      */
     public int getTransactionSupport() { return transactionSupport; }
  
     public Map getProperties() { return properties; }
  
     public String getPropertyType(String name)
     {
        Property prop = (Property) properties.get(name);
        return (prop==null) ? null : prop.type;
     }
  
     public String getAuthMechType() { return authMechType; }
  
     public boolean getReauthenticationSupport()
     {
        return reauthenticationSupport;
     }
  
     // XmlLoadable implementation ------------------------------------
  
     public void importXml(Element root) throws DeploymentException
     {
        log = Log.getLog();
  
        // First, a quick sanity check to ensure that we're looking at
        // the right kind of deployment descriptor
  
        String rootTag = root.getTagName();
        if (! rootTag.equals("connector"))
        {
           throw new DeploymentException("Not a resrouce adapter deployment " +
                                         "descriptor because its root tag, '" +
                                         rootTag + "', is not 'connector'");
        }
  
        // Then, we iterate over all the elements seeing if we're
        // interested
  
        invokeChildren(root);
     }
  
     // Y overrides ---------------------------------------------------
     
     // Package protected ---------------------------------------------
      
     // Protected -----------------------------------------------------
      
     // Private -------------------------------------------------------
  
     private void invokeChildren(Element element) throws DeploymentException
     {
        NodeList children = element.getChildNodes();
        for (int i=0; i<children.getLength(); ++i)
        {
           Node node = children.item(i);
           if (node.getNodeType() == Node.ELEMENT_NODE)
           {
              Element child = (Element) node;
              Method method = elementToMethod(child);
              if (method == null)
              {
                 // We don't handle this element. Hope it wasn't
                 // important.
                 log.warning("Element '" + child + "' not recognised.");
                 continue;
              }
  
              try
              {
                 method.invoke(this, new Object[] { child });
              }
              catch (InvocationTargetException ite)
              {
                 Throwable t = ite.getTargetException();
                 if (t instanceof DeploymentException)
                    throw (DeploymentException) t;
                 if (t instanceof Exception)
                    throw new DeploymentException("Exception handling element",
                                                  (Exception) t);
                 if (t instanceof Error) throw (Error) t;
                 throw new DeploymentException("WTF?: " + t.toString());
              }
              catch (Exception e)
              {
                 throw new DeploymentException("Exception handling element", e);
              }
           }
        }
     }
      
     private Method elementToMethod(Element element)
     {
        String tag = element.getTagName();
  
        StringBuffer methodName = new StringBuffer("set");
  
        // We can't be having hyphens in our method names
        //FIXME this should weed out illegal characters in general
        {
           int last_hyphen = -1;
           int next_hyphen;
           while ((next_hyphen = tag.indexOf('-', last_hyphen+1)) != -1)
           {
              String thisbit = tag.substring(last_hyphen+1, next_hyphen);
              methodName.append(toTitleCase(thisbit));
              last_hyphen = next_hyphen;
           }
           methodName.append(toTitleCase(tag.substring(last_hyphen+1)));
        }
  
        log.debug("methodName = '" + methodName + "'");
  
        try
        {
           return getClass().getDeclaredMethod(methodName.toString(),
                                               new Class[] { Element.class });
        }
        catch (NoSuchMethodException nsme)
        {
           return null;
        }
     }
  
     private String toTitleCase(String s)
     {
        if (s == null) return null;
        if (s.length() == 0) return s.toUpperCase();
        return s.substring(0, 1).toUpperCase() + s.substring(1);
     }
  
     private String getElementContent(Element element) throws DeploymentException
     {
        try
        {
           return MetaData.getElementContent(element);
        }
        catch (org.jboss.ejb.DeploymentException de)
        {
           throw new DeploymentException(de.getMessage(), de.getCause());
        }
     }
  
     // Setter methods for XML elements
  
     private void setDisplayName(Element element)
        throws DeploymentException
     {
        displayName = getElementContent(element);
     }
  
     private void setVendorName(Element element) {}
     private void setSpecVersion(Element element) {}
     private void setVersion(Element element) {}
     private void setEisType(Element element) {}
  
     private void setResourceadapter(Element element) throws DeploymentException
     {
        invokeChildren(element);
     }
  
     private void setManagedconnectionfactoryClass(Element element)
        throws DeploymentException
     {
        managedConnectionFactoryClass = getElementContent(element);
     }
  
     private void setConnectionfactoryInterface(Element element)
        throws DeploymentException
     {
        connectionFactoryInterface = getElementContent(element);
     }
  
     private void setConnectionfactoryImplClass(Element element)
        throws DeploymentException
     {
        connectionFactoryImplClass = getElementContent(element);
     }
  
     private void setConnectionInterface(Element element)
        throws DeploymentException
     {
        connectionInterface = getElementContent(element);
     }
  
     private void setConnectionImplClass(Element element)
        throws DeploymentException
     {
        connectionImplClass = getElementContent(element);
     }
  
     private void setTransactionSupport(Element element)
        throws DeploymentException
     {
        String s = getElementContent(element);
        int ts;
        if      (s.equals("no_transaction"   )) ts = TX_SUPPORT_NO;
        else if (s.equals("local_transaction")) ts = TX_SUPPORT_LOCAL;
        else if (s.equals("xa_transaction"   )) ts = TX_SUPPORT_XA;
        else
           throw new DeploymentException("Invalid transaction support '" +
                                         s + "', it must be one of " +
                                         "'no_transaction', " +
                                         "'local_transaction' or " +
                                         "'xa_transaction'");
        transactionSupport = ts;
     }
  
     private void setConfigProperty(Element element)
        throws DeploymentException
     {
        try
        {
           Element nameE= MetaData.getUniqueChild(element,
                                                  "config-property-name");
           Element typeE= MetaData.getUniqueChild(element,
                                                  "config-property-type");
           Element valueE= MetaData.getUniqueChild(element,
                                                   "config-property-value");
  
           Property p = new Property();
           p.name = getElementContent(nameE);
           p.type = getElementContent(typeE);
           p.value = getElementContent(valueE);
           properties.put(p.name, p);
        }
        catch (org.jboss.ejb.DeploymentException de)
        {
           throw new DeploymentException(de.getMessage(), de.getCause());
        }
     }
  
     private void setAuthMechanism(Element element) throws DeploymentException
     {
        invokeChildren(element);
     }
  
     private void setAuthMechType(Element element) throws DeploymentException
     {
        authMechType = getElementContent(element);
     }
  
     private void setCredentialInterface(Element element)
        throws DeploymentException
     {
        credentialInterface = getElementContent(element);
     }
  
     private void setReauthenticationSupport(Element element)
        throws DeploymentException
     {
        String value = getElementContent(element);
        if (!value.equals("true") && !value.equals("false"))
           throw new DeploymentException("reauthentication-support must be one " +
                                         "of 'true' or 'false', not '" + value +
                                         "'");
        reauthenticationSupport = Boolean.valueOf(value).booleanValue();
     }
  
     // Inner classes -------------------------------------------------
  
     public class Property
     {
        public String name;
        public String type;
        public String value;
     }
  }
  
  
  

Reply via email to