View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.spi;
18  
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.Map;
22  
23  import org.apache.logging.log4j.util.BiConsumer;
24  import org.apache.logging.log4j.util.ReadOnlyStringMap;
25  import org.apache.logging.log4j.util.PropertiesUtil;
26  import org.apache.logging.log4j.util.TriConsumer;
27  
28  /**
29   * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored is always
30   * immutable. This means the Map can be passed to other threads without concern that it will be updated. Since it is
31   * expected that the Map will be passed to many more log events than the number of keys it contains the performance
32   * should be much better than if the Map was copied for each event.
33   */
34  public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyStringMap {
35      private static final long serialVersionUID = 8218007901108944053L;
36  
37      /**
38       * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
39       * {@code ThreadLocal} (value is not "true") in the implementation.
40       */
41      public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
42  
43      private final boolean useMap;
44      private final ThreadLocal<Map<String, String>> localMap;
45  
46      private static boolean inheritableMap;
47      
48      static {
49          init();
50      }
51  
52      // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
53      // (This method is package protected for JUnit tests.)
54      static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
55          if (inheritableMap) {
56              return new InheritableThreadLocal<Map<String, String>>() {
57                  @Override
58                  protected Map<String, String> childValue(final Map<String, String> parentValue) {
59                      return parentValue != null && isMapEnabled //
60                      ? Collections.unmodifiableMap(new HashMap<>(parentValue)) //
61                              : null;
62                  }
63              };
64          }
65          // if not inheritable, return plain ThreadLocal with null as initial value
66          return new ThreadLocal<>();
67      }
68  
69      static void init() {
70          inheritableMap = PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP);
71      }
72      
73      public DefaultThreadContextMap() {
74          this(true);
75      }
76  
77      public DefaultThreadContextMap(final boolean useMap) {
78          this.useMap = useMap;
79          this.localMap = createThreadLocalMap(useMap);
80      }
81  
82      @Override
83      public void put(final String key, final String value) {
84          if (!useMap) {
85              return;
86          }
87          Map<String, String> map = localMap.get();
88          map = map == null ? new HashMap<String, String>(1) : new HashMap<>(map);
89          map.put(key, value);
90          localMap.set(Collections.unmodifiableMap(map));
91      }
92  
93      public void putAll(final Map<String, String> m) {
94          if (!useMap) {
95              return;
96          }
97          Map<String, String> map = localMap.get();
98          map = map == null ? new HashMap<String, String>(m.size()) : new HashMap<>(map);
99          for (final Map.Entry<String, String> e : m.entrySet()) {
100             map.put(e.getKey(), e.getValue());
101         }
102         localMap.set(Collections.unmodifiableMap(map));
103     }
104 
105     @Override
106     public String get(final String key) {
107         final Map<String, String> map = localMap.get();
108         return map == null ? null : map.get(key);
109     }
110 
111     @Override
112     public void remove(final String key) {
113         final Map<String, String> map = localMap.get();
114         if (map != null) {
115             final Map<String, String> copy = new HashMap<>(map);
116             copy.remove(key);
117             localMap.set(Collections.unmodifiableMap(copy));
118         }
119     }
120 
121     public void removeAll(final Iterable<String> keys) {
122         final Map<String, String> map = localMap.get();
123         if (map != null) {
124             final Map<String, String> copy = new HashMap<>(map);
125             for (final String key : keys) {
126                 copy.remove(key);
127             }
128             localMap.set(Collections.unmodifiableMap(copy));
129         }
130     }
131 
132     @Override
133     public void clear() {
134         localMap.remove();
135     }
136 
137     @Override
138     public Map<String, String> toMap() {
139         return getCopy();
140     }
141 
142     @Override
143     public boolean containsKey(final String key) {
144         final Map<String, String> map = localMap.get();
145         return map != null && map.containsKey(key);
146     }
147 
148     @Override
149     public <V> void forEach(final BiConsumer<String, ? super V> action) {
150         final Map<String, String> map = localMap.get();
151         if (map == null) {
152             return;
153         }
154         for (final Map.Entry<String, String> entry : map.entrySet()) {
155             //BiConsumer should be able to handle values of any type V. In our case the values are of type String.
156             @SuppressWarnings("unchecked")
157             final
158             V value = (V) entry.getValue();
159             action.accept(entry.getKey(), value);
160         }
161     }
162 
163     @Override
164     public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) {
165         final Map<String, String> map = localMap.get();
166         if (map == null) {
167             return;
168         }
169         for (final Map.Entry<String, String> entry : map.entrySet()) {
170             //TriConsumer should be able to handle values of any type V. In our case the values are of type String.
171             @SuppressWarnings("unchecked")
172             final
173             V value = (V) entry.getValue();
174             action.accept(entry.getKey(), value, state);
175         }
176     }
177 
178     @SuppressWarnings("unchecked")
179     @Override
180     public <V> V getValue(final String key) {
181         final Map<String, String> map = localMap.get();
182         return (V) (map == null ? null : map.get(key));
183     }
184 
185     @Override
186     public Map<String, String> getCopy() {
187         final Map<String, String> map = localMap.get();
188         return map == null ? new HashMap<String, String>() : new HashMap<>(map);
189     }
190 
191     @Override
192     public Map<String, String> getImmutableMapOrNull() {
193         return localMap.get();
194     }
195 
196     @Override
197     public boolean isEmpty() {
198         final Map<String, String> map = localMap.get();
199         return map == null || map.size() == 0;
200     }
201 
202     @Override
203     public int size() {
204         final Map<String, String> map = localMap.get();
205         return map == null ? 0 : map.size();
206     }
207 
208     @Override
209     public String toString() {
210         final Map<String, String> map = localMap.get();
211         return map == null ? "{}" : map.toString();
212     }
213 
214     @Override
215     public int hashCode() {
216         final int prime = 31;
217         int result = 1;
218         final Map<String, String> map = this.localMap.get();
219         result = prime * result + ((map == null) ? 0 : map.hashCode());
220         result = prime * result + Boolean.valueOf(this.useMap).hashCode();
221         return result;
222     }
223 
224     @Override
225     public boolean equals(final Object obj) {
226         if (this == obj) {
227             return true;
228         }
229         if (obj == null) {
230             return false;
231         }
232         if (obj instanceof DefaultThreadContextMap) {
233             final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
234             if (this.useMap != other.useMap) {
235                 return false;
236             }
237         }
238         if (!(obj instanceof ThreadContextMap)) {
239             return false;
240         }
241         final ThreadContextMap other = (ThreadContextMap) obj;
242         final Map<String, String> map = this.localMap.get();
243         final Map<String, String> otherMap = other.getImmutableMapOrNull();
244         if (map == null) {
245             if (otherMap != null) {
246                 return false;
247             }
248         } else if (!map.equals(otherMap)) {
249             return false;
250         }
251         return true;
252     }
253 }