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 */ 017package org.apache.logging.log4j.spi; 018 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Objects; 023 024import org.apache.logging.log4j.util.BiConsumer; 025import org.apache.logging.log4j.util.ReadOnlyStringMap; 026import org.apache.logging.log4j.util.PropertiesUtil; 027import org.apache.logging.log4j.util.TriConsumer; 028 029/** 030 * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored is always 031 * immutable. This means the Map can be passed to other threads without concern that it will be updated. Since it is 032 * expected that the Map will be passed to many more log events than the number of keys it contains the performance 033 * should be much better than if the Map was copied for each event. 034 */ 035public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyStringMap { 036 private static final long serialVersionUID = 8218007901108944053L; 037 038 /** 039 * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain 040 * {@code ThreadLocal} (value is not "true") in the implementation. 041 */ 042 public static final String INHERITABLE_MAP = "isThreadContextMapInheritable"; 043 044 private final boolean useMap; 045 private final ThreadLocal<Map<String, String>> localMap; 046 047 private static boolean inheritableMap; 048 049 static { 050 init(); 051 } 052 053 // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. 054 // (This method is package protected for JUnit tests.) 055 static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) { 056 if (inheritableMap) { 057 return new InheritableThreadLocal<Map<String, String>>() { 058 @Override 059 protected Map<String, String> childValue(final Map<String, String> parentValue) { 060 return parentValue != null && isMapEnabled // 061 ? Collections.unmodifiableMap(new HashMap<>(parentValue)) // 062 : null; 063 } 064 }; 065 } 066 // if not inheritable, return plain ThreadLocal with null as initial value 067 return new ThreadLocal<>(); 068 } 069 070 static void init() { 071 inheritableMap = PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP); 072 } 073 074 public DefaultThreadContextMap() { 075 this(true); 076 } 077 078 public DefaultThreadContextMap(final boolean useMap) { 079 this.useMap = useMap; 080 this.localMap = createThreadLocalMap(useMap); 081 } 082 083 @Override 084 public void put(final String key, final String value) { 085 if (!useMap) { 086 return; 087 } 088 Map<String, String> map = localMap.get(); 089 map = map == null ? new HashMap<>(1) : new HashMap<>(map); 090 map.put(key, value); 091 localMap.set(Collections.unmodifiableMap(map)); 092 } 093 094 public void putAll(final Map<String, String> m) { 095 if (!useMap) { 096 return; 097 } 098 Map<String, String> map = localMap.get(); 099 map = map == null ? new HashMap<>(m.size()) : new HashMap<>(map); 100 for (final Map.Entry<String, String> e : m.entrySet()) { 101 map.put(e.getKey(), e.getValue()); 102 } 103 localMap.set(Collections.unmodifiableMap(map)); 104 } 105 106 @Override 107 public String get(final String key) { 108 final Map<String, String> map = localMap.get(); 109 return map == null ? null : map.get(key); 110 } 111 112 @Override 113 public void remove(final String key) { 114 final Map<String, String> map = localMap.get(); 115 if (map != null) { 116 final Map<String, String> copy = new HashMap<>(map); 117 copy.remove(key); 118 localMap.set(Collections.unmodifiableMap(copy)); 119 } 120 } 121 122 public void removeAll(final Iterable<String> keys) { 123 final Map<String, String> map = localMap.get(); 124 if (map != null) { 125 final Map<String, String> copy = new HashMap<>(map); 126 for (final String key : keys) { 127 copy.remove(key); 128 } 129 localMap.set(Collections.unmodifiableMap(copy)); 130 } 131 } 132 133 @Override 134 public void clear() { 135 localMap.remove(); 136 } 137 138 @Override 139 public Map<String, String> toMap() { 140 return getCopy(); 141 } 142 143 @Override 144 public boolean containsKey(final String key) { 145 final Map<String, String> map = localMap.get(); 146 return map != null && map.containsKey(key); 147 } 148 149 @Override 150 public <V> void forEach(final BiConsumer<String, ? super V> action) { 151 final Map<String, String> map = localMap.get(); 152 if (map == null) { 153 return; 154 } 155 for (final Map.Entry<String, String> entry : map.entrySet()) { 156 //BiConsumer should be able to handle values of any type V. In our case the values are of type String. 157 @SuppressWarnings("unchecked") 158 final 159 V value = (V) entry.getValue(); 160 action.accept(entry.getKey(), value); 161 } 162 } 163 164 @Override 165 public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) { 166 final Map<String, String> map = localMap.get(); 167 if (map == null) { 168 return; 169 } 170 for (final Map.Entry<String, String> entry : map.entrySet()) { 171 //TriConsumer should be able to handle values of any type V. In our case the values are of type String. 172 @SuppressWarnings("unchecked") 173 final 174 V value = (V) entry.getValue(); 175 action.accept(entry.getKey(), value, state); 176 } 177 } 178 179 @SuppressWarnings("unchecked") 180 @Override 181 public <V> V getValue(final String key) { 182 final Map<String, String> map = localMap.get(); 183 return (V) (map == null ? null : map.get(key)); 184 } 185 186 @Override 187 public Map<String, String> getCopy() { 188 final Map<String, String> map = localMap.get(); 189 return map == null ? new HashMap<>() : new HashMap<>(map); 190 } 191 192 @Override 193 public Map<String, String> getImmutableMapOrNull() { 194 return localMap.get(); 195 } 196 197 @Override 198 public boolean isEmpty() { 199 final Map<String, String> map = localMap.get(); 200 return map == null || map.isEmpty(); 201 } 202 203 @Override 204 public int size() { 205 final Map<String, String> map = localMap.get(); 206 return map == null ? 0 : map.size(); 207 } 208 209 @Override 210 public String toString() { 211 final Map<String, String> map = localMap.get(); 212 return map == null ? "{}" : map.toString(); 213 } 214 215 @Override 216 public int hashCode() { 217 final int prime = 31; 218 int result = 1; 219 final Map<String, String> map = this.localMap.get(); 220 result = prime * result + ((map == null) ? 0 : map.hashCode()); 221 result = prime * result + Boolean.valueOf(this.useMap).hashCode(); 222 return result; 223 } 224 225 @Override 226 public boolean equals(final Object obj) { 227 if (this == obj) { 228 return true; 229 } 230 if (obj == null) { 231 return false; 232 } 233 if (obj instanceof DefaultThreadContextMap) { 234 final DefaultThreadContextMap other = (DefaultThreadContextMap) obj; 235 if (this.useMap != other.useMap) { 236 return false; 237 } 238 } 239 if (!(obj instanceof ThreadContextMap)) { 240 return false; 241 } 242 final ThreadContextMap other = (ThreadContextMap) obj; 243 final Map<String, String> map = this.localMap.get(); 244 final Map<String, String> otherMap = other.getImmutableMapOrNull(); 245 return Objects.equals(map, otherMap); 246 } 247}