    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     */
    017    package org.apache.logging.log4j.core.selector;
    018
    019    import java.lang.ref.WeakReference;
    020    import java.net.URI;
    021    import java.util.ArrayList;
    022    import java.util.Collection;
    023    import java.util.Collections;
    024    import java.util.List;
    025    import java.util.Map;
    026    import java.util.concurrent.ConcurrentHashMap;
    027    import java.util.concurrent.ConcurrentMap;
    028    import java.util.concurrent.atomic.AtomicReference;
    029
    030    import org.apache.logging.log4j.core.LoggerContext;
    031    import org.apache.logging.log4j.core.impl.ContextAnchor;
    032    import org.apache.logging.log4j.status.StatusLogger;
    033    import 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     */
    046    public 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    }