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        /**
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            final Map<String, String> map = localMap.get();
165            return map == null ? "{}" : map.toString();
166        }
167    
168        @Override
169        public int hashCode() {
170            final int prime = 31;
171            int result = 1;
172            final Map<String, String> map = this.localMap.get();
173            result = prime * result + ((map == null) ? 0 : map.hashCode());
174            result = prime * result + (this.useMap ? 1231 : 1237);
175            return result;
176        }
177    
178        @Override
179        public boolean equals(final Object obj) {
180            if (this == obj) {
181                return true;
182            }
183            if (obj == null) {
184                return false;
185            }
186            if (obj instanceof DefaultThreadContextMap) {
187                final DefaultThreadContextMap other = (DefaultThreadContextMap) obj;
188                if (this.useMap != other.useMap) {
189                    return false;
190                }
191            }
192            if (!(obj instanceof ThreadContextMap)) {
193                return false;
194            }
195            final ThreadContextMap other = (ThreadContextMap) obj;
196            final Map<String, String> map = this.localMap.get();
197            final Map<String, String> otherMap = other.getImmutableMapOrNull(); 
198            if (map == null) {
199                if (otherMap != null) {
200                    return false;
201                }
202            } else if (!map.equals(otherMap)) {
203                return false;
204            }
205            return true;
206        }
207    }