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