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