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 final Class<ConfigurationFactory> clazz = type.getPluginClass(); 115 final Order order = clazz.getAnnotation(Order.class); 116 final Integer weight = order.value(); 117 if (order != null) { 118 ordered.add(new WeightedFactory(weight, clazz)); 119 } 120 } catch (final Exception ex) { 121 LOGGER.warn("Unable to add class " + type.getPluginClass()); 122 } 123 } 124 for (final WeightedFactory wf : ordered) { 125 addFactory(list, wf.factoryClass); 126 } 127 factories = Collections.unmodifiableList(list); 128 } 129 } 130 } 131 132 return configFactory; 133 } 134 135 @SuppressWarnings("unchecked") 136 private static void addFactory(final List<ConfigurationFactory> list, final String factoryClass) { 137 try { 138 addFactory(list, (Class<ConfigurationFactory>) Class.forName(factoryClass)); 139 } catch (final ClassNotFoundException ex) { 140 LOGGER.error("Unable to load class " + factoryClass, ex); 141 } catch (final Exception ex) { 142 LOGGER.error("Unable to load class " + factoryClass, ex); 143 } 144 } 145 146 private static void addFactory(final List<ConfigurationFactory> list, 147 final Class<ConfigurationFactory> factoryClass) { 148 try { 149 list.add(factoryClass.newInstance()); 150 } catch (final Exception ex) { 151 LOGGER.error("Unable to create instance of " + factoryClass.getName(), ex); 152 } 153 } 154 155 /** 156 * Set the configuration factory. This method is not intended for general use and may not be thread safe. 157 * @param factory the ConfigurationFactory. 158 */ 159 public static void setConfigurationFactory(final ConfigurationFactory factory) { 160 configFactory = factory; 161 } 162 163 /** 164 * Reset the ConfigurationFactory to the default. This method is not intended for general use and may 165 * not be thread safe. 166 */ 167 public static void resetConfigurationFactory() { 168 configFactory = new Factory(); 169 } 170 171 /** 172 * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe. 173 * @param factory The factory to remove. 174 */ 175 public static void removeConfigurationFactory(final ConfigurationFactory factory) { 176 if (configFactory == factory) { 177 configFactory = new Factory(); 178 } 179 } 180 181 protected abstract String[] getSupportedTypes(); 182 183 protected boolean isActive() { 184 return true; 185 } 186 187 public abstract Configuration getConfiguration(ConfigurationSource source); 188 189 /** 190 * Returns the Configuration. 191 * @param name The configuration name. 192 * @param configLocation The configuration location. 193 * @return The Configuration. 194 */ 195 public Configuration getConfiguration(final String name, final URI configLocation) { 196 if (!isActive()) { 197 return null; 198 } 199 if (configLocation != null) { 200 final ConfigurationSource source = getInputFromURI(configLocation); 201 if (source != null) { 202 return getConfiguration(source); 203 } 204 } 205 return null; 206 } 207 208 /** 209 * Load the configuration from a URI. 210 * @param configLocation A URI representing the location of the configuration. 211 * @return The ConfigurationSource for the configuration. 212 */ 213 protected ConfigurationSource getInputFromURI(final URI configLocation) { 214 final File configFile = FileUtils.fileFromURI(configLocation); 215 if (configFile != null && configFile.exists() && configFile.canRead()) { 216 try { 217 return new ConfigurationSource(new FileInputStream(configFile), configFile); 218 } catch (final FileNotFoundException ex) { 219 LOGGER.error("Cannot locate file " + configLocation.getPath(), ex); 220 } 221 } 222 final String scheme = configLocation.getScheme(); 223 if (scheme == null || scheme.equals("classloader")) { 224 final ClassLoader loader = this.getClass().getClassLoader(); 225 final ConfigurationSource source = getInputFromResource(configLocation.getPath(), loader); 226 if (source != null) { 227 return source; 228 } 229 } 230 try { 231 return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.getPath()); 232 } catch (final MalformedURLException ex) { 233 LOGGER.error("Invalid URL " + configLocation.toString(), ex); 234 } catch (final IOException ex) { 235 LOGGER.error("Unable to access " + configLocation.toString(), ex); 236 } catch (final Exception ex) { 237 LOGGER.error("Unable to access " + configLocation.toString(), ex); 238 } 239 return null; 240 } 241 242 /** 243 * Load the configuration from the location represented by the String. 244 * @param config The configuration location. 245 * @param loader The default ClassLoader to use. 246 * @return The InputSource to use to read the configuration. 247 */ 248 protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) { 249 try { 250 final URL url = new URL(config); 251 return new ConfigurationSource(url.openStream(), FileUtils.fileFromURI(url.toURI())); 252 } catch (final Exception ex) { 253 final ConfigurationSource source = getInputFromResource(config, loader); 254 if (source == null) { 255 try { 256 final File file = new File(config); 257 return new ConfigurationSource(new FileInputStream(file), file); 258 } catch (final FileNotFoundException fnfe) { 259 // Ignore the exception 260 } 261 } 262 return source; 263 } 264 } 265 266 /** 267 * Retrieve the configuration via the ClassLoader. 268 * @param resource The resource to load. 269 * @param loader The default ClassLoader to use. 270 * @return The ConfigurationSource for the configuration. 271 */ 272 protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) { 273 final URL url = Loader.getResource(resource, loader); 274 if (url == null) { 275 return null; 276 } 277 InputStream is = null; 278 try { 279 is = url.openStream(); 280 } catch (final IOException ioe) { 281 return null; 282 } 283 if (is == null) { 284 return null; 285 } 286 287 if (FileUtils.isFile(url)) { 288 try { 289 return new ConfigurationSource(is, FileUtils.fileFromURI((url.toURI()))); 290 } catch (final URISyntaxException ex) { 291 // Just ignore the exception. 292 } 293 } 294 return new ConfigurationSource(is, resource); 295 } 296 297 /** 298 * Factory that chooses a ConfigurationFactory based on weighting. 299 */ 300 private static class WeightedFactory implements Comparable<WeightedFactory> { 301 private final int weight; 302 private final Class<ConfigurationFactory> factoryClass; 303 304 /** 305 * Constructor. 306 * @param weight The weight. 307 * @param clazz The class. 308 */ 309 public WeightedFactory(final int weight, final Class<ConfigurationFactory> clazz) { 310 this.weight = weight; 311 this.factoryClass = clazz; 312 } 313 314 public int compareTo(final WeightedFactory wf) { 315 final int w = wf.weight; 316 if (weight == w) { 317 return 0; 318 } else if (weight > w) { 319 return -1; 320 } else { 321 return 1; 322 } 323 } 324 } 325 326 /** 327 * Default Factory. 328 */ 329 private static class Factory extends ConfigurationFactory { 330 331 /** 332 * Default Factory Constructor. 333 * @param name The configuration name. 334 * @param configLocation The configuration location. 335 * @return The Configuration. 336 */ 337 @Override 338 public Configuration getConfiguration(final String name, final URI configLocation) { 339 340 if (configLocation == null) { 341 final String config = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY); 342 if (config != null) { 343 final ClassLoader loader = this.getClass().getClassLoader(); 344 final ConfigurationSource source = getInputFromString(config, loader); 345 if (source != null) { 346 for (final ConfigurationFactory factory : factories) { 347 final String[] types = factory.getSupportedTypes(); 348 if (types != null) { 349 for (final String type : types) { 350 if (type.equals("*") || config.endsWith(type)) { 351 final Configuration c = factory.getConfiguration(source); 352 if (c != null) { 353 return c; 354 } 355 } 356 } 357 } 358 } 359 } 360 } 361 } else { 362 for (final ConfigurationFactory factory : factories) { 363 final String[] types = factory.getSupportedTypes(); 364 if (types != null) { 365 for (final String type : types) { 366 if (type.equals("*") || configLocation.getPath().endsWith(type)) { 367 final Configuration config = factory.getConfiguration(name, configLocation); 368 if (config != null) { 369 return config; 370 } 371 } 372 } 373 } 374 } 375 } 376 377 Configuration config = getConfiguration(true, name); 378 if (config == null) { 379 config = getConfiguration(true, null); 380 if (config == null) { 381 config = getConfiguration(false, name); 382 if (config == null) { 383 config = getConfiguration(false, null); 384 } 385 } 386 } 387 return config != null ? config : new DefaultConfiguration(); 388 } 389 390 private Configuration getConfiguration(final boolean isTest, final String name) { 391 final boolean named = name != null && name.length() > 0; 392 final ClassLoader loader = this.getClass().getClassLoader(); 393 for (final ConfigurationFactory factory : factories) { 394 String configName; 395 final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX; 396 final String [] types = factory.getSupportedTypes(); 397 if (types == null) { 398 continue; 399 } 400 401 for (final String suffix : types) { 402 if (suffix.equals("*")) { 403 continue; 404 } 405 configName = named ? prefix + name + suffix : prefix + suffix; 406 407 final ConfigurationSource source = getInputFromResource(configName, loader); 408 if (source != null) { 409 return factory.getConfiguration(source); 410 } 411 } 412 } 413 return null; 414 } 415 416 @Override 417 public String[] getSupportedTypes() { 418 return null; 419 } 420 421 @Override 422 public Configuration getConfiguration(final ConfigurationSource source) { 423 if (source != null) { 424 final String config = source.getLocation(); 425 for (final ConfigurationFactory factory : factories) { 426 final String[] types = factory.getSupportedTypes(); 427 if (types != null) { 428 for (final String type : types) { 429 if (type.equals("*") || (config != null && config.endsWith(type))) { 430 final Configuration c = factory.getConfiguration(source); 431 if (c != null) { 432 return c; 433 } else { 434 LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config); 435 return null; 436 } 437 } 438 } 439 } 440 } 441 } 442 LOGGER.error("Cannot process configuration, input source is null"); 443 return null; 444 } 445 } 446 447 /** 448 * Represents the source for the logging configuration. 449 */ 450 public static class ConfigurationSource { 451 452 private File file; 453 454 private String location; 455 456 private InputStream stream; 457 458 public ConfigurationSource() { 459 } 460 461 public ConfigurationSource(final InputStream stream) { 462 this.stream = stream; 463 this .file = null; 464 this.location = null; 465 } 466 467 public ConfigurationSource(final InputStream stream, final File file) { 468 this.stream = stream; 469 this.file = file; 470 this.location = file.getAbsolutePath(); 471 } 472 473 public ConfigurationSource(final InputStream stream, final String location) { 474 this.stream = stream; 475 this.location = location; 476 this.file = null; 477 } 478 479 public File getFile() { 480 return file; 481 } 482 483 public void setFile(final File file) { 484 this.file = file; 485 } 486 487 public String getLocation() { 488 return location; 489 } 490 491 public void setLocation(final String location) { 492 this.location = location; 493 } 494 495 public InputStream getInputStream() { 496 return stream; 497 } 498 499 public void setInputStream(final InputStream stream) { 500 this.stream = stream; 501 } 502 } 503 }