/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jboss.management.j2ee;

import java.security.InvalidParameterException;
import java.util.Hashtable;
import java.util.Set;

import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.ObjectName;

import org.jboss.logging.Logger;
import org.jboss.management.j2ee.statistics.StatisticsProvider;
import org.jboss.mx.util.ObjectNameConverter;
import org.jboss.system.ServiceMBeanSupport;

/**
 * Root class of the JBoss JSR-77 implementation of J2EEManagedObject.
 *
 * @author <a href="mailto:andreas@jboss.org">Andreas Schaefer</a>.
 * @author Scott.Stark@jboss.org
 * @author thomas.diesler@jboss.org
 * @version $Revision: 1.27.4.1 $
 * @jmx:mbean extends="org.jboss.system.ServiceMBean"
 */
public abstract class J2EEManagedObject
   extends ServiceMBeanSupport
   implements J2EEManagedObjectMBean
{
   // Constants -----------------------------------------------------
   public static final String TYPE = "j2eeType";
   public static final String NAME = "name";

   // Attributes ----------------------------------------------------
   private static Logger classLog = Logger.getLogger(J2EEManagedObject.class);

   private ObjectName parentName = null;
   private ObjectName name = null;

   // Static --------------------------------------------------------

   /**
    * Retrieves the type out of an JSR-77 object name
    *
    * @param pName Object Name to check if null then
    *              it will be treated like NO type found
    * @return The type of the given Object Name or an EMPTY
    *         string if either Object Name null or type not found
    */
   protected static String getType(String pName)
   {
      String lType = null;
      if (pName != null)
      {
         ObjectName oname = newObjectName(pName);
         lType = (String) oname.getKeyPropertyList().get(TYPE);
      }
      // Return an empty string if type not found
      return lType == null ? "" : lType;
   }

   /**
    * Retrieves the type out of an JSR-77 object name
    *
    * @param pName Object Name to check if null then
    *              it will be treated like NO type found
    * @return The type of the given Object Name or an EMPTY
    *         string if either Object Name null or type not found
    */
   protected static String getType(ObjectName pName)
   {
      String lType = null;
      if (pName != null)
      {
         lType = (String) pName.getKeyPropertyList().get(TYPE);
      }
      // Return an empty string if type not found
      return lType == null ? "" : lType;
   }

   /**
    * Return the ObjectName that is represented by the given string.
    *
    * @param pName a object name
    */
   protected static ObjectName newObjectName(String pName)
   {
      try
      {
         return new ObjectName(pName);
      }
      catch (MalformedObjectNameException e)
      {
         throw new IllegalArgumentException("Invalid object name: " + pName);
      }
   }

   protected static ObjectName removeObject(MBeanServer pServer, String pSearchCriteria)
           throws JMException
   {
      ObjectName lSearch = ObjectNameConverter.convert(pSearchCriteria);
      classLog.debug("removeObject(), search for: " + pSearchCriteria +
              ", search criteria: " + lSearch);
      Set lNames = pServer.queryNames(lSearch, null);
      if (!lNames.isEmpty())
      {
         ObjectName lName = (ObjectName) lNames.iterator().next();
         pServer.unregisterMBean(lName);
         return lName;
      }
      return null;
   }

   protected static ObjectName removeObject(MBeanServer pServer, String pName, String pSearchCriteria)
           throws JMException
   {
      String lEncryptedName = ObjectNameConverter.convertCharacters(pName, true);
      ObjectName lSearch = new ObjectName(pSearchCriteria + "," + NAME + "=" + lEncryptedName);
      classLog.debug("removeObject(), name: " + pName +
              ", encrypted name: " + lEncryptedName +
              ", search criteria: " + lSearch);
      Set lNames = pServer.queryNames(lSearch, null);
      if (!lNames.isEmpty())
      {
         ObjectName lName = (ObjectName) lNames.iterator().next();
         pServer.unregisterMBean(lName);
         return lName;
      }
      return null;
   }

   // Constructors --------------------------------------------------

   /**
    * Constructor for the root J2EEDomain object
    *
    * @param domainName domain portion to use for the JMX ObjectName
    * @param j2eeType   JSR77 j2ee-type of the resource being created
    * @param resName    Name of the managed resource
    * @throws InvalidParameterException If the given Domain Name, Type or Name is null
    */
   public J2EEManagedObject(String domainName, String j2eeType, String resName)
           throws MalformedObjectNameException
   {
      if (domainName == null)
      {
         throw new InvalidParameterException("Domain Name must be set");
      }
      Hashtable lProperties = new Hashtable();
      lProperties.put(TYPE, j2eeType);
      lProperties.put(NAME, resName);
      name = ObjectNameConverter.convert(domainName, lProperties);
      log.debug("ctor, name: " + name);
   }

   /**
    * Constructor for any Managed Object except the root J2EEMangement.
    *
    * @param j2eeType        JSR77 j2ee-type of the resource being created
    * @param resName         name of the resource
    * @param jsr77ParentName Object Name of the parent of this Managed Object
    *                        which must be defined
    * @throws InvalidParameterException If the given Type, Name or Parent is null
    */
   public J2EEManagedObject(String j2eeType, String resName, ObjectName jsr77ParentName)
           throws MalformedObjectNameException,
           InvalidParentException
   {
      Hashtable lProperties = getParentKeys(jsr77ParentName);
      lProperties.put(TYPE, j2eeType);
      lProperties.put(NAME, resName);
      name = ObjectNameConverter.convert(J2EEDomain.getDomainName(), lProperties);
      setparent(jsr77ParentName.getCanonicalName());
   }

   // Public --------------------------------------------------------

   // J2EEManagedObjectMBean implementation ----------------------------------------------

   /**
    **/
   public ObjectName getObjectName()
   {
      return name;
   }

   /**
    * @jmx:managed-attribute
    */
   public String getobjectName()
   {
      return name.getCanonicalName();
   }

   /**
    * @jmx:managed-attribute
    */
   public String getparent()
   {
      return parentName.getCanonicalName();
   }

   /**
    * @jmx:managed-attribute
    */
   public void setparent(String pParent)
           throws InvalidParentException
   {
      if (pParent == null)
      {
         throw new InvalidParameterException("Parent must be set");
      }
      parentName = newObjectName(pParent);
   }

   /**
    * @jmx:managed-operation
    */
   public void addChild(ObjectName pChild)
   {
   }

   /**
    * @jmx:managed-operation
    */
   public void removeChild(ObjectName pChild)
   {
   }

   // J2EEManagedObject implementation ----------------------------------------------

   /**
    * @jmx:managed-attribute
    */
   public boolean isstateManageable()
   {
      return this instanceof StateManageable;
   }

   /**
    * @jmx:managed-attribute
    */
   public boolean isstatisticsProvider()
   {
      return this instanceof StatisticsProvider;
   }

   /**
    * @jmx:managed-attribute
    */
   public boolean iseventProvider()
   {
      return this instanceof EventProvider;
   }

   // ServiceMBeanSupport overrides ---------------------------------------------------

   public ObjectName getObjectName(MBeanServer pServer, ObjectName pName)
   {
      return getObjectName();
   }

   /**
    * Last steps to be done after MBean is registered on MBeanServer. This
    * method is made final because it contains vital steps mandatory to all
    * J2EEManagedObjects. To perform your own Post-Creation steps please
    * override {@link #postCreation postCreation()} method.
    */
   public final void postRegister(Boolean registrationDone)
   {
      // This try-catch block is here because of debugging purposes because
      // runtime exception in JMX client is a awful thing to figure out
      try
      {
         log.debug("postRegister(), parent: " + parentName);
         if (registrationDone.booleanValue())
         {
            // Let the subclass handle post creation steps
            postCreation();
            if (parentName != null)
            {
               try
               {
                  // Notify the parent about its new child
                  if (parentName.getKeyProperty("name").compareTo("null") != 0)
                  {
                     getServer().invoke(parentName,
                             "addChild",
                             new Object[]{name},
                             new String[]{ObjectName.class.getName()});
                  }
                  else
                  {
                     ObjectName j2eeServerName = J2EEDomain.getDomainServerName(server);
                     server.invoke(j2eeServerName,
                             "addChild",
                             new Object[]{name},
                             new String[]{ObjectName.class.getName()});
                  }
               }
               catch (JMException e)
               {
                  log.debug("Failed to add child", e);
                  registrationDone = Boolean.FALSE;
               }
            }
         }
      }
      catch (RuntimeException re)
      {
         log.debug("postRegister() caught this exception", re);
         throw re;
      }
      super.postRegister(registrationDone);
   }

   /**
    * Last steps to be done before MBean is unregistered on MBeanServer. This
    * method is made final because it contains vital steps mandatory to all
    * J2EEManagedObjects. To perform your own Pre-Destruction steps please
    * override {@link #preDestruction preDestruction()} method.
    */
   public final void preDeregister()
           throws Exception
   {
      log.debug("preDeregister(), parent: " + parentName);
      // Only remove child if it is a child (root has not parent)
      if (parentName != null)
      {
         try
         {
            // Notify the parent about the removal of its child
            server.invoke(parentName,
                    "removeChild",
                    new Object[]{name},
                    new String[]{ObjectName.class.getName()});
         }
         catch (InstanceNotFoundException infe)
         {
         }
         preDestruction();
      }
   }

   /**
    * An overload of the super sendNotification that only takes the event
    * type and msg. The source will be set to the managed object name, the
    * sequence will be the getNextNotificationSequenceNumber() value, and the
    * timestamp System.currentTimeMillis().
    *
    * @param type the notification event type
    * @param info the notification event msg info
    */
   public void sendNotification(String type, String info)
   {
      Notification msg = new Notification(type, this.getObjectName(),
              this.getNextNotificationSequenceNumber(),
              System.currentTimeMillis(),
              info);
      super.sendNotification(msg);
   }

   // Object overrides ---------------------------------------------------

   public String toString()
   {
      return "J2EEManagedObject [ name: " + name + ", parent: " + parentName + " ];";
   }

   // Package protected ---------------------------------------------

   // Protected -----------------------------------------------------

   protected void postCreation()
   {
   }

   protected void preDestruction()
   {
   }

   /**
    * This method can be overwritten by any subclass which must
    * return &lt;parent-j2eeType&gt; indicating its parents. By
    * default it returns an empty hashtable instance.
    *
    * @param pParent The direct parent of this class
    * @return An empty hashtable
    */
   protected Hashtable getParentKeys(ObjectName pParent)
   {
      return new Hashtable();
   }

   // Private -------------------------------------------------------

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