/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.resource.connectionmanager;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.Notification;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.resource.ResourceException;
import javax.resource.spi.ConnectionManager;
import javax.resource.spi.ConnectionRequestInfo;
import javax.resource.spi.ManagedConnection;
import javax.resource.spi.ManagedConnectionFactory;
import javax.resource.spi.ResourceAdapter;
import javax.resource.spi.ResourceAdapterAssociation;
import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;
import javax.transaction.xa.XAResource;

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.logging.Logger;
import org.jboss.metadata.MetaData;
import org.jboss.resource.connectionmanager.xa.XAResourceWrapperImpl;
import org.jboss.resource.metadata.ConfigPropertyMetaData;
import org.jboss.resource.metadata.ConnectionDefinitionMetaData;
import org.jboss.resource.metadata.ConnectorMetaData;
import org.jboss.resource.metadata.DescriptionGroupMetaData;
import org.jboss.resource.metadata.mcf.ManagedConnectionFactoryDeploymentMetaData;
import org.jboss.resource.metadata.mcf.ManagedConnectionFactoryPropertyMetaData;
import org.jboss.resource.metadata.mcf.SecurityMetaData;
import org.jboss.resource.metadata.mcf.XAConnectionPropertyMetaData;
import org.jboss.resource.metadata.mcf.XADataSourceDeploymentMetaData;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.security.SecurityContextFactory;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SubjectFactory;
import org.jboss.system.ServiceDynamicMBeanSupport;
import org.jboss.tm.XAResourceRecovery;
import org.jboss.tm.XAResourceRecoveryRegistry;
import org.jboss.util.Classes;
import org.jboss.util.NestedRuntimeException;
import org.jboss.util.StringPropertyReplacer;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * A ManagedConnectionFactoryDeployment.
 * 
 * @author <a href="weston.price@jboss.org">Weston Price</a>
 * @version $Revision: 105780 $
 */
public class ManagedConnectionFactoryDeployment extends ServiceDynamicMBeanSupport 
   implements ManagedConnectionFactory, XAResourceRecovery
{

   /** The serialVersionUID */
   private static final long serialVersionUID = -8448602289610025849L;

   public final static String MCF_ATTRIBUTE_CHANGED_NOTIFICATION = "jboss.mcfattributechangednotification";

   private Logger log = Logger.getLogger(getClass());

   //Hack to use previous ra.xml parsing code until xslt deployment is written.
   private ObjectName oldRarDeployment;

   private String rarName;

   private String connectionDefinition;

   private String vendorName;

   private String specVersion;

   private String eisType;

   private String version;

   private String managedConnectionFactoryClass;

   private String connectionFactoryInterface;

   private String connectionFactoryImplClass;

   private String connectionInterface;

   private String connectionImplClass;

   private String transactionSupport;

   private Element managedConnectionFactoryProperties;

   private String authenticationMechanismType;

   private String credentialInterface;

   private boolean reauthenticationSupport;

   private Class mcfClass;

   private String jndiName;
   
   private ManagedConnectionFactory mcf;
   
   private ManagedConnectionFactoryDeploymentMetaData dmd;
   
   private ConnectorMetaData cmd;

   private boolean recoveryRegistered = false;
   private XAResourceRecoveryRegistry xrrr = null;
   private SubjectFactory subjectFactory = null;
   private ManagedConnection recoverMC = null;
   private String securityDomain = null;
   private String recoverSecurityDomain = null;
   private String recoverUserName = null;
   private String recoverPassword = null;
   private String connectionManager = null;
   
   /**
    * Default managed constructor for RARDeployment mbeans.
    */
   public ManagedConnectionFactoryDeployment()
   {
   }
   
   public ManagedConnectionFactoryDeployment(ConnectorMetaData cmd, 
                                             ManagedConnectionFactoryDeploymentMetaData dmd,
                                             String connectionManager)
   {
      this.cmd = cmd;
      this.dmd = dmd;
      this.connectionManager = connectionManager;
   }
   
   public String getJndiName()
   {
      return this.jndiName;
   }
   
   public void setJndiName(String jndiName)
   {
      this.jndiName = jndiName;
      
   }
   public String getRARName()
   {
      return rarName;
   }

   public void setRARName(String rarName)
   {
      this.rarName = rarName;
   }

   public String getConnectionDefinition()
   {
      return connectionDefinition;
   }

   public void setConnectionDefinition(String connectionDefinition)
   {
      this.connectionDefinition = connectionDefinition;
   }

   public String getVendorName()
   {
      return vendorName;
   }

   public void setVendorName(String vendorName)
   {
      this.vendorName = vendorName;
   }

   public String getSpecVersion()
   {
      return specVersion;
   }

   public void setSpecVersion(String specVersion)
   {
      this.specVersion = specVersion;
   }

   public String getEisType()
   {
      return eisType;
   }

   public void setEisType(String eisType)
   {
      this.eisType = eisType;
   }

   public String getVersion()
   {
      return version;
   }

   public void setVersion(String version)
   {
      this.version = version;
   }

   public String getManagedConnectionFactoryClass()
   {
      return managedConnectionFactoryClass;
   }

   public void setManagedConnectionFactoryClass(final String managedConnectionFactoryClass)
   {
      this.managedConnectionFactoryClass = managedConnectionFactoryClass;
   }

   public String getConnectionFactoryInterface()
   {
      return connectionFactoryInterface;
   }

   public void setConnectionFactoryInterface(String connectionFactoryInterface)
   {
      this.connectionFactoryInterface = connectionFactoryInterface;
   }

   public String getConnectionFactoryImplClass()
   {
      return connectionFactoryImplClass;
   }

   public void setConnectionFactoryImplClass(String connectionFactoryImplClass)
   {
      this.connectionFactoryImplClass = connectionFactoryImplClass;
   }

   public String getConnectionInterface()
   {
      return connectionInterface;
   }

   public void setConnectionInterface(String connectionInterface)
   {
      this.connectionInterface = connectionInterface;
   }

   public String getConnectionImplClass()
   {
      return connectionImplClass;
   }

   public void setConnectionImplClass(String connectionImplClass)
   {
      this.connectionImplClass = connectionImplClass;
   }

   public String getTransactionSupport()
   {
      return transactionSupport;
   }

   public void setTransactionSupport(String transactionSupport)
   {
      this.transactionSupport = transactionSupport;
   }

   public Element getManagedConnectionFactoryProperties()
   {
      return managedConnectionFactoryProperties;
   }

   public void setManagedConnectionFactoryProperties(Element managedConnectionFactoryProperties)
   {
      this.managedConnectionFactoryProperties = managedConnectionFactoryProperties;
   }

   public String getAuthenticationMechanismType()
   {
      return authenticationMechanismType;
   }

   public void setAuthenticationMechanismType(String authenticationMechanismType)
   {
      this.authenticationMechanismType = authenticationMechanismType;
   }

   public String getCredentialInterface()
   {
      return credentialInterface;
   }

   public void setCredentialInterface(String credentialInterface)
   {
      this.credentialInterface = credentialInterface;
   }

   public boolean isReauthenticationSupport()
   {
      return reauthenticationSupport;
   }

   public void setReauthenticationSupport(boolean reauthenticationSupport)
   {
      this.reauthenticationSupport = reauthenticationSupport;
   }

   public ManagedConnectionFactory getMcfInstance()
   {
      return mcf;
   }

   public XAResourceRecoveryRegistry getXAResourceRecoveryRegistry()
   {
      return xrrr;
   }

   public void setXAResourceRecoveryRegistry(XAResourceRecoveryRegistry xrrr)
   {
      this.xrrr = xrrr;
   }

   public SubjectFactory getSubjectFactory()
   {
      return subjectFactory;
   }

   public void setSubjectFactory(SubjectFactory sf)
   {
      this.subjectFactory = sf;
   }

   public String getConnectionManager()
   {
      return connectionManager;
   }

   protected void startService() throws Exception
   {
      if (mcf != null)
         throw new DeploymentException("Stop the RARDeployment before restarting it");

      ResourceAdapter resourceAdapter = null;
      ConnectionDefinitionMetaData cdmd = cmd.getConnectionDefinition(dmd.getConnectionDefinition());
      
      try
      {
         resourceAdapter = (ResourceAdapter) getServer().getAttribute(oldRarDeployment, "ResourceAdapter");

         if (cdmd == null)
            throw new DeploymentException("ConnectionDefinition '" + connectionDefinition + "' not found in rar '"
                  + rarName + "'");

         setManagedConnectionFactoryClass(cdmd.getManagedConnectionFactoryClass());
         setReauthenticationSupport(cmd.getReauthenticationSupport());
      }
      catch (Exception e)
      {
         throw new DeploymentException("couldn't get oldRarDeployment! " + oldRarDeployment, e);
      }
      
      try
      {
         mcfClass = Thread.currentThread().getContextClassLoader().loadClass(cdmd.getManagedConnectionFactoryClass());
      }
      catch (ClassNotFoundException cnfe)
      {
         log.error("Could not find ManagedConnectionFactory class: " + managedConnectionFactoryClass, cnfe);
         throw new DeploymentException("Could not find ManagedConnectionFactory class: "
               + managedConnectionFactoryClass);
      }
      try
      {
         mcf = (ManagedConnectionFactory) mcfClass.newInstance();
      }
      catch (Exception e)
      {
         log.error("Could not instantiate ManagedConnectionFactory: " + managedConnectionFactoryClass, e);
         throw new DeploymentException("Could not instantiate ManagedConnectionFactory: "
               + managedConnectionFactoryClass);
      }
      
      if (cmd != null)
      {
         // Set the resource adapter properties
         setMcfProperties(cmd.getProperties(), false);
         // Set the connection definition properties
         setMcfProperties(cdmd.getProperties(), true);
      }
      
      setManagedConnectionFactoryProperties(dmd.getManagedConnectionFactoryProperties());

      if (resourceAdapter != null && mcf instanceof ResourceAdapterAssociation)
      {
         ResourceAdapterAssociation raa = (ResourceAdapterAssociation) mcf;
         raa.setResourceAdapter(resourceAdapter);
      }

      if (dmd instanceof XADataSourceDeploymentMetaData)
      {
         XADataSourceDeploymentMetaData xdsdm = (XADataSourceDeploymentMetaData)dmd;

         if (getXAResourceRecoveryRegistry() != null && !xdsdm.isNoRecover())
         {
            // If we have an XAResourceRecoveryRegistry and the deployment is XA
            // lets register it for XA Resource Recovery using the "recover" definitions
            // from the -ds.xml file. Fallback to the standard definitions for
            // user name, password. Keep a seperate reference to the security-domain

            SecurityMetaData recoverSecurityDomainMD = xdsdm.getRecoverSecurityMetaData();
            if (recoverSecurityDomainMD != null)
               recoverSecurityDomain = recoverSecurityDomainMD.getDomain();

            SecurityMetaData securityDomainMD = xdsdm.getSecurityMetaData();
            if (securityDomainMD != null)
               securityDomain = securityDomainMD.getDomain();

            recoverUserName = xdsdm.getRecoverUserName();
            recoverPassword = xdsdm.getRecoverPassWord();

            if (recoverUserName == null)
            {
               for (XAConnectionPropertyMetaData xcpmd : xdsdm.getXADataSourceProperties())
               {
                  if (xcpmd.getName().equals("User"))
                     recoverUserName = xcpmd.getValue();
               }
            }

            if (recoverPassword == null)
            {
               for (XAConnectionPropertyMetaData xcpmd : xdsdm.getXADataSourceProperties())
               {
                  if (xcpmd.getName().equals("Password"))
                     recoverPassword = xcpmd.getValue();
               }
            }

            if (recoverUserName == null)
               recoverUserName = xdsdm.getUserName();

            if (recoverPassword == null) 
               recoverPassword = xdsdm.getPassWord();

            if (log.isDebugEnabled())
               log.debug("Registered for XA Resource Recovery: " + xdsdm.getJndiName());

            if (log.isDebugEnabled())
            {
               if (recoverUserName != null)
               {
                  log.debug("RecoverUserName=" + recoverUserName);
               }
               else if (recoverSecurityDomain != null)
               {
                  log.debug("RecoverSecurityDomain=" + recoverSecurityDomain);
               }
               else if (securityDomain != null)
               {
                  log.debug("RecoverSecurityDomain=" + securityDomain);
               }
            }

            getXAResourceRecoveryRegistry().addXAResourceRecovery(this);
            recoveryRegistered = true;
         }
      }
   }

   protected void stopService()
   {
      if (recoveryRegistered)
      {
         if (getXAResourceRecoveryRegistry() != null)
         {
            close(recoverMC);

            getXAResourceRecoveryRegistry().removeXAResourceRecovery(this);
            recoveryRegistered = false;

            if (log.isDebugEnabled())
               log.debug("Unregistered for XA Resource Recovery: " + dmd.getJndiName());
         }
      }

      mcf = null;
      mcfClass = null;
   }

   /**
    * Provides XAResource(s) to the transaction system for recovery purposes.
    *
    * @return An array of XAResource objects for use in transaction recovery
    * In most cases the implementation will need to return only a single XAResource in the array.
    * For more sophisticated cases, such as where multiple different connection types are supported,
    * it may be necessary to return more than one.
    *
    * The Resource should be instantiated in such a way as to carry the necessary permissions to
    * allow transaction recovery. For some deployments it may therefore be necessary or desirable to
    * provide resource(s) based on e.g. database connection parameters such as username other than those
    * used for the regular application connections to the same resource manager. 
    */
   public XAResource[] getXAResources()
   {
      if (recoveryRegistered)
      {
         try
         {
            Subject subject = getSubject();

            // Check if we got a valid Subject instance; requirement for recovery
            if (subject != null)
            {
               ManagedConnection mc = open(subject);
               XAResource xaResource = null;

               try
               {
                  xaResource = mc.getXAResource();
               }
               catch (ResourceException reconnect)
               {
                  close(mc);
                  mc = open(subject);
                  xaResource = mc.getXAResource();
               }

               try
               {
                  ObjectName on = new ObjectName(connectionManager);

                  boolean wrapXAResource = false;
                  boolean padXid = false;
                  Boolean isSameRMOverrideValue = Boolean.FALSE;

                  Boolean b = null;

                  b = (Boolean)getServer().getAttribute(on, "WrapXAResource");
                  if (b != null)
                     wrapXAResource = b.booleanValue();

                  b = (Boolean)getServer().getAttribute(on, "PadXid");
                  if (b != null)
                     padXid = b.booleanValue();

                  b = (Boolean)getServer().getAttribute(on, "IsSameRMOverrideValue");
                  if (b != null)
                     isSameRMOverrideValue = b;

                  if(wrapXAResource)
                  {
                     String eisProductName = null;
                     String eisProductVersion = null;

                     try
                     {
                        eisProductName = mc.getMetaData().getEISProductName();
                        eisProductVersion = mc.getMetaData().getEISProductVersion();
                     }
                     catch (ResourceException re)
                     {
                        // Ignore
                     }

                     xaResource = new XAResourceWrapperImpl(xaResource, 
                                                            padXid,
                                                            isSameRMOverrideValue,
                                                            eisProductName,
                                                            eisProductVersion);
                  }
               }
               catch (Throwable t)
               {
                  // Ignore
               }

               if (log.isDebugEnabled())
                  log.debug("Recovery XAResource=" + xaResource + " for " + dmd.getJndiName());
            
               return new XAResource[] {xaResource};
            }
            else
            {
               if (log.isDebugEnabled())
                  log.debug("Subject for recovery was null");
            }
         }
         catch (ResourceException re)
         {
            if (log.isDebugEnabled())
               log.debug("Error during recovery", re);
         }
      }
      else
      {
         if (log.isDebugEnabled())
            log.debug("Recovery not registered for " + dmd.getJndiName());
      }

      return new XAResource[0];
   }

   /**
    * This method provide the Subject used for the XA Resource Recovery
    * integration with the XAResourceRecoveryRegistry.
    *
    * This isn't done through the SecurityAssociation functionality of JBossSX
    * as the Subject returned here should only be used for recovery.
    *
    * @return The recovery subject; <code>null</code> if no Subject could be created
    */
   private Subject getSubject()
   {
      return AccessController.doPrivileged(new PrivilegedAction<Subject>() 
      {
         public Subject run()
         {
            if (recoverUserName != null && recoverPassword != null)
            {
               // User name and password use-case
               Subject subject = new Subject();

               // Principals
               Principal p = new SimplePrincipal(recoverUserName);
               subject.getPrincipals().add(p);

               // PrivateCredentials
               PasswordCredential pc = new PasswordCredential(recoverUserName, recoverPassword.toCharArray());
               pc.setManagedConnectionFactory(mcf);
               subject.getPrivateCredentials().add(pc);

               // PublicCredentials
               // None

               if (log.isDebugEnabled())
                  log.debug("Recovery Subject=" + subject);

               return subject;
            }
            else
            {
               // Security-domain use-case
               try
               {
                  // Create a security context on the association
                  SecurityContext securityContext = SecurityContextFactory.createSecurityContext(recoverSecurityDomain);
                  SecurityContextAssociation.setSecurityContext(securityContext);
               
                  // Unauthenticated
                  Subject unauthenticated = new Subject();
                  
                  // Leave the subject empty as we don't have any information to do the
                  // authentication with - and we only need it to be able to get the
                  // real subject from the SubjectFactory
                  
                  // Set the authenticated subject
                  securityContext.getSubjectInfo().setAuthenticatedSubject(unauthenticated);

                  // Select the domain
                  String domain = recoverSecurityDomain;
                  if (domain == null)
                     domain = securityDomain;

                  if (domain != null)
                  {
                     // Use the unauthenticated subject to get the real recovery subject instance
                     Subject subject = subjectFactory.createSubject(domain);

                     if (log.isDebugEnabled())
                        log.debug("Recovery Subject=" + subject);
                     
                     return subject;
                  }
                  else
                  {
                     if (log.isDebugEnabled())
                        log.debug("RecoverySecurityDomain was empty");
                  }
               }
               catch (Throwable t)
               {
                  log.debug("Exception during getSubject()" + t.getMessage(), t);
               }

               return null;
            }
         }
      });
   }

   /**
    * Open a managed connection
    * @param s The subject
    * @return The managed connection
    * @exception ResourceException Thrown in case of an error
    */
   private ManagedConnection open(Subject s) throws ResourceException
   {
      if (recoverMC == null)
      {
         recoverMC = createManagedConnection(s, null);
      }

      return recoverMC;
   }

   /**
    * Close a managed connection
    * @param mc The managed connection
    */
   private void close(ManagedConnection mc)
   {
      if (mc != null)
      {
         try
         {
            mc.cleanup();
         }
         catch (ResourceException ire)
         {
            if (log.isDebugEnabled())
               log.debug("Error during recovery cleanup", ire);
         }
      }

      if (mc != null)
      {
         try
         {
            mc.destroy();
         }
         catch (ResourceException ire)
         {
            if (log.isDebugEnabled())
               log.debug("Error during recovery destroy", ire);
         }
      }

      mc = null;
   }
   
   public void setManagedConnectionFactoryAttribute(String name, Class clazz, Object value)
   {
      setManagedConnectionFactoryAttribute(name, clazz, value, false);
   }
   
   protected void setManagedConnectionFactoryAttribute(String name, Class clazz, Object value, boolean mustExist)
   {
      if (name == null || name.length() == 0)
         throw new IllegalArgumentException("Null or empty attribute name " + name);
      String setterName = "set" + Character.toUpperCase(name.charAt(0));
      if (name.length() > 1)
         setterName = setterName.concat(name.substring(1));
      Method setter;
      try
      {
         setter = mcfClass.getMethod(setterName, new Class[] {clazz});
      }
      catch (NoSuchMethodException nsme)
      {
         String error = "The class '" + mcfClass.toString() + "' has no setter for config property '" + name + "'"; 
         if (mustExist)
            throw new IllegalArgumentException(error);
         else
         {
            log.trace(error, nsme);
            return;
         }
      }
      try
      {
         setter.invoke(mcf, new Object[] {value});
         log.debug("set property " + name + " to value " + value);
      }
      catch (Exception e)
      {
         String error = "Unable to invoke setter method '" + setter + "' " + "on object '" + mcf + "'";
         if (e instanceof InvocationTargetException)
            throw new NestedRuntimeException(error, ((InvocationTargetException) e).getCause());
         else
            throw new NestedRuntimeException(error, e);
      }
      sendNotification(new Notification(MCF_ATTRIBUTE_CHANGED_NOTIFICATION, getServiceName(),
            getNextNotificationSequenceNumber()));
   }

   public Object getManagedConnectionFactoryAttribute(String name)
   {
      if (name == null || name.length() == 0)
         throw new IllegalArgumentException("Null or empty attribute name " + name);
      String getterName = "get" + Character.toUpperCase(name.charAt(0));
      if (name.length() > 1)
         getterName = getterName.concat(name.substring(1));
      Method getter;
      try
      {
         getter = mcfClass.getMethod(getterName, new Class[] {});
      }
      catch (NoSuchMethodException e)
      {
         String msg = "The class '" + mcfClass + "' has no getter("
            + getterName + ") for config property '" + name + "'";
         log.debug(msg, e);
         throw new IllegalArgumentException(msg);
      }
      try
      {
         Object value = getter.invoke(mcf, new Object[]{});
         log.debug("get property " + name + ": value " + value);
         return value;
      }
      catch (Exception e)
      {
         String error = "Unable to invoke getter method '" + getter + "' " + "on object '" + mcf + "'";
         log.debug(error, e);
         if (e instanceof InvocationTargetException)
            throw new NestedRuntimeException(error, ((InvocationTargetException) e).getCause());
         else
            throw new NestedRuntimeException(error, e);
      }
   }

   protected void setMcfProperties(Collection properties, boolean mustExist) throws DeploymentException
   {
      for (Iterator i = properties.iterator(); i.hasNext();)
      {
         ConfigPropertyMetaData cpmd = (ConfigPropertyMetaData) i.next();
         String name = cpmd.getName();
         String type = cpmd.getType();
         String value = cpmd.getValue();
         if (name == null || name.length() == 0 || value == null || value.length() == 0)
         {
            log.debug("Not setting config property '" + name + "'");
            continue;
         }
         // see if it is a primitive type first
         Class clazz = Classes.getPrimitiveTypeForName(type);
         if (clazz == null)
         {
            //not primitive, look for it.
            try
            {
               clazz = Thread.currentThread().getContextClassLoader().loadClass(type);
            }
            catch (ClassNotFoundException cnfe)
            {
               log.warn("Unable to find class '" + type + "' for " + "property '" + name + "' - skipping property.");
               continue;
            }
         }
         PropertyEditor pe = PropertyEditorManager.findEditor(clazz);
         if (pe == null)
         {
            log.warn("Unable to find a PropertyEditor for class '" + clazz + "' of property '" + name + "' - "
                  + "skipping property");
            continue;
         }
         // TODO: should happen in parsing layer?
         value = StringPropertyReplacer.replaceProperties(value);
         log.debug("setting property: " + name + " to value " + value);
         try
         {
            pe.setAsText(value);
         }
         catch (IllegalArgumentException iae)
         {
            log.warn("Value '" + value + "' is not valid for property '" + name + "' of class '" + clazz
                  + "' - skipping " + "property");
            continue;
         }
         Object v = pe.getValue();
         setManagedConnectionFactoryAttribute(name, clazz, v, mustExist);
      }
   }
   
   protected void setManagedConnectionFactoryProperties(List<ManagedConnectionFactoryPropertyMetaData> properties)
   {
      
      for (ManagedConnectionFactoryPropertyMetaData property : properties)
      {
         String name = property.getName();
         String type = property.getType();
         String value = property.getValue();
         
         if (name == null || name.length() == 0 || value == null || value.length() == 0)
         {
            log.debug("Not setting config property '" + name + "'");
            continue;
         }
         
         if (type == null || type.length() == 0)
         {
            // Default to String for convenience.
            type = "java.lang.String";
         }
         Class clazz = Classes.getPrimitiveTypeForName(type);
         if (clazz == null)
         {
            //not primitive, look for it.
            try
            {
               clazz = Thread.currentThread().getContextClassLoader().loadClass(type);
            }
            catch (ClassNotFoundException cnfe)
            {
               log.warn("Unable to find class '" + type + "' for " + "property '" + name
                     + "' - skipping property.");
               continue;
            }
         }
         PropertyEditor pe = PropertyEditorManager.findEditor(clazz);
         if (pe == null)
         {
            log.warn("Unable to find a PropertyEditor for class '" + clazz + "' of property '" + name + "' - "
                  + "skipping property");
            continue;
         }
         // TODO: should happen in parsing layer?
         value = StringPropertyReplacer.replaceProperties(value);
         log.debug("setting property: " + name + " to value " + value);
         try
         {
            pe.setAsText(value);
         }
         catch (IllegalArgumentException iae)
         {
            log.warn("Value '" + value + "' is not valid for property '" + name + "' of class '" + clazz
                  + "' - skipping " + "property");
            continue;
         }
         Object v = pe.getValue();
         setManagedConnectionFactoryAttribute(name, clazz, v);
      
      }
      
      
   }

   public Object createConnectionFactory() throws ResourceException
   {
      return mcf.createConnectionFactory();
   }

   public Object createConnectionFactory(ConnectionManager cxManager) throws ResourceException
   {
      return mcf.createConnectionFactory(cxManager);
   }

   public ManagedConnection createManagedConnection(Subject subject,
         ConnectionRequestInfo cxRequestInfo) throws ResourceException
   {
      return mcf.createManagedConnection(subject, cxRequestInfo);
   }

   public boolean equals(Object other)
   {
      return mcf.equals(other);
   }

   public java.io.PrintWriter getLogWriter() throws ResourceException
   {
      return mcf.getLogWriter();
   }

   public String toString()
   {
      StringBuffer buffer = new StringBuffer();
      buffer.append(getClass().getName());
      buffer.append('@');
      buffer.append(Integer.toHexString(System.identityHashCode(this)));
      return buffer.toString();
   }
   
   public int hashCode()
   {
      return mcf.hashCode();
   }

   public ManagedConnection matchManagedConnections(java.util.Set connectionSet, Subject subject,
         ConnectionRequestInfo cxRequestInfo) throws ResourceException
   {
      return mcf.matchManagedConnections(connectionSet, subject, cxRequestInfo);
   }

   public void setLogWriter(java.io.PrintWriter out) throws ResourceException
   {
      mcf.setLogWriter(out);
   }

   @Override
   protected MBeanAttributeInfo[] getInternalAttributeInfo()
   {
      List<MBeanAttributeInfo> attributes = new ArrayList<MBeanAttributeInfo>();
      attributes.add(new MBeanAttributeInfo("RARName", String.class.getName(), "The ResourceAdapter deployment name.", true, false, false));
      attributes.add(new MBeanAttributeInfo("OldRarDeployment", javax.management.ObjectName.class.getName(), "The Connection Defintion class name.", true, true, false));
      attributes.add(new MBeanAttributeInfo("ConnectionDefinition", String.class.getName(), "The Connection Defintion class name.", true, false, false));
      attributes.add(new MBeanAttributeInfo("VendorName", String.class.getName(), "The Vendor Name.", true, false, false));
      attributes.add(new MBeanAttributeInfo("SpecVersion", String.class.getName(), "The Specification Version.", true, false, false));
      attributes.add(new MBeanAttributeInfo("EisType", String.class.getName(), "The Enterprise Information System type.", true, false, false));
      attributes.add(new MBeanAttributeInfo("Version", String.class.getName(), "The ResourceAdapter version.", true, false, false));
      attributes.add(new MBeanAttributeInfo("ManagedConnectionFactoryClass", String.class.getName(), "The ManagedConnectionFactory class", true, false, false));
      attributes.add(new MBeanAttributeInfo("ConnectionFactoryImpl", String.class.getName(), "The Connection Factory implementation", true, false, false));
      attributes.add(new MBeanAttributeInfo("ConnectionInterface", String.class.getName(), "The Connection Inteface", true, false, false));
      attributes.add(new MBeanAttributeInfo("ConnectionImplClass", String.class.getName(), "The Connection Implementation class", true, false, false));
      attributes.add(new MBeanAttributeInfo("TransactionSupport", String.class.getName(), "The Transaction Support", true, false, false));
      attributes.add(new MBeanAttributeInfo("AuthenticationMechanismType", String.class.getName(), "The Authentication Mechanism Type", true, false, false));
      attributes.add(new MBeanAttributeInfo("CredentialInterface", String.class.getName(), "The Credential Interface", true, false, false));
      attributes.add(new MBeanAttributeInfo("ReauthenticationSupport", Boolean.class.getName(), "The Reauthentication Support", true, false, true));
      attributes.add(new MBeanAttributeInfo("McfInstance", "javax.resource.spi.ManagedConnectionFactory", "The ManagedConnectionFactory instance", true, false, false));
      attributes.add(new MBeanAttributeInfo("XAResourceRecoveryRegistry", XAResourceRecoveryRegistry.class.getName(), "The XAResource Recovery Registry", true, false, false));
      attributes.add(new MBeanAttributeInfo("SubjectFactory", SubjectFactory.class.getName(), "The subject factory used for recovery", true, false, false));
      attributes.add(new MBeanAttributeInfo("ConnectionManager", String.class.getName(), "The connection manager object name", true, false, false));
      
//      ConnectionDefinitionMetaData cdmd = cmd.getConnectionDefinition(this.connectionDefinition);
      
//      Collection mcfProps = cdmd.getProperties();
//      
//      for(Iterator iter = mcfProps.iterator(); iter.hasNext();)
//      {
//         ConfigPropertyMetaData cpmd = (ConfigPropertyMetaData)iter.next();         
//         attributes.add(new MBeanAttributeInfo(cpmd.getName(), cpmd.getType(), cpmd.getDescription().getDescription(), true, false, false));         
//      }
      MBeanAttributeInfo[] info = attributes.toArray(new MBeanAttributeInfo[attributes.size()]);
      return info;
            
   }
   
   @Override
   protected void setInternalAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException
   {
      if("OldRarDeployment".equals(attribute.getName()))
      {
         this.oldRarDeployment = (ObjectName)attribute.getValue();
      }
      else if("XAResourceRecoveryRegistry".equals(attribute.getName()))
      {
         this.xrrr = (XAResourceRecoveryRegistry)attribute.getValue();
      }
      else if("SubjectFactory".equals(attribute.getName()))
      {
         this.subjectFactory = (SubjectFactory)attribute.getValue();
      }
   }

   @Override
   protected MBeanOperationInfo[] getInternalOperationInfo()
   {
      MBeanOperationInfo[] operations = new MBeanOperationInfo[1]; 
      MBeanParameterInfo[] getMCFAttributeParamInfo = new MBeanParameterInfo[]{ new MBeanParameterInfo("ManagedConnectionFactoryAttributeName", String.class.getName(), "The ManagedConnectionFactoryAttribute name")};
      operations[0] = new MBeanOperationInfo("getManagedConnectionFactoryAttribute", "Inspect the value of a ManagedConnectionFactory attribute", getMCFAttributeParamInfo, Object.class.getName(), MBeanOperationInfo.INFO);
      return operations;
      
   }
   
   @Override
   protected Object internalInvoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException
   {
      if(actionName.equals("getManagedConnectionFactoryAttribute"))
      {
         String param = (String)params[0];
         return getManagedConnectionFactoryAttribute(param);
   
      }
      else
      {
         return super.internalInvoke(actionName, params, signature);
      }
   
   }

   @Override
   protected Object getInternalAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException
   {
      ConnectionDefinitionMetaData cdmd = cmd.getConnectionDefinition(dmd.getConnectionDefinition());
      
      Object result = null;
      
      if("RARName".equals(attribute))
      {
         result = rarName;         
      
      }else if("OldRarDeployment".equals(attribute))
      {
         result = oldRarDeployment;
      }
      else if("ConnectionDefinition".equals(attribute))
      {
         result = dmd.getConnectionDefinition();         
      
      }else if("VendorName".equals(attribute))
      {
         result = cmd.getVendorName();         
      
      }else if("SpecVersion".equals(attribute))
      {
         result = cmd.getVersion();         
      
      }else if("EisType".equals(attribute))
      {
         result = cmd.getEISType();               
      
      }else if("Version".equals(attribute))
      {
         result = cmd.getRAVersion();
      
      }else if("ManagedConnectionFactoryClass".equals(attribute))
      {
         result = cdmd.getManagedConnectionFactoryClass();         

      }else if("ConnectionInterface".equals(attribute))
      {
         result = cdmd.getConnectionInterfaceClass();         
      }
      else if("ConnectionFactoryImpl".equals(attribute))
      {
         result = cdmd.getConnectionFactoryImplementationClass();
         
      }else if("ConnectionImplClass".equals(attribute))
      {
         result = cdmd.getConnectionImplementationClass();
      
      }
      else if("TransactionSupport".equals(attribute))
      {
         result = dmd.getTransactionSupportMetaData();         
      
      }
      else if("AuthenticationMechanismType".equals(attribute))
      {
         result = cmd.getAuthenticationMechanism().getAuthenticationMechansimType();      
      
      }
      else if("CredentialInterface".equals(attribute))
      {
         result = cmd.getAuthenticationMechanism().getCredentialInterfaceClass(); 
      
      }
      else if("ReauthenticationSupport".equals(attribute))
      {
         result = cmd.getReauthenticationSupport();
               
      }
      else if("McfInstance".equals(attribute))
      {
         result = mcf;               
      }
      else if("XAResourceRecoveryRegistry".equals(attribute))
      {
         result = xrrr;               
      }
      else if("SubjectFactory".equals(attribute))
      {
         result = subjectFactory;               
      }
      else if("ConnectionManager".equals(attribute))
      {
         result = connectionManager;               
      }
      
      if(result == null)
      {
         result = super.getInternalAttribute(attribute);
      }
      
      return result;
   }
   
   protected String getInternalDescription()
   {
      String description = null;
      DescriptionGroupMetaData dgmd = cmd.getDescription();
      if (dgmd != null)
         description = dgmd.getDescription();
      if (description == null)
         description = "RAR Deployment ";
      return description;
   }
}
