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