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.pattern;
15  
16  import java.util.HashMap;
17  import java.util.Map;
18  import java.util.function.Supplier;
19  
20  import ch.qos.logback.core.Context;
21  import ch.qos.logback.core.CoreConstants;
22  import ch.qos.logback.core.LayoutBase;
23  import ch.qos.logback.core.pattern.color.ConverterSupplierByClassName;
24  import ch.qos.logback.core.pattern.parser.Node;
25  import ch.qos.logback.core.pattern.parser.Parser;
26  import ch.qos.logback.core.spi.ScanException;
27  import ch.qos.logback.core.status.ErrorStatus;
28  import ch.qos.logback.core.status.StatusManager;
29  
30  abstract public class PatternLayoutBase<E> extends LayoutBase<E> {
31  
32      static final int INTIAL_STRING_BUILDER_SIZE = 256;
33      Converter<E> head;
34      String pattern;
35      protected PostCompileProcessor<E> postCompileProcessor;
36  
37      /**
38       * <p>It should be noted that the default converter map is a static variable. Thus, changes made
39       * through {@link #getDefaultConverterSupplierMap()} apply to all instances of this class.
40       * </p>
41       *
42       * <p>The {@link #getInstanceConverterMap} variable allows for very specific extensions
43       * without impacting other instances</p>
44       */
45      Map<String, Supplier<DynamicConverter>> instanceConverterMap = new HashMap<>();
46      protected boolean outputPatternAsHeader = false;
47  
48      /**
49       * Concrete implementations of this class are responsible for elaborating the
50       * mapping between pattern words and supplying converter instances.
51       * 
52       * @return A map associating pattern words to the names of converter suppliers
53       * @since 1.5.13
54       */
55       protected abstract Map<String, Supplier<DynamicConverter>> getDefaultConverterSupplierMap();
56  
57      /**
58       * <p>BEWARE: The map of type String,String for mapping conversion words is deprecated.
59       * Use {@link #getDefaultConverterSupplierMap()} instead.</p>
60       *
61       * <p>Existing code such as getDefaultMap().put("k", X.class.getName()) should be replaced by
62       * getDefaultConverterSupplierMap().put("k", X::new) </p>
63       *
64       * <p>Note that values in the map will still be taken into account and processed correctly.</p>
65       *
66       * @return a map of keys and class names
67       */
68      @Deprecated
69      abstract public Map<String, String> getDefaultConverterMap();
70  
71      /**
72       * Returns a map where the default converter map is merged with the map
73       * contained in the context.
74       */
75      public Map<String, Supplier<DynamicConverter>> getEffectiveConverterMap() {
76          Map<String, Supplier<DynamicConverter>> effectiveMap = new HashMap<>();
77  
78          // add the least specific map fist
79          Map<String, Supplier<DynamicConverter>> defaultConverterSupplierMap = getDefaultConverterSupplierMap();
80          if (defaultConverterSupplierMap != null) {
81              effectiveMap.putAll(defaultConverterSupplierMap);
82          }
83  
84          caterForLegacyConverterMaps(effectiveMap);
85  
86          // contextMap is more specific than the default map
87          Context context = getContext();
88          if (context != null) {
89              @SuppressWarnings("unchecked")
90              Map<String, Supplier<DynamicConverter>> contextMap = (Map<String, Supplier<DynamicConverter>>) context
91                      .getObject(CoreConstants.PATTERN_RULE_REGISTRY_FOR_SUPPLIERS);
92              if (contextMap != null) {
93                  effectiveMap.putAll(contextMap);
94              }
95          }
96          // set the most specific map last
97          effectiveMap.putAll(instanceConverterMap);
98          return effectiveMap;
99      }
100 
101     /**
102      * Add class name values into the effective map to support external extensions
103      * and subclasses.
104      *
105      * @param effectiveMap
106      */
107     private void caterForLegacyConverterMaps(Map<String, Supplier<DynamicConverter>> effectiveMap) {
108         Map<String, String> mapFromContext = (Map<String, String>) this.context
109                         .getObject(CoreConstants.PATTERN_RULE_REGISTRY);
110 
111         migrateFromStringMapToSupplierMap(mapFromContext, effectiveMap);
112 
113         Map<String, String> defaultConverterMap = getDefaultConverterMap();
114         migrateFromStringMapToSupplierMap(defaultConverterMap, effectiveMap);
115     }
116 
117     private void migrateFromStringMapToSupplierMap(Map<String, String> legacyMap, Map<String, Supplier<DynamicConverter>> targetSupplierMap) {
118         if(legacyMap == null)
119             return;
120 
121         // this transformation is for backward compatibility of existing code
122         for(Map.Entry<String, String> entry: legacyMap.entrySet()) {
123             String key = entry.getKey();
124             String converterClassName = entry.getValue();
125             ConverterSupplierByClassName converterSupplierByClassName = new ConverterSupplierByClassName(key, converterClassName);
126             converterSupplierByClassName.setContext(getContext());
127             targetSupplierMap.put(key, converterSupplierByClassName);
128         }
129 
130     }
131 
132     public void start() {
133         if (pattern == null || pattern.length() == 0) {
134             addError("Empty or null pattern.");
135             return;
136         }
137         try {
138             Parser<E> p = new Parser<E>(pattern);
139             if (getContext() != null) {
140                 p.setContext(getContext());
141             }
142             Node t = p.parse();
143             this.head = p.compile(t, getEffectiveConverterMap());
144             if (postCompileProcessor != null) {
145                 postCompileProcessor.process(context, head);
146             }
147             ConverterUtil.setContextForConverters(getContext(), head);
148             ConverterUtil.startConverters(this.head);
149             super.start();
150         } catch (ScanException sce) {
151             StatusManager sm = getContext().getStatusManager();
152             sm.add(new ErrorStatus("Failed to parse pattern \"" + getPattern() + "\".", this, sce));
153         }
154     }
155 
156     public void setPostCompileProcessor(PostCompileProcessor<E> postCompileProcessor) {
157         this.postCompileProcessor = postCompileProcessor;
158     }
159 
160     /**
161      *
162      * @param head
163      * @deprecated Use {@link ConverterUtil#setContextForConverters} instead. This
164      *             method will be removed in future releases.
165      */
166     protected void setContextForConverters(Converter<E> head) {
167         ConverterUtil.setContextForConverters(getContext(), head);
168     }
169 
170     protected String writeLoopOnConverters(E event) {
171         StringBuilder strBuilder = new StringBuilder(INTIAL_STRING_BUILDER_SIZE);
172         Converter<E> c = head;
173         while (c != null) {
174             c.write(strBuilder, event);
175             c = c.getNext();
176         }
177         return strBuilder.toString();
178     }
179 
180     public String getPattern() {
181         return pattern;
182     }
183 
184     public void setPattern(String pattern) {
185         this.pattern = pattern;
186     }
187 
188     public String toString() {
189         return this.getClass().getName() + "(\"" + getPattern() + "\")";
190     }
191 
192     public Map<String, Supplier<DynamicConverter>> getInstanceConverterMap() {
193         return instanceConverterMap;
194     }
195 
196     protected String getPresentationHeaderPrefix() {
197         return CoreConstants.EMPTY_STRING;
198     }
199 
200     public boolean isOutputPatternAsHeader() {
201         return outputPatternAsHeader;
202     }
203 
204     public void setOutputPatternAsHeader(boolean outputPatternAsHeader) {
205         this.outputPatternAsHeader = outputPatternAsHeader;
206     }
207 
208     @Override
209     public String getPresentationHeader() {
210         if (outputPatternAsHeader)
211             return getPresentationHeaderPrefix() + pattern;
212         else
213             return super.getPresentationHeader();
214     }
215 }