/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt 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;

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.Logger;
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 (toby.allsopp@peace.com)
 * @author  <a href="mailto:adrian@jboss.com">Adrian Brock</a>
 * @version    $Revision: 1.14.2.4 $
 */
public class RARMetaData
       implements XmlLoadable
{
   public final static int TX_SUPPORT_NO = 0;
   public final static int TX_SUPPORT_LOCAL = 1;
   public final static int TX_SUPPORT_XA = 2;

   private Logger category = Logger.getLogger(RARMetaData.class);

   private Element resourceAdapterElement;

   private ClassLoader classLoader;

   private String displayName;

   private String managedConnectionFactoryClass;
   private String connectionFactoryInterface;
   private String connectionFactoryImplClass;
   private String connectionInterface;
   private String connectionImplClass;

   private int transactionSupport;

   private Map properties = new HashMap();

   private String authMechType;
   private String credentialInterface;

   private boolean reauthenticationSupport;

   /**
    *  Sets the ClassLoader attribute of the RARMetaData object
    *
    * @param  cl  The new ClassLoader value
    */
   public void setClassLoader(ClassLoader cl)
   {
      classLoader = cl;
   }

   public Element getResourceAdapterElement()
   {
      return resourceAdapterElement;
   }

   /**
    *  The class loader to use for the resource adapter's classes
    *
    * @return    The ClassLoader value
    */
   public ClassLoader getClassLoader()
   {
      return classLoader;
   }

   /**
    *  Gets the DisplayName attribute of the RARMetaData object
    *
    * @return    The DisplayName value
    */
   public String getDisplayName()
   {
      return displayName;
   }

   /**
    *  Gets the ManagedConnectionFactoryClass attribute of the RARMetaData
    *  object
    *
    * @return    The ManagedConnectionFactoryClass value
    */
   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;
   }

   /**
    *  Gets the Properties attribute of the RARMetaData object
    *
    * @return    The Properties value
    */
   public Map getProperties()
   {
      return properties;
   }

   /**
    *  Gets the PropertyType attribute of the RARMetaData object
    *
    * @param  name  Description of Parameter
    * @return       The PropertyType value
    */
   public String getPropertyType(String name)
   {
      Property prop = (Property)properties.get(name);
      return (prop == null) ? null : prop.type;
   }

   /**
    *  Gets the AuthMechType attribute of the RARMetaData object
    *
    * @return    The AuthMechType value
    */
   public String getAuthMechType()
   {
      return authMechType;
   }

   /**
    *  Gets the ReauthenticationSupport attribute of the RARMetaData object
    *
    * @return    The ReauthenticationSupport value
    */
   public boolean getReauthenticationSupport()
   {
      return reauthenticationSupport;
   }

   /**
    *  #Description of the Method
    *
    * @param  root                     Description of Parameter
    * @exception  DeploymentException  Description of Exception
    */
   public void importXml(Element root)
          throws DeploymentException
   {

      // 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 resource 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);
   }

   // 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
   {
      resourceAdapterElement = element;
      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("NoTransaction"))
      {
         ts = TX_SUPPORT_NO;
      }
      else if (s.equals("LocalTransaction"))
      {
         ts = TX_SUPPORT_LOCAL;
      }
      else if (s.equals("XATransaction"))
      {
         ts = TX_SUPPORT_XA;
      }
      else
      {
         throw new DeploymentException("Invalid transaction support '" +
               s + "', it must be one of " +
               "'NoTransaction', " +
               "'LocalTransaction' or " +
               "'XATransaction'");
      }
      transactionSupport = ts;
   }

   private void setConfigProperty(Element element)
          throws DeploymentException
   {
      Element nameE = MetaData.getUniqueChild(element,
            "config-property-name");
      Element typeE = MetaData.getUniqueChild(element,
            "config-property-type");
      Element valueE = MetaData.getOptionalChild(element,
            "config-property-value");
      Element descE = MetaData.getOptionalChild(element,
            "description");

      Property p = new Property();
      p.name = getElementContent(nameE);
      p.type = getElementContent(typeE);
      if (valueE != null)
      {
         p.value = getElementContent(valueE);
      }
      if (descE != null) 
      {
         p.desc = getElementContent(descE);      
      }

      properties.put(p.name, p);
   }

   private void setLicense(Element element)
          throws DeploymentException
   {
      Element requiredE = MetaData.getUniqueChild(element,
            "license-required");
      Element descriptionE = MetaData.getOptionalChild(element,
            "description");
      boolean required =
            Boolean.valueOf(getElementContent(requiredE).trim()).booleanValue();
      if (required)
      {
         category.info("Required license terms present. See deployment " +
               "descriptor.");
      }
      else
      {
         category.info("License terms present. See deployment descriptor.");
      }
   }

   private void setDescription(Element element)
          throws DeploymentException
   {
      category.info("Loading " + getElementContent(element).trim());
   }

   private void setAuthenticationMechanism(Element element)
          throws DeploymentException
   {
      invokeChildren(element);
   }

   private void setAuthenticationMechanismType(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();
   }

   private void setSecurityPermission(Element element)
      throws DeploymentException
   {
      Element permSpec = MetaData.getUniqueChild(element,
         "security-permission-spec");
      String spec = getElementContent(permSpec);
      if (category.isDebugEnabled())
         category.debug("Ignoring security-permission("+spec+")");
   }

   private String getElementContent(Element element)
          throws DeploymentException
   {
      return MetaData.getElementContent(element);
   }

   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.
               category.debug("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", 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)));
      }

      if (category.isDebugEnabled())
         category.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);
   }

   // Inner classes -------------------------------------------------

   /**
    * 
    */
   public class Property
   {
      public String name;
      public String type;
      public String value;
      public String desc;
   }
}
