View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.selector;
15  
16  import static ch.qos.logback.classic.ClassicConstants.JNDI_CONFIGURATION_RESOURCE;
17  import static ch.qos.logback.classic.ClassicConstants.JNDI_CONTEXT_NAME;
18  
19  import java.net.URL;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import javax.naming.Context;
27  import javax.naming.NamingException;
28  
29  import ch.qos.logback.classic.LoggerContext;
30  import ch.qos.logback.classic.joran.JoranConfigurator;
31  import ch.qos.logback.classic.util.ContextInitializer;
32  import ch.qos.logback.core.joran.spi.JoranException;
33  import ch.qos.logback.core.status.InfoStatus;
34  import ch.qos.logback.core.status.StatusManager;
35  import ch.qos.logback.core.status.StatusUtil;
36  import ch.qos.logback.core.status.WarnStatus;
37  import ch.qos.logback.core.util.JNDIUtil;
38  import ch.qos.logback.core.util.Loader;
39  import ch.qos.logback.core.util.StatusPrinter;
40  
41  /**
42   * A class that allows the LoggerFactory to access an environment-based
43   * LoggerContext.
44   * <p/>
45   * To add in catalina.sh
46   * <p/>
47   * JAVA_OPTS="$JAVA_OPTS "-Dlogback.ContextSelector=JNDI""
48   *
49   * @author Ceki G&uuml;lc&uuml;
50   * @author S&eacute;bastien Pennec
51   */
52  public class ContextJNDISelector implements ContextSelector {
53  
54      private final Map<String, LoggerContext> synchronizedContextMap;
55      private final LoggerContext defaultContext;
56  
57      private static final ThreadLocal<LoggerContext> threadLocal = new ThreadLocal<LoggerContext>();
58  
59      public ContextJNDISelector(LoggerContext context) {
60          synchronizedContextMap = Collections.synchronizedMap(new HashMap<String, LoggerContext>());
61          defaultContext = context;
62      }
63  
64      public LoggerContext getDefaultLoggerContext() {
65          return defaultContext;
66      }
67  
68      public LoggerContext detachLoggerContext(String loggerContextName) {
69          return synchronizedContextMap.remove(loggerContextName);
70      }
71  
72      public LoggerContext getLoggerContext() {
73          String contextName = null;
74          Context ctx = null;
75  
76          // First check if ThreadLocal has been set already
77          LoggerContext lc = threadLocal.get();
78          if (lc != null) {
79              return lc;
80          }
81  
82          try {
83              // We first try to find the name of our
84              // environment's LoggerContext
85              ctx = JNDIUtil.getInitialContext();
86              contextName = (String) JNDIUtil.lookupString(ctx, JNDI_CONTEXT_NAME);
87          } catch (NamingException ne) {
88              // We can't log here
89          }
90  
91          if (contextName == null) {
92              // We return the default context
93              return defaultContext;
94          } else {
95              // Let's see if we already know such a context
96              LoggerContext loggerContext = synchronizedContextMap.get(contextName);
97  
98              if (loggerContext == null) {
99                  // 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 }