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