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  
36      /**
37       * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
38       * {@code ThreadLocal} (value is not "true") in the implementation.
39       */
40      public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
41  
42      private final boolean useMap;
43      private final ThreadLocal<Map<String, String>> localMap;
44  
45      public DefaultThreadContextMap() {
46          this(true);
47      }
48  
49      public DefaultThreadContextMap(final boolean useMap) {
50          this.useMap = useMap;
51          this.localMap = createThreadLocalMap(useMap);
52      }
53  
54      // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
55      // (This method is package protected for JUnit tests.)
56      static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
57          final PropertiesUtil managerProps = PropertiesUtil.getProperties();
58          final boolean inheritable = managerProps.getBooleanProperty(INHERITABLE_MAP);
59          if (inheritable) {
60              return new InheritableThreadLocal<Map<String, String>>() {
61                  @Override
62                  protected Map<String, String> childValue(final Map<String, String> parentValue) {
63                      return parentValue != null && isMapEnabled //
64                      ? Collections.unmodifiableMap(new HashMap<>(parentValue)) //
65                              : null;
66                  }
67              };
68          }
69          // if not inheritable, return plain ThreadLocal with null as initial value
70          return new ThreadLocal<>();
71      }
72  
73      @Override
74      public void put(final String key, final String value) {
75          if (!useMap) {
76              return;
77          }
78          Map<String, String> map = localMap.get();
79          map = map == null ? new HashMap<String, String>(1) : new HashMap<>(map);
80          map.put(key, value);
81          localMap.set(Collections.unmodifiableMap(map));
82      }
83  
84      public void putAll(final Map<String, String> m) {
85          if (!useMap) {
86              return;
87          }
88          Map<String, String> map = localMap.get();
89          map = map == null ? new HashMap<String, String>(m.size()) : new HashMap<>(map);
90          for (final Map.Entry<String, String> e : m.entrySet()) {
91              map.put(e.getKey(), e.getValue());
92          }
93          localMap.set(Collections.unmodifiableMap(map));
94      }
95  
96      @Override
97      public String get(final String key) {
98          final Map<String, String> map = localMap.get();
99          return map == null ? null : map.get(key);
100     }
101 
102     @Override
103     public void remove(final String key) {
104         final Map<String, String> map = localMap.get();
105         if (map != null) {
106             final Map<String, String> copy = new HashMap<>(map);
107             copy.remove(key);
108             localMap.set(Collections.unmodifiableMap(copy));
109         }
110     }
111 
112     public void removeAll(final Iterable<String> keys) {
113         final Map<String, String> map = localMap.get();
114         if (map != null) {
115             final Map<String, String> copy = new HashMap<>(map);
116             for (final String key : keys) {
117                 copy.remove(key);
118             }
119             localMap.set(Collections.unmodifiableMap(copy));
120         }
121     }
122 
123     @Override
124     public void clear() {
125         localMap.remove();
126     }
127 
128     @Override
129     public Map<String, String> toMap() {
130         return getCopy();
131     }
132 
133     @Override
134     public boolean containsKey(final String key) {
135         final Map<String, String> map = localMap.get();
136         return map != null && map.containsKey(key);
137     }
138 
139     @Override
140     public <V> void forEach(final BiConsumer<String, ? super V> action) {
141         final Map<String, String> map = localMap.get();
142         if (map == null) {
143             return;
144         }
145         for (final Map.Entry<String, String> entry : map.entrySet()) {
146             action.accept(entry.getKey(), (V) entry.getValue());
147         }
148     }
149 
150     @Override
151     public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) {
152         final Map<String, String> map = localMap.get();
153         if (map == null) {
154             return;
155         }
156         for (final Map.Entry<String, String> entry : map.entrySet()) {
157             action.accept(entry.getKey(), (V) entry.getValue(), state);
158         }
159     }
160 
161     @SuppressWarnings("unchecked")
162     @Override
163     public <V> V getValue(final String key) {
164         final Map<String, String> map = localMap.get();
165         return (V) (map == null ? null : map.get(key));
166     }
167 
168     @Override
169     public Map<String, String> getCopy() {
170         final Map<String, String> map = localMap.get();
171         return map == null ? new HashMap<String, String>() : new HashMap<>(map);
172     }
173 
174     @Override
175     public Map<String, String> getImmutableMapOrNull() {
176         return localMap.get();
177     }
178 
179     @Override
180     public boolean isEmpty() {
181         final Map<String, String> map = localMap.get();
182         return map == null || map.size() == 0;
183     }
184 
185     @Override
186     public int size() {
187         final Map<String, String> map = localMap.get();
188         return map == null ? 0 : map.size();
189     }
190 
191     @Override
192     public String toString() {
193         final Map<String, String> map = localMap.get();
194         return map == null ? "{}" : map.toString();
195     }
196 
197     @Override
198     public int hashCode() {
199         final int prime = 31;
200         int result = 1;
201         final Map<String, String> map = this.localMap.get();
202         result = prime * result + ((map == null) ? 0 : map.hashCode());
203         result = prime * result + Boolean.valueOf(this.useMap).hashCode();
204         return result;
205     }
206 
207     @Override
208     public boolean equals(final Object obj) {
209         if (this == obj) {
210             return true;
211         }
212         if (obj == null) {
213             return false;
214         }
215         if (obj instanceof DefaultThreadContextMap) {
216             final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
217             if (this.useMap != other.useMap) {
218                 return false;
219             }
220         }
221         if (!(obj instanceof ThreadContextMap)) {
222             return false;
223         }
224         final ThreadContextMap other = (ThreadContextMap) obj;
225         final Map<String, String> map = this.localMap.get();
226         final Map<String, String> otherMap = other.getImmutableMapOrNull();
227         if (map == null) {
228             if (otherMap != null) {
229                 return false;
230             }
231         } else if (!map.equals(otherMap)) {
232             return false;
233         }
234         return true;
235     }
236 }