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 */
017package org.apache.logging.log4j.core.selector;
018
019import java.lang.ref.WeakReference;
020import java.net.URI;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.List;
025import java.util.Map;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028import java.util.concurrent.atomic.AtomicReference;
029
030import org.apache.logging.log4j.core.LoggerContext;
031import org.apache.logging.log4j.core.helpers.Loader;
032import org.apache.logging.log4j.core.impl.ContextAnchor;
033import org.apache.logging.log4j.core.impl.ReflectiveCallerClassUtility;
034import 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 */
047public 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}