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      @Override
66      public void put(final String key, final String value) {
67          if (!useMap) {
68              return;
69          }
70          Map<String, String> map = localMap.get();
71          map = map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
72          map.put(key, value);
73          localMap.set(Collections.unmodifiableMap(map));
74      }
75  
76      @Override
77      public String get(final String key) {
78          final Map<String, String> map = localMap.get();
79          return map == null ? null : map.get(key);
80      }
81  
82      @Override
83      public void remove(final String key) {
84          final Map<String, String> map = localMap.get();
85          if (map != null) {
86              final Map<String, String> copy = new HashMap<String, String>(map);
87              copy.remove(key);
88              localMap.set(Collections.unmodifiableMap(copy));
89          }
90      }
91  
92      @Override
93      public void clear() {
94          localMap.remove();
95      }
96  
97      @Override
98      public boolean containsKey(final String key) {
99          final Map<String, String> map = localMap.get();
100         return map != null && map.containsKey(key);
101     }
102 
103     @Override
104     public Map<String, String> getCopy() {
105         final Map<String, String> map = localMap.get();
106         return map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
107     }
108 
109     @Override
110     public Map<String, String> getImmutableMapOrNull() {
111         return localMap.get();
112     }
113 
114     @Override
115     public boolean isEmpty() {
116         final Map<String, String> map = localMap.get();
117         return map == null || map.size() == 0;
118     }
119 
120     @Override
121     public String toString() {
122         final Map<String, String> map = localMap.get();
123         return map == null ? "{}" : map.toString();
124     }
125 
126     @Override
127     public int hashCode() {
128         final int prime = 31;
129         int result = 1;
130         final Map<String, String> map = this.localMap.get();
131         result = prime * result + ((map == null) ? 0 : map.hashCode());
132         result = prime * result + (this.useMap ? 1231 : 1237);
133         return result;
134     }
135 
136     @Override
137     public boolean equals(final Object obj) {
138         if (this == obj) {
139             return true;
140         }
141         if (obj == null) {
142             return false;
143         }
144         if (obj instanceof DefaultThreadContextMap) {
145             final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
146             if (this.useMap != other.useMap) {
147                 return false;
148             }
149         }
150         if (!(obj instanceof ThreadContextMap)) {
151             return false;
152         }
153         final ThreadContextMap other = (ThreadContextMap) obj;
154         final Map<String, String> map = this.localMap.get();
155         final Map<String, String> otherMap = other.getImmutableMapOrNull(); 
156         if (map == null) {
157             if (otherMap != null) {
158                 return false;
159             }
160         } else if (!map.equals(otherMap)) {
161             return false;
162         }
163         return true;
164     }
165 }