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 */ 017 package org.apache.logging.log4j.core.config; 018 019 import java.io.File; 020 import java.io.FileInputStream; 021 import java.io.FileNotFoundException; 022 import java.io.IOException; 023 import java.io.InputStream; 024 import java.net.MalformedURLException; 025 import java.net.URI; 026 import java.net.URISyntaxException; 027 import java.net.URL; 028 import java.util.ArrayList; 029 import java.util.Collection; 030 import java.util.Collections; 031 import java.util.List; 032 import java.util.Map; 033 import java.util.TreeSet; 034 import java.util.concurrent.locks.Lock; 035 import java.util.concurrent.locks.ReentrantLock; 036 037 import org.apache.logging.log4j.Level; 038 import org.apache.logging.log4j.Logger; 039 import org.apache.logging.log4j.core.config.plugins.util.PluginManager; 040 import org.apache.logging.log4j.core.config.plugins.util.PluginType; 041 import org.apache.logging.log4j.core.lookup.Interpolator; 042 import org.apache.logging.log4j.core.lookup.StrSubstitutor; 043 import org.apache.logging.log4j.core.util.FileUtils; 044 import org.apache.logging.log4j.core.util.Loader; 045 import org.apache.logging.log4j.status.StatusLogger; 046 import org.apache.logging.log4j.util.PropertiesUtil; 047 048 /** 049 * ConfigurationFactory allows the configuration implementation to be 050 * dynamically chosen in 1 of 3 ways: 051 * <ol> 052 * <li>A system property named "log4j.configurationFactory" can be set with the 053 * name of the ConfigurationFactory to be used.</li> 054 * <li> 055 * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called 056 * with the instance of the ConfigurationFactory to be used. This must be called 057 * before any other calls to Log4j.</li> 058 * <li> 059 * A ConfigurationFactory implementation can be added to the classpath and 060 * configured as a plugin. The Order annotation should be used to configure the 061 * factory to be the first one inspected. See 062 * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.</li> 063 * </ol> 064 * 065 * If the ConfigurationFactory that was added returns null on a call to 066 * getConfiguration the any other ConfigurationFactories found as plugins will 067 * be called in their respective order. DefaultConfiguration is always called 068 * last if no configuration has been returned. 069 */ 070 public abstract class ConfigurationFactory { 071 /** 072 * Allow the ConfigurationFactory class to be specified as a system property. 073 */ 074 public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory"; 075 076 /** 077 * Allow the location of the configuration file to be specified as a system property. 078 */ 079 public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile"; 080 081 /** 082 * Allow subclasses access to the status logger without creating another instance. 083 */ 084 protected static final Logger LOGGER = StatusLogger.getLogger(); 085 086 /** 087 * File name prefix for test configurations. 088 */ 089 protected static final String TEST_PREFIX = "log4j2-test"; 090 091 /** 092 * File name prefix for standard configurations. 093 */ 094 protected static final String DEFAULT_PREFIX = "log4j2"; 095 096 /** 097 * The name of the classloader URI scheme. 098 */ 099 private static final String CLASS_LOADER_SCHEME = "classloader"; 100 101 /** 102 * The name of the classpath URI scheme, synonymous with the classloader URI scheme. 103 */ 104 private static final String CLASS_PATH_SCHEME = "classpath"; 105 106 private static volatile List<ConfigurationFactory> factories = null; 107 108 private static ConfigurationFactory configFactory = new Factory(); 109 110 protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator()); 111 112 private static final Lock LOCK = new ReentrantLock(); 113 114 /** 115 * Returns the ConfigurationFactory. 116 * @return the ConfigurationFactory. 117 */ 118 public static ConfigurationFactory getInstance() { 119 // volatile works in Java 1.6+, so double-checked locking also works properly 120 //noinspection DoubleCheckedLocking 121 if (factories == null) { 122 LOCK.lock(); 123 try { 124 if (factories == null) { 125 final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>(); 126 final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY); 127 if (factoryClass != null) { 128 addFactory(list, factoryClass); 129 } 130 final PluginManager manager = new PluginManager("ConfigurationFactory"); 131 manager.collectPlugins(); 132 final Map<String, PluginType<?>> plugins = manager.getPlugins(); 133 final Collection<WeightedFactory> ordered = new TreeSet<WeightedFactory>(); 134 for (final PluginType<?> type : plugins.values()) { 135 try { 136 @SuppressWarnings("unchecked") 137 final Class<ConfigurationFactory> clazz = (Class<ConfigurationFactory>)type.getPluginClass(); 138 final Order order = clazz.getAnnotation(Order.class); 139 if (order != null) { 140 final int weight = order.value(); 141 ordered.add(new WeightedFactory(weight, clazz)); 142 } 143 } catch (final Exception ex) { 144 LOGGER.warn("Unable to add class {}", type.getPluginClass()); 145 } 146 } 147 for (final WeightedFactory wf : ordered) { 148 addFactory(list, wf.factoryClass); 149 } 150 // see above comments about double-checked locking 151 //noinspection NonThreadSafeLazyInitialization 152 factories = Collections.unmodifiableList(list); 153 } 154 } finally { 155 LOCK.unlock(); 156 } 157 } 158 159 LOGGER.debug("Using configurationFactory {}", configFactory); 160 return configFactory; 161 } 162 163 @SuppressWarnings("unchecked") 164 private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) { 165 try { 166 addFactory(list, (Class<ConfigurationFactory>) Loader.loadClass(factoryClass)); 167 } catch (final Exception ex) { 168 LOGGER.error("Unable to load class {}", factoryClass, ex); 169 } 170 } 171 172 private static void addFactory(final Collection<ConfigurationFactory> list, 173 final Class<ConfigurationFactory> factoryClass) { 174 try { 175 list.add(factoryClass.getConstructor().newInstance()); 176 } catch (final Exception ex) { 177 LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex); 178 } 179 } 180 181 /** 182 * Set the configuration factory. This method is not intended for general use and may not be thread safe. 183 * @param factory the ConfigurationFactory. 184 */ 185 public static void setConfigurationFactory(final ConfigurationFactory factory) { 186 configFactory = factory; 187 } 188 189 /** 190 * Reset the ConfigurationFactory to the default. This method is not intended for general use and may 191 * not be thread safe. 192 */ 193 public static void resetConfigurationFactory() { 194 configFactory = new Factory(); 195 } 196 197 /** 198 * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe. 199 * @param factory The factory to remove. 200 */ 201 public static void removeConfigurationFactory(final ConfigurationFactory factory) { 202 if (configFactory == factory) { 203 configFactory = new Factory(); 204 } 205 } 206 207 protected abstract String[] getSupportedTypes(); 208 209 protected boolean isActive() { 210 return true; 211 } 212 213 public abstract Configuration getConfiguration(ConfigurationSource source); 214 215 /** 216 * Returns the Configuration. 217 * @param name The configuration name. 218 * @param configLocation The configuration location. 219 * @return The Configuration. 220 */ 221 public Configuration getConfiguration(final String name, final URI configLocation) { 222 if (!isActive()) { 223 return null; 224 } 225 if (configLocation != null) { 226 final ConfigurationSource source = getInputFromUri(configLocation); 227 if (source != null) { 228 return getConfiguration(source); 229 } 230 } 231 return null; 232 } 233 234 /** 235 * Load the configuration from a URI. 236 * @param configLocation A URI representing the location of the configuration. 237 * @return The ConfigurationSource for the configuration. 238 */ 239 protected ConfigurationSource getInputFromUri(final URI configLocation) { 240 final File configFile = FileUtils.fileFromUri(configLocation); 241 if (configFile != null && configFile.exists() && configFile.canRead()) { 242 try { 243 return new ConfigurationSource(new FileInputStream(configFile), configFile); 244 } catch (final FileNotFoundException ex) { 245 LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex); 246 } 247 } 248 final String scheme = configLocation.getScheme(); 249 final boolean isClassLoaderScheme = scheme != null && scheme.equals(CLASS_LOADER_SCHEME); 250 final boolean isClassPathScheme = scheme != null && !isClassLoaderScheme && scheme.equals(CLASS_PATH_SCHEME); 251 if (scheme == null || isClassLoaderScheme || isClassPathScheme) { 252 final ClassLoader loader = Loader.getThreadContextClassLoader(); 253 String path; 254 if (isClassLoaderScheme || isClassPathScheme) { 255 path = configLocation.getSchemeSpecificPart(); 256 } else { 257 path = configLocation.getPath(); 258 } 259 final ConfigurationSource source = getInputFromResource(path, loader); 260 if (source != null) { 261 return source; 262 } 263 } 264 try { 265 return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL()); 266 } catch (final MalformedURLException ex) { 267 LOGGER.error("Invalid URL {}", configLocation.toString(), ex); 268 } catch (final Exception ex) { 269 LOGGER.error("Unable to access {}", configLocation.toString(), ex); 270 } 271 return null; 272 } 273 274 /** 275 * Load the configuration from the location represented by the String. 276 * @param config The configuration location. 277 * @param loader The default ClassLoader to use. 278 * @return The InputSource to use to read the configuration. 279 */ 280 protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) { 281 try { 282 final URL url = new URL(config); 283 return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI())); 284 } catch (final Exception ex) { 285 final ConfigurationSource source = getInputFromResource(config, loader); 286 if (source == null) { 287 try { 288 final File file = new File(config); 289 return new ConfigurationSource(new FileInputStream(file), file); 290 } catch (final FileNotFoundException fnfe) { 291 // Ignore the exception 292 LOGGER.catching(Level.DEBUG, fnfe); 293 } 294 } 295 return source; 296 } 297 } 298 299 /** 300 * Retrieve the configuration via the ClassLoader. 301 * @param resource The resource to load. 302 * @param loader The default ClassLoader to use. 303 * @return The ConfigurationSource for the configuration. 304 */ 305 protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) { 306 final URL url = Loader.getResource(resource, loader); 307 if (url == null) { 308 return null; 309 } 310 InputStream is = null; 311 try { 312 is = url.openStream(); 313 } catch (final IOException ioe) { 314 LOGGER.catching(Level.DEBUG, ioe); 315 return null; 316 } 317 if (is == null) { 318 return null; 319 } 320 321 if (FileUtils.isFile(url)) { 322 try { 323 return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI())); 324 } catch (final URISyntaxException ex) { 325 // Just ignore the exception. 326 LOGGER.catching(Level.DEBUG, ex); 327 } 328 } 329 return new ConfigurationSource(is, url); 330 } 331 332 /** 333 * Factory that chooses a ConfigurationFactory based on weighting. 334 */ 335 private static class WeightedFactory implements Comparable<WeightedFactory> { 336 private final int weight; 337 private final Class<ConfigurationFactory> factoryClass; 338 339 /** 340 * Constructor. 341 * @param weight The weight. 342 * @param clazz The class. 343 */ 344 public WeightedFactory(final int weight, final Class<ConfigurationFactory> clazz) { 345 this.weight = weight; 346 this.factoryClass = clazz; 347 } 348 349 @Override 350 public int compareTo(final WeightedFactory wf) { 351 final int w = wf.weight; 352 if (weight == w) { 353 return 0; 354 } else if (weight > w) { 355 return -1; 356 } else { 357 return 1; 358 } 359 } 360 } 361 362 /** 363 * Default Factory. 364 */ 365 private static class Factory extends ConfigurationFactory { 366 367 /** 368 * Default Factory Constructor. 369 * @param name The configuration name. 370 * @param configLocation The configuration location. 371 * @return The Configuration. 372 */ 373 @Override 374 public Configuration getConfiguration(final String name, final URI configLocation) { 375 376 if (configLocation == null) { 377 final String config = this.substitutor.replace( 378 PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY)); 379 if (config != null) { 380 ConfigurationSource source = null; 381 try { 382 source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config)); 383 } catch (final Exception ex) { 384 // Ignore the error and try as a String. 385 LOGGER.catching(Level.DEBUG, ex); 386 } 387 if (source == null) { 388 final ClassLoader loader = this.getClass().getClassLoader(); 389 source = getInputFromString(config, loader); 390 } 391 if (source != null) { 392 for (final ConfigurationFactory factory : factories) { 393 final String[] types = factory.getSupportedTypes(); 394 if (types != null) { 395 for (final String type : types) { 396 if (type.equals("*") || config.endsWith(type)) { 397 final Configuration c = factory.getConfiguration(source); 398 if (c != null) { 399 return c; 400 } 401 } 402 } 403 } 404 } 405 } 406 } 407 } else { 408 for (final ConfigurationFactory factory : factories) { 409 final String[] types = factory.getSupportedTypes(); 410 if (types != null) { 411 for (final String type : types) { 412 if (type.equals("*") || configLocation.toString().endsWith(type)) { 413 final Configuration config = factory.getConfiguration(name, configLocation); 414 if (config != null) { 415 return config; 416 } 417 } 418 } 419 } 420 } 421 } 422 423 Configuration config = getConfiguration(true, name); 424 if (config == null) { 425 config = getConfiguration(true, null); 426 if (config == null) { 427 config = getConfiguration(false, name); 428 if (config == null) { 429 config = getConfiguration(false, null); 430 } 431 } 432 } 433 return config != null ? config : new DefaultConfiguration(); 434 } 435 436 private Configuration getConfiguration(final boolean isTest, final String name) { 437 final boolean named = name != null && name.length() > 0; 438 final ClassLoader loader = this.getClass().getClassLoader(); 439 for (final ConfigurationFactory factory : factories) { 440 String configName; 441 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX; 442 final String [] types = factory.getSupportedTypes(); 443 if (types == null) { 444 continue; 445 } 446 447 for (final String suffix : types) { 448 if (suffix.equals("*")) { 449 continue; 450 } 451 configName = named ? prefix + name + suffix : prefix + suffix; 452 453 final ConfigurationSource source = getInputFromResource(configName, loader); 454 if (source != null) { 455 return factory.getConfiguration(source); 456 } 457 } 458 } 459 return null; 460 } 461 462 @Override 463 public String[] getSupportedTypes() { 464 return null; 465 } 466 467 @Override 468 public Configuration getConfiguration(final ConfigurationSource source) { 469 if (source != null) { 470 final String config = source.getLocation(); 471 for (final ConfigurationFactory factory : factories) { 472 final String[] types = factory.getSupportedTypes(); 473 if (types != null) { 474 for (final String type : types) { 475 if (type.equals("*") || config != null && config.endsWith(type)) { 476 final Configuration c = factory.getConfiguration(source); 477 if (c != null) { 478 LOGGER.debug("Loaded configuration from {}", source); 479 return c; 480 } 481 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config); 482 return null; 483 } 484 } 485 } 486 } 487 } 488 LOGGER.error("Cannot process configuration, input source is null"); 489 return null; 490 } 491 } 492 }