/*
 * 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.monitor.alarm;

import java.util.Map;
import java.util.HashMap;

import javax.management.Notification;
import javax.management.AttributeChangeNotification;

/**
 * AlarmManager
 *
 * @author  <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
 *
 * @version $Revision: 1.1.2.4 $
**/
public class AlarmManager
{
   // Private/Protected Data ----------------------------------------
   
   // mediates the related MBean
   protected MBeanImplAccess mbeanImpl;
   
   // holds the alarm type-to-AlarmInfo mappings
   private Map typeMap;
   
   // Constructors --------------------------------------------------
   
   /**
    * CTOR
   **/
   public AlarmManager(MBeanImplAccess mbeanImpl)
   {
      this.mbeanImpl = mbeanImpl;
      this.typeMap = new HashMap();
   }
   
   // High-level part of the interface used to support Statefull Alarms.
   // The sending of the actual AlarmNotifications is done through the
   // sendAlarmNotification() method.

   /**
    * Sets the severity of an Alarm, keyed by its type, without producing
    * an AlarmNotification
   **/
   public void setSeverity(String type, int severity)
   {
      synchronized (this) {
         AlarmInfo ai = (AlarmInfo)this.typeMap.get(type);
         
         if (ai == null)
            this.typeMap.put(type, new AlarmInfo(severity));
         else
            ai.setSeverity(severity);
      }
   }

   /**
    * Gets the severity of an alarm, keyed by its type.
   **/
   public int getSeverity(String type)
   {
      AlarmInfo ai;
      
      synchronized (this) {
         ai = (AlarmInfo)this.typeMap.get(type);
      
         // if alarm did not exist, added with a default severity
         if (ai == null) {
            ai = new AlarmInfo(Alarm.SEVERITY_NORMAL);
            this.typeMap.put(type, ai);
         }
      }
      return ai.getSeverity();
   }
   
   /**
    * Gets the severity of an alarm, keyed by its type, as String
   **/
   public String getSeverityAsString(String type)
   {
      return Alarm.SEVERITY_STRINGS[getSeverity(type)];      
   }

   /**
    * Sets the alarm, keyed by its type, to a particular state.
    * If severity has changed an AlarmNotification will be thrown.
    * The alarmState of the AlarmNotification will be either
    * Alarm.STATE_CREATED, Alarm.STATE_CHANGED or Alarm.STATE_CLEARED.
   **/   
   public void setAlarm(String type, int severity, String message, Object userData)
   {
      AlarmInfo ai;
      
      synchronized (this) {
         ai = (AlarmInfo)this.typeMap.get(type);
         
         // if alarm did not exist, added with a default severity
         if (ai == null) {
            ai = new AlarmInfo(Alarm.SEVERITY_NORMAL);
            this.typeMap.put(type, ai);
         }
      }
      
      // There must be a small race condition here if 2 threads
      // set the same severity, thus producing duplicate notifications
      // Not a big deal...
      int oldSeverity = ai.getSeverity();
      
      // if the severity has changed, send an AlarmNotification
      if (severity != oldSeverity) {
         
         // store the new severity
         ai.setSeverity(severity);
         
         if (severity == Alarm.SEVERITY_NORMAL)
            sendAlarmNotification(
               type, Alarm.STATE_CLEARED, severity, message, userData);
         else if (oldSeverity == Alarm.SEVERITY_NORMAL)
            sendAlarmNotification(
               type, Alarm.STATE_CREATED, severity, message, userData);
         else
            sendAlarmNotification(
               type, Alarm.STATE_CHANGED, severity, message, userData);
      }
   }
   
   /**
    * See set Alarm above
    *
    * Essentially a helper method that will populate the userData field
    * of the Notification with a HashMap, containing a single key/value pair.
    *
    * Note, that an AlarmNotification will not be thrown if there is no
    * severity change.
   **/
   public void setAlarm(
      String type, int severity, String message, String key, Object value)
   {
      HashMap map = new HashMap();
      map.put(key, value);
      this.setAlarm(type, severity, message, map);      
   }
      
   // Low-level part of the interface used to generate and send
   // various types of notifications, including AlarmNotifications
   // corresponding to Stateless Alarms.
   
   /**
    * Generates and sends an AlarmNotification
    *
    * Essentially a helper method that will populate the userData field
    * of the Notification with a HashMap, containing a single key/value pair.
   **/
   public void sendAlarmNotification(
      String type, int alarmState, int severity,
      String message,
      String key, Object value)
   {
      HashMap map = new HashMap();
      map.put(key, value);
      this.sendAlarmNotification(type, alarmState, severity, message, map);
   }
   
   /**
    * Generates and sends an AlarmNotification.
    *
    * An alarmState of Alarm.STATE_CLEARED forces severity to SEVERITY_NORMAL
    *
    * source, sequenceNumber, timeStamp
    * will be automatically filled.
   **/   
   public void sendAlarmNotification(
      String type, int alarmState, int severity,
      String message, Object userData)
   {
      Notification n = new AlarmNotification(
         type,
         this.mbeanImpl.getMBeanName(), // source
         this.mbeanImpl.getSequenceNumber(),
         System.currentTimeMillis(),
         message,
         alarmState,
         severity         
      );
      n.setUserData(userData);
      
      // send it away
      this.mbeanImpl.emitNotification(n);
   }

   /**
    * Generates and sends an AttributeChangeNotification.
    *
    * source, sequenceNumber, timeStamp
    * will be automatically filled in.
   **/   
   public void sendAttributeChangeNotification(
      String type, String message, Object userData,
      String attributeName, String attributeType,
      Object oldValue, Object newValue)
   {
      Notification n = new AttributeChangeNotification(
         this.mbeanImpl.getMBeanName(), // source
         this.mbeanImpl.getSequenceNumber(),
         System.currentTimeMillis(),
         message,
         attributeName,
         attributeType,
         oldValue,
         newValue
      );
      n.setUserData(userData);
      
      // send it away
      this.mbeanImpl.emitNotification(n);
   }

   /**
    * Generates and sends a simple Notification.
    *
    * source, sequenceNumber, timeStamp
    * will be automatically filled in.
   **/   
   public void sendNotification(String type, String message, Object userData)
   {
      Notification n = new Notification(
         type,
         this.mbeanImpl.getMBeanName(), // source
         this.mbeanImpl.getSequenceNumber(),
         System.currentTimeMillis(),
         message
      );
      n.setUserData(userData);
      
      // send it away
      this.mbeanImpl.emitNotification(n);
   }

   // Inner Class ---------------------------------------------------
   
   /**
    * Simple Data Holder
   **/
   private static class AlarmInfo
   {
      private int severity;
      
      public AlarmInfo(int severity)
      {
         this.severity = severity;
      }
   
      public void setSeverity(int severity)
      {
         this.severity = severity;
      }
      
      public int getSeverity()
      {
         return this.severity;
      }
   }      
}
