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