View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.config;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.net.MalformedURLException;
25  import java.net.URI;
26  import java.net.URISyntaxException;
27  import java.net.URL;
28  import java.util.ArrayList;
29  import java.util.Collections;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.TreeSet;
34  
35  import org.apache.logging.log4j.Logger;
36  import org.apache.logging.log4j.core.config.plugins.PluginManager;
37  import org.apache.logging.log4j.core.config.plugins.PluginType;
38  import org.apache.logging.log4j.core.helpers.FileUtils;
39  import org.apache.logging.log4j.core.helpers.Loader;
40  import org.apache.logging.log4j.core.lookup.Interpolator;
41  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
42  import org.apache.logging.log4j.status.StatusLogger;
43  import org.apache.logging.log4j.util.PropertiesUtil;
44  
45  /**
46   * ConfigurationFactory allows the configuration implementation to be
47   * dynamically chosen in 1 of 3 ways:
48   * <ol>
49   * <li>A system property named "log4j.configurationFactory" can be set with the
50   * name of the ConfigurationFactory to be used.</li>
51   * <li>
52   * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called
53   * with the instance of the ConfigurationFactory to be used. This must be called
54   * before any other calls to Log4j.</li>
55   * <li>
56   * A ConfigurationFactory implementation can be added to the classpath and
57   * configured as a plugin. The Order annotation should be used to configure the
58   * factory to be the first one inspected. See
59   * {@linkplain XMLConfigurationFactory} for an example.</li>
60   * </ol>
61   *
62   * If the ConfigurationFactory that was added returns null on a call to
63   * getConfiguration the any other ConfigurationFactories found as plugins will
64   * be called in their respective order. DefaultConfiguration is always called
65   * last if no configuration has been returned.
66   */
67  public abstract class ConfigurationFactory {
68      /**
69       * Allow the ConfigurationFactory class to be specified as a system property.
70       */
71      public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
72  
73      /**
74       * Allow the location of the configuration file to be specified as a system property.
75       */
76      public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
77  
78      /**
79       * Allow subclasses access to the status logger without creating another instance.
80       */
81      protected static final Logger LOGGER = StatusLogger.getLogger();
82  
83      /**
84       * File name prefix for test configurations.
85       */
86      protected static final String TEST_PREFIX = "log4j2-test";
87  
88      /**
89       * File name prefix for standard configurations.
90       */
91      protected static final String DEFAULT_PREFIX = "log4j2";
92  
93      /**
94       * The name of the classloader URI scheme.
95       */
96      private static final String CLASS_LOADER_SCHEME = "classloader";
97      private static final int CLASS_LOADER_SCHEME_LENGTH = CLASS_LOADER_SCHEME.length() + 1;
98  
99      /**
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(new URI(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 }