View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.spi;
15  
16  import ch.qos.logback.core.CoreConstants;
17  
18  import java.util.*;
19  
20  /**
21   * An abstract implementation of the ComponentTracker interface. Derived classes
22   * must implement {@link #buildComponent(String)},
23   * {@link #processPriorToRemoval(Object)}, and {@link #isComponentStale(Object)}
24   * methods as appropriate for their component type.
25   *
26   * @param <C> component type
27   *
28   * @author Tommy Becker
29   * @author Ceki Gulcu
30   * @author David Roussel
31   */
32  abstract public class AbstractComponentTracker<C> implements ComponentTracker<C> {
33      private static final boolean ACCESS_ORDERED = true;
34  
35      // Components in lingering state last 10 seconds
36      final public static long LINGERING_TIMEOUT = 10 * CoreConstants.MILLIS_IN_ONE_SECOND;
37  
38      /**
39       * The minimum amount of time that has to elapse between successive removal
40       * iterations.
41       */
42      final public static long WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS = CoreConstants.MILLIS_IN_ONE_SECOND;
43  
44      protected int maxComponents = DEFAULT_MAX_COMPONENTS;
45      protected long timeout = DEFAULT_TIMEOUT;
46  
47      // an access-ordered map. Least recently accessed element will be removed after
48      // a 'timeout'
49      LinkedHashMap<String, Entry<C>> liveMap = new LinkedHashMap<String, Entry<C>>(32, .75f, ACCESS_ORDERED);
50  
51      // an access-ordered map. Least recently accessed element will be removed after
52      // LINGERING_TIMEOUT
53      LinkedHashMap<String, Entry<C>> lingerersMap = new LinkedHashMap<String, Entry<C>>(16, .75f, ACCESS_ORDERED);
54      long lastCheck = 0;
55  
56      /**
57       * Stop or clean the component.
58       *
59       * @param component
60       */
61      abstract protected void processPriorToRemoval(C component);
62  
63      /**
64       * Build a component based on the key.
65       *
66       * @param key
67       * @return
68       */
69      abstract protected C buildComponent(String key);
70  
71      /**
72       * Components can declare themselves stale. Such components may be removed
73       * before they time out.
74       *
75       * @param c
76       * @return
77       */
78      protected abstract boolean isComponentStale(C c);
79  
80      public int getComponentCount() {
81          return liveMap.size() + lingerersMap.size();
82      }
83  
84      /**
85       * Get an entry from the liveMap, if not found search the lingerersMap.
86       *
87       * @param key
88       * @return
89       */
90      private Entry<C> getFromEitherMap(String key) {
91          Entry<C> entry = liveMap.get(key);
92          if (entry != null)
93              return entry;
94          else {
95              return lingerersMap.get(key);
96          }
97      }
98  
99      /**
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 }