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