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;
022import java.util.Objects;
023
024import org.apache.logging.log4j.util.BiConsumer;
025import org.apache.logging.log4j.util.ReadOnlyStringMap;
026import org.apache.logging.log4j.util.PropertiesUtil;
027import org.apache.logging.log4j.util.TriConsumer;
028
029/**
030 * The actual ThreadContext Map. A new ThreadContext Map is created each time it is updated and the Map stored is always
031 * immutable. This means the Map can be passed to other threads without concern that it will be updated. Since it is
032 * expected that the Map will be passed to many more log events than the number of keys it contains the performance
033 * should be much better than if the Map was copied for each event.
034 */
035public class DefaultThreadContextMap implements ThreadContextMap, ReadOnlyStringMap {
036    private static final long serialVersionUID = 8218007901108944053L;
037
038    /**
039     * Property name ({@value} ) for selecting {@code InheritableThreadLocal} (value "true") or plain
040     * {@code ThreadLocal} (value is not "true") in the implementation.
041     */
042    public static final String INHERITABLE_MAP = "isThreadContextMapInheritable";
043
044    private final boolean useMap;
045    private final ThreadLocal<Map<String, String>> localMap;
046
047    private static boolean inheritableMap;
048    
049    static {
050        init();
051    }
052
053    // LOG4J2-479: by default, use a plain ThreadLocal, only use InheritableThreadLocal if configured.
054    // (This method is package protected for JUnit tests.)
055    static ThreadLocal<Map<String, String>> createThreadLocalMap(final boolean isMapEnabled) {
056        if (inheritableMap) {
057            return new InheritableThreadLocal<Map<String, String>>() {
058                @Override
059                protected Map<String, String> childValue(final Map<String, String> parentValue) {
060                    return parentValue != null && isMapEnabled //
061                    ? Collections.unmodifiableMap(new HashMap<>(parentValue)) //
062                            : null;
063                }
064            };
065        }
066        // if not inheritable, return plain ThreadLocal with null as initial value
067        return new ThreadLocal<>();
068    }
069
070    static void init() {
071        inheritableMap = PropertiesUtil.getProperties().getBooleanProperty(INHERITABLE_MAP);
072    }
073    
074    public DefaultThreadContextMap() {
075        this(true);
076    }
077
078    public DefaultThreadContextMap(final boolean useMap) {
079        this.useMap = useMap;
080        this.localMap = createThreadLocalMap(useMap);
081    }
082
083    @Override
084    public void put(final String key, final String value) {
085        if (!useMap) {
086            return;
087        }
088        Map<String, String> map = localMap.get();
089        map = map == null ? new HashMap<>(1) : new HashMap<>(map);
090        map.put(key, value);
091        localMap.set(Collections.unmodifiableMap(map));
092    }
093
094    public void putAll(final Map<String, String> m) {
095        if (!useMap) {
096            return;
097        }
098        Map<String, String> map = localMap.get();
099        map = map == null ? new HashMap<>(m.size()) : new HashMap<>(map);
100        for (final Map.Entry<String, String> e : m.entrySet()) {
101            map.put(e.getKey(), e.getValue());
102        }
103        localMap.set(Collections.unmodifiableMap(map));
104    }
105
106    @Override
107    public String get(final String key) {
108        final Map<String, String> map = localMap.get();
109        return map == null ? null : map.get(key);
110    }
111
112    @Override
113    public void remove(final String key) {
114        final Map<String, String> map = localMap.get();
115        if (map != null) {
116            final Map<String, String> copy = new HashMap<>(map);
117            copy.remove(key);
118            localMap.set(Collections.unmodifiableMap(copy));
119        }
120    }
121
122    public void removeAll(final Iterable<String> keys) {
123        final Map<String, String> map = localMap.get();
124        if (map != null) {
125            final Map<String, String> copy = new HashMap<>(map);
126            for (final String key : keys) {
127                copy.remove(key);
128            }
129            localMap.set(Collections.unmodifiableMap(copy));
130        }
131    }
132
133    @Override
134    public void clear() {
135        localMap.remove();
136    }
137
138    @Override
139    public Map<String, String> toMap() {
140        return getCopy();
141    }
142
143    @Override
144    public boolean containsKey(final String key) {
145        final Map<String, String> map = localMap.get();
146        return map != null && map.containsKey(key);
147    }
148
149    @Override
150    public <V> void forEach(final BiConsumer<String, ? super V> action) {
151        final Map<String, String> map = localMap.get();
152        if (map == null) {
153            return;
154        }
155        for (final Map.Entry<String, String> entry : map.entrySet()) {
156            //BiConsumer should be able to handle values of any type V. In our case the values are of type String.
157            @SuppressWarnings("unchecked")
158            final
159            V value = (V) entry.getValue();
160            action.accept(entry.getKey(), value);
161        }
162    }
163
164    @Override
165    public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) {
166        final Map<String, String> map = localMap.get();
167        if (map == null) {
168            return;
169        }
170        for (final Map.Entry<String, String> entry : map.entrySet()) {
171            //TriConsumer should be able to handle values of any type V. In our case the values are of type String.
172            @SuppressWarnings("unchecked")
173            final
174            V value = (V) entry.getValue();
175            action.accept(entry.getKey(), value, state);
176        }
177    }
178
179    @SuppressWarnings("unchecked")
180    @Override
181    public <V> V getValue(final String key) {
182        final Map<String, String> map = localMap.get();
183        return (V) (map == null ? null : map.get(key));
184    }
185
186    @Override
187    public Map<String, String> getCopy() {
188        final Map<String, String> map = localMap.get();
189        return map == null ? new HashMap<>() : new HashMap<>(map);
190    }
191
192    @Override
193    public Map<String, String> getImmutableMapOrNull() {
194        return localMap.get();
195    }
196
197    @Override
198    public boolean isEmpty() {
199        final Map<String, String> map = localMap.get();
200        return map == null || map.isEmpty();
201    }
202
203    @Override
204    public int size() {
205        final Map<String, String> map = localMap.get();
206        return map == null ? 0 : map.size();
207    }
208
209    @Override
210    public String toString() {
211        final Map<String, String> map = localMap.get();
212        return map == null ? "{}" : map.toString();
213    }
214
215    @Override
216    public int hashCode() {
217        final int prime = 31;
218        int result = 1;
219        final Map<String, String> map = this.localMap.get();
220        result = prime * result + ((map == null) ? 0 : map.hashCode());
221        result = prime * result + Boolean.valueOf(this.useMap).hashCode();
222        return result;
223    }
224
225    @Override
226    public boolean equals(final Object obj) {
227        if (this == obj) {
228            return true;
229        }
230        if (obj == null) {
231            return false;
232        }
233        if (obj instanceof DefaultThreadContextMap) {
234            final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
235            if (this.useMap != other.useMap) {
236                return false;
237            }
238        }
239        if (!(obj instanceof ThreadContextMap)) {
240            return false;
241        }
242        final ThreadContextMap other = (ThreadContextMap) obj;
243        final Map<String, String> map = this.localMap.get();
244        final Map<String, String> otherMap = other.getImmutableMapOrNull();
245        return Objects.equals(map, otherMap);
246    }
247}