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    @Override
066    public void put(final String key, final String value) {
067        if (!useMap) {
068            return;
069        }
070        Map<String, String> map = localMap.get();
071        map = map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
072        map.put(key, value);
073        localMap.set(Collections.unmodifiableMap(map));
074    }
075
076    @Override
077    public String get(final String key) {
078        final Map<String, String> map = localMap.get();
079        return map == null ? null : map.get(key);
080    }
081
082    @Override
083    public void remove(final String key) {
084        final Map<String, String> map = localMap.get();
085        if (map != null) {
086            final Map<String, String> copy = new HashMap<String, String>(map);
087            copy.remove(key);
088            localMap.set(Collections.unmodifiableMap(copy));
089        }
090    }
091
092    @Override
093    public void clear() {
094        localMap.remove();
095    }
096
097    @Override
098    public boolean containsKey(final String key) {
099        final Map<String, String> map = localMap.get();
100        return map != null && map.containsKey(key);
101    }
102
103    @Override
104    public Map<String, String> getCopy() {
105        final Map<String, String> map = localMap.get();
106        return map == null ? new HashMap<String, String>() : new HashMap<String, String>(map);
107    }
108
109    @Override
110    public Map<String, String> getImmutableMapOrNull() {
111        return localMap.get();
112    }
113
114    @Override
115    public boolean isEmpty() {
116        final Map<String, String> map = localMap.get();
117        return map == null || map.size() == 0;
118    }
119
120    @Override
121    public String toString() {
122        final Map<String, String> map = localMap.get();
123        return map == null ? "{}" : map.toString();
124    }
125
126    @Override
127    public int hashCode() {
128        final int prime = 31;
129        int result = 1;
130        final Map<String, String> map = this.localMap.get();
131        result = prime * result + ((map == null) ? 0 : map.hashCode());
132        result = prime * result + (this.useMap ? 1231 : 1237);
133        return result;
134    }
135
136    @Override
137    public boolean equals(final Object obj) {
138        if (this == obj) {
139            return true;
140        }
141        if (obj == null) {
142            return false;
143        }
144        if (obj instanceof DefaultThreadContextMap) {
145            final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
146            if (this.useMap != other.useMap) {
147                return false;
148            }
149        }
150        if (!(obj instanceof ThreadContextMap)) {
151            return false;
152        }
153        final ThreadContextMap other = (ThreadContextMap) obj;
154        final Map<String, String> map = this.localMap.get();
155        final Map<String, String> otherMap = other.getImmutableMapOrNull(); 
156        if (map == null) {
157            if (otherMap != null) {
158                return false;
159            }
160        } else if (!map.equals(otherMap)) {
161            return false;
162        }
163        return true;
164    }
165}