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