001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017 package org.apache.logging.log4j.spi;
018
019 import java.util.Collections;
020 import java.util.HashMap;
021 import java.util.Map;
022
023 import org.apache.logging.log4j.util.PropertiesUtil;
024
025 /**
026 * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored
027 * is always immutable. This means the Map can be passed to other threads without concern that it will be updated.
028 * Since it is expected that the Map will be passed to many more log events than the number of keys it contains
029 * the performance should be much better than if the Map was copied for each event.
030 */
031 public class DefaultThreadContextMap implements ThreadContextMap {
032 /**
033 * Property name ({@value}) for selecting {@code InheritableThreadLocal} (value "true")
034 * or plain {@code ThreadLocal} (value is not "true") in the implementation.
035 */
036 public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
037
038 private final boolean useMap;
039 private final ThreadLocal<Map<String, String>> localMap;
040
041 public DefaultThreadContextMap(final boolean useMap) {
042 this.useMap = useMap;
043 this.localMap = createThreadLocalMap(useMap);
044 }
045
046 // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
047 // (This method is package protected for JUnit tests.)
048 static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
049 final PropertiesUtil managerProps = PropertiesUtil.getProperties();
050 final boolean inheritable = managerProps.getBooleanProperty(INHERITABLE_MAP);
051 if (inheritable) {
052 return new InheritableThreadLocal<Map<String, String>>() {
053 @Override
054 protected Map<String, String> childValue(final Map<String, String> parentValue) {
055 return parentValue != null && isMapEnabled //
056 ? Collections.unmodifiableMap(new HashMap<String, String>(parentValue)) //
057 : null;
058 }
059 };
060 }
061 // if not inheritable, return plain ThreadLocal with null as initial value
062 return new ThreadLocal<Map<String, String>>();
063 }
064
065 @Override
066 public void put(final String key, final String value) {
067 if (!useMap) {
068 return;
069 }
070 Map<String, String> map = localMap.get();
071 map = map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
072 map.put(key, value);
073 localMap.set(Collections.unmodifiableMap(map));
074 }
075
076 @Override
077 public String get(final String key) {
078 final Map<String, String> map = localMap.get();
079 return map == null ? null : map.get(key);
080 }
081
082 @Override
083 public void remove(final String key) {
084 final Map<String, String> map = localMap.get();
085 if (map != null) {
086 final Map<String, String> copy = new HashMap<String, String>(map);
087 copy.remove(key);
088 localMap.set(Collections.unmodifiableMap(copy));
089 }
090 }
091
092 @Override
093 public void clear() {
094 localMap.remove();
095 }
096
097 @Override
098 public boolean containsKey(final String key) {
099 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 }