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 =
147                        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                    ConfigurationSource source = null;
397                    try {
398                        source = getInputFromUri(NetUtils.toURI(configLocationStr));
399                    } catch (final Exception ex) {
400                        // Ignore the error and try as a String.
401                        LOGGER.catching(Level.DEBUG, ex);
402                    }
403                    if (source == null) {
404                        final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
405                        source = getInputFromString(configLocationStr, loader);
406                    }
407                    if (source != null) {
408                        for (final ConfigurationFactory factory : getFactories()) {
409                            final String[] types = factory.getSupportedTypes();
410                            if (types != null) {
411                                for (final String type : types) {
412                                    if (type.equals("*") || configLocationStr.endsWith(type)) {
413                                        final Configuration config = factory.getConfiguration(source);
414                                        if (config != null) {
415                                            return config;
416                                        }
417                                    }
418                                }
419                            }
420                        }
421                    }
422                } else {
423                    for (final ConfigurationFactory factory : getFactories()) {
424                        final String[] types = factory.getSupportedTypes();
425                        if (types != null) {
426                            for (final String type : types) {
427                                if (type.equals("*")) {
428                                    final Configuration config = factory.getConfiguration(name, configLocation);
429                                    if (config != null) {
430                                        return config;
431                                    }
432                                }
433                            }
434                        }
435                    }
436                }
437            } else {
438                // configLocation != null
439                final String configLocationStr = configLocation.toString();
440                for (final ConfigurationFactory factory : getFactories()) {
441                    final String[] types = factory.getSupportedTypes();
442                    if (types != null) {
443                        for (final String type : types) {
444                            if (type.equals("*") || configLocationStr.endsWith(type)) {
445                                final Configuration config = factory.getConfiguration(name, configLocation);
446                                if (config != null) {
447                                    return config;
448                                }
449                            }
450                        }
451                    }
452                }
453            }
454
455            Configuration config = getConfiguration(true, name);
456            if (config == null) {
457                config = getConfiguration(true, null);
458                if (config == null) {
459                    config = getConfiguration(false, name);
460                    if (config == null) {
461                        config = getConfiguration(false, null);
462                    }
463                }
464            }
465            if (config != null) {
466                return config;
467            }
468            LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
469            return new DefaultConfiguration();
470        }
471
472        private Configuration getConfiguration(final boolean isTest, final String name) {
473            final boolean named = Strings.isNotEmpty(name);
474            final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
475            for (final ConfigurationFactory factory : getFactories()) {
476                String configName;
477                final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
478                final String [] types = factory.getSupportedTypes();
479                if (types == null) {
480                    continue;
481                }
482
483                for (final String suffix : types) {
484                    if (suffix.equals("*")) {
485                        continue;
486                    }
487                    configName = named ? prefix + name + suffix : prefix + suffix;
488
489                    final ConfigurationSource source = getInputFromResource(configName, loader);
490                    if (source != null) {
491                        return factory.getConfiguration(source);
492                    }
493                }
494            }
495            return null;
496        }
497
498        @Override
499        public String[] getSupportedTypes() {
500            return null;
501        }
502
503        @Override
504        public Configuration getConfiguration(final ConfigurationSource source) {
505            if (source != null) {
506                final String config = source.getLocation();
507                for (final ConfigurationFactory factory : getFactories()) {
508                    final String[] types = factory.getSupportedTypes();
509                    if (types != null) {
510                        for (final String type : types) {
511                            if (type.equals("*") || config != null && config.endsWith(type)) {
512                                final Configuration c = factory.getConfiguration(source);
513                                if (c != null) {
514                                    LOGGER.debug("Loaded configuration from {}", source);
515                                    return c;
516                                }
517                                LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config);
518                                return null;
519                            }
520                        }
521                    }
522                }
523            }
524            LOGGER.error("Cannot process configuration, input source is null");
525            return null;
526        }
527    }
528
529    static List<ConfigurationFactory> getFactories() {
530        return factories;
531    }
532}