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    }