/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache license, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the license for the specific language governing permissions and * limitations under the license. */ package org.apache.logging.log4j.spi; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.logging.log4j.util.BiConsumer; import org.apache.logging.log4j.util.ReadOnlyStringMap; import org.apache.logging.log4j.util.PropertiesUtil; import org.apache.logging.log4j.util.TriConsumer; /** * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored is always * immutable. This means the Map can be passed to other threads without concern that it will be updated. Since it is * expected that the Map will be passed to many more log events than the number of keys it contains the performance * should be much better than if the Map was copied for each event. */ public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyStringMap { /** * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain * {@code ThreadLocal} (value is not "true") in the implementation. */ public static final String INHERITABLE_MAP = "isThreadContextMapInheritable"; private final boolean useMap; private final ThreadLocal<Map<String, String>> localMap; public DefaultThreadContextMap() { this(true); } public DefaultThreadContextMap(final boolean useMap) { this.useMap = useMap; this.localMap = createThreadLocalMap(useMap); } // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. // (This method is package protected for JUnit tests.) static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) { final PropertiesUtil managerProps = PropertiesUtil.getProperties(); final boolean inheritable = managerProps.getBooleanProperty(INHERITABLE_MAP); if (inheritable) { return new InheritableThreadLocal<Map<String, String>>() { @Override protected Map<String, String> childValue(final Map<String, String> parentValue) { return parentValue != null && isMapEnabled // ? Collections.unmodifiableMap(new HashMap<>(parentValue)) // : null; } }; } // if not inheritable, return plain ThreadLocal with null as initial value return new ThreadLocal<>(); } @Override public void put(final String key, final String value) { if (!useMap) { return; } Map<String, String> map = localMap.get(); map = map == null ? new HashMap<String, String>(1) : new HashMap<>(map); map.put(key, value); localMap.set(Collections.unmodifiableMap(map)); } public void putAll(final Map<String, String> m) { if (!useMap) { return; } Map<String, String> map = localMap.get(); map = map == null ? new HashMap<String, String>(m.size()) : new HashMap<>(map); for (final Map.Entry<String, String> e : m.entrySet()) { map.put(e.getKey(), e.getValue()); } localMap.set(Collections.unmodifiableMap(map)); } @Override public String get(final String key) { final Map<String, String> map = localMap.get(); return map == null ? null : map.get(key); } @Override public void remove(final String key) { final Map<String, String> map = localMap.get(); if (map != null) { final Map<String, String> copy = new HashMap<>(map); copy.remove(key); localMap.set(Collections.unmodifiableMap(copy)); } } public void removeAll(final Iterable<String> keys) { final Map<String, String> map = localMap.get(); if (map != null) { final Map<String, String> copy = new HashMap<>(map); for (final String key : keys) { copy.remove(key); } localMap.set(Collections.unmodifiableMap(copy)); } } @Override public void clear() { localMap.remove(); } @Override public Map<String, String> toMap() { return getCopy(); } @Override public boolean containsKey(final String key) { final Map<String, String> map = localMap.get(); return map != null && map.containsKey(key); } @Override public <V> void forEach(final BiConsumer<String, ? super V> action) { final Map<String, String> map = localMap.get(); if (map == null) { return; } for (final Map.Entry<String, String> entry : map.entrySet()) { action.accept(entry.getKey(), (V) entry.getValue()); } } @Override public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) { final Map<String, String> map = localMap.get(); if (map == null) { return; } for (final Map.Entry<String, String> entry : map.entrySet()) { action.accept(entry.getKey(), (V) entry.getValue(), state); } } @SuppressWarnings("unchecked") @Override public <V> V getValue(final String key) { final Map<String, String> map = localMap.get(); return (V) (map == null ? null : map.get(key)); } @Override public Map<String, String> getCopy() { final Map<String, String> map = localMap.get(); return map == null ? new HashMap<String, String>() : new HashMap<>(map); } @Override public Map<String, String> getImmutableMapOrNull() { return localMap.get(); } @Override public boolean isEmpty() { final Map<String, String> map = localMap.get(); return map == null || map.size() == 0; } @Override public int size() { final Map<String, String> map = localMap.get(); return map == null ? 0 : map.size(); } @Override public String toString() { final Map<String, String> map = localMap.get(); return map == null ? "{}" : map.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; final Map<String, String> map = this.localMap.get(); result = prime * result + ((map == null) ? 0 : map.hashCode()); result = prime * result + Boolean.valueOf(this.useMap).hashCode(); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (obj instanceof DefaultThreadContextMap) { final DefaultThreadContextMap other = (DefaultThreadContextMap) obj; if (this.useMap != other.useMap) { return false; } } if (!(obj instanceof ThreadContextMap)) { return false; } final ThreadContextMap other = (ThreadContextMap) obj; final Map<String, String> map = this.localMap.get(); final Map<String, String> otherMap = other.getImmutableMapOrNull(); if (map == null) { if (otherMap != null) { return false; } } else if (!map.equals(otherMap)) { return false; } return true; } }