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.PropertiesUtil;
024
025/**
026 * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored
027 * is always immutable. This means the Map can be passed to other threads without concern that it will be updated.
028 * Since it is expected that the Map will be passed to many more log events than the number of keys it contains
029 * the performance should be much better than if the Map was copied for each event.
030 */
031public class DefaultThreadContextMap implements ThreadContextMap {
032    /** 
033     * Property name ({@value}) for selecting {@code InheritableThreadLocal} (value "true")
034     * or plain {@code ThreadLocal} (value is not "true") in the implementation.
035     */
036    public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
037
038    private final boolean useMap;
039    private final ThreadLocal<Map<String, String>> localMap;
040
041    public DefaultThreadContextMap(final boolean useMap) {
042        this.useMap = useMap;
043        this.localMap = createThreadLocalMap(useMap);
044    }
045    
046    // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
047    // (This method is package protected for JUnit tests.)
048    static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
049        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
050        final boolean inheritable = managerProps.getBooleanProperty(INHERITABLE_MAP);
051        if (inheritable) {
052            return new InheritableThreadLocal<Map<String, String>>() {
053                @Override
054                protected Map<String, String> childValue(final Map<String, String> parentValue) {
055                    return parentValue != null && isMapEnabled //
056                            ? Collections.unmodifiableMap(new HashMap<String, String>(parentValue)) //
057                            : null;
058                }
059            };
060        }
061        // if not inheritable, return plain ThreadLocal with null as initial value
062        return new ThreadLocal<Map<String, String>>();
063    }
064
065    /**
066     * Put a context value (the <code>o</code> parameter) as identified
067     * with the <code>key</code> parameter into the current thread's
068     * context map.
069     * <p/>
070     * <p>If the current thread does not have a context map it is
071     * created as a side effect.
072     * @param key The key name.
073     * @param value The key value.
074     */
075    @Override
076    public void put(final String key, final String value) {
077        if (!useMap) {
078            return;
079        }
080        Map<String, String> map = localMap.get();
081        map = map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
082        map.put(key, value);
083        localMap.set(Collections.unmodifiableMap(map));
084    }
085
086    /**
087     * Get the context identified by the <code>key</code> parameter.
088     * <p/>
089     * <p>This method has no side effects.
090     * @param key The key to locate.
091     * @return The value associated with the key or null.
092     */
093    @Override
094    public String get(final String key) {
095        final Map<String, String> map = localMap.get();
096        return map == null ? null : map.get(key);
097    }
098
099    /**
100     * Remove the the context identified by the <code>key</code>
101     * parameter.
102     * @param key The key to remove.
103     */
104    @Override
105    public void remove(final String key) {
106        final Map<String, String> map = localMap.get();
107        if (map != null) {
108            final Map<String, String> copy = new HashMap<String, String>(map);
109            copy.remove(key);
110            localMap.set(Collections.unmodifiableMap(copy));
111        }
112    }
113
114    /**
115     * Clear the context.
116     */
117    @Override
118    public void clear() {
119        localMap.remove();
120    }
121
122    /**
123     * Determine if the key is in the context.
124     * @param key The key to locate.
125     * @return True if the key is in the context, false otherwise.
126     */
127    @Override
128    public boolean containsKey(final String key) {
129        final Map<String, String> map = localMap.get();
130        return map != null && map.containsKey(key);
131    }
132
133    /**
134     * Returns a non-{@code null} mutable copy of the ThreadContext Map.
135     * @return a non-{@code null} mutable copy of the context.
136     */
137    @Override
138    public Map<String, String> getCopy() {
139        final Map<String, String> map = localMap.get();
140        return map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
141    }
142
143    /**
144     * Returns either {@code null} or an immutable view of the context Map.
145     * @return the Context Map.
146     */
147    @Override
148    public Map<String, String> getImmutableMapOrNull() {
149        return localMap.get();
150    }
151
152    /**
153     * Returns true if the Map is empty.
154     * @return true if the Map is empty, false otherwise.
155     */
156    @Override
157    public boolean isEmpty() {
158        final Map<String, String> map = localMap.get();
159        return map == null || map.size() == 0;
160    }
161
162    @Override
163    public String toString() {
164        Map<String, String> map = localMap.get();
165        return map == null ? "{}" : map.toString();
166    }
167}