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