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 org.apache.logging.log4j.LogManager;
020import org.apache.logging.log4j.Logger;
021import org.apache.logging.log4j.ThreadContext;
022import org.apache.logging.log4j.status.StatusLogger;
023import org.apache.logging.log4j.util.Constants;
024import org.apache.logging.log4j.util.PropertiesUtil;
025import org.apache.logging.log4j.util.ProviderUtil;
026
027/**
028 * Creates the ThreadContextMap instance used by the ThreadContext.
029 * <p>
030 * If {@link Constants#ENABLE_THREADLOCALS Log4j can use ThreadLocals}, a garbage-free StringMap-based context map can
031 * be installed by setting system property {@code log4j2.garbagefree.threadContextMap} to {@code true}.
032 * </p><p>
033 * Furthermore, any custom {@code ThreadContextMap} can be installed by setting system property
034 * {@code log4j2.threadContextMap} to the fully qualified class name of the class implementing the
035 * {@code ThreadContextMap} interface. (Also implement the {@code ReadOnlyThreadContextMap} interface if your custom
036 * {@code ThreadContextMap} implementation should be accessible to applications via the
037 * {@link ThreadContext#getThreadContextMap()} method.)
038 * </p><p>
039 * Instead of system properties, the above can also be specified in a properties file named
040 * {@code log4j2.component.properties} in the classpath.
041 * </p>
042 *
043 * @see ThreadContextMap
044 * @see ReadOnlyThreadContextMap
045 * @see org.apache.logging.log4j.ThreadContext
046 * @since 2.7
047 */
048public final class ThreadContextMapFactory {
049    private static final Logger LOGGER = StatusLogger.getLogger();
050    private static final String THREAD_CONTEXT_KEY = "log4j2.threadContextMap";
051    private static final String GC_FREE_THREAD_CONTEXT_KEY = "log4j2.garbagefree.threadContextMap";
052    
053    private static boolean GcFreeThreadContextKey;
054    private static String ThreadContextMapName;
055
056    static {
057        initPrivate();
058    }
059    
060    /**
061     * Initializes static variables based on system properties. Normally called when this class is initialized by the VM
062     * and when Log4j is reconfigured.
063     */
064    public static void init() {
065        CopyOnWriteSortedArrayThreadContextMap.init();
066        GarbageFreeSortedArrayThreadContextMap.init();
067        DefaultThreadContextMap.init();
068        initPrivate();
069    }
070
071    /**
072     * Initializes static variables based on system properties. Normally called when this class is initialized by the VM
073     * and when Log4j is reconfigured.
074     */
075    private static void initPrivate() {
076        final PropertiesUtil properties = PropertiesUtil.getProperties();
077        ThreadContextMapName = properties.getStringProperty(THREAD_CONTEXT_KEY);
078        GcFreeThreadContextKey = properties.getBooleanProperty(GC_FREE_THREAD_CONTEXT_KEY);
079    }
080    
081    private ThreadContextMapFactory() {
082    }
083
084    public static ThreadContextMap createThreadContextMap() {
085        final ClassLoader cl = ProviderUtil.findClassLoader();
086        ThreadContextMap result = null;
087        if (ThreadContextMapName != null) {
088            try {
089                final Class<?> clazz = cl.loadClass(ThreadContextMapName);
090                if (ThreadContextMap.class.isAssignableFrom(clazz)) {
091                    result = (ThreadContextMap) clazz.newInstance();
092                }
093            } catch (final ClassNotFoundException cnfe) {
094                LOGGER.error("Unable to locate configured ThreadContextMap {}", ThreadContextMapName);
095            } catch (final Exception ex) {
096                LOGGER.error("Unable to create configured ThreadContextMap {}", ThreadContextMapName, ex);
097            }
098        }
099        if (result == null && ProviderUtil.hasProviders() && LogManager.getFactory() != null) { //LOG4J2-1658
100            final String factoryClassName = LogManager.getFactory().getClass().getName();
101            for (final Provider provider : ProviderUtil.getProviders()) {
102                if (factoryClassName.equals(provider.getClassName())) {
103                    final Class<? extends ThreadContextMap> clazz = provider.loadThreadContextMap();
104                    if (clazz != null) {
105                        try {
106                            result = clazz.newInstance();
107                            break;
108                        } catch (final Exception e) {
109                            LOGGER.error("Unable to locate or load configured ThreadContextMap {}",
110                                    provider.getThreadContextMap(), e);
111                            result = createDefaultThreadContextMap();
112                        }
113                    }
114                }
115            }
116        }
117        if (result == null) {
118            result = createDefaultThreadContextMap();
119        }
120        return result;
121    }
122
123    private static ThreadContextMap createDefaultThreadContextMap() {
124        if (Constants.ENABLE_THREADLOCALS) {
125            if (GcFreeThreadContextKey) {
126                return new GarbageFreeSortedArrayThreadContextMap();
127            }
128            return new CopyOnWriteSortedArrayThreadContextMap();
129        }
130        return new DefaultThreadContextMap(true);
131    }
132}