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