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ülcü 050 * @author Sé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}