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}