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 must implement
22   * {@link #buildComponent(String)}, {@link #processPriorToRemoval(Object)}, and {@link #isComponentStale(Object)}
23   * methods as appropriate for their component type.
24   *
25   * @param <C> component type
26   *
27   * @author Tommy Becker
28   * @author Ceki Gulcu
29   * @author David Roussel
30   */
31  abstract public class AbstractComponentTracker<C> implements ComponentTracker<C> {
32      private static final boolean ACCESS_ORDERED = true;
33  
34      // Components in lingering state last 10 seconds
35      final public static long LINGERING_TIMEOUT = 10 * CoreConstants.MILLIS_IN_ONE_SECOND;
36  
37      /**
38       * The minimum amount of time that has to elapse between successive removal iterations.
39       */
40      final public static long WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS = CoreConstants.MILLIS_IN_ONE_SECOND;
41  
42      protected int maxComponents = DEFAULT_MAX_COMPONENTS;
43      protected long timeout = DEFAULT_TIMEOUT;
44  
45      // an access ordered map. Least recently accessed element will be removed after a 'timeout'
46      LinkedHashMap<String, Entry<C>> liveMap = new LinkedHashMap<String, Entry<C>>(32, .75f, ACCESS_ORDERED);
47  
48      // an access ordered map. Least recently accessed element will be removed after LINGERING_TIMEOUT
49      LinkedHashMap<String, Entry<C>> lingerersMap = new LinkedHashMap<String, Entry<C>>(16, .75f, ACCESS_ORDERED);
50      long lastCheck = 0;
51  
52      /**
53       * Stop or clean the component.
54       *
55       * @param component
56       */
57      abstract protected void processPriorToRemoval(C component);
58  
59      /**
60       * Build a component based on the key.
61       *
62       * @param key
63       * @return
64       */
65      abstract protected C buildComponent(String key);
66  
67      /**
68       * Components can declare themselves stale. Such components may be
69       * removed before they time out.
70       *
71       * @param c
72       * @return
73       */
74      protected abstract boolean isComponentStale(C c);
75  
76      public int getComponentCount() {
77          return liveMap.size() + lingerersMap.size();
78      }
79  
80      /**
81       * Get an entry from the liveMap, if not found search the lingerersMap.
82       *
83       * @param key
84       * @return
85       */
86      private Entry<C> getFromEitherMap(String key) {
87          Entry<C> entry = liveMap.get(key);
88          if (entry != null)
89              return entry;
90          else {
91              return lingerersMap.get(key);
92          }
93      }
94  
95      /**
96       * {@inheritDoc}
97       *
98       * <p>Note that this method is synchronized.</p>
99       *
100      * @param key {@inheritDoc}
101      * @return {@inheritDoc}
102      *
103      */
104     public synchronized C find(String key) {
105         Entry<C> entry = getFromEitherMap(key);
106         if (entry == null)
107             return null;
108         else
109             return entry.component;
110     }
111 
112     /**
113      *  {@inheritDoc}
114      *
115      * <p>Note that this method is atomic, i.e. synchronized.</p>
116      *
117      * @param key {@inheritDoc}
118      * @param timestamp {@inheritDoc}
119      * @return {@inheritDoc}
120      */
121     public synchronized C getOrCreate(String key, long timestamp) {
122         Entry<C> entry = getFromEitherMap(key);
123         if (entry == null) {
124             C c = buildComponent(key);
125             entry = new Entry<C>(key, c, timestamp);
126             // new entries go into the main map
127             liveMap.put(key, entry);
128         } else {
129             entry.setTimestamp(timestamp);
130         }
131         return entry.component;
132     }
133 
134     /**
135      * Mark component identified by 'key' as having reached its end-of-life.
136      *
137      * @param key
138      */
139     public void endOfLife(String key) {
140         Entry<C> entry = liveMap.remove(key);
141         if (entry == null)
142             return;
143         lingerersMap.put(key, entry);
144     }
145 
146     /**
147      * Clear (and detach) components which are stale. Components which have not
148      * been accessed for more than a user-specified duration are deemed stale.
149      *
150      * @param now
151      */
152     public synchronized void removeStaleComponents(long now) {
153         if (isTooSoonForRemovalIteration(now))
154             return;
155         removeExcedentComponents();
156         removeStaleComponentsFromMainMap(now);
157         removeStaleComponentsFromLingerersMap(now);
158     }
159 
160     private void removeExcedentComponents() {
161         genericStaleComponentRemover(liveMap, 0, byExcedent);
162     }
163 
164     private void removeStaleComponentsFromMainMap(long now) {
165         genericStaleComponentRemover(liveMap, now, byTimeout);
166     }
167 
168     private void removeStaleComponentsFromLingerersMap(long now) {
169         genericStaleComponentRemover(lingerersMap, now, byLingering);
170     }
171 
172     private void genericStaleComponentRemover(LinkedHashMap<String, Entry<C>> map, long now, RemovalPredicator<C> removalPredicator) {
173         Iterator<Map.Entry<String, Entry<C>>> iter = map.entrySet().iterator();
174         while (iter.hasNext()) {
175             Map.Entry<String, Entry<C>> mapEntry = iter.next();
176             Entry<C> entry = mapEntry.getValue();
177             if (removalPredicator.isSlatedForRemoval(entry, now)) {
178                 iter.remove();
179                 C c = entry.component;
180                 processPriorToRemoval(c);
181             } else {
182                 break;
183             }
184         }
185     }
186 
187     private RemovalPredicator<C> byExcedent = new RemovalPredicator<C>() {
188         public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
189             return (liveMap.size() > maxComponents);
190         }
191     };
192 
193     private RemovalPredicator<C> byTimeout = new RemovalPredicator<C>() {
194         public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
195             return isEntryStale(entry, timestamp);
196         }
197     };
198     private RemovalPredicator<C> byLingering = new RemovalPredicator<C>() {
199         public boolean isSlatedForRemoval(Entry<C> entry, long timestamp) {
200             return isEntryDoneLingering(entry, timestamp);
201         }
202     };
203 
204     private boolean isTooSoonForRemovalIteration(long now) {
205         if (lastCheck + WAIT_BETWEEN_SUCCESSIVE_REMOVAL_ITERATIONS > now) {
206             return true;
207         }
208         lastCheck = now;
209         return false;
210     }
211 
212     private boolean isEntryStale(Entry<C> entry, long now) {
213         // stopped or improperly started appenders are considered stale
214         // see also http://jira.qos.ch/browse/LBCLASSIC-316
215         C c = entry.component;
216         if (isComponentStale(c))
217             return true;
218 
219         return ((entry.timestamp + timeout) < now);
220     }
221 
222     private boolean isEntryDoneLingering(Entry<C> entry, long now) {
223         return ((entry.timestamp + LINGERING_TIMEOUT) < now);
224     }
225 
226     public Set<String> allKeys() {
227         HashSet<String> allKeys = new HashSet<String>(liveMap.keySet());
228         allKeys.addAll(lingerersMap.keySet());
229         return allKeys;
230     }
231 
232     public Collection<C> allComponents() {
233         List<C> allComponents = new ArrayList<C>();
234         for (Entry<C> e : liveMap.values())
235             allComponents.add(e.component);
236         for (Entry<C> e : lingerersMap.values())
237             allComponents.add(e.component);
238 
239         return allComponents;
240     }
241 
242     public long getTimeout() {
243         return timeout;
244     }
245 
246     public void setTimeout(long timeout) {
247         this.timeout = timeout;
248     }
249 
250     public int getMaxComponents() {
251         return maxComponents;
252     }
253 
254     public void setMaxComponents(int maxComponents) {
255         this.maxComponents = maxComponents;
256     }
257 
258     // ================================================================
259     private interface RemovalPredicator<C> {
260         boolean isSlatedForRemoval(Entry<C> entry, long timestamp);
261     }
262 
263     // ================================================================
264     private static class Entry<C> {
265         String key;
266         C component;
267         long timestamp;
268 
269         Entry(String k, C c, long timestamp) {
270             this.key = k;
271             this.component = c;
272             this.timestamp = timestamp;
273         }
274 
275         public void setTimestamp(long timestamp) {
276             this.timestamp = timestamp;
277         }
278 
279         @Override
280         public int hashCode() {
281             return key.hashCode();
282         }
283 
284         @Override
285         public boolean equals(Object obj) {
286             if (this == obj)
287                 return true;
288             if (obj == null)
289                 return false;
290             if (getClass() != obj.getClass())
291                 return false;
292             @SuppressWarnings("unchecked")
293             final Entry<C> other = (Entry<C>) obj;
294             if (key == null) {
295                 if (other.key != null)
296                     return false;
297             } else if (!key.equals(other.key))
298                 return false;
299             if (component == null) {
300                 if (other.component != null)
301                     return false;
302             } else if (!component.equals(other.component))
303                 return false;
304             return true;
305         }
306 
307         @Override
308         public String toString() {
309             return "(" + key + ", " + component + ")";
310         }
311     }
312 }