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