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.core.selector;
018
019import java.lang.ref.WeakReference;
020import java.net.URI;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028import java.util.concurrent.atomic.AtomicReference;
029
030import org.apache.logging.log4j.core.LoggerContext;
031import org.apache.logging.log4j.core.impl.ContextAnchor;
032import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
033import org.apache.logging.log4j.status.StatusLogger;
034import org.apache.logging.log4j.util.StackLocatorUtil;
035
036/**
037 * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers assigned
038 * to static variables to be released along with the classes that own then. Other ContextSelectors will generally cause
039 * Loggers associated with classes loaded from different ClassLoaders to be co-mingled. This is a problem if, for
040 * example, a web application is undeployed as some of the Loggers being released may be associated with a Class in a
041 * parent ClassLoader, which will generally have negative consequences.
042 *
043 * The main downside to this ContextSelector is that Configuration is more challenging.
044 *
045 * This ContextSelector should not be used with a Servlet Filter such as the Log4jServletFilter.
046 */
047public class ClassLoaderContextSelector implements ContextSelector, LoggerContextShutdownAware {
048
049    private static final AtomicReference<LoggerContext> DEFAULT_CONTEXT = new AtomicReference<>();
050
051    protected static final StatusLogger LOGGER = StatusLogger.getLogger();
052
053    protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
054            new ConcurrentHashMap<>();
055
056    @Override
057    public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext loggerContext) {
058        if (loggerContext instanceof LoggerContext) {
059            removeContext((LoggerContext) loggerContext);
060        }
061    }
062
063    @Override
064    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
065        return getContext(fqcn, loader, currentContext, null);
066    }
067
068    @Override
069    public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
070            final URI configLocation) {
071        if (currentContext) {
072            final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
073            if (ctx != null) {
074                return ctx;
075            }
076            return getDefault();
077        } else if (loader != null) {
078            return locateContext(loader, configLocation);
079        } else {
080            final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
081            if (clazz != null) {
082                return locateContext(clazz.getClassLoader(), configLocation);
083            }
084            final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
085            if (lc != null) {
086                return lc;
087            }
088            return getDefault();
089        }
090    }
091
092    @Override
093    public void removeContext(final LoggerContext context) {
094        for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
095            final LoggerContext ctx = entry.getValue().get().get();
096            if (ctx == context) {
097                CONTEXT_MAP.remove(entry.getKey());
098            }
099        }
100    }
101
102    @Override
103    public List<LoggerContext> getLoggerContexts() {
104        final List<LoggerContext> list = new ArrayList<>();
105        final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
106        for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
107            final LoggerContext ctx = ref.get().get();
108            if (ctx != null) {
109                list.add(ctx);
110            }
111        }
112        return Collections.unmodifiableList(list);
113    }
114
115    private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) {
116        // LOG4J2-477: class loader may be null
117        final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
118        final String name = toContextMapKey(loader);
119        AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
120        if (ref == null) {
121            if (configLocation == null) {
122                ClassLoader parent = loader.getParent();
123                while (parent != null) {
124
125                    ref = CONTEXT_MAP.get(toContextMapKey(parent));
126                    if (ref != null) {
127                        final WeakReference<LoggerContext> r = ref.get();
128                        final LoggerContext ctx = r.get();
129                        if (ctx != null) {
130                            return ctx;
131                        }
132                    }
133                    parent = parent.getParent();
134                    /*  In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be
135                    configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader.
136                    In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the
137                    ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader
138                    that is configured by the WebAppContextListener.
139
140                    ClassLoader threadLoader = null;
141                    try {
142                        threadLoader = Thread.currentThread().getContextClassLoader();
143                    } catch (Exception ex) {
144                        // Ignore SecurityException
145                    }
146                    if (threadLoader != null && threadLoader == parent) {
147                        break;
148                    } else {
149                        parent = parent.getParent();
150                    } */
151                }
152            }
153            LoggerContext ctx = createContext(name, configLocation);
154            final AtomicReference<WeakReference<LoggerContext>> r = new AtomicReference<>();
155            r.set(new WeakReference<>(ctx));
156            CONTEXT_MAP.putIfAbsent(name, r);
157            ctx = CONTEXT_MAP.get(name).get().get();
158            return ctx;
159        }
160        final WeakReference<LoggerContext> weakRef = ref.get();
161        LoggerContext ctx = weakRef.get();
162        if (ctx != null) {
163            if (ctx.getConfigLocation() == null && configLocation != null) {
164                LOGGER.debug("Setting configuration to {}", configLocation);
165                ctx.setConfigLocation(configLocation);
166            } else if (ctx.getConfigLocation() != null && configLocation != null
167                    && !ctx.getConfigLocation().equals(configLocation)) {
168                LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation,
169                        ctx.getConfigLocation());
170            }
171            return ctx;
172        }
173        ctx = createContext(name, configLocation);
174        ref.compareAndSet(weakRef, new WeakReference<>(ctx));
175        return ctx;
176    }
177
178    protected LoggerContext createContext(final String name, final URI configLocation) {
179        return new LoggerContext(name, null, configLocation);
180    }
181
182    protected String toContextMapKey(final ClassLoader loader) {
183        return Integer.toHexString(System.identityHashCode(loader));
184    }
185
186    protected LoggerContext getDefault() {
187        final LoggerContext ctx = DEFAULT_CONTEXT.get();
188        if (ctx != null) {
189            return ctx;
190        }
191        DEFAULT_CONTEXT.compareAndSet(null, createContext(defaultContextName(), null));
192        return DEFAULT_CONTEXT.get();
193    }
194
195    protected String defaultContextName() {
196        return "Default";
197    }
198}