/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (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.logging;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import alma.acs.logging.RepeatGuard.Logic;
/**
* This class can be used as a convenient map that keeps {@link RepeatGuard} objects associated with <code>String IDs</code>.
* For example, if your application repeatedly sends an event related to the unavailability of some component,
* and you want to limit these events on a per-component basis, then you either need to keep and use a separate RepeatGuard object for every component instance,
* or alternatively you can use one instance of <code>MultipleRepeatGuard</code> and take the component names as <code>ID</code>s.
* <p>
* The additional benefit is that this class optionally offers a bounded cache for the various RepeatGuards, which limits memory consumption for cases
* in which the number of possible IDs is large, e.g. if the ID is the log message coming from unknown / 3rd party source.
* The drawback is that the least accessed RepeatGuards will be removed internally when the cache is full and will be re-created
* upon access, which may distort the original guarding behavior (e.g. because the first check() of the re-created RepeatGuard always returns true,
* even if the old RepeatGuard object would have returned false for another 100 invocations, or because {@linkplain RepeatGuard#counterAtLastExecution()}
* will not be accurate.
*
* @author hsommer
* @since ACS 8.0.0
*/
public class MultipleRepeatGuard
{
private final long defaultInterval;
private final TimeUnit defaultTimeUnit;
private final int defaultMaxRepetitions;
private final Logic defaultLogic;
private final BoundedHashMap<String, RepeatGuard> guards;
/////////////////////////////////////////////////////////////////////////////////////
//////////////////////////// Repeat Guard functionality /////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
/**
* Constructor for a <code>MultipleRepeatGuard</code> with a bounded RepeatGuard cache.
* <p>
* The first 4 parameters are the same as in {@link RepeatGuard#RepeatGuard(long, TimeUnit, int, Logic)}
* and are used as defaults for all RepeatGuards created by this class.
* To change these values for particular RepeatGuards, use {@link #setRepeatGuard(String, RepeatGuard)}
* using a new RepeatGuard object or the one obtained beforehand from {@link #getRepeatGuard(String)}.
*
* @param interval Time interval (in <code>timeUnit</code> units).
* @param timeUnit Time unit of <code>interval</code> parameter.
* @param maxRepetitions Maximum number of repetitions.
* @param logic Evaluation logic for <code>interval</code> and <code>maxRepetitions</code>.
* The logic will be "reduced" automatically if <code>interval</code> or <code>maxRepetitions</code>
* have a value <= 0, so as to be based only on the other positive value.
* @param maxCacheSize
* Number of <code>ID</code>s (as used in {@link #check(String)}) whose repetition we'll keep track of.
* Setting this value higher will use more memory;
* setting it lower than the actual number of IDs will result in unnecessary execution of the guarded actions,
* as the matching <code>RepeatGuard</code>s will be lost and re-created.
* @throws IllegalArgumentException if maxRepetitions <= 0 && interval <= 0
*/
public MultipleRepeatGuard(long interval, TimeUnit timeUnit, int maxRepetitions, Logic logic, int maxCacheSize) {
this.defaultInterval = interval;
this.defaultTimeUnit = timeUnit;
this.defaultMaxRepetitions = maxRepetitions;
this.defaultLogic = logic;
guards = new BoundedHashMap<String, RepeatGuard>(maxCacheSize, true);
}
/**
* Constructor for a <code>MultipleRepeatGuard</code> without a bounded RepeatGuard cache
* (which means that memory limits indirectly set the bound, with possible side effects).
* @see #MultipleRepeatGuard(long, TimeUnit, int, Logic, int)
*/
public MultipleRepeatGuard(long interval, TimeUnit timeUnit, int maxRepetitions, Logic logic) {
this(interval, timeUnit, maxRepetitions, logic, Integer.MAX_VALUE);
}
/**
* @see RepeatGuard#check()
*/
public synchronized boolean check(String ID) {
RepeatGuard guard = getOrCreateRepeatGuard(ID);
return guard.check();
}
/**
* @see RepeatGuard#checkAndIncrement()
*/
public synchronized boolean checkAndIncrement(String ID) {
RepeatGuard guard = getOrCreateRepeatGuard(ID);
return guard.checkAndIncrement();
}
/**
* @see RepeatGuard#increment()
*/
public synchronized void increment(String ID) {
RepeatGuard guard = getOrCreateRepeatGuard(ID);
guard.increment();
}
/**
* @see RepeatGuard#counter()
*/
public synchronized int counter(String ID) {
RepeatGuard guard = getOrCreateRepeatGuard(ID);
return guard.counter();
}
/**
* Note that depending on the RepeatGuard cache limit, the RepeatGuard for the given ID
* may get re-created during this call, and thus the counter may be 0
* even though the old "true" counter was > 0.
*
* @see RepeatGuard#counterAtLastExecution()
*/
public synchronized int counterAtLastExecution(String ID) {
RepeatGuard guard = getOrCreateRepeatGuard(ID);
return guard.counterAtLastExecution();
}
/////////////////////////////////////////////////////////////////////////////////////
////////////////////////////// Map administration ///////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
public synchronized boolean hasRepeatGuard(String ID) {
return guards.containsKey(ID);
}
public synchronized RepeatGuard getRepeatGuard(String ID) {
return getOrCreateRepeatGuard(ID);
}
/**
* Sets a possibly modified <code>RepeatGuard</code> for the given <code>ID</code>,
* which allows for some <code>RepeatGuard</code>s having different settings than those passed in
* {@link #MultipleRepeatGuard(long, TimeUnit, int, Logic, int)}.
* @param ID
* @param guard
*/
public synchronized void setRepeatGuard(String ID, RepeatGuard guard) {
guards.put(ID, guard);
}
/**
* Gets the actual number of RepeatGuards in the internal cache,
* regardless of whether there is an upper limit and whether it has been reached etc.
*/
public int getCacheSize() {
return guards.size();
}
public synchronized void clearCache() {
guards.clear();
}
public void setCacheLimit(int maxEntries) {
guards.setMaxEntries(maxEntries);
}
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////// Auxiliary methods and inner classes /////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
private synchronized RepeatGuard getOrCreateRepeatGuard(String ID) {
RepeatGuard guard = guards.get(ID);
if (guard == null) {
guard = new RepeatGuard(defaultInterval, defaultTimeUnit, defaultMaxRepetitions, defaultLogic);
guards.put(ID, guard);
}
return guard;
}
/**
* @TODO This class could be moved up to module jacsutil later.
*/
private static class BoundedHashMap<K,V> extends LinkedHashMap<K,V> {
private volatile int maxEntries;
/**
* Constructor.
* The initial capacity of this map is determined as <code>Math.min(16, maxEntries)</code>.
* The load factor is taken as 0.75 which is the default for other map types.
* <p>
* @param maxEntries the maximum capacity of this map.
* @param accessOrder
* if true, then this map is an LRU (least-recently used) cache that will remove the least recently used entries
* when the number of stored entries has reached <code>maxEntries</code> and a new entry is stored;
* otherwise insertion order is used (removing the entry that was inserted first).
* See {@link LinkedHashMap#LinkedHashMap(int, float, boolean)}.
*/
BoundedHashMap(int maxEntries, boolean accessOrder) {
super(Math.min(16, maxEntries), 0.75f, accessOrder);
setMaxEntries(maxEntries);
}
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > maxEntries;
}
/**
* Sets a new value for the maximum number of entries in this map,
* overriding the <code>maxEntries</code> value given in the constructor
* or in previous invocations of this method.
* @param maxEntries must be a positive number
*/
void setMaxEntries(int maxEntries) {
if (maxEntries <= 0) {
throw new IllegalArgumentException("maxEntries must be >= 1");
}
this.maxEntries = maxEntries;
}
}
}