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}