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                             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 }