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