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