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     */
017    package org.apache.logging.log4j.spi;
018    
019    import java.util.Collections;
020    import java.util.HashMap;
021    import java.util.Map;
022    
023    import 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     */
031    public 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    }