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