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.util.ArrayList;
028    import java.util.Collection;
029    import java.util.Collections;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.concurrent.ConcurrentHashMap;
033    import java.util.concurrent.ConcurrentMap;
034    import java.util.concurrent.atomic.AtomicReference;
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 JNDIContextFilter.
046     */
047    public class ClassLoaderContextSelector implements ContextSelector {
048    
049        private static final AtomicReference<LoggerContext> context = new AtomicReference<LoggerContext>();
050    
051        private static PrivateSecurityManager securityManager;
052    
053        private static Method getCallerClass;
054    
055        private static final StatusLogger logger = StatusLogger.getLogger();
056    
057        private static final ConcurrentMap<String, AtomicReference<WeakReference<LoggerContext>>> contextMap =
058            new ConcurrentHashMap<String, AtomicReference<WeakReference<LoggerContext>>>();
059    
060        static {
061            setupCallerCheck();
062        }
063    
064        public LoggerContext getContext(String fqcn, ClassLoader loader, boolean currentContext) {
065            if (currentContext) {
066                LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
067                if (ctx != null) {
068                    return ctx;
069                }
070                return getDefault();
071            } else if (loader != null) {
072                return locateContext(loader, null);
073            } else {
074                if (getCallerClass != null) {
075                    try {
076                        Class clazz = Class.class;
077                        boolean next = false;
078                        for (int index = 2; clazz != null; ++index) {
079                            Object[] params = new Object[] {index};
080                            clazz = (Class) getCallerClass.invoke(null, params);
081                            if (clazz == null) {
082                                break;
083                            }
084                            if (clazz.getName().equals(fqcn)) {
085                                next = true;
086                                continue;
087                            }
088                            if (next) {
089                                break;
090                            }
091                        }
092                        if (clazz != null) {
093                            return locateContext(clazz.getClassLoader(), null);
094                        }
095                    } catch (Exception ex) {
096                        // logger.debug("Unable to determine caller class via Sun Reflection", ex);
097                    }
098                }
099    
100                if (securityManager != null) {
101                    Class clazz = securityManager.getCaller(fqcn);
102                    if (clazz != null) {
103                        return locateContext(clazz.getClassLoader(), null);
104                    }
105                }
106    
107                Throwable t = new Throwable();
108                boolean next = false;
109                String name = null;
110                for (StackTraceElement element : t.getStackTrace()) {
111                    if (element.getClassName().equals(fqcn)) {
112                        next = true;
113                        continue;
114                    }
115                    if (next) {
116                        name = element.getClassName();
117                        break;
118                    }
119                }
120                if (name != null) {
121                    try {
122                        return locateContext(Loader.loadClass(name).getClassLoader(), null);
123                    } catch (ClassNotFoundException ex) {
124                        //System.out.println("Could not load class " + name);
125                    }
126                }
127                LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
128                if (lc != null) {
129                    return lc;
130                }
131                return getDefault();
132            }
133        }
134    
135        public void removeContext(LoggerContext context) {
136            for (Map.Entry<String, AtomicReference<WeakReference<LoggerContext>>> entry : contextMap.entrySet()) {
137                LoggerContext ctx = entry.getValue().get().get();
138                if (ctx == context) {
139                    contextMap.remove(entry.getKey());
140                }
141            }
142        }
143    
144        public List<LoggerContext> getLoggerContexts() {
145            List<LoggerContext> list = new ArrayList<LoggerContext>();
146            Collection<AtomicReference<WeakReference<LoggerContext>>> coll = contextMap.values();
147            for (AtomicReference<WeakReference<LoggerContext>> ref : coll) {
148                LoggerContext ctx = ref.get().get();
149                if (ctx != null) {
150                    list.add(ctx);
151                }
152            }
153            return Collections.unmodifiableList(list);
154        }
155    
156        private LoggerContext locateContext(ClassLoader loader, String configLocation) {
157            String name = loader.toString();
158            AtomicReference<WeakReference<LoggerContext>> ref = contextMap.get(name);
159            if (ref == null) {
160                LoggerContext ctx = new LoggerContext(name, null, configLocation);
161                AtomicReference<WeakReference<LoggerContext>> r =
162                    new AtomicReference<WeakReference<LoggerContext>>();
163                r.set(new WeakReference<LoggerContext>(ctx));
164                contextMap.putIfAbsent(loader.toString(), r);
165                ctx = contextMap.get(name).get().get();
166                return ctx;
167            } else {
168                WeakReference<LoggerContext> r = ref.get();
169                LoggerContext ctx = r.get();
170                if (ctx != null) {
171                    return ctx;
172                }
173                ctx = new LoggerContext(name, null, configLocation);
174                ref.compareAndSet(r, new WeakReference<LoggerContext>(ctx));
175                return ctx;
176            }
177        }
178    
179        private static void setupCallerCheck() {
180            try {
181                ClassLoader loader = Loader.getClassLoader();
182                Class clazz = loader.loadClass("sun.reflect.Reflection");
183                Method[] methods = clazz.getMethods();
184                for (Method method : methods) {
185                    int modifier = method.getModifiers();
186                    if (method.getName().equals("getCallerClass") && Modifier.isStatic(modifier)) {
187                        getCallerClass = method;
188                        break;
189                    }
190                }
191            } catch (ClassNotFoundException cnfe) {
192                logger.debug("sun.reflect.Reflection is not installed");
193            }
194            try {
195                securityManager = new PrivateSecurityManager();
196            } catch (Exception ex) {
197                ex.printStackTrace();
198                logger.debug("Unable to install security manager", ex);
199            }
200        }
201    
202        private LoggerContext getDefault() {
203            LoggerContext ctx = context.get();
204            if (ctx != null) {
205                return ctx;
206            }
207            context.compareAndSet(null, new LoggerContext("Default"));
208            return context.get();
209        }
210    
211        /**
212         * SecurityManager that will locate the caller of the Log4j2 API.
213         */
214        private static class PrivateSecurityManager extends SecurityManager {
215    
216            public Class getCaller(String fqcn) {
217                Class[] classes = getClassContext();
218                boolean next = false;
219                for (Class clazz : classes) {
220                    if (clazz.getName().equals(fqcn)) {
221                        next = true;
222                        continue;
223                    }
224                    if (next) {
225                        return clazz;
226                    }
227                }
228                return null;
229            }
230        }
231    
232    }