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