/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package javax.management.monitor;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;

import org.jboss.mx.util.RunnableScheduler;
import org.jboss.mx.util.SchedulableRunnable;

import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;

/**
 * The monitor service.
 *
 * <p><b>Revisions:</b>
 * <p><b>20020319 Adrian Brock:</b>
 * <ul>
 * <li>Notify using the object name and fix the notification payloads
 * </ul>
 *
 * @author <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>
 * @version $Revision: 1.6.4.2 $
 */
public abstract class Monitor
  extends NotificationBroadcasterSupport
  implements MonitorMBean, MBeanRegistration, Serializable
{
  // Constants -----------------------------------------------------

  /**
   * The amount by which arrays are incremented.
   */
  protected final int capacityIncrement = 16;

  /**
   * Used to reset errors in {@link #alreadyNotified}.
   */
  protected static final int RESET_FLAGS_ALREADY_NOTIFIED = 0;

  /**
   * An observed attribute type error has been notified.
   */
  protected static final int RUNTIME_ERROR_NOTIFIED = 1;

  /**
   * An observed object error has been notified.
   */
  protected static final int OBSERVED_OBJECT_ERROR_NOTIFIED = 2;

  /**
   * An observed attribute error has been notified.
   */
  protected static final int OBSERVED_ATTRIBUTE_ERROR_NOTIFIED = 4;

  /**
   * An observed attribute type error has been notified.
   */
  protected static final int OBSERVED_ATTRIBUTE_TYPE_ERROR_NOTIFIED = 8;

  // Attributes ----------------------------------------------------

  /**
   * The number of valid elements in the arrays.
   */
  protected int elementCount = 0;

  /**
   * The granularity period.
   */
  long granularityPeriod = 10000;

  /**
   * The observed attribute.
   */
  String observedAttribute = null;

  /**
   * The observed objects.
   */
  ConcurrentHashMap observedObjects = new ConcurrentHashMap();

  /**
   * Whether the service is active.
   */
  boolean active = false;

  /**
   * The server this service is registered in.
   */
  protected MBeanServer server;

  /**
   * The object name of this monitor.
   */
  ObjectName objectName;

  /**
   * The errors that have already been notified.
   * REVIEW: Check
   * @deprecated use {@link #alreadyNotifieds}[0]
   */
  protected int alreadyNotified = 0;

  /**
   * The errors that have already been notified.
   */
  protected int[] alreadyNotifieds = new int[0];

  /**
   * @deprecated use {@link #getClassLoaderFor(ObjectName)} to obtain the
   * appropriate classloader for deserialization
   */
  protected String dbgTag = null;

  /**
   * The runnable monitor.
   */
  private MonitorRunnable monitorRunnable;

  /**
   * The notification sequence number.
   */
  private long sequenceNumber;
  
  // Static --------------------------------------------------------

  /**
   * The scheduler.
   */
  static RunnableScheduler scheduler;

  /**
   * Start the scheduler
   */
  static
  {
     scheduler = new RunnableScheduler();
     scheduler.start();
  }
  
  // Constructors --------------------------------------------------

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

  // MonitorMBean implementation -----------------------------------

  public long getGranularityPeriod()
  {
    return granularityPeriod;
  }

  public String getObservedAttribute()
  {
    return observedAttribute;
  }

  public ObjectName getObservedObject()
  {
     ObservedObject object = getFirstObservedObject();
     if (object != null)
        return object.getObjectName();
     else
        return null;
  }

  public ObjectName[] getObservedObjects()
  {
    Set set = new HashSet(observedObjects.keySet());
    elementCount = set.size();
    ObjectName[] result = new ObjectName[set.size()];
    alreadyNotifieds = new int[set.size()];
    int count = 0;
    for (Iterator i = set.iterator(); i.hasNext();)
    {
       ObservedObject object = (ObservedObject) i.next();
       result[count] = object.getObjectName();
       alreadyNotifieds[count++] = object.getAlreadyNotified();
    }
    return result;
  }

  public synchronized boolean isActive()
  {
    return active;
  }

  public void setGranularityPeriod(long period)
    throws IllegalArgumentException
  {
    if (period <= 0)
      throw new IllegalArgumentException("Period must be positive.");
    granularityPeriod = period;
  }

  public void setObservedAttribute(String attribute)
  {
    observedAttribute = attribute;
    resetAlreadyNotified();
  }

  public void setObservedObject(ObjectName object)
  {
     observedObjects.clear();
     addObservedObject(object);
  }

  public void addObservedObject(ObjectName object)
  {
     if (object == null)
        throw new IllegalArgumentException("null object name");
     ObservedObject o = new ObservedObject(object);
     initObservedObject(o);
     observedObjects.put(object, o);
  }

  public void removeObservedObject(ObjectName object)
  {
     if (object == null)
        throw new IllegalArgumentException("null object name");
     observedObjects.remove(object);
  }

  public boolean containsObservedObject(ObjectName object)
  {
     if (object == null)
        throw new IllegalArgumentException("null object name");
     return observedObjects.containsKey(object);
  }

  public synchronized void start()
  {
    // Ignore if already active
    if (active)
      return;
    active = true;

    for (Iterator i = retrieveObservedObjects().values().iterator(); i.hasNext();)
    {
       ObservedObject object = (ObservedObject) i.next();
       initObservedObject(object);
    }

    // Start the monitor runnable
    monitorRunnable = new MonitorRunnable(this);
  }

  public synchronized void stop()
  {
    // Ignore if not active
    if (!active)
      return;

    // Stop the monitor runnable
    active = false;
    monitorRunnable.setScheduler(null);
    monitorRunnable = null;
  }

  public String toString()
  {
     StringBuffer buffer = new StringBuffer(100);
     buffer.append(getClass()).append(System.identityHashCode(this)).append(": {");
     buffer.append(" objectName=").append(objectName);
     return buffer.append("}").toString();
  }

  // MBeanRegistrationImplementation overrides ---------------------

  public ObjectName preRegister(MBeanServer server, ObjectName objectName)
    throws Exception
  {
    // Remember the server.
    this.server = server;

    // Remember the object name.
    this.objectName = objectName;

    // Use the passed object name.
    return objectName;
  }

  public void postRegister(Boolean registrationDone)
  {
  }

  public void preDeregister()
    throws Exception
  {
    // Stop the monitor before deregistration.
    stop();
  }

  public void postDeregister()
  {
  }

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

  /**
   * Get an observed object
   */
  ObservedObject retrieveObservedObject(ObjectName name)
  {
     return (ObservedObject) observedObjects.get(name);
  }

  /**
   * retrieve the observed objects
   */
  Map retrieveObservedObjects()
  {
     return observedObjects;
  }

  /**
   * retrieve the first observed objects
   */
  ObservedObject getFirstObservedObject()
  {
     Iterator i = observedObjects.values().iterator();
     if (i.hasNext())
        return (ObservedObject) i.next();
     else
        return null;
  }

  void initObservedObject(ObservedObject object)
  {
     object.resetAlreadyNotified();
     object.setDerivedGaugeTimeStamp(System.currentTimeMillis());
  }

  /**
   * Run the monitor on each observed object
   */
  void runMonitor()
  {
    // Loop through each mbean
    for (Iterator i = observedObjects.values().iterator(); i.hasNext() && isActive();)
       runMonitor((ObservedObject) i.next());
  }

  /**
   * Run the monitor.<p>
   *
   * Retrieves the monitored attribute and passes it to each service.<p>
   *
   * Peforms the common error processing.
   *
   * @param object the mbean to run the monitor on
   */
  void runMonitor(ObservedObject object)
  {
    // Monitor for uncaught errors
    try
    {
      MBeanInfo mbeanInfo = null;
      try
      {
        mbeanInfo = server.getMBeanInfo(object.getObjectName());
      }
      catch (InstanceNotFoundException e)
      {
        sendObjectErrorNotification(object, "The observed object is not registered.");
        return;
      }

      // Get the attribute information
      MBeanAttributeInfo[] mbeanAttributeInfo = mbeanInfo.getAttributes();
      MBeanAttributeInfo attributeInfo = null;
      for (int i = 0; i < mbeanAttributeInfo.length; i++)
      {
        if (mbeanAttributeInfo[i].getName().equals(observedAttribute))
        {
          attributeInfo = mbeanAttributeInfo[i];
          break;
        }
      }

      // The attribute must exist
      if (attributeInfo == null)
      {
        sendAttributeErrorNotification(object,
          "The observed attribute does not exist");
        return;
      }

      // The attribute must exist
      if (!attributeInfo.isReadable())
      {
        sendAttributeErrorNotification(object, "Attribute not readable.");
        return;
      }

      // Get the value
      Object value = null;
      try
      {
        value = server.getAttribute(object.getObjectName(), observedAttribute);
      }
      catch (InstanceNotFoundException e)
      {
        sendObjectErrorNotification(object, "The observed object is not registered.");
        return;
      }

      // Check for null value
      if (value == null)
      {
        sendAttributeTypeErrorNotification(object, "Attribute is null");
        return;
      }

      // Now pass the value to the respective monitor.
      monitor(object, attributeInfo, value);
    }
    // Notify an unexcepted error
    catch (Throwable e)
    {
      sendRuntimeErrorNotification(object, "General error: " + e.toString());
    }
  }

  /**
   * Perform the monitor specific processing. 
   *
   * @param object the ObservedObject.
   * @param attributeInfo the MBean attribute information.
   * @param value the value to monitor.
   */
  abstract void monitor(ObservedObject object, MBeanAttributeInfo attributeInfo, Object value)
    throws Exception;

  /**
   * Sends the notification
   *
   * @param object the observedObject.
   * @param type the notification type.
   * @param timestamp the time of the notification.
   * @param message the human readable message to send.
   * @param attribute the attribute name.
   * @param gauge the derived gauge.
   * @param trigger the trigger value.
   */
  void sendNotification(ObservedObject object, String type, long timestamp, String message, 
      String attribute, Object gauge, Object trigger)
  {
    long seq = 0;
    synchronized (this)
    {
      seq = ++sequenceNumber;
    }
    if (timestamp == 0)
      timestamp = System.currentTimeMillis();
    sendNotification(new MonitorNotification(type, objectName, seq,
                     timestamp, message, gauge,
                     attribute, object.getObjectName(), trigger));
  }

  /**
   * Send a runtime error notification.
   *
   * @param object the observedObject.
   * @param message the human readable message to send.
   */
  void sendRuntimeErrorNotification(ObservedObject object, String message)
  {
    if (object.notAlreadyNotified(RUNTIME_ERROR_NOTIFIED))
      sendNotification(object, MonitorNotification.RUNTIME_ERROR, 0,
        message, observedAttribute, null, null);
  }

  /**
   * Send an object error notification.
   *
   * @param object the observedObject.
   * @param message the human readable message to send.
   */
  void sendObjectErrorNotification(ObservedObject object, String message)
  {
    if (object.notAlreadyNotified(OBSERVED_OBJECT_ERROR_NOTIFIED))
      sendNotification(object, MonitorNotification.OBSERVED_OBJECT_ERROR, 0,
        message, observedAttribute, null, null);
  }

  /**
   * Send an attribute error notification.
   *
   * @param object the observedObject.
   * @param message the human readable message to send.
   */
  void sendAttributeErrorNotification(ObservedObject object, String message)
  {
    if (object.notAlreadyNotified(OBSERVED_ATTRIBUTE_ERROR_NOTIFIED))
      sendNotification(object, MonitorNotification.OBSERVED_ATTRIBUTE_ERROR, 0,
        message, observedAttribute, null, null);
  }

  /**
   * Send an attribute type error notification.
   *
   * @param object the observedObject.
   * @param message the human readable message to send.
   */
  void sendAttributeTypeErrorNotification(ObservedObject object, String message)
  {
    if (object.notAlreadyNotified(OBSERVED_ATTRIBUTE_TYPE_ERROR_NOTIFIED))
      sendNotification(object, MonitorNotification.OBSERVED_ATTRIBUTE_TYPE_ERROR, 0,
        message, observedAttribute, null, null);
  }

  /**
   * Reset the already notifieds
   */
  void resetAlreadyNotified()
  {
     for (Iterator i = observedObjects.values().iterator(); i.hasNext();)
       ((ObservedObject) i.next()).resetAlreadyNotified();
  }

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

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

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

  /**
   * A monitor runnable.
   */
  private static class MonitorRunnable
     extends SchedulableRunnable
  {
    // Attributes ----------------------------------------------------

    // The monitoring to perform
    private Monitor monitor;

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

    /**
     * Create a monitor runnable to periodically perform monitoring.
     *
     * @param monitor the monitoring to perform.
     */
    public MonitorRunnable(Monitor monitor)
    {
      this.monitor = monitor;
      setScheduler(scheduler);
    }

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

    // SchedulableRunnable overrides ---------------------------------

    /**
     * Run the montior
     */
    public void doRun()
    {
        // Perform the monitoring
        monitor.runMonitor();
 
        // Reschedule
        setNextRun(System.currentTimeMillis() + monitor.granularityPeriod);
    } 
  }
}
