/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2013, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core.spi;
import ch.qos.logback.core.CoreConstants;
import java.util.*;
/**
* An abstract implementation of the ComponentTracker interface. Derived classes must implement
* {@link #buildComponent(String)}, {@link #processPriorToRemoval(Object)}, and {@link #isComponentStale(Object)}
* methods as appropriate for their component type.
*
* @param <C> component type
*
* @author Tommy Becker
* @author Ceki Gulcu
* @author David Roussel
*/
abstract public class AbstractComponentTracker<C> implements ComponentTracker<C> {
private static final boolean ACCESS_ORDERED = true;
// Components in lingering state last 10 seconds
final public static long LINGERING_TIMEOUT = 10 * CoreConstants.MILLIS_IN_ONE_SECOND;
/**
* The minimum amount of time that has to elapse between successive removal iterations.
*/
final public static long WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS = CoreConstants.MILLIS_IN_ONE_SECOND;
protected int maxComponents = DEFAULT_MAX_COMPONENTS;
protected long timeout = DEFAULT_TIMEOUT;
// an access ordered map. Least recently accessed element will be removed after a 'timeout'
LinkedHashMap<String, Entry<C>> liveMap = new LinkedHashMap<String, Entry<C>>(32, .75f, ACCESS_ORDERED);
// an access ordered map. Least recently accessed element will be removed after LINGERING_TIMEOUT
LinkedHashMap<String, Entry<C>> lingerersMap = new LinkedHashMap<String, Entry<C>>(16, .75f, ACCESS_ORDERED);
long lastCheck = 0;
/**
* Stop or clean the component.
*
* @param component
*/
abstract protected void processPriorToRemoval(C component);
/**
* Build a component based on the key.
*
* @param key
* @return
*/
abstract protected C buildComponent(String key);
/**
* Components can declare themselves stale. Such components may be
* removed before they time out.
*
* @param c
* @return
*/
protected abstract boolean isComponentStale(C c);
public int getComponentCount() {
return liveMap.size() + lingerersMap.size();
}
/**
* Get an entry from the liveMap, if not found search the lingerersMap.
*
* @param key
* @return
*/
private Entry<C> getFromEitherMap(String key) {
Entry<C> entry = liveMap.get(key);
if (entry != null)
return entry;
else {
return lingerersMap.get(key);
}
}
/**
* {@inheritDoc}
*
* <p>Note that this method is synchronized.</p>
*
* @param key {@inheritDoc}
* @return {@inheritDoc}
*
*/
public synchronized C find(String key) {
Entry<C> entry = getFromEitherMap(key);
if (entry == null) return null;
else return entry.component;
}
/**
* {@inheritDoc}
*
* <p>Note that this method is atomic, i.e. synchronized.</p>
*
* @param key {@inheritDoc}
* @param timestamp {@inheritDoc}
* @return {@inheritDoc}
*/
public synchronized C getOrCreate(String key, long timestamp) {
Entry<C> entry = getFromEitherMap(key);
if (entry == null) {
C c = buildComponent(key);
entry = new Entry(key, c, timestamp);
// new entries go into the main map
liveMap.put(key, entry);
} else {
entry.setTimestamp(timestamp);
}
return entry.component;
}
/**
* Mark component identified by 'key' as having reached its end-of-life.
*
* @param key
*/
public void endOfLife(String key) {
Entry entry = liveMap.remove(key);
if (entry == null)
return;
lingerersMap.put(key, entry);
}
/**
* Clear (and detach) components which are stale. Components which have not
* been accessed for more than a user-specified duration are deemed stale.
*
* @param now
*/
public synchronized void removeStaleComponents(long now) {
if (isTooSoonForRemovalIteration(now)) return;
removeExcedentComponents();
removeStaleComponentsFromMainMap(now);
removeStaleComponentsFromLingerersMap(now);
}
private void removeExcedentComponents() {
genericStaleComponentRemover(liveMap, 0, byExcedent);
}
private void removeStaleComponentsFromMainMap(long now) {
genericStaleComponentRemover(liveMap, now, byTimeout);
}
private void removeStaleComponentsFromLingerersMap(long now) {
genericStaleComponentRemover(lingerersMap, now, byLingering);
}
private void genericStaleComponentRemover(LinkedHashMap<String, Entry<C>> map, long now,
RemovalPredicator<C> removalPredicator) {
Iterator<Map.Entry<String, Entry<C>>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Entry<C>> mapEntry = iter.next();
Entry<C> entry = mapEntry.getValue();
if (removalPredicator.isSlatedForRemoval(entry, now)) {
iter.remove();
C c = entry.component;
processPriorToRemoval(c);
} else {
break;
}
}
}
private RemovalPredicator<C> byExcedent = new RemovalPredicator<C>() {
public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
return (liveMap.size() > maxComponents);
}
};
private RemovalPredicator<C> byTimeout = new RemovalPredicator<C>() {
public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
return isEntryStale(entry, timestamp);
}
};
private RemovalPredicator<C> byLingering = new RemovalPredicator<C>() {
public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
return isEntryDoneLingering(entry, timestamp);
}
};
private boolean isTooSoonForRemovalIteration(long now) {
if (lastCheck + WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS > now) {
return true;
}
lastCheck = now;
return false;
}
private boolean isEntryStale(Entry<C> entry, long now) {
// stopped or improperly started appenders are considered stale
// see also http://jira.qos.ch/browse/LBCLASSIC-316
C c = entry.component;
if (isComponentStale(c))
return true;
return ((entry.timestamp + timeout) < now);
}
private boolean isEntryDoneLingering(Entry entry, long now) {
return ((entry.timestamp + LINGERING_TIMEOUT) < now);
}
public Set<String> allKeys() {
HashSet<String> allKeys = new HashSet<String>(liveMap.keySet());
allKeys.addAll(lingerersMap.keySet());
return allKeys;
}
public Collection<C> allComponents() {
List<C> allComponents = new ArrayList<C>();
for (Entry<C> e : liveMap.values())
allComponents.add(e.component);
for (Entry<C> e : lingerersMap.values())
allComponents.add(e.component);
return allComponents;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public int getMaxComponents() {
return maxComponents;
}
public void setMaxComponents(int maxComponents) {
this.maxComponents = maxComponents;
}
// ================================================================
private interface RemovalPredicator<C> {
boolean isSlatedForRemoval(Entry<C> entry, long timestamp);
}
// ================================================================
private static class Entry<C> {
String key;
C component;
long timestamp;
Entry(String k, C c, long timestamp) {
this.key = k;
this.component = c;
this.timestamp = timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Entry other = (Entry) obj;
if (key == null) {
if (other.key != null)
return false;
} else if (!key.equals(other.key))
return false;
if (component == null) {
if (other.component != null)
return false;
} else if (!component.equals(other.component))
return false;
return true;
}
@Override
public String toString() {
return "(" + key + ", " + component + ")";
}
}
}