| /* |
| * 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 java.util.Objects; |
| |
| import org.apache.logging.log4j.util.ReadOnlyStringMap; |
| import org.apache.logging.log4j.util.StringMap; |
| import org.apache.logging.log4j.util.PropertiesUtil; |
| import org.apache.logging.log4j.util.SortedArrayStringMap; |
| |
| /** |
| * {@code SortedArrayStringMap}-based implementation of the {@code ThreadContextMap} interface that attempts not to |
| * create temporary objects. Adding and removing key-value pairs will not create temporary objects. |
| * <p> |
| * This implementation does <em>not</em> make a copy of its contents on every operation, so this data structure cannot |
| * be passed to log events. Instead, client code needs to copy the contents when interacting with another thread. |
| * </p> |
| * @since 2.7 |
| */ |
| class GarbageFreeSortedArrayThreadContextMap implements ReadOnlyThreadContextMap, ObjectThreadContextMap { |
| |
| /** |
| * 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"; |
| |
| /** |
| * The default initial capacity. |
| */ |
| protected static final int DEFAULT_INITIAL_CAPACITY = 16; |
| |
| /** |
| * System property name that can be used to control the data structure's initial capacity. |
| */ |
| protected static final String PROPERTY_NAME_INITIAL_CAPACITY = "log4j2.ThreadContext.initial.capacity"; |
| |
| protected final ThreadLocal<StringMap> localMap; |
| |
| private static volatile int initialCapacity; |
| private static volatile boolean inheritableMap; |
| |
| /** |
| * Initializes static variables based on system properties. Normally called when this class is initialized by the VM |
| * and when Log4j is reconfigured. |
| */ |
| static void init() { |
| final PropertiesUtil properties = PropertiesUtil.getProperties(); |
| initialCapacity = properties.getIntegerProperty(PROPERTY_NAME_INITIAL_CAPACITY, DEFAULT_INITIAL_CAPACITY); |
| inheritableMap = properties.getBooleanProperty(INHERITABLE_MAP); |
| } |
| |
| static { |
| init(); |
| } |
| |
| public GarbageFreeSortedArrayThreadContextMap() { |
| this.localMap = createThreadLocalMap(); |
| } |
| |
| // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured. |
| // (This method is package protected for JUnit tests.) |
| private ThreadLocal<StringMap> createThreadLocalMap() { |
| if (inheritableMap) { |
| return new InheritableThreadLocal<StringMap>() { |
| @Override |
| protected StringMap childValue(final StringMap parentValue) { |
| return parentValue != null ? createStringMap(parentValue) : null; |
| } |
| }; |
| } |
| // if not inheritable, return plain ThreadLocal with null as initial value |
| return new ThreadLocal<>(); |
| } |
| |
| /** |
| * Returns an implementation of the {@code StringMap} used to back this thread context map. |
| * <p> |
| * Subclasses may override. |
| * </p> |
| * @return an implementation of the {@code StringMap} used to back this thread context map |
| */ |
| protected StringMap createStringMap() { |
| return new SortedArrayStringMap(initialCapacity); |
| } |
| |
| /** |
| * Returns an implementation of the {@code StringMap} used to back this thread context map, pre-populated |
| * with the contents of the specified context data. |
| * <p> |
| * Subclasses may override. |
| * </p> |
| * @param original the key-value pairs to initialize the returned context data with |
| * @return an implementation of the {@code StringMap} used to back this thread context map |
| */ |
| protected StringMap createStringMap(final ReadOnlyStringMap original) { |
| return new SortedArrayStringMap(original); |
| } |
| |
| private StringMap getThreadLocalMap() { |
| StringMap map = localMap.get(); |
| if (map == null) { |
| map = createStringMap(); |
| localMap.set(map); |
| } |
| return map; |
| } |
| |
| @Override |
| public void put(final String key, final String value) { |
| getThreadLocalMap().putValue(key, value); |
| } |
| |
| @Override |
| public void putValue(final String key, final Object value) { |
| getThreadLocalMap().putValue(key, value); |
| } |
| |
| @Override |
| public void putAll(final Map<String, String> values) { |
| if (values == null || values.isEmpty()) { |
| return; |
| } |
| final StringMap map = getThreadLocalMap(); |
| for (final Map.Entry<String, String> entry : values.entrySet()) { |
| map.putValue(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @Override |
| public <V> void putAllValues(final Map<String, V> values) { |
| if (values == null || values.isEmpty()) { |
| return; |
| } |
| final StringMap map = getThreadLocalMap(); |
| for (final Map.Entry<String, V> entry : values.entrySet()) { |
| map.putValue(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| @Override |
| public String get(final String key) { |
| return (String) getValue(key); |
| } |
| |
| @Override |
| public <V> V getValue(final String key) { |
| final StringMap map = localMap.get(); |
| return map == null ? null : map.<V>getValue(key); |
| } |
| |
| @Override |
| public void remove(final String key) { |
| final StringMap map = localMap.get(); |
| if (map != null) { |
| map.remove(key); |
| } |
| } |
| |
| @Override |
| public void removeAll(final Iterable<String> keys) { |
| final StringMap map = localMap.get(); |
| if (map != null) { |
| for (final String key : keys) { |
| map.remove(key); |
| } |
| } |
| } |
| |
| @Override |
| public void clear() { |
| final StringMap map = localMap.get(); |
| if (map != null) { |
| map.clear(); |
| } |
| } |
| |
| @Override |
| public boolean containsKey(final String key) { |
| final StringMap map = localMap.get(); |
| return map != null && map.containsKey(key); |
| } |
| |
| @Override |
| public Map<String, String> getCopy() { |
| final StringMap map = localMap.get(); |
| return map == null ? new HashMap<String, String>() : map.toMap(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public StringMap getReadOnlyContextData() { |
| StringMap map = localMap.get(); |
| if (map == null) { |
| map = createStringMap(); |
| localMap.set(map); |
| } |
| return map; |
| } |
| |
| @Override |
| public Map<String, String> getImmutableMapOrNull() { |
| final StringMap map = localMap.get(); |
| return map == null ? null : Collections.unmodifiableMap(map.toMap()); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| final StringMap map = localMap.get(); |
| return map == null || map.size() == 0; |
| } |
| |
| @Override |
| public String toString() { |
| final StringMap map = localMap.get(); |
| return map == null ? "{}" : map.toString(); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| final StringMap map = this.localMap.get(); |
| result = prime * result + ((map == null) ? 0 : map.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(final Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (!(obj instanceof ThreadContextMap)) { |
| return false; |
| } |
| final ThreadContextMap other = (ThreadContextMap) obj; |
| final Map<String, String> map = this.getImmutableMapOrNull(); |
| final Map<String, String> otherMap = other.getImmutableMapOrNull(); |
| return Objects.equals(map, otherMap); |
| } |
| } |