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