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