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