/*
 * 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.management.j2ee.cluster;

import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.NotificationFilter;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;

import org.jboss.ha.framework.interfaces.HAPartition;
import org.jboss.management.j2ee.J2EEDomain;
import org.jboss.mx.util.ObjectNameFactory;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.system.server.ServerConfigUtil;

/**
 * This class enables a client to get a management view (JSR-77) on a
 * JBoss Cluster. It contains the entire logic to map all the {@link
 * org.jboss.management.j2ee.J2EEManagedObject Managed Object (MO) of
 * all the nodes in the cluster to one MO. The same applies to State
 * Management and Performance Statistics.
 *
 * @author Andreas Schaefer
 * @version $Revision: 57197 $ <p>
 * @jmx:mbean extends="org.jboss.system.ServiceMBean"
 */
public class HAManagementService
        extends ServiceMBeanSupport
        implements HAManagementServiceMBean
{

   // Constants -----------------------------------------------------
   
   private final static String SERVICE_NAME = "HAManagementService";
   /**
    * The default object name.
    */
   ObjectName OBJECT_NAME = ObjectNameFactory.create("jboss:service=HAManagement");
   
   // Attributes ----------------------------------------------------
   
   private ObjectName mHAManagementName;
   private ObjectName mClusterPartitionName;
   private String mBackgroundPartition = ServerConfigUtil.getDefaultPartitionName();
   private HAPartition mPartition;
   
   // Static --------------------------------------------------------
   
   // Constructors --------------------------------------------------
   
   /**
    * Creates a undefined clustering management service. Before this
    * MBean can be started the HAPartition must be up and running.
    * <p/>
    * ATTENTION: The current implemenation is a brute force one because
    * it is retrieving the data on every hit. Later on this has to be
    * resolved with a caching to avoid unecessary round-trips.
    */
   public HAManagementService()
   {
   }
   
   // Public --------------------------------------------------------
   
   /**
    **/
   public Object _getAttribute(ObjectName pName, String pAttribute)
   {
      Object lReturn = null;
      try
      {
         lReturn = server.getAttribute(pName, pAttribute);
      }
      catch (Exception e)
      {
         // Ignore them
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public Object getAttribute(ObjectName pName, String pAttribute)
           throws
           MBeanException,
           AttributeNotFoundException,
           InstanceNotFoundException,
           ReflectionException,
           RemoteException
   {
      // First we try to get the attribute from the local node
      // and if not found the look for an instance out in the
      // node. This does not apply for JVMs.
      //AS ToDo: JVMs are not supported yet
      Object lReturn = null;
      try
      {
         lReturn = server.getAttribute(pName, pAttribute);
      }
      catch (InstanceNotFoundException infe)
      {
         // Ignore and search on the cluster
         Object[] lArguments = new Object[]{
            pName,
            pAttribute
         };
         List lValues = null;
         try
         {
            lValues = mPartition.callMethodOnCluster(SERVICE_NAME,
                    "_getAttribute",
                    lArguments,
               new Class[]{ObjectName.class, String.class},
               false
            );
         }
         catch (Exception e)
         {
            throw new RemoteException("Could not get management attributes on the cluster", e);
         }
         Iterator i = lValues.iterator();
         while (i.hasNext())
         {
            Object lValue = i.next();
            if (lValue != null)
            {
               lReturn = lValue;
               break;
            }
         }
         if (lReturn == null)
         {
            // Throw the instance not found exception because there is none
            throw infe;
         }
      }
      return lReturn;
   }

   /**
    **/
   public AttributeList _getAttributes(ObjectName pName, String[] pAttributes)
   {
      AttributeList lReturn = null;
      try
      {
         lReturn = server.getAttributes(pName, pAttributes);
      }
      catch (Exception e)
      {
         // Ignore them
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public AttributeList getAttributes(ObjectName pName, String[] pAttributes)
           throws
           InstanceNotFoundException,
           ReflectionException,
           RemoteException
   {
      // First we try to get the attribute from the local node
      // and if not found the look for an instance out in the
      // node. This does not apply for JVMs.
      //AS ToDo: JVMs are not supported yet
      //AS ToDo: Now only the first list is taken, shall we add a merge capabilities ?
      AttributeList lReturn = null;
      try
      {
         lReturn = server.getAttributes(pName, pAttributes);
      }
      catch (InstanceNotFoundException infe)
      {
         // Ignore and search on the cluster
         Object[] lArguments = new Object[]{
            pName,
            pAttributes
         };
         List lValues = null;
         try
         {
            lValues = mPartition.callMethodOnCluster(SERVICE_NAME,
                    "_getAttributes",
                    lArguments,
               new Class[]{ObjectName.class, String[].class},
               false
            );
         }
         catch (Exception e)
         {
            throw new RemoteException("Could not get management attributes on the cluster", e);
         }
         Iterator i = lValues.iterator();
         while (i.hasNext())
         {
            Object lValue = i.next();
            if (lValue != null)
            {
               lReturn = (AttributeList) lValue;
               break;
            }
         }
         if (lReturn == null)
         {
            // Throw the instance not found exception because there is none
            throw infe;
         }
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public String getDefaultDomain()
           throws RemoteException
   {
      return J2EEDomain.getDomainName();
   }

   /**
    * @jmx:managed-attribute
    */
   public Integer getMBeanCount()
           throws RemoteException
   {
      try
      {
         return new Integer(queryNames(new ObjectName("*:*"),
                 null).size());
      }
      catch (Exception e)
      {
      }
      return new Integer(0);
   }

   /**
    * @jmx:managed-attribute
    */
   public MBeanInfo getMBeanInfo(ObjectName pName)
           throws
           IntrospectionException,
           InstanceNotFoundException,
           ReflectionException,
           RemoteException
   {
      return server.getMBeanInfo(pName);
   }

   /**
    * @jmx:managed-attribute
    */
   public javax.management.j2ee.ListenerRegistration getListenerRegistry()
           throws RemoteException
   {
      return null;
/*
      return new ListenerRegistration(
         (ManagementHome) mContext.getEJBObject().getEJBHome(),
         new String[] {}
      );
*/
   }

   /**
    **/
   public Object _invoke(ObjectName pName, String pOperationName, Object[] pParams, String[] pSignature)
   {
      Object lReturn = null;
      try
      {
         log.info("_invoke(), name: " + pName + ", operation: " + pOperationName +
                 ", params: " + pParams + ", signature: " + pSignature);
         lReturn = server.invoke(pName,
                 pOperationName,
                 pParams,
                 pSignature);
      }
      catch (Exception e)
      {
         // Return the exception instead
         lReturn = e;
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public Object invoke(ObjectName pName, String pOperationName, Object[] pParams, String[] pSignature)
           throws
           InstanceNotFoundException,
           MBeanException,
           ReflectionException,
           RemoteException
   {
      Object lReturn = null;
      InstanceNotFoundException lException = null;
      log.info("invoke(), name: " + pName + ", operation: " + pOperationName +
              ", params: " + pParams + ", signature: " + pSignature);
      try
      {
         lReturn = server.invoke(pName, pOperationName, pParams, pSignature);
      }
      catch (InstanceNotFoundException infe)
      {
         lException = infe;
      }
      Object[] lArguments = new Object[]{
         pName,
         pOperationName,
         pParams,
         pSignature
      };
      List lValues = null;
      try
      {
         log.info("call _invoke()");
         lValues = mPartition.callMethodOnCluster(SERVICE_NAME,
                 "_invoke",
                 lArguments,
            new Class[]{ObjectName.class, String.class, Object[].class, String[].class},
            true
         );
      }
      catch (Exception e)
      {
         throw new RemoteException("Could not get management attributes on the cluster", e);
      }
      Iterator i = lValues.iterator();
      while (i.hasNext())
      {
         Object lValue = i.next();
         if (lValue instanceof Throwable)
         {
            log.debug("invoke a method on the cluster caused an exception: " + lValue);
            if (lValue instanceof InstanceNotFoundException)
            {
               // Go ahead when INFE is found
               continue;
            }
         }
         // A non-INFE is found therefore ignore all the other INFE
         lException = null;
         if (lValue != null)
         {
            lReturn = lValue;
            break;
         }
      }
      if (lException != null)
      {
         // Only if all calls throws an INFE it will throw this exception
         throw lException;
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public boolean isRegistered(ObjectName pName)
           throws RemoteException
   {
      return server.isRegistered(pName);
   }

   /**
    * @jmx:managed-attribute
    */
   public Set queryNames(ObjectName pName, QueryExp pQuery)
           throws RemoteException
   {
      return server.queryNames(pName, pQuery);
   }

   /**
    **/
   public Object _setAttribute(ObjectName pName, Attribute pAttribute)
   {
      Object lReturn = null;
      try
      {
         log.info("_setAttribute(), name: " + pName + ", attribute: " + pAttribute);
         server.setAttribute(pName, pAttribute);
      }
      catch (Exception e)
      {
         // Return the exception instead
         lReturn = e;
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public void setAttribute(ObjectName pName, Attribute pAttribute)
           throws
           AttributeNotFoundException,
           InstanceNotFoundException,
           InvalidAttributeValueException,
           MBeanException,
           ReflectionException,
           RemoteException
   {
      InstanceNotFoundException lException = null;
      try
      {
         server.setAttribute(pName, pAttribute);
      }
      catch (InstanceNotFoundException infe)
      {
         lException = infe;   // Remember that instance was not found
      }
      Object[] lArguments = new Object[]{
         pName,
         pAttribute
      };
      List lValues = null;
      try
      {
         log.info("call _setAttribute()");
         lValues = mPartition.callMethodOnCluster(SERVICE_NAME,
                 "_setAttribute",
                 lArguments,
            new Class[]{ObjectName.class, Attribute.class},
            true
         );
      }
      catch (Exception e)
      {
         throw new RemoteException("Could not set management attributes on the cluster", e);
      }
      Iterator i = lValues.iterator();
      while (i.hasNext())
      {
         Object lValue = i.next();
         // If one value is null (because the method does not return a value) then
         // everything is fine
         if (lValue instanceof Throwable)
         {
            log.debug("invoke a method on the cluster caused an exception: " + lValue);
            if (lValue instanceof InstanceNotFoundException)
            {
               if (lException == null)
               {
                  lException = (InstanceNotFoundException) lValue; // Remember this exception
               }
            }
            else
            {
               // Only Throwables are returned
               if (lValue instanceof AttributeNotFoundException)
               {
                  throw (AttributeNotFoundException) lValue;
               }
               if (lValue instanceof InvalidAttributeValueException)
               {
                  throw (InvalidAttributeValueException) lValue;
               }
               if (lValue instanceof MBeanException)
               {
                  throw (MBeanException) lValue;
               }
               if (lValue instanceof ReflectionException)
               {
                  throw (ReflectionException) lValue;
               }
               throw new RemoteException(lValue.toString());
            }
         }
      }
      if (lException != null)
      {
         throw lException; // Now this exception can be thrown because it has low priority
      }
   }

   /**
    **/
   public Object _setAttributes(ObjectName pName, AttributeList pAttributes)
   {
      Object lReturn = null;
      try
      {
         log.info("_setAttributes(), name: " + pName + ", attribute: " + pAttributes);
         server.setAttributes(pName, pAttributes);
      }
      catch (Exception e)
      {
         // Return the exception instead
         lReturn = e;
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public AttributeList setAttributes(ObjectName pName, AttributeList pAttributes)
           throws
           InstanceNotFoundException,
           ReflectionException,
           RemoteException
   {
      Object lReturn = null;
      InstanceNotFoundException lException = null;
      try
      {
         lReturn = server.setAttributes(pName, pAttributes);
      }
      catch (InstanceNotFoundException infe)
      {
         lException = infe;
      }
      Object[] lArguments = new Object[]{
         pName,
         pAttributes
      };
      List lValues = null;
      try
      {
         log.info("call _setAttributes()");
         lValues = mPartition.callMethodOnCluster(SERVICE_NAME,
                 "_setAttributes",
                 lArguments,
            new Class[]{ObjectName.class, AttributeList.class},
            true
         );
      }
      catch (Exception e)
      {
         throw new RemoteException("Could not set management attributes on the cluster", e);
      }
      Iterator i = lValues.iterator();
      while (i.hasNext())
      {
         Object lValue = i.next();
         // If one value is null (because the method does not return a value) then
         // everything is fine
         if (lValue instanceof Throwable)
         {
            log.debug("set Attributes on the cluster caused an exception: " + lValue);
            if (lValue instanceof InstanceNotFoundException)
            {
               if (lException == null)
               {
                  lException = (InstanceNotFoundException) lValue; // Remember this exception
               }
            }
            else
            {
               if (lValue instanceof ReflectionException)
               {
                  throw (ReflectionException) lValue;
               }
               throw new RemoteException(lValue.toString());
            }
         }
      }
      if (lException != null)
      {
         // If no other exception is thrown then the INFE will be thrown here
         throw lException;
      }
      return (AttributeList) lReturn;
   }

   /**
    **/
   public Object _createMBean(String pClass,
                              ObjectName pName,
                              Object[] pParameters,
                              String[] pSignature)
   {
      Object lReturn = null;
      try
      {
         log.info("_createMBean(), name: " + pName);
         lReturn = server.createMBean(pClass, pName, pParameters, pSignature);
      }
      catch (Exception e)
      {
         // Return the exception instead
         lReturn = e;
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public ObjectInstance createMBean(String pClass,
                                     ObjectName pName,
                                     Object[] pParameters,
                                     String[] pSignature)
           throws
           InstanceAlreadyExistsException,
           MBeanException,
           MBeanRegistrationException,
           NotCompliantMBeanException,
           ReflectionException,
           RemoteException
   {
      List lValues = null;
      Object[] lArguments = new Object[]{
         pClass,
         pName,
         pParameters,
         pSignature
      };
      try
      {
         log.info("call _createMBean()");
         lValues = mPartition.callMethodOnCluster(SERVICE_NAME,
                 "_createMBean",
                 lArguments,
            new Class[]{String.class, ObjectName.class, Object[].class, String[].class},
            false
         );
      }
      catch (Exception e)
      {
         //AS ToDo: must be checked later how to go ahead here
         throw new RemoteException("Could not create a MBean on the cluster", e);
      }
      Iterator i = lValues.iterator();
      ObjectInstance lInstance = null;
      Throwable lException = null;
      while (i.hasNext())
      {
         Object lValue = i.next();
         if (lValue instanceof ObjectInstance)
         {
            if (lInstance == null)
            {
               lInstance = (ObjectInstance) lValue;
            }
         }
         else if (lValue instanceof Throwable)
         {
            if (lException == null)
            {
               lException = (Throwable) lValue;
            }
         }
      }
      if (lException != null)
      {
         if (lInstance != null)
         {
            // Remove all existing MBeans
            try
            {
               unregisterMBean(lInstance.getObjectName());
            }
            catch (Exception e)
            {
               // Ignore any exception
            }
         }
         if (lException instanceof InstanceAlreadyExistsException)
         {
            throw (InstanceAlreadyExistsException) lException;
         }
         if (lException instanceof MBeanException)
         {
            throw (MBeanException) lException;
         }
         if (lException instanceof MBeanRegistrationException)
         {
            throw (MBeanRegistrationException) lException;
         }
         if (lException instanceof NotCompliantMBeanException)
         {
            throw (NotCompliantMBeanException) lException;
         }
         if (lException instanceof ReflectionException)
         {
            throw (ReflectionException) lException;
         }
         throw new RemoteException(lException.toString());
      }
      return lInstance;
   }

   /**
    **/
   public Object _unregisterMBean(ObjectName pName)
   {
      Object lReturn = null;
      try
      {
         log.info("_unregisterMBean(), name: " + pName);
         server.unregisterMBean(pName);
      }
      catch (Exception e)
      {
         // Return the exception instead
         lReturn = e;
      }
      return lReturn;
   }

   /**
    * @jmx:managed-attribute
    */
   public void unregisterMBean(ObjectName pName)
           throws
           InstanceNotFoundException,
           MBeanRegistrationException,
           RemoteException
   {
      List lValues = null;
      Object[] lArguments = new Object[]{
         pName
      };
      try
      {
         log.info("call _unregisterMBean()");
         lValues = mPartition.callMethodOnCluster(SERVICE_NAME,
                 "_unregisterMBean",
                 lArguments,
            new Class[]{ObjectName.class},
            false
         );
      }
      catch (Exception e)
      {
         //AS ToDo: must be checked later how to go ahead here
         throw new RemoteException("Could not unregister a MBean on the cluster", e);
      }
      Iterator i = lValues.iterator();
      Throwable lException = null;
      while (i.hasNext())
      {
         Object lValue = i.next();
         if (lValue instanceof Throwable)
         {
            lException = (Throwable) lValue;
            break;
         }
      }
      if (lException != null)
      {
         if (lException instanceof InstanceNotFoundException)
         {
            throw (InstanceNotFoundException) lException;
         }
         if (lException instanceof MBeanRegistrationException)
         {
            throw (MBeanRegistrationException) lException;
         }
         throw new RemoteException(lException.toString());
      }
   }

   /**
    * @jmx:managed-attribute
    */
   public void addNotificationListener(ObjectName pBroadcaster,
                                       ObjectName pListener,
                                       NotificationFilter pFilter,
                                       Object pHandback)
           throws
           InstanceNotFoundException,
           RemoteException
   {
      server.addNotificationListener(pBroadcaster, pListener, pFilter, pHandback);
   }

   /**
    * @jmx:managed-attribute
    */
   public void removeNotificationListener(ObjectName pBroadcaster,
                                          ObjectName pListener)
           throws
           InstanceNotFoundException,
           ListenerNotFoundException,
           RemoteException
   {
      server.removeNotificationListener(pBroadcaster, pListener);
   }
   
   // MBeanRegistration implementation ----------------------------------------
   
   /**
    * Saves the MBeanServer reference
    */
   public ObjectName preRegister(MBeanServer pServer, ObjectName pName)
           throws Exception
   {
      super.preRegister(pServer, pName);
      log.info("HA Management Service MBean online");
      mHAManagementName = new ObjectName(OBJECT_NAME + ",Partition=" + mBackgroundPartition);

      return mHAManagementName;
   }

   /**
    * Removes the Notification Listener
    */
   public void preDeregister()
           throws Exception
   {
      super.preDeregister();
   }
   
   // Service implementation ----------------------------------------

   public String getName()
   {
      return "HA Management Service";
   }

   /**
    * Looks up the Server Config instance to figure out the
    * temp-directory and the farm-deploy-directory
    */
   protected void createService() throws Exception
   {
   }

   /**
    * Register itself as RPC-Handler to the HA-Partition
    * and add the farm deployment directory to the scanner
    */
   protected void startService()
           throws Exception
   {
      mClusterPartitionName = new ObjectName("jboss:service=" + mBackgroundPartition);

      log.debug("registerRPCHandler");
      mPartition = (HAPartition) server.getAttribute(mClusterPartitionName,
              "HAPartition");
      mPartition.registerRPCHandler(SERVICE_NAME, this);
   }

   /**
    * Remove the farm deployment directory from the scanner
    */
   protected void stopService()
   {
   }

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

   /**
    * Go through the myriad of nested JMX exception to pull out the true
    * exception if possible and log it.
    *
    * @param e The exception to be logged.
    */
   private void logException(Throwable e)
   {
      Throwable t = org.jboss.mx.util.JMXExceptionDecoder.decode(e);
      log.error("operation failed", t);
   }

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