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.core.joran.spi.JoranException;
033import ch.qos.logback.core.status.InfoStatus;
034import ch.qos.logback.core.status.StatusManager;
035import ch.qos.logback.core.status.StatusUtil;
036import ch.qos.logback.core.status.WarnStatus;
037import ch.qos.logback.core.util.JNDIUtil;
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.lookupString(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 = null;
128
129        try {
130            jndiEntryForConfigResource = JNDIUtil.lookupString(ctx, JNDI_CONFIGURATION_RESOURCE);
131        } catch (NamingException e) {
132            sm.add(new WarnStatus("JNDI lookup failed", this, e));
133        }
134        // Do we have a dedicated configuration file?
135        if (jndiEntryForConfigResource != null) {
136            sm.add(new InfoStatus("Searching for [" + jndiEntryForConfigResource + "]", this));
137            URL url = urlByResourceName(sm, jndiEntryForConfigResource);
138            if (url == null) {
139                String msg = "The jndi resource [" + jndiEntryForConfigResource + "] for context ["
140                        + loggerContext.getName() + "] does not lead to a valid file";
141                sm.add(new WarnStatus(msg, this));
142            }
143            return url;
144        } else {
145            String resourceByConvention = conventionalConfigFileName(loggerContext.getName());
146            return urlByResourceName(sm, resourceByConvention);
147        }
148    }
149
150    private URL urlByResourceName(StatusManager sm, String resourceName) {
151        sm.add(new InfoStatus("Searching for [" + resourceName + "]", this));
152        URL url = Loader.getResource(resourceName, Loader.getTCL());
153        if (url != null) {
154            return url;
155        }
156        return Loader.getResourceBySelfClassLoader(resourceName);
157    }
158
159    private void configureLoggerContextByURL(LoggerContext context, URL url) {
160        try {
161            JoranConfigurator configurator = new JoranConfigurator();
162            context.reset();
163            configurator.setContext(context);
164            configurator.doConfigure(url);
165        } catch (JoranException e) {
166        }
167        StatusPrinter.printInCaseOfErrorsOrWarnings(context);
168    }
169
170    public List<String> getContextNames() {
171        List<String> list = new ArrayList<String>();
172        list.addAll(synchronizedContextMap.keySet());
173        return list;
174    }
175
176    public LoggerContext getLoggerContext(String name) {
177        return synchronizedContextMap.get(name);
178    }
179
180    /**
181     * Returns the number of managed contexts Used for testing purposes
182     *
183     * @return the number of managed contexts
184     */
185    public int getCount() {
186        return synchronizedContextMap.size();
187    }
188
189    /**
190     * These methods are used by the LoggerContextFilter.
191     * <p/>
192     * They provide a way to tell the selector which context to use, thus saving the
193     * cost of a JNDI call at each new request.
194     *
195     * @param context
196     */
197    public void setLocalContext(LoggerContext context) {
198        threadLocal.set(context);
199    }
200
201    public void removeLocalContext() {
202        threadLocal.remove();
203    }
204
205}