001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.spi; 015 016import ch.qos.logback.core.CoreConstants; 017 018import java.util.*; 019 020/** 021 * An abstract implementation of the ComponentTracker interface. Derived classes 022 * must implement {@link #buildComponent(String)}, 023 * {@link #processPriorToRemoval(Object)}, and {@link #isComponentStale(Object)} 024 * methods as appropriate for their component type. 025 * 026 * @param <C> component type 027 * 028 * @author Tommy Becker 029 * @author Ceki Gulcu 030 * @author David Roussel 031 */ 032abstract public class AbstractComponentTracker<C> implements ComponentTracker<C> { 033 private static final boolean ACCESS_ORDERED = true; 034 035 // Components in lingering state last 10 seconds 036 final public static long LINGERING_TIMEOUT = 10 * CoreConstants.MILLIS_IN_ONE_SECOND; 037 038 /** 039 * The minimum amount of time that has to elapse between successive removal 040 * iterations. 041 */ 042 final public static long WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS = CoreConstants.MILLIS_IN_ONE_SECOND; 043 044 protected int maxComponents = DEFAULT_MAX_COMPONENTS; 045 protected long timeout = DEFAULT_TIMEOUT; 046 047 // an access-ordered map. Least recently accessed element will be removed after 048 // a 'timeout' 049 LinkedHashMap<String, Entry<C>> liveMap = new LinkedHashMap<String, Entry<C>>(32, .75f, ACCESS_ORDERED); 050 051 // an access-ordered map. Least recently accessed element will be removed after 052 // LINGERING_TIMEOUT 053 LinkedHashMap<String, Entry<C>> lingerersMap = new LinkedHashMap<String, Entry<C>>(16, .75f, ACCESS_ORDERED); 054 long lastCheck = 0; 055 056 /** 057 * Stop or clean the component. 058 * 059 * @param component 060 */ 061 abstract protected void processPriorToRemoval(C component); 062 063 /** 064 * Build a component based on the key. 065 * 066 * @param key 067 * @return 068 */ 069 abstract protected C buildComponent(String key); 070 071 /** 072 * Components can declare themselves stale. Such components may be removed 073 * before they time out. 074 * 075 * @param c 076 * @return 077 */ 078 protected abstract boolean isComponentStale(C c); 079 080 public int getComponentCount() { 081 return liveMap.size() + lingerersMap.size(); 082 } 083 084 /** 085 * Get an entry from the liveMap, if not found search the lingerersMap. 086 * 087 * @param key 088 * @return 089 */ 090 private Entry<C> getFromEitherMap(String key) { 091 Entry<C> entry = liveMap.get(key); 092 if (entry != null) 093 return entry; 094 else { 095 return lingerersMap.get(key); 096 } 097 } 098 099 /** 100 * {@inheritDoc} 101 * 102 * <p> 103 * Note that this method is synchronized. 104 * </p> 105 * 106 * @param key {@inheritDoc} 107 * @return {@inheritDoc} 108 * 109 */ 110 public synchronized C find(String key) { 111 Entry<C> entry = getFromEitherMap(key); 112 if (entry == null) 113 return null; 114 else 115 return entry.component; 116 } 117 118 /** 119 * {@inheritDoc} 120 * 121 * <p> 122 * Note that this method is atomic, i.e. synchronized. 123 * </p> 124 * 125 * @param key {@inheritDoc} 126 * @param timestamp {@inheritDoc} 127 * @return {@inheritDoc} 128 */ 129 public synchronized C getOrCreate(String key, long timestamp) { 130 Entry<C> entry = getFromEitherMap(key); 131 if (entry == null) { 132 C c = buildComponent(key); 133 entry = new Entry<C>(key, c, timestamp); 134 // new entries go into the main map 135 liveMap.put(key, entry); 136 } else { 137 entry.setTimestamp(timestamp); 138 } 139 return entry.component; 140 } 141 142 /** 143 * Mark component identified by 'key' as having reached its end-of-life. 144 * 145 * @param key 146 */ 147 public void endOfLife(String key) { 148 Entry<C> entry = liveMap.remove(key); 149 if (entry == null) 150 return; 151 lingerersMap.put(key, entry); 152 } 153 154 /** 155 * Clear (and detach) components which are stale. Components which have not been 156 * accessed for more than a user-specified duration are deemed stale. 157 * 158 * @param now 159 */ 160 public synchronized void removeStaleComponents(long now) { 161 if (isTooSoonForRemovalIteration(now)) 162 return; 163 removeExcedentComponents(); 164 removeStaleComponentsFromMainMap(now); 165 removeStaleComponentsFromLingerersMap(now); 166 } 167 168 private void removeExcedentComponents() { 169 genericStaleComponentRemover(liveMap, 0, byExcedent); 170 } 171 172 private void removeStaleComponentsFromMainMap(long now) { 173 genericStaleComponentRemover(liveMap, now, byTimeout); 174 } 175 176 private void removeStaleComponentsFromLingerersMap(long now) { 177 genericStaleComponentRemover(lingerersMap, now, byLingering); 178 } 179 180 private void genericStaleComponentRemover(LinkedHashMap<String, Entry<C>> map, long now, 181 RemovalPredicator<C> removalPredicator) { 182 Iterator<Map.Entry<String, Entry<C>>> iter = map.entrySet().iterator(); 183 while (iter.hasNext()) { 184 Map.Entry<String, Entry<C>> mapEntry = iter.next(); 185 Entry<C> entry = mapEntry.getValue(); 186 if (removalPredicator.isSlatedForRemoval(entry, now)) { 187 iter.remove(); 188 C c = entry.component; 189 processPriorToRemoval(c); 190 } else { 191 break; 192 } 193 } 194 } 195 196 private RemovalPredicator<C> byExcedent = new RemovalPredicator<C>() { 197 public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) { 198 return (liveMap.size() > maxComponents); 199 } 200 }; 201 202 private RemovalPredicator<C> byTimeout = new RemovalPredicator<C>() { 203 public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) { 204 return isEntryStale(entry, timestamp); 205 } 206 }; 207 private RemovalPredicator<C> byLingering = new RemovalPredicator<C>() { 208 public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) { 209 return isEntryDoneLingering(entry, timestamp); 210 } 211 }; 212 213 private boolean isTooSoonForRemovalIteration(long now) { 214 if (lastCheck + WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS > now) { 215 return true; 216 } 217 lastCheck = now; 218 return false; 219 } 220 221 private boolean isEntryStale(Entry<C> entry, long now) { 222 // stopped or improperly started appenders are considered stale 223 // see also http://jira.qos.ch/browse/LBCLASSIC-316 224 C c = entry.component; 225 if (isComponentStale(c)) 226 return true; 227 228 return ((entry.timestamp + timeout) < now); 229 } 230 231 private boolean isEntryDoneLingering(Entry<C> entry, long now) { 232 return ((entry.timestamp + LINGERING_TIMEOUT) < now); 233 } 234 235 public Set<String> allKeys() { 236 HashSet<String> allKeys = new HashSet<String>(liveMap.keySet()); 237 allKeys.addAll(lingerersMap.keySet()); 238 return allKeys; 239 } 240 241 public Collection<C> allComponents() { 242 List<C> allComponents = new ArrayList<C>(); 243 for (Entry<C> e : liveMap.values()) 244 allComponents.add(e.component); 245 for (Entry<C> e : lingerersMap.values()) 246 allComponents.add(e.component); 247 248 return allComponents; 249 } 250 251 public long getTimeout() { 252 return timeout; 253 } 254 255 public void setTimeout(long timeout) { 256 this.timeout = timeout; 257 } 258 259 public int getMaxComponents() { 260 return maxComponents; 261 } 262 263 public void setMaxComponents(int maxComponents) { 264 this.maxComponents = maxComponents; 265 } 266 267 // ================================================================ 268 private interface RemovalPredicator<C> { 269 boolean isSlatedForRemoval(Entry<C> entry, long timestamp); 270 } 271 272 // ================================================================ 273 private static class Entry<C> { 274 String key; 275 C component; 276 long timestamp; 277 278 Entry(String k, C c, long timestamp) { 279 this.key = k; 280 this.component = c; 281 this.timestamp = timestamp; 282 } 283 284 public void setTimestamp(long timestamp) { 285 this.timestamp = timestamp; 286 } 287 288 @Override 289 public int hashCode() { 290 return key.hashCode(); 291 } 292 293 @Override 294 public boolean equals(Object obj) { 295 if (this == obj) 296 return true; 297 if (obj == null) 298 return false; 299 if (getClass() != obj.getClass()) 300 return false; 301 @SuppressWarnings("unchecked") 302 final Entry<C> other = (Entry<C>) obj; 303 if (key == null) { 304 if (other.key != null) 305 return false; 306 } else if (!key.equals(other.key)) 307 return false; 308 if (component == null) { 309 if (other.component != null) 310 return false; 311 } else if (!component.equals(other.component)) 312 return false; 313 return true; 314 } 315 316 @Override 317 public String toString() { 318 return "(" + key + ", " + component + ")"; 319 } 320 } 321}