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