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.PropertiesUtil;
24  
25  /**
26   * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored
27   * is always immutable. This means the Map can be passed to other threads without concern that it will be updated.
28   * Since it is expected that the Map will be passed to many more log events than the number of keys it contains
29   * the performance should be much better than if the Map was copied for each event.
30   */
31  public class DefaultThreadContextMap implements ThreadContextMap {
32      /** 
33       * Property name ({@value}) for selecting {@code InheritableThreadLocal} (value "true")
34       * or plain {@code ThreadLocal} (value is not "true") in the implementation.
35       */
36      public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
37  
38      private final boolean useMap;
39      private final ThreadLocal<Map<String, String>> localMap;
40  
41      public DefaultThreadContextMap(final boolean useMap) {
42          this.useMap = useMap;
43          this.localMap = createThreadLocalMap(useMap);
44      }
45      
46      // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
47      // (This method is package protected for JUnit tests.)
48      static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
49          final PropertiesUtil managerProps = PropertiesUtil.getProperties();
50          final boolean inheritable = managerProps.getBooleanProperty(INHERITABLE_MAP);
51          if (inheritable) {
52              return new InheritableThreadLocal<Map<String, String>>() {
53                  @Override
54                  protected Map<String, String> childValue(final Map<String, String> parentValue) {
55                      return parentValue != null && isMapEnabled //
56                              ? Collections.unmodifiableMap(new HashMap<String, String>(parentValue)) //
57                              : null;
58                  }
59              };
60          }
61          // if not inheritable, return plain ThreadLocal with null as initial value
62          return new ThreadLocal<Map<String, String>>();
63      }
64  
65      /**
66       * Put a context value (the <code>o</code> parameter) as identified
67       * with the <code>key</code> parameter into the current thread's
68       * context map.
69       * <p/>
70       * <p>If the current thread does not have a context map it is
71       * created as a side effect.
72       * @param key The key name.
73       * @param value The key value.
74       */
75      @Override
76      public void put(final String key, final String value) {
77          if (!useMap) {
78              return;
79          }
80          Map<String, String> map = localMap.get();
81          map = map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
82          map.put(key, value);
83          localMap.set(Collections.unmodifiableMap(map));
84      }
85  
86      /**
87       * Get the context identified by the <code>key</code> parameter.
88       * <p/>
89       * <p>This method has no side effects.
90       * @param key The key to locate.
91       * @return The value associated with the key or null.
92       */
93      @Override
94      public String get(final String key) {
95          final Map<String, String> map = localMap.get();
96          return map == null ? null : map.get(key);
97      }
98  
99      /**
100      * Remove the the context identified by the <code>key</code>
101      * parameter.
102      * @param key The key to remove.
103      */
104     @Override
105     public void remove(final String key) {
106         final Map<String, String> map = localMap.get();
107         if (map != null) {
108             final Map<String, String> copy = new HashMap<String, String>(map);
109             copy.remove(key);
110             localMap.set(Collections.unmodifiableMap(copy));
111         }
112     }
113 
114     /**
115      * Clear the context.
116      */
117     @Override
118     public void clear() {
119         localMap.remove();
120     }
121 
122     /**
123      * Determine if the key is in the context.
124      * @param key The key to locate.
125      * @return True if the key is in the context, false otherwise.
126      */
127     @Override
128     public boolean containsKey(final String key) {
129         final Map<String, String> map = localMap.get();
130         return map != null && map.containsKey(key);
131     }
132 
133     /**
134      * Returns a non-{@code null} mutable copy of the ThreadContext Map.
135      * @return a non-{@code null} mutable copy of the context.
136      */
137     @Override
138     public Map<String, String> getCopy() {
139         final Map<String, String> map = localMap.get();
140         return map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
141     }
142 
143     /**
144      * Returns either {@code null} or an immutable view of the context Map.
145      * @return the Context Map.
146      */
147     @Override
148     public Map<String, String> getImmutableMapOrNull() {
149         return localMap.get();
150     }
151 
152     /**
153      * Returns true if the Map is empty.
154      * @return true if the Map is empty, false otherwise.
155      */
156     @Override
157     public boolean isEmpty() {
158         final Map<String, String> map = localMap.get();
159         return map == null || map.size() == 0;
160     }
161 
162     @Override
163     public String toString() {
164         final Map<String, String> map = localMap.get();
165         return map == null ? "{}" : map.toString();
166     }
167 
168     @Override
169     public int hashCode() {
170         final int prime = 31;
171         int result = 1;
172         final Map<String, String> map = this.localMap.get();
173         result = prime * result + ((map == null) ? 0 : map.hashCode());
174         result = prime * result + (this.useMap ? 1231 : 1237);
175         return result;
176     }
177 
178     @Override
179     public boolean equals(final Object obj) {
180         if (this == obj) {
181             return true;
182         }
183         if (obj == null) {
184             return false;
185         }
186         if (obj instanceof DefaultThreadContextMap) {
187             final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
188             if (this.useMap != other.useMap) {
189                 return false;
190             }
191         }
192         if (!(obj instanceof ThreadContextMap)) {
193             return false;
194         }
195         final ThreadContextMap other = (ThreadContextMap) obj;
196         final Map<String, String> map = this.localMap.get();
197         final Map<String, String> otherMap = other.getImmutableMapOrNull(); 
198         if (map == null) {
199             if (otherMap != null) {
200                 return false;
201             }
202         } else if (!map.equals(otherMap)) {
203             return false;
204         }
205         return true;
206     }
207 }