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