    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    }