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}