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.ReflectionUtil;
034
035/**
036 * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers
037 * assigned to static variables to be released along with the classes that own then. Other ContextSelectors
038 * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled.
039 * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be
040 * associated with a Class in a 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> CONTEXT = new AtomicReference<LoggerContext>();
049
050    protected static final StatusLogger LOGGER = StatusLogger.getLogger();
051
052    protected static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
053        new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
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 = ReflectionUtil.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<LoggerContext>();
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 = new LoggerContext(name, null, configLocation);
146            final AtomicReference<WeakReference<LoggerContext>> r =
147                new AtomicReference<WeakReference<LoggerContext>>();
148            r.set(new WeakReference<LoggerContext>(ctx));
149            CONTEXT_MAP.putIfAbsent(name, r);
150            ctx = CONTEXT_MAP.get(name).get().get();
151            return ctx;
152        }
153        final WeakReference<LoggerContext> weakRef = ref.get();
154        LoggerContext ctx = weakRef.get();
155        if (ctx != null) {
156            if (ctx.getConfigLocation() == null && configLocation != null) {
157                LOGGER.debug("Setting configuration to {}", configLocation);
158                ctx.setConfigLocation(configLocation);
159            } else if (ctx.getConfigLocation() != null && configLocation != null &&
160                !ctx.getConfigLocation().equals(configLocation)) {
161                LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation,
162                    ctx.getConfigLocation());
163            }
164            return ctx;
165        }
166        ctx = new LoggerContext(name, null, configLocation);
167        ref.compareAndSet(weakRef, new WeakReference<LoggerContext>(ctx));
168        return ctx;
169    }
170
171    private String toContextMapKey(final ClassLoader loader) {
172        return String.valueOf(System.identityHashCode(loader));
173    }
174
175    protected LoggerContext getDefault() {
176        final LoggerContext ctx = CONTEXT.get();
177        if (ctx != null) {
178            return ctx;
179        }
180        CONTEXT.compareAndSet(null, new LoggerContext("Default"));
181        return CONTEXT.get();
182    }
183
184}