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