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 java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileNotFoundException;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.net.MalformedURLException;
025    import java.net.URI;
026    import java.net.URISyntaxException;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.Collections;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.concurrent.locks.Lock;
034    import java.util.concurrent.locks.ReentrantLock;
035    
036    import org.apache.logging.log4j.Level;
037    import org.apache.logging.log4j.Logger;
038    import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
039    import org.apache.logging.log4j.core.config.plugins.util.PluginType;
040    import org.apache.logging.log4j.core.lookup.Interpolator;
041    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
042    import org.apache.logging.log4j.core.util.FileUtils;
043    import org.apache.logging.log4j.core.util.Loader;
044    import org.apache.logging.log4j.core.util.ReflectionUtil;
045    import org.apache.logging.log4j.status.StatusLogger;
046    import org.apache.logging.log4j.util.LoaderUtil;
047    import org.apache.logging.log4j.util.PropertiesUtil;
048    
049    /**
050     * Factory class for parsed {@link Configuration} objects from a configuration file.
051     * ConfigurationFactory allows the configuration implementation to be
052     * dynamically chosen in 1 of 3 ways:
053     * <ol>
054     * <li>A system property named "log4j.configurationFactory" can be set with the
055     * name of the ConfigurationFactory to be used.</li>
056     * <li>
057     * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called
058     * with the instance of the ConfigurationFactory to be used. This must be called
059     * before any other calls to Log4j.</li>
060     * <li>
061     * A ConfigurationFactory implementation can be added to the classpath and configured as a plugin in the
062     * {@link #CATEGORY ConfigurationFactory} category. The {@link Order} annotation should be used to configure the
063     * factory to be the first one inspected. See
064     * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.</li>
065     * </ol>
066     *
067     * If the ConfigurationFactory that was added returns null on a call to
068     * getConfiguration the any other ConfigurationFactories found as plugins will
069     * be called in their respective order. DefaultConfiguration is always called
070     * last if no configuration has been returned.
071     */
072    public abstract class ConfigurationFactory {
073        /**
074         * Allow the ConfigurationFactory class to be specified as a system property.
075         */
076        public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
077    
078        /**
079         * Allow the location of the configuration file to be specified as a system property.
080         */
081        public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
082    
083        /**
084         * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin}
085         * class.
086         *
087         * @since 2.1
088         */
089        public static final String CATEGORY = "ConfigurationFactory";
090    
091        /**
092         * Allow subclasses access to the status logger without creating another instance.
093         */
094        protected static final Logger LOGGER = StatusLogger.getLogger();
095    
096        /**
097         * File name prefix for test configurations.
098         */
099        protected static final String TEST_PREFIX = "log4j2-test";
100    
101        /**
102         * File name prefix for standard configurations.
103         */
104        protected static final String DEFAULT_PREFIX = "log4j2";
105    
106        /**
107         * The name of the classloader URI scheme.
108         */
109        private static final String CLASS_LOADER_SCHEME = "classloader";
110    
111        /**
112         * The name of the classpath URI scheme, synonymous with the classloader URI scheme.
113         */
114        private static final String CLASS_PATH_SCHEME = "classpath";
115    
116        private static volatile List<ConfigurationFactory> factories = null;
117    
118        private static ConfigurationFactory configFactory = new Factory();
119    
120        protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator());
121    
122        private static final Lock LOCK = new ReentrantLock();
123    
124        /**
125         * Returns the ConfigurationFactory.
126         * @return the ConfigurationFactory.
127         */
128        public static ConfigurationFactory getInstance() {
129            // volatile works in Java 1.6+, so double-checked locking also works properly
130            //noinspection DoubleCheckedLocking
131            if (factories == null) {
132                LOCK.lock();
133                try {
134                    if (factories == null) {
135                        final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();
136                        final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
137                        if (factoryClass != null) {
138                            addFactory(list, factoryClass);
139                        }
140                        final PluginManager manager = new PluginManager(CATEGORY);
141                        manager.collectPlugins();
142                        final Map<String, PluginType<?>> plugins = manager.getPlugins();
143                        final List<Class<? extends ConfigurationFactory>> ordered =
144                            new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());
145                        for (final PluginType<?> type : plugins.values()) {
146                            try {
147                                ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
148                            } catch (final Exception ex) {
149                                LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
150                            }
151                        }
152                        Collections.sort(ordered, OrderComparator.getInstance());
153                        for (final Class<? extends ConfigurationFactory> clazz : ordered) {
154                            addFactory(list, clazz);
155                        }
156                        // see above comments about double-checked locking
157                        //noinspection NonThreadSafeLazyInitialization
158                        factories = Collections.unmodifiableList(list);
159                    }
160                } finally {
161                    LOCK.unlock();
162                }
163            }
164    
165            LOGGER.debug("Using configurationFactory {}", configFactory);
166            return configFactory;
167        }
168    
169        private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) {
170            try {
171                addFactory(list, LoaderUtil.loadClass(factoryClass).asSubclass(ConfigurationFactory.class));
172            } catch (final Exception ex) {
173                LOGGER.error("Unable to load class {}", factoryClass, ex);
174            }
175        }
176    
177        private static void addFactory(final Collection<ConfigurationFactory> list,
178                                       final Class<? extends ConfigurationFactory> factoryClass) {
179            try {
180                list.add(ReflectionUtil.instantiate(factoryClass));
181            } catch (final Exception ex) {
182                LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex);
183            }
184        }
185    
186        /**
187         * Set the configuration factory. This method is not intended for general use and may not be thread safe.
188         * @param factory the ConfigurationFactory.
189         */
190        public static void setConfigurationFactory(final ConfigurationFactory factory) {
191            configFactory = factory;
192        }
193    
194        /**
195         * Reset the ConfigurationFactory to the default. This method is not intended for general use and may
196         * not be thread safe.
197         */
198        public static void resetConfigurationFactory() {
199            configFactory = new Factory();
200        }
201    
202        /**
203         * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe.
204         * @param factory The factory to remove.
205         */
206        public static void removeConfigurationFactory(final ConfigurationFactory factory) {
207            if (configFactory == factory) {
208                configFactory = new Factory();
209            }
210        }
211    
212        protected abstract String[] getSupportedTypes();
213    
214        protected boolean isActive() {
215            return true;
216        }
217    
218        public abstract Configuration getConfiguration(ConfigurationSource source);
219    
220        /**
221         * Returns the Configuration.
222         * @param name The configuration name.
223         * @param configLocation The configuration location.
224         * @return The Configuration.
225         */
226        public Configuration getConfiguration(final String name, final URI configLocation) {
227            if (!isActive()) {
228                return null;
229            }
230            if (configLocation != null) {
231                final ConfigurationSource source = getInputFromUri(configLocation);
232                if (source != null) {
233                    return getConfiguration(source);
234                }
235            }
236            return null;
237        }
238    
239        /**
240         * Returns the Configuration obtained using a given ClassLoader.
241         *
242         * @param name The configuration name.
243         * @param configLocation A URI representing the location of the configuration.
244         * @param loader The default ClassLoader to use. If this is {@code null}, then the
245         *               {@linkplain LoaderUtil#getThreadContextClassLoader() default ClassLoader} will be used.
246         * @return The Configuration.
247         * @since 2.1
248         */
249        public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
250            if (!isActive()) {
251                return null;
252            }
253            if (loader == null) {
254                return getConfiguration(name, configLocation);
255            }
256            if (isClassLoaderUri(configLocation)) {
257                final String path = extractClassLoaderUriPath(configLocation);
258                final ConfigurationSource source = getInputFromResource(path, loader);
259                if (source != null) {
260                    final Configuration configuration = getConfiguration(source);
261                    if (configuration != null) {
262                        return configuration;
263                    }
264                }
265            }
266            return getConfiguration(name, configLocation);
267        }
268    
269        /**
270         * Load the configuration from a URI.
271         * @param configLocation A URI representing the location of the configuration.
272         * @return The ConfigurationSource for the configuration.
273         */
274        protected ConfigurationSource getInputFromUri(final URI configLocation) {
275            final File configFile = FileUtils.fileFromUri(configLocation);
276            if (configFile != null && configFile.exists() && configFile.canRead()) {
277                try {
278                    return new ConfigurationSource(new FileInputStream(configFile), configFile);
279                } catch (final FileNotFoundException ex) {
280                    LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
281                }
282            }
283            if (isClassLoaderUri(configLocation)) {
284                final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
285                final String path = extractClassLoaderUriPath(configLocation);
286                final ConfigurationSource source = getInputFromResource(path, loader);
287                if (source != null) {
288                    return source;
289                }
290            }
291            if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
292                LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
293                return null;
294            }
295            try {
296                return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
297            } catch (final MalformedURLException ex) {
298                LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
299            } catch (final Exception ex) {
300                LOGGER.error("Unable to access {}", configLocation.toString(), ex);
301            }
302            return null;
303        }
304    
305        private static boolean isClassLoaderUri(final URI uri) {
306            if (uri == null) {
307                return false;
308            }
309            final String scheme = uri.getScheme();
310            return scheme == null || scheme.equals(CLASS_LOADER_SCHEME) || scheme.equals(CLASS_PATH_SCHEME);
311        }
312    
313        private static String extractClassLoaderUriPath(final URI uri) {
314            return uri.getScheme() == null ? uri.getPath() : uri.getSchemeSpecificPart();
315        }
316    
317        /**
318         * Load the configuration from the location represented by the String.
319         * @param config The configuration location.
320         * @param loader The default ClassLoader to use.
321         * @return The InputSource to use to read the configuration.
322         */
323        protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) {
324            try {
325                final URL url = new URL(config);
326                return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI()));
327            } catch (final Exception ex) {
328                final ConfigurationSource source = getInputFromResource(config, loader);
329                if (source == null) {
330                    try {
331                        final File file = new File(config);
332                        return new ConfigurationSource(new FileInputStream(file), file);
333                    } catch (final FileNotFoundException fnfe) {
334                        // Ignore the exception
335                        LOGGER.catching(Level.DEBUG, fnfe);
336                    }
337                }
338                return source;
339            }
340        }
341    
342        /**
343         * Retrieve the configuration via the ClassLoader.
344         * @param resource The resource to load.
345         * @param loader The default ClassLoader to use.
346         * @return The ConfigurationSource for the configuration.
347         */
348        protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) {
349            final URL url = Loader.getResource(resource, loader);
350            if (url == null) {
351                return null;
352            }
353            InputStream is = null;
354            try {
355                is = url.openStream();
356            } catch (final IOException ioe) {
357                LOGGER.catching(Level.DEBUG, ioe);
358                return null;
359            }
360            if (is == null) {
361                return null;
362            }
363    
364            if (FileUtils.isFile(url)) {
365                try {
366                    return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
367                } catch (final URISyntaxException ex) {
368                    // Just ignore the exception.
369                    LOGGER.catching(Level.DEBUG, ex);
370                }
371            }
372            return new ConfigurationSource(is, url);
373        }
374    
375        /**
376         * Default Factory.
377         */
378        private static class Factory extends ConfigurationFactory {
379    
380            /**
381             * Default Factory Constructor.
382             * @param name The configuration name.
383             * @param configLocation The configuration location.
384             * @return The Configuration.
385             */
386            @Override
387            public Configuration getConfiguration(final String name, final URI configLocation) {
388    
389                if (configLocation == null) {
390                    final String config = this.substitutor.replace(
391                        PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
392                    if (config != null) {
393                        ConfigurationSource source = null;
394                        try {
395                            source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
396                        } catch (final Exception ex) {
397                            // Ignore the error and try as a String.
398                            LOGGER.catching(Level.DEBUG, ex);
399                        }
400                        if (source == null) {
401                            final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
402                            source = getInputFromString(config, loader);
403                        }
404                        if (source != null) {
405                            for (final ConfigurationFactory factory : factories) {
406                                final String[] types = factory.getSupportedTypes();
407                                if (types != null) {
408                                    for (final String type : types) {
409                                        if (type.equals("*") || config.endsWith(type)) {
410                                            final Configuration c = factory.getConfiguration(source);
411                                            if (c != null) {
412                                                return c;
413                                            }
414                                        }
415                                    }
416                                }
417                            }
418                        }
419                    }
420                } else {
421                    for (final ConfigurationFactory factory : factories) {
422                        final String[] types = factory.getSupportedTypes();
423                        if (types != null) {
424                            for (final String type : types) {
425                                if (type.equals("*") || configLocation.toString().endsWith(type)) {
426                                    final Configuration config = factory.getConfiguration(name, configLocation);
427                                    if (config != null) {
428                                        return config;
429                                    }
430                                }
431                            }
432                        }
433                    }
434                }
435    
436                Configuration config = getConfiguration(true, name);
437                if (config == null) {
438                    config = getConfiguration(true, null);
439                    if (config == null) {
440                        config = getConfiguration(false, name);
441                        if (config == null) {
442                            config = getConfiguration(false, null);
443                        }
444                    }
445                }
446                if (config != null) {
447                    return config;
448                }
449                LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
450                return new DefaultConfiguration();
451            }
452    
453            private Configuration getConfiguration(final boolean isTest, final String name) {
454                final boolean named = name != null && name.length() > 0;
455                final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
456                for (final ConfigurationFactory factory : factories) {
457                    String configName;
458                    final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
459                    final String [] types = factory.getSupportedTypes();
460                    if (types == null) {
461                        continue;
462                    }
463    
464                    for (final String suffix : types) {
465                        if (suffix.equals("*")) {
466                            continue;
467                        }
468                        configName = named ? prefix + name + suffix : prefix + suffix;
469    
470                        final ConfigurationSource source = getInputFromResource(configName, loader);
471                        if (source != null) {
472                            return factory.getConfiguration(source);
473                        }
474                    }
475                }
476                return null;
477            }
478    
479            @Override
480            public String[] getSupportedTypes() {
481                return null;
482            }
483    
484            @Override
485            public Configuration getConfiguration(final ConfigurationSource source) {
486                if (source != null) {
487                    final String config = source.getLocation();
488                    for (final ConfigurationFactory factory : factories) {
489                        final String[] types = factory.getSupportedTypes();
490                        if (types != null) {
491                            for (final String type : types) {
492                                if (type.equals("*") || config != null && config.endsWith(type)) {
493                                    final Configuration c = factory.getConfiguration(source);
494                                    if (c != null) {
495                                        LOGGER.debug("Loaded configuration from {}", source);
496                                        return c;
497                                    }
498                                    LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config);
499                                    return null;
500                                }
501                            }
502                        }
503                    }
504                }
505                LOGGER.error("Cannot process configuration, input source is null");
506                return null;
507            }
508        }
509    }