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    }