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