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.model.processor; 015 016import java.net.URL; 017import java.util.ArrayList; 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Stack; 023import java.util.function.Supplier; 024 025import ch.qos.logback.core.Appender; 026import ch.qos.logback.core.Context; 027import ch.qos.logback.core.joran.GenericXMLConfigurator; 028import ch.qos.logback.core.joran.JoranConstants; 029import ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry; 030import ch.qos.logback.core.joran.util.beans.BeanDescriptionCache; 031import ch.qos.logback.core.model.Model; 032import ch.qos.logback.core.model.util.VariableSubstitutionsHelper; 033import ch.qos.logback.core.spi.AppenderAttachable; 034import ch.qos.logback.core.spi.ContextAwareBase; 035import ch.qos.logback.core.spi.ContextAwarePropertyContainer; 036 037public class ModelInterpretationContext extends ContextAwareBase implements ContextAwarePropertyContainer { 038 039 Stack<Object> objectStack; 040 Stack<Model> modelStack; 041 042 043 URL topURL; 044 Boolean topScanBoolean = null; 045 046 /** 047 * A supplier of JoranConfigurator instances. 048 * 049 * May be null. 050 * 051 * @since 1.5.5 052 */ 053 Supplier<? extends GenericXMLConfigurator> configuratorSupplier; 054 055 056 Map<String, Object> objectMap; 057 protected VariableSubstitutionsHelper variableSubstitutionsHelper; 058 protected Map<String, String> importMap; 059 060 final private BeanDescriptionCache beanDescriptionCache; 061 final DefaultNestedComponentRegistry defaultNestedComponentRegistry = new DefaultNestedComponentRegistry(); 062 List<DependencyDefinition> dependencyDefinitionList = new ArrayList<>(); 063 final List<String> startedDependees = new ArrayList<>(); 064 065 Object configuratorHint; 066 067 Model topModel; 068 069 public ModelInterpretationContext(Context context) { 070 this(context, null); 071 } 072 073 public ModelInterpretationContext(Context context, Object configuratorHint) { 074 this.context = context; 075 this.configuratorHint = configuratorHint; 076 this.objectStack = new Stack<>(); 077 this.modelStack = new Stack<>(); 078 this.beanDescriptionCache = new BeanDescriptionCache(context); 079 objectMap = new HashMap<>(5); 080 variableSubstitutionsHelper = new VariableSubstitutionsHelper(context); 081 importMap = new HashMap<>(5); 082 } 083 084 public ModelInterpretationContext(ModelInterpretationContext otherMic) { 085 this(otherMic.context, otherMic.configuratorHint); 086 importMap = new HashMap<>(otherMic.importMap); 087 variableSubstitutionsHelper = new VariableSubstitutionsHelper(context, otherMic.getCopyOfPropertyMap()); 088 defaultNestedComponentRegistry.duplicate(otherMic.getDefaultNestedComponentRegistry()); 089 createAppenderBags(); 090 } 091 092 public Map<String, Object> getObjectMap() { 093 return objectMap; 094 } 095 096 public void createAppenderBags() { 097 objectMap.put(JoranConstants.APPENDER_BAG, new HashMap<String, Appender<?>>()); 098 objectMap.put(JoranConstants.APPENDER_REF_BAG, new HashMap<String, AppenderAttachable<?>>()); 099 } 100 101 public Model getTopModel() { 102 return topModel; 103 } 104 105 public void setTopModel(Model topModel) { 106 this.topModel = topModel; 107 } 108 109 // modelStack ================================= 110 111 public void pushModel(Model m) { 112 modelStack.push(m); 113 } 114 115 public Model peekModel() { 116 return modelStack.peek(); 117 } 118 119 public boolean isModelStackEmpty() { 120 return modelStack.isEmpty(); 121 } 122 123 public Model popModel() { 124 return modelStack.pop(); 125 } 126 127 // =================== object stack 128 129 public Stack<Object> getObjectStack() { 130 return objectStack; 131 } 132 133 public boolean isObjectStackEmpty() { 134 return objectStack.isEmpty(); 135 } 136 137 public Object peekObject() { 138 return objectStack.peek(); 139 } 140 141 public void pushObject(Object o) { 142 objectStack.push(o); 143 } 144 145 public Object popObject() { 146 return objectStack.pop(); 147 } 148 149 public Object getObject(int i) { 150 return objectStack.get(i); 151 } 152 153 // ===================== END object stack 154 155 public Object getConfiguratorHint() { 156 return configuratorHint; 157 } 158 159 public void setConfiguratorHint(Object configuratorHint) { 160 this.configuratorHint = configuratorHint; 161 } 162 163 public BeanDescriptionCache getBeanDescriptionCache() { 164 return beanDescriptionCache; 165 } 166 167 /** 168 * Performs variable substitution on the provided {@code ref} string. 169 * 170 * <p>Value substitution will follow the order</p> 171 * <ol> 172 * <li>properties defined in this {@link ModelInterpretationContext}</li> 173 * <li>properties defined in the {@link Context context} of this {@link ModelInterpretationContext}</li> 174 * <li>System properties</li> 175 * <li>Environment variables</li> 176 * </ol> 177 * 178 * <p>If value substitution occurs it will be output as a status message, unless marked confidential, that is, 179 * if {@code ref} contains the case-insensitive strings PASSWORD, SECRET or CONFIDENTIAL.</p> 180 * 181 * @param ref the string that may contain variables to be substituted; can be {@code null} 182 * @return the string with substitutions applied if applicable; may return {@code null} if {@code ref} is {@code null} 183 */ 184 public String subst(String ref) { 185 186 String substituted = variableSubstitutionsHelper.subst(ref); 187 if(ref != null && !ref.equals(substituted) ) { 188 String sanitized = variableSubstitutionsHelper.sanitizeIfConfidential(ref, substituted); 189 addInfo("value \""+sanitized+"\" substituted for \""+ref+"\""); 190 } 191 return substituted; 192 } 193 194 public DefaultNestedComponentRegistry getDefaultNestedComponentRegistry() { 195 return defaultNestedComponentRegistry; 196 } 197 198 // ================================== dependencies 199 200 public void addDependencyDefinition(DependencyDefinition dd) { 201 dependencyDefinitionList.add(dd); 202 } 203 204 public List<DependencyDefinition> getDependencyDefinitions() { 205 return Collections.unmodifiableList(dependencyDefinitionList); 206 } 207 208 public List<String> getDependencyNamesForModel(Model model) { 209 List<String> dependencyList = new ArrayList<>(); 210 for (DependencyDefinition dd : dependencyDefinitionList) { 211 if (dd.getDepender() == model) { 212 dependencyList.add(dd.getDependency()); 213 } 214 } 215 return dependencyList; 216 } 217 218 public boolean hasDependers(String dependencyName) { 219 220 if (dependencyName == null || dependencyName.trim().length() == 0) { 221 new IllegalArgumentException("Empty dependeeName name not allowed here"); 222 } 223 224 for (DependencyDefinition dd : dependencyDefinitionList) { 225 if (dd.dependency.equals(dependencyName)) 226 return true; 227 } 228 229 return false; 230 } 231 232 233 public void markStartOfNamedDependee(String name) { 234 startedDependees.add(name); 235 } 236 237 public boolean isNamedDependemcyStarted(String name) { 238 return startedDependees.contains(name); 239 } 240 241 // ========================================== object map 242 243 /** 244 * Add a property to the properties of this execution context. If the property 245 * exists already, it is overwritten. 246 */ 247 @Override 248 public void addSubstitutionProperty(String key, String value) { 249 variableSubstitutionsHelper.addSubstitutionProperty(key, value); 250 } 251 252 /** 253 * If a key is found in propertiesMap then return it. Otherwise, delegate to the 254 * context. 255 */ 256 public String getProperty(String key) { 257 return variableSubstitutionsHelper.getProperty(key); 258 } 259 260 @Override 261 public Map<String, String> getCopyOfPropertyMap() { 262 return variableSubstitutionsHelper.getCopyOfPropertyMap(); 263 } 264 265 // imports =================================================================== 266 267 /** 268 * Add an import to the importMao 269 * 270 * @param stem the class to import 271 * @param fqcn the fully qualified name of the class 272 * 273 * @since 1.3 274 */ 275 public void addImport(String stem, String fqcn) { 276 importMap.put(stem, fqcn); 277 } 278 279 public Map<String, String> getImportMapCopy() { 280 return new HashMap<>(importMap); 281 } 282 283 284 /** 285 * Given a stem, get the fully qualified name of the class corresponding to the 286 * stem. For unknown stems, returns the stem as is. If stem is null, null is 287 * returned. 288 * 289 * @param stem may be null 290 * @return fully qualified name of the class corresponding to the stem. For 291 * unknown stems, returns the stem as is. If stem is null, null is 292 * returned. 293 * @since 1.3 294 */ 295 public String getImport(String stem) { 296 if (stem == null) 297 return null; 298 299 String result = importMap.get(stem); 300 if (result == null) 301 return stem; 302 else 303 return result; 304 } 305 306 /** 307 * Returns a supplier of {@link GenericXMLConfigurator} instance. The returned value may be null. 308 * 309 * @return a supplier of {@link GenericXMLConfigurator} instance, may be null 310 */ 311 @Override 312 public Supplier<? extends GenericXMLConfigurator> getConfiguratorSupplier() { 313 return this.configuratorSupplier; 314 } 315 316 /** 317 * 318 * @param configuratorSupplier 319 */ 320 public void setConfiguratorSupplier(Supplier<? extends GenericXMLConfigurator> configuratorSupplier) { 321 this.configuratorSupplier = configuratorSupplier; 322 } 323 324 325 public URL getTopURL() { 326 return topURL; 327 } 328 329 public void setTopURL(URL topURL) { 330 this.topURL = topURL; 331 } 332 333 public Boolean getTopScanBoolean() { 334 return topScanBoolean; 335 } 336 public void setTopScanBoolean(Boolean topScanBoolean) { 337 this.topScanBoolean = topScanBoolean; 338 } 339}