/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2011 * Copyright by ESO (in the framework of the ALMA collaboration), * All rights reserved * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package alma.acs.alarmsystem.source; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import alma.acs.concurrent.ThreadLoopRunner; import alma.acs.concurrent.ThreadLoopRunner.CancelableRunnable; import alma.acs.concurrent.ThreadLoopRunner.ScheduleDelayMode; /** * The map of active and inactive alarms. * <P> * Alarms that are not updated after {@link #ALARM_ACTIVITY_TIME} are removed * from the map so that they will be sent again to the alarm service. * <P> * The class is thread safe i.e. the methods can be called without any further locking. * <P> * <EM>Implementation note</EM>: * All the methods add new entries or update existing pairs in the {@link #alarms} * map with the exception of the periodic thread {@link AlarmsMapRunnable#run()} * that remove elements that are in the map unchanged since more the {@link #ALARM_ACTIVITY_TIME}. * <P> * {@link AlarmsMap} does not need any locking because the map contains immutable * {@link AlarmInfo} objects and the map itself is implemented by a {@link ConcurrentHashMap}. * * @author acaproni */ public class AlarmsMap { /** * The data stored for each alarm in the map. * <P> * Objects of this class are immutable. * * @author acaproni */ public static class AlarmInfo { /** * The time of the last update of this alarm */ public final long time; /** * The state active/terminate of the alarm */ public final boolean active; /** * Constructor * * @param active The initial state active/terminate of the alarm */ public AlarmInfo(boolean active) { this.active=active; this.time=System.currentTimeMillis(); } } /** * The thread to delete the alarms older than the time interval. * <P> * This thread is scheduled by the ThreadLoopRunner. * Note that this thread is the only one that removes entries from the map: * all other methods update or add entries. * * @author acaproni */ public class AlarmsMapRunnable extends CancelableRunnable { /** * Check the timestamps of the entries in the map and remove those that * have not been changed since more then ALARM_ACTIVITY_TIME. */ public void run() { for (String key: alarms.keySet()) { if (shouldTerminate) { return; } AlarmInfo info = alarms.get(key); // Info can't be null because this is the only // thread removing items from the map // We check nevertheless to improve safety. if (info!=null) { if (System.currentTimeMillis()-ALARM_ACTIVITY_TIME*1000>info.time) { alarms.remove(key); } } } } } /** * The alarms that have no activity after the following time interval * are removed from the map. * <P> * In practice, it means that an alarm with the same state will be sent again * if it arrives after <code>ALARM_ACTIVITY_TIME</code> seconds. */ public static final int ALARM_ACTIVITY_TIME=30; /** * The map of alarms. * <P> * The key is the alarm ID i.e. FF:FM:FC. * The value is an {@link AlarmInfo} with boolean set to <code>true</code> * if the alarm has been activated, <code>false</code> otherwise. */ private final Map<String, AlarmInfo> alarms = new ConcurrentHashMap<String, AlarmsMap.AlarmInfo>(); /** * The runner for scheduling the thread to delete old alarms * from the map */ private final ThreadLoopRunner loopRunner; /** * The logger */ private final Logger logger; /** * Constructor * * @param threadFactory The thread factory to schedule the timer loop * @param logger The logger */ public AlarmsMap(ThreadFactory threadFactory, Logger logger) { this.logger=logger; loopRunner = new ThreadLoopRunner(new AlarmsMapRunnable(), ALARM_ACTIVITY_TIME, TimeUnit.SECONDS, threadFactory, logger, "alarm_timer"); } /** * Return the active alarms in the map * * @return the active alarms in the map */ public Collection<String> getActiveAlarms() { Vector<String>ret = new Vector<String>(); Set<String> keys=alarms.keySet(); for (String key: keys) { if (alarms.get(key).active) { ret.add(key); } } return ret; } /** * An alarms has been raised and must be added to the map. * * @param alarmID The ID of the alarm * @return <code>true</code> if the alarm with the give ID was already present * in the list and it was active; <code>false</code> otherwise. */ public boolean raise(String alarmID) { AlarmInfo info=alarms.get(alarmID); boolean ret=(info!=null) && (info.active); alarms.put(alarmID, new AlarmInfo(true)); return ret; } /** * An alarm has been cleared and must be added to the map. * * @param alarmID The ID of the alarm * @return <code>true</code> if the alarm with the give ID was already present * in the list and it was terminated; <code>false</code> otherwise. */ public boolean clear(String alarmID) { AlarmInfo info=alarms.get(alarmID); boolean ret=(info!=null) && (!info.active); alarms.put(alarmID, new AlarmInfo(false)); return ret; } /** * Start the thread to delete the oldest alarms */ public void start() { loopRunner.setDelayMode(ScheduleDelayMode.FIXED_DELAY); loopRunner.runLoop(); } /** * Shutdown the thread a frees the resources */ public void shutdown() { try { if (loopRunner.shutdown(1, TimeUnit.SECONDS)) { logger.finest("Thread shut down"); } else { logger.warning("Failed to cleanly shut down the AlarmsMap thread"); } } catch (InterruptedException ie) { logger.warning("AlarmsMap thread interrupted while shutting down."); } clear(); } /** * The size of the map * * @return The size of the map * * @see ConcurrentHashMap#size() */ public int size() { return alarms.size(); } /** * * @param key The key to look for in the map * * @return <code>true</code> if the map contains an item with the given ket */ public boolean containsKey(Object key) { return alarms.containsKey(key); } /** * Empty the map. * <P> * This method should not be used because there is no need to clear the content of the * map during normal operations. * Especially it must not be invoked when the periodic thread {@link AlarmsMapRunnable#run()} * is checking the timestamps of the entries because it can generate a NPE */ private void clear() { alarms.clear(); } /** * * @param key The key to look for * @return The object with the given key */ public AlarmInfo get(Object key) { return alarms.get(key); } /** * This method returns a set of the keys in the map at the moment when the * method has been called. It is meant to be used for testing purposes only. * <BR> * Getting elements from the returned set could lead to <code>null</code> values * if ssuch a value has been removed by the thread. * @return A set with the key in the map */ public Set<String> keySet() { return alarms.keySet(); } }