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.core.impl.ReflectiveCallerClassUtility;
033    import org.apache.logging.log4j.core.util.Loader;
034    import org.apache.logging.log4j.status.StatusLogger;
035    
036    /**
037     * This ContextSelector chooses a LoggerContext based upon the ClassLoader of the caller. This allows Loggers
038     * assigned to static variables to be released along with the classes that own then. Other ContextSelectors
039     * will generally cause Loggers associated with classes loaded from different ClassLoaders to be co-mingled.
040     * This is a problem if, for example, a web application is undeployed as some of the Loggers being released may be
041     * associated with a Class in a 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     */
047    public class ClassLoaderContextSelector implements ContextSelector {
048    
049        private static final AtomicReference<LoggerContext> CONTEXT = new AtomicReference<LoggerContext>();
050    
051        private static final PrivateSecurityManager SECURITY_MANAGER;
052    
053        private static final StatusLogger LOGGER = StatusLogger.getLogger();
054    
055        private static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> CONTEXT_MAP =
056            new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
057    
058        static {
059            if (ReflectiveCallerClassUtility.isSupported()) {
060                SECURITY_MANAGER = null;
061            } else {
062                PrivateSecurityManager securityManager;
063                try {
064                    securityManager = new PrivateSecurityManager();
065                    if (securityManager.getCaller(ClassLoaderContextSelector.class.getName()) == null) {
066                        // This shouldn't happen.
067                        securityManager = null;
068                        LOGGER.error("Unable to obtain call stack from security manager.");
069                    }
070                } catch (final Exception e) {
071                    securityManager = null;
072                    LOGGER.debug("Unable to install security manager", e);
073                }
074                SECURITY_MANAGER = securityManager;
075            }
076        }
077    
078        @Override
079        public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
080            return getContext(fqcn, loader, currentContext, null);
081        }
082    
083        @Override
084        public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
085                                        final URI configLocation) {
086            if (currentContext) {
087                final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
088                if (ctx != null) {
089                    return ctx;
090                }
091                return getDefault();
092            } else if (loader != null) {
093                return locateContext(loader, configLocation);
094            } else {
095                if (ReflectiveCallerClassUtility.isSupported()) {
096                    try {
097                        Class<?> clazz = Class.class;
098                        boolean next = false;
099                        for (int index = 2; clazz != null; ++index) {
100                            clazz = ReflectiveCallerClassUtility.getCaller(index);
101                            if (clazz == null) {
102                                break;
103                            }
104                            if (clazz.getName().equals(fqcn)) {
105                                next = true;
106                                continue;
107                            }
108                            if (next) {
109                                break;
110                            }
111                        }
112                        if (clazz != null) {
113                            return locateContext(clazz.getClassLoader(), configLocation);
114                        }
115                    } catch (final Exception ex) {
116                        // logger.debug("Unable to determine caller class via Sun Reflection", ex);
117                    }
118                }
119    
120                if (SECURITY_MANAGER != null) {
121                    final Class<?> clazz = SECURITY_MANAGER.getCaller(fqcn);
122                    if (clazz != null) {
123                        final ClassLoader ldr = clazz.getClassLoader() != null ? clazz.getClassLoader() :
124                            ClassLoader.getSystemClassLoader();
125                        return locateContext(ldr, configLocation);
126                    }
127                }
128    
129                final Throwable t = new Throwable();
130                boolean next = false;
131                String name = null;
132                for (final StackTraceElement element : t.getStackTrace()) {
133                    if (element.getClassName().equals(fqcn)) {
134                        next = true;
135                        continue;
136                    }
137                    if (next) {
138                        name = element.getClassName();
139                        break;
140                    }
141                }
142                if (name != null) {
143                    try {
144                        return locateContext(Loader.loadClass(name).getClassLoader(), configLocation);
145                    } catch (final ClassNotFoundException ignore) {
146                        //this is ok
147                    }
148                }
149                final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
150                if (lc != null) {
151                    return lc;
152                }
153                return getDefault();
154            }
155        }
156    
157        @Override
158        public void removeContext(final LoggerContext context) {
159            for (final Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : CONTEXT_MAP.entrySet()) {
160                final LoggerContext ctx = entry.getValue().get().get();
161                if (ctx == context) {
162                    CONTEXT_MAP.remove(entry.getKey());
163                }
164            }
165        }
166    
167        @Override
168        public List<LoggerContext> getLoggerContexts() {
169            final List<LoggerContext> list = new ArrayList<LoggerContext>();
170            final Collection<AtomicReference<WeakReference<LoggerContext>>> coll = CONTEXT_MAP.values();
171            for (final AtomicReference<WeakReference<LoggerContext>> ref : coll) {
172                final LoggerContext ctx = ref.get().get();
173                if (ctx != null) {
174                    list.add(ctx);
175                }
176            }
177            return Collections.unmodifiableList(list);
178        }
179    
180        private LoggerContext locateContext(final ClassLoader loaderOrNull, final URI configLocation) {
181            // LOG4J2-477: class loader may be null
182            final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
183            final String name = loader.toString();
184            AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
185            if (ref == null) {
186                if (configLocation == null) {
187                    ClassLoader parent = loader.getParent();
188                    while (parent != null) {
189    
190                        ref = CONTEXT_MAP.get(parent.toString());
191                        if (ref != null) {
192                            final WeakReference<LoggerContext> r = ref.get();
193                            final LoggerContext ctx = r.get();
194                            if (ctx != null) {
195                                return ctx;
196                            }
197                        }
198                        parent = parent.getParent();
199                        /*  In Tomcat 6 the parent of the JSP classloader is the webapp classloader which would be
200                        configured by the WebAppContextListener. The WebAppClassLoader is also the ThreadContextClassLoader.
201                        In JBoss 5 the parent of the JSP ClassLoader is the WebAppClassLoader which is also the
202                        ThreadContextClassLoader. However, the parent of the WebAppClassLoader is the ClassLoader
203                        that is configured by the WebAppContextListener.
204    
205                        ClassLoader threadLoader = null;
206                        try {
207                            threadLoader = Thread.currentThread().getContextClassLoader();
208                        } catch (Exception ex) {
209                            // Ignore SecurityException
210                        }
211                        if (threadLoader != null && threadLoader == parent) {
212                            break;
213                        } else {
214                            parent = parent.getParent();
215                        } */
216                    }
217                }
218                LoggerContext ctx = new LoggerContext(name, null, configLocation);
219                final AtomicReference<WeakReference<LoggerContext>> r =
220                    new AtomicReference<WeakReference<LoggerContext>>();
221                r.set(new WeakReference<LoggerContext>(ctx));
222                CONTEXT_MAP.putIfAbsent(name, r);
223                ctx = CONTEXT_MAP.get(name).get().get();
224                return ctx;
225            }
226            final WeakReference<LoggerContext> r = ref.get();
227            LoggerContext ctx = r.get();
228            if (ctx != null) {
229                if (ctx.getConfigLocation() == null && configLocation != null) {
230                    LOGGER.debug("Setting configuration to {}", configLocation);
231                    ctx.setConfigLocation(configLocation);
232                } else if (ctx.getConfigLocation() != null && configLocation != null &&
233                    !ctx.getConfigLocation().equals(configLocation)) {
234                    LOGGER.warn("locateContext called with URI {}. Existing LoggerContext has URI {}", configLocation,
235                        ctx.getConfigLocation());
236                }
237                return ctx;
238            }
239            ctx = new LoggerContext(name, null, configLocation);
240            ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx));
241            return ctx;
242        }
243    
244        private LoggerContext getDefault() {
245            final LoggerContext ctx = CONTEXT.get();
246            if (ctx != null) {
247                return ctx;
248            }
249            CONTEXT.compareAndSet(null, new LoggerContext("Default"));
250            return CONTEXT.get();
251        }
252    
253        /**
254         * SecurityManager that will locate the caller of the Log4j 2 API.
255         */
256        private static class PrivateSecurityManager extends SecurityManager {
257    
258            public Class<?> getCaller(final String fqcn) {
259                final Class<?>[] classes = getClassContext();
260                boolean next = false;
261                for (final Class<?> clazz : classes) {
262                    if (clazz.getName().equals(fqcn)) {
263                        next = true;
264                        continue;
265                    }
266                    if (next) {
267                        return clazz;
268                    }
269                }
270                return null;
271            }
272        }
273    
274    }