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