1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, 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 v2.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;
15  
16  import static ch.qos.logback.core.CoreConstants.CONTEXT_NAME_KEY;
17  import static ch.qos.logback.core.CoreConstants.HOSTNAME_KEY;
18  
19  import java.util.ArrayList;
20  import java.util.HashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.concurrent.*;
24  import java.util.concurrent.locks.ReentrantLock;
25  
26  import ch.qos.logback.core.rolling.helper.FileNamePattern;
27  import ch.qos.logback.core.spi.ConfigurationEvent;
28  import ch.qos.logback.core.spi.ConfigurationEventListener;
29  import ch.qos.logback.core.spi.LifeCycle;
30  import ch.qos.logback.core.spi.SequenceNumberGenerator;
31  import ch.qos.logback.core.status.InfoStatus;
32  import ch.qos.logback.core.status.StatusManager;
33  import ch.qos.logback.core.util.ExecutorServiceUtil;
34  import ch.qos.logback.core.util.NetworkAddressUtil;
35  
36  public class ContextBase implements Context, LifeCycle {
37  
38      private long birthTime = System.currentTimeMillis();
39  
40      private String name;
41      private StatusManager sm = new BasicStatusManager();
42      // TODO propertyMap should be observable so that we can be notified
43      // when it changes so that a new instance of propertyMap can be
44      // serialized. For the time being, we ignore this shortcoming.
45      Map<String, String> propertyMap = new HashMap<String, String>();
46      Map<String, Object> objectMap = new ConcurrentHashMap<>();
47  
48      protected ReentrantLock configurationLock = new ReentrantLock();
49  
50      final private List<ConfigurationEventListener> configurationEventListenerList = new CopyOnWriteArrayList<>();
51  
52      private ScheduledExecutorService scheduledExecutorService;
53  
54      private ThreadPoolExecutor threadPoolExecutor;
55      private ExecutorService alternateExecutorService;
56  
57  
58      protected List<ScheduledFuture<?>> scheduledFutures = new ArrayList<ScheduledFuture<?>>(1);
59      private LifeCycleManager lifeCycleManager;
60      private SequenceNumberGenerator sequenceNumberGenerator;
61  
62      // 'started' is used to mitigate race conditions in stop() and possibly other methods, hence the volatile qualifier.
63      private volatile boolean started;
64  
65      public ContextBase() {
66      }
67  
68      public StatusManager getStatusManager() {
69          return sm;
70      }
71  
72      /**
73       * Set the {@link StatusManager} for this context. Note that by default this
74       * context is initialized with a {@link BasicStatusManager}. A null value for
75       * the 'statusManager' argument is not allowed.
76       * 
77       * <p>
78       * A malicious attacker can set the status manager to a dummy instance,
79       * disabling internal error reporting.
80       *
81       * @param statusManager the new status manager
82       */
83      public void setStatusManager(StatusManager statusManager) {
84          // this method was added in response to http://jira.qos.ch/browse/LBCORE-35
85          if (statusManager == null) {
86              throw new IllegalArgumentException("null StatusManager not allowed");
87          }
88          this.sm = statusManager;
89      }
90  
91      public Map<String, String> getCopyOfPropertyMap() {
92          return new HashMap<String, String>(propertyMap);
93      }
94  
95      public void putProperty(String key, String val) {
96          if (HOSTNAME_KEY.equalsIgnoreCase(key)) {
97              putHostnameProperty(val);
98          } else {
99              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 }