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 org.apache.logging.log4j.Logger;
20  import org.apache.logging.log4j.core.config.plugins.PluginManager;
21  import org.apache.logging.log4j.core.config.plugins.PluginType;
22  import org.apache.logging.log4j.core.helpers.FileUtils;
23  import org.apache.logging.log4j.core.helpers.Loader;
24  import org.apache.logging.log4j.status.StatusLogger;
25  import org.apache.logging.log4j.util.PropertiesUtil;
26  
27  import java.io.File;
28  import java.io.FileInputStream;
29  import java.io.FileNotFoundException;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.net.MalformedURLException;
33  import java.net.URI;
34  import java.net.URISyntaxException;
35  import java.net.URL;
36  import java.util.ArrayList;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.TreeSet;
41  
42  /**
43   * ConfigurationFactory allows the configuration implementation to be
44   * dynamically chosen in 1 of 3 ways:
45   * <ol>
46   * <li>A system property named "log4j.configurationFactory" can be set with the
47   * name of the ConfigurationFactory to be used.</li>
48   * <li>
49   * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called
50   * with the instance of the ConfigurationFactory to be used. This must be called
51   * before any other calls to Log4j.</li>
52   * <li>
53   * A ConfigurationFactory implementation can be added to the classpath and
54   * configured as a plugin. The Order annotation should be used to configure the
55   * factory to be the first one inspected. See
56   * {@linkplain XMLConfigurationFactory} for an example.</li>
57   * </ol>
58   *
59   * If the ConfigurationFactory that was added returns null on a call to
60   * getConfiguration the any other ConfigurationFactories found as plugins will
61   * be called in their respective order. DefaultConfiguration is always called
62   * last if no configuration has been returned.
63   */
64  public abstract class ConfigurationFactory {
65      /**
66       * Allow the ConfigurationFactory class to be specified as a system property.
67       */
68      public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
69  
70      /**
71       * Allow the location of the configuration file to be specified as a system property.
72       */
73      public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
74  
75      /**
76       * Allow subclasses access to the status logger without creating another instance.
77       */
78      protected static final Logger LOGGER = StatusLogger.getLogger();
79  
80      /**
81       * File name prefix for test configurations.
82       */
83      protected static final String TEST_PREFIX = "log4j2-test";
84  
85      /**
86       * File name prefix for standard configurations.
87       */
88      protected static final String DEFAULT_PREFIX = "log4j2";
89  
90      private static List<ConfigurationFactory> factories = new ArrayList<ConfigurationFactory>();
91  
92      private static ConfigurationFactory configFactory = new Factory();
93  
94      /**
95       * Returns the ConfigurationFactory.
96       * @return the ConfigurationFactory.
97       */
98      public static ConfigurationFactory getInstance() {
99          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 }