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