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