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 ThreadContextMapFactory() {
054    }
055
056    public static ThreadContextMap createThreadContextMap() {
057        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
058        final String threadContextMapName = managerProps.getStringProperty(THREAD_CONTEXT_KEY);
059        final ClassLoader cl = ProviderUtil.findClassLoader();
060        ThreadContextMap result = null;
061        if (threadContextMapName != null) {
062            try {
063                final Class<?> clazz = cl.loadClass(threadContextMapName);
064                if (ThreadContextMap.class.isAssignableFrom(clazz)) {
065                    result = (ThreadContextMap) clazz.newInstance();
066                }
067            } catch (final ClassNotFoundException cnfe) {
068                LOGGER.error("Unable to locate configured ThreadContextMap {}", threadContextMapName);
069            } catch (final Exception ex) {
070                LOGGER.error("Unable to create configured ThreadContextMap {}", threadContextMapName, ex);
071            }
072        }
073        if (result == null && ProviderUtil.hasProviders() && LogManager.getFactory() != null) { //LOG4J2-1658
074            final String factoryClassName = LogManager.getFactory().getClass().getName();
075            for (final Provider provider : ProviderUtil.getProviders()) {
076                if (factoryClassName.equals(provider.getClassName())) {
077                    final Class<? extends ThreadContextMap> clazz = provider.loadThreadContextMap();
078                    if (clazz != null) {
079                        try {
080                            result = clazz.newInstance();
081                            break;
082                        } catch (final Exception e) {
083                            LOGGER.error("Unable to locate or load configured ThreadContextMap {}",
084                                    provider.getThreadContextMap(), e);
085                            result = createDefaultThreadContextMap();
086                        }
087                    }
088                }
089            }
090        }
091        if (result == null) {
092            result = createDefaultThreadContextMap();
093        }
094        return result;
095    }
096
097    private static ThreadContextMap createDefaultThreadContextMap() {
098        if (Constants.ENABLE_THREADLOCALS) {
099            if (PropertiesUtil.getProperties().getBooleanProperty(GC_FREE_THREAD_CONTEXT_KEY)) {
100                return new GarbageFreeSortedArrayThreadContextMap();
101            }
102            return new CopyOnWriteSortedArrayThreadContextMap();
103        }
104        return new DefaultThreadContextMap(true);
105    }
106}