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}