001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.selector;
015
016import static ch.qos.logback.classic.ClassicConstants.JNDI_CONFIGURATION_RESOURCE;
017import static ch.qos.logback.classic.ClassicConstants.JNDI_CONTEXT_NAME;
018
019import java.net.URL;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import javax.naming.Context;
027import javax.naming.NamingException;
028
029import ch.qos.logback.classic.LoggerContext;
030import ch.qos.logback.classic.joran.JoranConfigurator;
031import ch.qos.logback.classic.util.ContextInitializer;
032import ch.qos.logback.classic.util.JNDIUtil;
033import ch.qos.logback.core.joran.spi.JoranException;
034import ch.qos.logback.core.status.InfoStatus;
035import ch.qos.logback.core.status.StatusManager;
036import ch.qos.logback.core.status.StatusUtil;
037import ch.qos.logback.core.status.WarnStatus;
038import ch.qos.logback.core.util.Loader;
039import ch.qos.logback.core.util.StatusPrinter;
040
041/**
042 * A class that allows the LoggerFactory to access an environment-based
043 * LoggerContext.
044 * <p/>
045 * To add in catalina.sh
046 * <p/>
047 * JAVA_OPTS="$JAVA_OPTS "-Dlogback.ContextSelector=JNDI""
048 *
049 * @author Ceki G&uuml;lc&uuml;
050 * @author S&eacute;bastien Pennec
051 */
052public class ContextJNDISelector implements ContextSelector {
053
054    private final Map<String, LoggerContext> synchronizedContextMap;
055    private final LoggerContext defaultContext;
056
057    private static final ThreadLocal<LoggerContext> threadLocal = new ThreadLocal<LoggerContext>();
058
059    public ContextJNDISelector(LoggerContext context) {
060        synchronizedContextMap = Collections.synchronizedMap(new HashMap<String, LoggerContext>());
061        defaultContext = context;
062    }
063
064    public LoggerContext getDefaultLoggerContext() {
065        return defaultContext;
066    }
067
068    public LoggerContext detachLoggerContext(String loggerContextName) {
069        return synchronizedContextMap.remove(loggerContextName);
070    }
071
072    public LoggerContext getLoggerContext() {
073        String contextName = null;
074        Context ctx = null;
075
076        // First check if ThreadLocal has been set already
077        LoggerContext lc = threadLocal.get();
078        if (lc != null) {
079            return lc;
080        }
081
082        try {
083            // We first try to find the name of our
084            // environment's LoggerContext
085            ctx = JNDIUtil.getInitialContext();
086            contextName = (String) JNDIUtil.lookup(ctx, JNDI_CONTEXT_NAME);
087        } catch (NamingException ne) {
088            // We can't log here
089        }
090
091        if (contextName == null) {
092            // We return the default context
093            return defaultContext;
094        } else {
095            // Let's see if we already know such a context
096            LoggerContext loggerContext = synchronizedContextMap.get(contextName);
097
098            if (loggerContext == null) {
099                // We have to create a new LoggerContext
100                loggerContext = new LoggerContext();
101                loggerContext.setName(contextName);
102                synchronizedContextMap.put(contextName, loggerContext);
103                URL url = findConfigFileURL(ctx, loggerContext);
104                if (url != null) {
105                    configureLoggerContextByURL(loggerContext, url);
106                } else {
107                    try {
108                        new ContextInitializer(loggerContext).autoConfig();
109                    } catch (JoranException je) {
110                    }
111                }
112                // logback-292
113                if (!StatusUtil.contextHasStatusListener(loggerContext))
114                    StatusPrinter.printInCaseOfErrorsOrWarnings(loggerContext);
115            }
116            return loggerContext;
117        }
118    }
119
120    private String conventionalConfigFileName(String contextName) {
121        return "logback-" + contextName + ".xml";
122    }
123
124    private URL findConfigFileURL(Context ctx, LoggerContext loggerContext) {
125        StatusManager sm = loggerContext.getStatusManager();
126
127        String jndiEntryForConfigResource = JNDIUtil.lookup(ctx, JNDI_CONFIGURATION_RESOURCE);
128        // Do we have a dedicated configuration file?
129        if (jndiEntryForConfigResource != null) {
130            sm.add(new InfoStatus("Searching for [" + jndiEntryForConfigResource + "]", this));
131            URL url = urlByResourceName(sm, jndiEntryForConfigResource);
132            if (url == null) {
133                String msg = "The jndi resource [" + jndiEntryForConfigResource + "] for context [" + loggerContext.getName()
134                                + "] does not lead to a valid file";
135                sm.add(new WarnStatus(msg, this));
136            }
137            return url;
138        } else {
139            String resourceByConvention = conventionalConfigFileName(loggerContext.getName());
140            return urlByResourceName(sm, resourceByConvention);
141        }
142    }
143
144    private URL urlByResourceName(StatusManager sm, String resourceName) {
145        sm.add(new InfoStatus("Searching for [" + resourceName + "]", this));
146        URL url = Loader.getResource(resourceName, Loader.getTCL());
147        if (url != null) {
148            return url;
149        }
150        return Loader.getResourceBySelfClassLoader(resourceName);
151    }
152
153    private void configureLoggerContextByURL(LoggerContext context, URL url) {
154        try {
155            JoranConfigurator configurator = new JoranConfigurator();
156            context.reset();
157            configurator.setContext(context);
158            configurator.doConfigure(url);
159        } catch (JoranException e) {
160        }
161        StatusPrinter.printInCaseOfErrorsOrWarnings(context);
162    }
163
164    public List<String> getContextNames() {
165        List<String> list = new ArrayList<String>();
166        list.addAll(synchronizedContextMap.keySet());
167        return list;
168    }
169
170    public LoggerContext getLoggerContext(String name) {
171        return synchronizedContextMap.get(name);
172    }
173
174    /**
175     * Returns the number of managed contexts Used for testing purposes
176     *
177     * @return the number of managed contexts
178     */
179    public int getCount() {
180        return synchronizedContextMap.size();
181    }
182
183    /**
184     * These methods are used by the LoggerContextFilter.
185     * <p/>
186     * They provide a way to tell the selector which context to use, thus saving
187     * the cost of a JNDI call at each new request.
188     *
189     * @param context
190     */
191    public void setLocalContext(LoggerContext context) {
192        threadLocal.set(context);
193    }
194
195    public void removeLocalContext() {
196        threadLocal.remove();
197    }
198
199}