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;
015
016import static ch.qos.logback.core.CoreConstants.CONTEXT_NAME_KEY;
017import static ch.qos.logback.core.CoreConstants.FA_FILENAME_COLLISION_MAP;
018import static ch.qos.logback.core.CoreConstants.HOSTNAME_KEY;
019import static ch.qos.logback.core.CoreConstants.RFA_FILENAME_PATTERN_COLLISION_MAP;
020
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.concurrent.*;
026import java.util.concurrent.locks.ReentrantLock;
027
028import ch.qos.logback.core.rolling.helper.FileNamePattern;
029import ch.qos.logback.core.spi.ConfigurationEvent;
030import ch.qos.logback.core.spi.ConfigurationEventListener;
031import ch.qos.logback.core.spi.LifeCycle;
032import ch.qos.logback.core.spi.SequenceNumberGenerator;
033import ch.qos.logback.core.status.InfoStatus;
034import ch.qos.logback.core.status.StatusManager;
035import ch.qos.logback.core.util.ExecutorServiceUtil;
036import ch.qos.logback.core.util.NetworkAddressUtil;
037
038public class ContextBase implements Context, LifeCycle {
039
040    private long birthTime = System.currentTimeMillis();
041
042    private String name;
043    private StatusManager sm = new BasicStatusManager();
044    // TODO propertyMap should be observable so that we can be notified
045    // when it changes so that a new instance of propertyMap can be
046    // serialized. For the time being, we ignore this shortcoming.
047    Map<String, String> propertyMap = new HashMap<String, String>();
048    Map<String, Object> objectMap = new ConcurrentHashMap<>();
049
050    protected ReentrantLock configurationLock = new ReentrantLock();
051
052    final private List<ConfigurationEventListener> configurationEventListenerList = new CopyOnWriteArrayList<>();
053
054    private ScheduledExecutorService scheduledExecutorService;
055
056    private ThreadPoolExecutor threadPoolExecutor;
057    private ExecutorService alternateExecutorService;
058
059
060    protected List<ScheduledFuture<?>> scheduledFutures = new ArrayList<ScheduledFuture<?>>(1);
061    private LifeCycleManager lifeCycleManager;
062    private SequenceNumberGenerator sequenceNumberGenerator;
063
064    // 'started' is used to mitigate race conditions in stop() and possibly other methods, hence the volatile qualifier.
065    private volatile boolean started;
066
067    public ContextBase() {
068        initCollisionMaps();
069    }
070
071    public StatusManager getStatusManager() {
072        return sm;
073    }
074
075    /**
076     * Set the {@link StatusManager} for this context. Note that by default this
077     * context is initialized with a {@link BasicStatusManager}. A null value for
078     * the 'statusManager' argument is not allowed.
079     * 
080     * <p>
081     * A malicious attacker can set the status manager to a dummy instance,
082     * disabling internal error reporting.
083     *
084     * @param statusManager the new status manager
085     */
086    public void setStatusManager(StatusManager statusManager) {
087        // this method was added in response to http://jira.qos.ch/browse/LBCORE-35
088        if (statusManager == null) {
089            throw new IllegalArgumentException("null StatusManager not allowed");
090        }
091        this.sm = statusManager;
092    }
093
094    public Map<String, String> getCopyOfPropertyMap() {
095        return new HashMap<String, String>(propertyMap);
096    }
097
098    public void putProperty(String key, String val) {
099        if (HOSTNAME_KEY.equalsIgnoreCase(key)) {
100            putHostnameProperty(val);
101        } else {
102            this.propertyMap.put(key, val);
103        }
104    }
105
106    protected void initCollisionMaps() {
107        putObject(FA_FILENAME_COLLISION_MAP, new HashMap<String, String>());
108        putObject(RFA_FILENAME_PATTERN_COLLISION_MAP, new HashMap<String, FileNamePattern>());
109    }
110
111    @Override
112    public void addSubstitutionProperty(String key, String value) {
113        if (key == null || value == null) {
114            return;
115        }
116        // values with leading or trailing spaces are bad. We remove them now.
117        value = value.trim();
118        propertyMap.put(key, value);
119    }
120
121    /**
122     * Given a key, return the corresponding property value. If invoked with the
123     * special key "CONTEXT_NAME", the name of the context is returned.
124     *
125     * @param key
126     * @return
127     */
128    public String getProperty(String key) {
129        if (CONTEXT_NAME_KEY.equals(key))
130            return getName();
131        if (HOSTNAME_KEY.equalsIgnoreCase(key)) {
132            return lazyGetHostname();
133        }
134
135        return (String) this.propertyMap.get(key);
136    }
137
138    private String lazyGetHostname() {
139        String hostname = (String) this.propertyMap.get(HOSTNAME_KEY);
140        if (hostname == null) {
141            hostname = new NetworkAddressUtil(this).safelyGetLocalHostName();
142            putHostnameProperty(hostname);
143        }
144        return hostname;
145    }
146
147    private void putHostnameProperty(String hostname) {
148        String existingHostname = (String) this.propertyMap.get(HOSTNAME_KEY);
149        if (existingHostname == null) {
150            this.propertyMap.put(CoreConstants.HOSTNAME_KEY, hostname);
151        } else {
152
153        }
154    }
155
156    public Object getObject(String key) {
157        return objectMap.get(key);
158    }
159
160    public void putObject(String key, Object value) {
161        objectMap.put(key, value);
162    }
163
164    public void removeObject(String key) {
165        objectMap.remove(key);
166    }
167
168    public String getName() {
169        return name;
170    }
171
172    @Override
173    public void start() {
174        // We'd like to create the executor service here, but we can't;
175        // ContextBase has not always implemented LifeCycle and there are *many*
176        // uses (mostly in tests) that would need to be modified.
177        started = true;
178    }
179
180    public void stop() {
181        // We don't check "started" here, because the executor services use
182        // lazy initialization, rather than being created in the start method
183        stopExecutorServices();
184
185        started = false;
186    }
187
188    public boolean isStarted() {
189        return started;
190    }
191
192    /**
193     * Clear the internal objectMap and all properties. Removes any registered
194     * shutdown hook.
195     */
196    public void reset() {
197
198        removeShutdownHook();
199        getLifeCycleManager().reset();
200        propertyMap.clear();
201        objectMap.clear();
202    }
203
204    /**
205     * The context name can be set only if it is not already set, or if the current
206     * name is the default context name, namely "default", or if the current name
207     * and the old name are the same.
208     *
209     * @throws IllegalStateException if the context already has a name, other than
210     *                               "default".
211     */
212    public void setName(String name) throws IllegalStateException {
213        if (name != null && name.equals(this.name)) {
214            return; // idempotent naming
215        }
216        if (this.name == null || CoreConstants.DEFAULT_CONTEXT_NAME.equals(this.name)) {
217            this.name = name;
218        } else {
219            throw new IllegalStateException("Context has been already given a name");
220        }
221    }
222
223    public long getBirthTime() {
224        return birthTime;
225    }
226
227    public ReentrantLock getConfigurationLock() {
228        return configurationLock;
229    }
230
231
232
233    @Override
234    public synchronized ExecutorService getExecutorService() {
235        if (threadPoolExecutor == null) {
236            threadPoolExecutor = (ThreadPoolExecutor) ExecutorServiceUtil.newThreadPoolExecutor();
237        }
238        return threadPoolExecutor;
239    }
240
241    @Override
242    public synchronized ExecutorService getAlternateExecutorService() {
243        if(alternateExecutorService == null) {
244            alternateExecutorService = ExecutorServiceUtil.newAlternateThreadPoolExecutor();
245        }
246        return alternateExecutorService;
247    }
248
249        @Override
250    public synchronized ScheduledExecutorService getScheduledExecutorService() {
251        if (scheduledExecutorService == null) {
252            scheduledExecutorService = ExecutorServiceUtil.newScheduledExecutorService();
253        }
254        return scheduledExecutorService;
255    }
256
257    private synchronized void stopExecutorServices() {
258        ExecutorServiceUtil.shutdown(scheduledExecutorService);
259        scheduledExecutorService = null;
260
261        ExecutorServiceUtil.shutdown(threadPoolExecutor);
262        threadPoolExecutor = null;
263    }
264
265    private void removeShutdownHook() {
266        Thread hook = (Thread) getObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
267        if (hook != null) {
268            removeObject(CoreConstants.SHUTDOWN_HOOK_THREAD);
269
270            try {
271                sm.add(new InfoStatus("Removing shutdownHook " + hook, this));
272                Runtime runtime = Runtime.getRuntime();
273                boolean result = runtime.removeShutdownHook(hook);
274                sm.add(new InfoStatus("ShutdownHook removal result: " + result, this));
275            } catch (IllegalStateException e) {
276                // if JVM is already shutting down, ISE is thrown
277                // no need to do anything else
278            }
279        }
280    }
281
282    public void register(LifeCycle component) {
283        getLifeCycleManager().register(component);
284    }
285
286    /**
287     * Gets the life cycle manager for this context.
288     * <p>
289     * The default implementation lazily initializes an instance of
290     * {@link LifeCycleManager}. Subclasses may override to provide a custom manager
291     * implementation, but must take care to return the same manager object for each
292     * call to this method.
293     * <p>
294     * This is exposed primarily to support instrumentation for unit testing.
295     * 
296     * @return manager object
297     */
298    synchronized LifeCycleManager getLifeCycleManager() {
299        if (lifeCycleManager == null) {
300            lifeCycleManager = new LifeCycleManager();
301        }
302        return lifeCycleManager;
303    }
304
305    @Override
306    public String toString() {
307        return name;
308    }
309
310    @Override
311    public void addScheduledFuture(ScheduledFuture<?> scheduledFuture) {
312        scheduledFutures.add(scheduledFuture);
313    }
314
315    /**
316     * @deprecated replaced by getCopyOfScheduledFutures
317     */
318    @Deprecated
319    public List<ScheduledFuture<?>> getScheduledFutures() {
320        return getCopyOfScheduledFutures();
321    }
322
323    public List<ScheduledFuture<?>> getCopyOfScheduledFutures() {
324        return new ArrayList<ScheduledFuture<?>>(scheduledFutures);
325    }
326
327    public SequenceNumberGenerator getSequenceNumberGenerator() {
328        return sequenceNumberGenerator;
329    }
330
331    public void setSequenceNumberGenerator(SequenceNumberGenerator sequenceNumberGenerator) {
332        this.sequenceNumberGenerator = sequenceNumberGenerator;
333    }
334
335    @Override
336    public void addConfigurationEventListener(ConfigurationEventListener listener) {
337        configurationEventListenerList.add(listener);
338    }
339
340    @Override
341    public void removeConfigurationEventListener(ConfigurationEventListener listener) {
342        configurationEventListenerList.remove(listener);
343    }
344
345    @Override
346    public void fireConfigurationEvent(ConfigurationEvent configurationEvent) {
347        configurationEventListenerList.forEach( l -> l.listen(configurationEvent));
348    }
349}