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 036 /** 037 * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain 038 * {@code ThreadLocal} (value is not "true") in the implementation. 039 */ 040 public static final String INHERITABLE_MAP = "isThreadContextMapInheritable"; 041 042 private final boolean useMap; 043 private final ThreadLocal<Map<String, String>> localMap; 044 045 public DefaultThreadContextMap() { 046 this(true); 047 } 048 049 public DefaultThreadContextMap(final boolean useMap) { 050 this.useMap = useMap; 051 this.localMap = createThreadLocalMap(useMap); 052 } 053 054 // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. 055 // (This method is package protected for JUnit tests.) 056 static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) { 057 final PropertiesUtil managerProps = PropertiesUtil.getProperties(); 058 final boolean inheritable = managerProps.getBooleanProperty(INHERITABLE_MAP); 059 if (inheritable) { 060 return new InheritableThreadLocal<Map<String, String>>() { 061 @Override 062 protected Map<String, String> childValue(final Map<String, String> parentValue) { 063 return parentValue != null && isMapEnabled // 064 ? Collections.unmodifiableMap(new HashMap<>(parentValue)) // 065 : null; 066 } 067 }; 068 } 069 // if not inheritable, return plain ThreadLocal with null as initial value 070 return new ThreadLocal<>(); 071 } 072 073 @Override 074 public void put(final String key, final String value) { 075 if (!useMap) { 076 return; 077 } 078 Map<String, String> map = localMap.get(); 079 map = map == null ? new HashMap<String, String>(1) : new HashMap<>(map); 080 map.put(key, value); 081 localMap.set(Collections.unmodifiableMap(map)); 082 } 083 084 public void putAll(final Map<String, String> m) { 085 if (!useMap) { 086 return; 087 } 088 Map<String, String> map = localMap.get(); 089 map = map == null ? new HashMap<String, String>(m.size()) : new HashMap<>(map); 090 for (final Map.Entry<String, String> e : m.entrySet()) { 091 map.put(e.getKey(), e.getValue()); 092 } 093 localMap.set(Collections.unmodifiableMap(map)); 094 } 095 096 @Override 097 public String get(final String key) { 098 final Map<String, String> map = localMap.get(); 099 return map == null ? null : map.get(key); 100 } 101 102 @Override 103 public void remove(final String key) { 104 final Map<String, String> map = localMap.get(); 105 if (map != null) { 106 final Map<String, String> copy = new HashMap<>(map); 107 copy.remove(key); 108 localMap.set(Collections.unmodifiableMap(copy)); 109 } 110 } 111 112 public void removeAll(final Iterable<String> keys) { 113 final Map<String, String> map = localMap.get(); 114 if (map != null) { 115 final Map<String, String> copy = new HashMap<>(map); 116 for (final String key : keys) { 117 copy.remove(key); 118 } 119 localMap.set(Collections.unmodifiableMap(copy)); 120 } 121 } 122 123 @Override 124 public void clear() { 125 localMap.remove(); 126 } 127 128 @Override 129 public Map<String, String> toMap() { 130 return getCopy(); 131 } 132 133 @Override 134 public boolean containsKey(final String key) { 135 final Map<String, String> map = localMap.get(); 136 return map != null && map.containsKey(key); 137 } 138 139 @Override 140 public <V> void forEach(final BiConsumer<String, ? super V> action) { 141 final Map<String, String> map = localMap.get(); 142 if (map == null) { 143 return; 144 } 145 for (final Map.Entry<String, String> entry : map.entrySet()) { 146 action.accept(entry.getKey(), (V) entry.getValue()); 147 } 148 } 149 150 @Override 151 public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) { 152 final Map<String, String> map = localMap.get(); 153 if (map == null) { 154 return; 155 } 156 for (final Map.Entry<String, String> entry : map.entrySet()) { 157 action.accept(entry.getKey(), (V) entry.getValue(), state); 158 } 159 } 160 161 @SuppressWarnings("unchecked") 162 @Override 163 public <V> V getValue(final String key) { 164 final Map<String, String> map = localMap.get(); 165 return (V) (map == null ? null : map.get(key)); 166 } 167 168 @Override 169 public Map<String, String> getCopy() { 170 final Map<String, String> map = localMap.get(); 171 return map == null ? new HashMap<String, String>() : new HashMap<>(map); 172 } 173 174 @Override 175 public Map<String, String> getImmutableMapOrNull() { 176 return localMap.get(); 177 } 178 179 @Override 180 public boolean isEmpty() { 181 final Map<String, String> map = localMap.get(); 182 return map == null || map.size() == 0; 183 } 184 185 @Override 186 public int size() { 187 final Map<String, String> map = localMap.get(); 188 return map == null ? 0 : map.size(); 189 } 190 191 @Override 192 public String toString() { 193 final Map<String, String> map = localMap.get(); 194 return map == null ? "{}" : map.toString(); 195 } 196 197 @Override 198 public int hashCode() { 199 final int prime = 31; 200 int result = 1; 201 final Map<String, String> map = this.localMap.get(); 202 result = prime * result + ((map == null) ? 0 : map.hashCode()); 203 result = prime * result + Boolean.valueOf(this.useMap).hashCode(); 204 return result; 205 } 206 207 @Override 208 public boolean equals(final Object obj) { 209 if (this == obj) { 210 return true; 211 } 212 if (obj == null) { 213 return false; 214 } 215 if (obj instanceof DefaultThreadContextMap) { 216 final DefaultThreadContextMap other = (DefaultThreadContextMap) obj; 217 if (this.useMap != other.useMap) { 218 return false; 219 } 220 } 221 if (!(obj instanceof ThreadContextMap)) { 222 return false; 223 } 224 final ThreadContextMap other = (ThreadContextMap) obj; 225 final Map<String, String> map = this.localMap.get(); 226 final Map<String, String> otherMap = other.getImmutableMapOrNull(); 227 if (map == null) { 228 if (otherMap != null) { 229 return false; 230 } 231 } else if (!map.equals(otherMap)) { 232 return false; 233 } 234 return true; 235 } 236}