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