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.ReadOnlyStringMap;
24  import org.apache.logging.log4j.util.SortedArrayStringMap;
25  import org.apache.logging.log4j.util.StringMap;
26  import org.apache.logging.log4j.util.PropertiesUtil;
27  
28  /**
29   * {@code SortedArrayStringMap}-based implementation of the {@code ThreadContextMap} interface that creates a copy of
30   * the data structure on every modification. Any particular instance of the data structure is a snapshot of the
31   * ThreadContext at some point in time and can safely be passed off to other threads.  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   * @since 2.7
36   */
37  class CopyOnWriteSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap, CopyOnWrite {
38  
39      /**
40       * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
41       * {@code ThreadLocal} (value is not "true") in the implementation.
42       */
43      public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
44  
45      /**
46       * The default initial capacity.
47       */
48      protected static final int DEFAULT_INITIAL_CAPACITY = 16;
49  
50      /**
51       * System property name that can be used to control the data structure's initial capacity.
52       */
53      protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity";
54  
55      private static final StringMap EMPTY_CONTEXT_DATA = new SortedArrayStringMap(1);
56      
57      private static volatile int initialCapacity;
58      private static volatile boolean inheritableMap;
59  
60      /**
61       * Initializes static variables based on system properties. Normally called when this class is initialized by the VM
62       * and when Log4j is reconfigured.
63       */
64      static void init() {
65          final PropertiesUtil properties = PropertiesUtil.getProperties();
66          initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY);
67          inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP);
68      }
69      
70      static {
71          EMPTY_CONTEXT_DATA.freeze();
72          init();
73      }
74  
75      private final ThreadLocal<StringMap> localMap;
76  
77      public CopyOnWriteSortedArrayThreadContextMap() {
78          this.localMap = createThreadLocalMap();
79      }
80  
81      // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
82      // (This method is package protected for JUnit tests.)
83      private ThreadLocal<StringMap> createThreadLocalMap() {
84          if (inheritableMap) {
85              return new InheritableThreadLocal<StringMap>() {
86                  @Override
87                  protected StringMap childValue(final StringMap parentValue) {
88                      if (parentValue == null) {
89                          return null;
90                      }
91                      final StringMap stringMap = createStringMap(parentValue);
92                      stringMap.freeze();
93                      return stringMap;
94                  }
95              };
96          }
97          // if not inheritable, return plain ThreadLocal with null as initial value
98          return new ThreadLocal<>();
99      }
100 
101     /**
102      * Returns an implementation of the {@code StringMap} used to back this thread context map.
103      * <p>
104      * Subclasses may override.
105      * </p>
106      * @return an implementation of the {@code StringMap} used to back this thread context map
107      */
108     protected StringMap createStringMap() {
109         return new SortedArrayStringMap(initialCapacity);
110     }
111 
112     /**
113      * Returns an implementation of the {@code StringMap} used to back this thread context map, pre-populated
114      * with the contents of the specified context data.
115      * <p>
116      * Subclasses may override.
117      * </p>
118      * @param original the key-value pairs to initialize the returned context data with
119      * @return an implementation of the {@code StringMap} used to back this thread context map
120      */
121     protected StringMap createStringMap(final ReadOnlyStringMap original) {
122         return new SortedArrayStringMap(original);
123     }
124 
125     @Override
126     public void put(final String key, final String value) {
127         putValue(key, value);
128     }
129 
130     @Override
131     public void putValue(final String key, final Object value) {
132         StringMap map = localMap.get();
133         map = map == null ? createStringMap() : createStringMap(map);
134         map.putValue(key, value);
135         map.freeze();
136         localMap.set(map);
137     }
138 
139     @Override
140     public void putAll(final Map<String, String> values) {
141         if (values == null || values.isEmpty()) {
142             return;
143         }
144         StringMap map = localMap.get();
145         map = map == null ? createStringMap() : createStringMap(map);
146         for (final Map.Entry<String, String> entry : values.entrySet()) {
147             map.putValue(entry.getKey(), entry.getValue());
148         }
149         map.freeze();
150         localMap.set(map);
151     }
152 
153     @Override
154     public <V> void putAllValues(final Map<String, V> values) {
155         if (values == null || values.isEmpty()) {
156             return;
157         }
158         StringMap map = localMap.get();
159         map = map == null ? createStringMap() : createStringMap(map);
160         for (final Map.Entry<String, V> entry : values.entrySet()) {
161             map.putValue(entry.getKey(), entry.getValue());
162         }
163         map.freeze();
164         localMap.set(map);
165     }
166 
167     @Override
168     public String get(final String key) {
169         return (String) getValue(key);
170     }
171 
172     @Override
173     public <V> V getValue(final String key) {
174         final StringMap map = localMap.get();
175         return map == null ? null : map.<V>getValue(key);
176     }
177 
178     @Override
179     public void remove(final String key) {
180         final StringMap map = localMap.get();
181         if (map != null) {
182             final StringMap copy = createStringMap(map);
183             copy.remove(key);
184             copy.freeze();
185             localMap.set(copy);
186         }
187     }
188 
189     @Override
190     public void removeAll(final Iterable<String> keys) {
191         final StringMap map = localMap.get();
192         if (map != null) {
193             final StringMap copy = createStringMap(map);
194             for (final String key : keys) {
195                 copy.remove(key);
196             }
197             copy.freeze();
198             localMap.set(copy);
199         }
200     }
201 
202     @Override
203     public void clear() {
204         localMap.remove();
205     }
206 
207     @Override
208     public boolean containsKey(final String key) {
209         final StringMap map = localMap.get();
210         return map != null && map.containsKey(key);
211     }
212 
213     @Override
214     public Map<String, String> getCopy() {
215         final StringMap map = localMap.get();
216         return map == null ? new HashMap<String, String>() : map.toMap();
217     }
218 
219     /**
220      * {@inheritDoc}
221      */
222     @Override
223     public StringMap getReadOnlyContextData() {
224         final StringMap map = localMap.get();
225         return map == null ? EMPTY_CONTEXT_DATA : map;
226     }
227 
228     @Override
229     public Map<String, String> getImmutableMapOrNull() {
230         final StringMap map = localMap.get();
231         return map == null ? null : Collections.unmodifiableMap(map.toMap());
232     }
233 
234     @Override
235     public boolean isEmpty() {
236         final StringMap map = localMap.get();
237         return map == null || map.size() == 0;
238     }
239 
240     @Override
241     public String toString() {
242         final StringMap map = localMap.get();
243         return map == null ? "{}" : map.toString();
244     }
245 
246     @Override
247     public int hashCode() {
248         final int prime = 31;
249         int result = 1;
250         final StringMap map = this.localMap.get();
251         result = prime * result + ((map == null) ? 0 : map.hashCode());
252         return result;
253     }
254 
255     @Override
256     public boolean equals(final Object obj) {
257         if (this == obj) {
258             return true;
259         }
260         if (obj == null) {
261             return false;
262         }
263         if (!(obj instanceof ThreadContextMap)) {
264             return false;
265         }
266         final ThreadContextMap other = (ThreadContextMap) obj;
267         final Map<String, String> map = this.getImmutableMapOrNull();
268         final Map<String, String> otherMap = other.getImmutableMapOrNull();
269         if (map == null) {
270             if (otherMap != null) {
271                 return false;
272             }
273         } else if (!map.equals(otherMap)) {
274             return false;
275         }
276         return true;
277     }
278 }