001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements. See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache license, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License. You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the license for the specific language governing permissions and
015     * limitations under the license.
016     */
017    package org.apache.logging.log4j.core.config;
018    
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.FileNotFoundException;
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.net.MalformedURLException;
025    import java.net.URI;
026    import java.net.URISyntaxException;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.Collections;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.TreeSet;
034    import java.util.concurrent.locks.Lock;
035    import java.util.concurrent.locks.ReentrantLock;
036    
037    import org.apache.logging.log4j.Level;
038    import org.apache.logging.log4j.Logger;
039    import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
040    import org.apache.logging.log4j.core.config.plugins.util.PluginType;
041    import org.apache.logging.log4j.core.lookup.Interpolator;
042    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
043    import org.apache.logging.log4j.core.util.FileUtils;
044    import org.apache.logging.log4j.core.util.Loader;
045    import org.apache.logging.log4j.status.StatusLogger;
046    import org.apache.logging.log4j.util.PropertiesUtil;
047    
048    /**
049     * ConfigurationFactory allows the configuration implementation to be
050     * dynamically chosen in 1 of 3 ways:
051     * <ol>
052     * <li>A system property named "log4j.configurationFactory" can be set with the
053     * name of the ConfigurationFactory to be used.</li>
054     * <li>
055     * {@linkplain #setConfigurationFactory(ConfigurationFactory)} can be called
056     * with the instance of the ConfigurationFactory to be used. This must be called
057     * before any other calls to Log4j.</li>
058     * <li>
059     * A ConfigurationFactory implementation can be added to the classpath and
060     * configured as a plugin. The Order annotation should be used to configure the
061     * factory to be the first one inspected. See
062     * {@linkplain org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory} for an example.</li>
063     * </ol>
064     *
065     * If the ConfigurationFactory that was added returns null on a call to
066     * getConfiguration the any other ConfigurationFactories found as plugins will
067     * be called in their respective order. DefaultConfiguration is always called
068     * last if no configuration has been returned.
069     */
070    public abstract class ConfigurationFactory {
071        /**
072         * Allow the ConfigurationFactory class to be specified as a system property.
073         */
074        public static final String CONFIGURATION_FACTORY_PROPERTY = "log4j.configurationFactory";
075    
076        /**
077         * Allow the location of the configuration file to be specified as a system property.
078         */
079        public static final String CONFIGURATION_FILE_PROPERTY = "log4j.configurationFile";
080    
081        /**
082         * Allow subclasses access to the status logger without creating another instance.
083         */
084        protected static final Logger LOGGER = StatusLogger.getLogger();
085    
086        /**
087         * File name prefix for test configurations.
088         */
089        protected static final String TEST_PREFIX = "log4j2-test";
090    
091        /**
092         * File name prefix for standard configurations.
093         */
094        protected static final String DEFAULT_PREFIX = "log4j2";
095    
096        /**
097         * The name of the classloader URI scheme.
098         */
099        private static final String CLASS_LOADER_SCHEME = "classloader";
100    
101        /**
102         * The name of the classpath URI scheme, synonymous with the classloader URI scheme.
103         */
104        private static final String CLASS_PATH_SCHEME = "classpath";
105    
106        private static volatile List<ConfigurationFactory> factories = null;
107    
108        private static ConfigurationFactory configFactory = new Factory();
109    
110        protected final StrSubstitutor substitutor = new StrSubstitutor(new Interpolator());
111    
112        private static final Lock LOCK = new ReentrantLock();
113    
114        /**
115         * Returns the ConfigurationFactory.
116         * @return the ConfigurationFactory.
117         */
118        public static ConfigurationFactory getInstance() {
119            // volatile works in Java 1.6+, so double-checked locking also works properly
120            //noinspection DoubleCheckedLocking
121            if (factories == null) {
122                LOCK.lock();
123                try {
124                    if (factories == null) {
125                        final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();
126                        final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
127                        if (factoryClass != null) {
128                            addFactory(list, factoryClass);
129                        }
130                        final PluginManager manager = new PluginManager("ConfigurationFactory");
131                        manager.collectPlugins();
132                        final Map<String, PluginType<?>> plugins = manager.getPlugins();
133                        final Collection<WeightedFactory> ordered = new TreeSet<WeightedFactory>();
134                        for (final PluginType<?> type : plugins.values()) {
135                            try {
136                                @SuppressWarnings("unchecked")
137                                final Class<ConfigurationFactory> clazz = (Class<ConfigurationFactory>)type.getPluginClass();
138                                final Order order = clazz.getAnnotation(Order.class);
139                                if (order != null) {
140                                    final int weight = order.value();
141                                    ordered.add(new WeightedFactory(weight, clazz));
142                                }
143                            } catch (final Exception ex) {
144                                LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
145                            }
146                        }
147                        for (final WeightedFactory wf : ordered) {
148                            addFactory(list, wf.factoryClass);
149                        }
150                        // see above comments about double-checked locking
151                        //noinspection NonThreadSafeLazyInitialization
152                        factories = Collections.unmodifiableList(list);
153                    }
154                } finally {
155                    LOCK.unlock();
156                }
157            }
158    
159            LOGGER.debug("Using configurationFactory {}", configFactory);
160            return configFactory;
161        }
162    
163        @SuppressWarnings("unchecked")
164        private static void addFactory(final Collection<ConfigurationFactory> list, final String factoryClass) {
165            try {
166                addFactory(list, (Class<ConfigurationFactory>) Loader.loadClass(factoryClass));
167            } catch (final Exception ex) {
168                LOGGER.error("Unable to load class {}", factoryClass, ex);
169            }
170        }
171    
172        private static void addFactory(final Collection<ConfigurationFactory> list,
173                                       final Class<ConfigurationFactory> factoryClass) {
174            try {
175                list.add(factoryClass.getConstructor().newInstance());
176            } catch (final Exception ex) {
177                LOGGER.error("Unable to create instance of {}", factoryClass.getName(), ex);
178            }
179        }
180    
181        /**
182         * Set the configuration factory. This method is not intended for general use and may not be thread safe.
183         * @param factory the ConfigurationFactory.
184         */
185        public static void setConfigurationFactory(final ConfigurationFactory factory) {
186            configFactory = factory;
187        }
188    
189        /**
190         * Reset the ConfigurationFactory to the default. This method is not intended for general use and may
191         * not be thread safe.
192         */
193        public static void resetConfigurationFactory() {
194            configFactory = new Factory();
195        }
196    
197        /**
198         * Remove the ConfigurationFactory. This method is not intended for general use and may not be thread safe.
199         * @param factory The factory to remove.
200         */
201        public static void removeConfigurationFactory(final ConfigurationFactory factory) {
202            if (configFactory == factory) {
203                configFactory = new Factory();
204            }
205        }
206    
207        protected abstract String[] getSupportedTypes();
208    
209        protected boolean isActive() {
210            return true;
211        }
212    
213        public abstract Configuration getConfiguration(ConfigurationSource source);
214    
215        /**
216         * Returns the Configuration.
217         * @param name The configuration name.
218         * @param configLocation The configuration location.
219         * @return The Configuration.
220         */
221        public Configuration getConfiguration(final String name, final URI configLocation) {
222            if (!isActive()) {
223                return null;
224            }
225            if (configLocation != null) {
226                final ConfigurationSource source = getInputFromUri(configLocation);
227                if (source != null) {
228                    return getConfiguration(source);
229                }
230            }
231            return null;
232        }
233    
234        /**
235         * Load the configuration from a URI.
236         * @param configLocation A URI representing the location of the configuration.
237         * @return The ConfigurationSource for the configuration.
238         */
239        protected ConfigurationSource getInputFromUri(final URI configLocation) {
240            final File configFile = FileUtils.fileFromUri(configLocation);
241            if (configFile != null && configFile.exists() && configFile.canRead()) {
242                try {
243                    return new ConfigurationSource(new FileInputStream(configFile), configFile);
244                } catch (final FileNotFoundException ex) {
245                    LOGGER.error("Cannot locate file {}", configLocation.getPath(), ex);
246                }
247            }
248            final String scheme = configLocation.getScheme();
249            final boolean isClassLoaderScheme = scheme != null && scheme.equals(CLASS_LOADER_SCHEME);
250            final boolean isClassPathScheme = scheme != null && !isClassLoaderScheme && scheme.equals(CLASS_PATH_SCHEME);
251            if (scheme == null || isClassLoaderScheme || isClassPathScheme) {
252                final ClassLoader loader = Loader.getThreadContextClassLoader();
253                String path;
254                if (isClassLoaderScheme || isClassPathScheme) {
255                    path = configLocation.getSchemeSpecificPart();
256                } else {
257                    path = configLocation.getPath();
258                }
259                final ConfigurationSource source = getInputFromResource(path, loader);
260                if (source != null) {
261                    return source;
262                }
263            }
264            if (!configLocation.isAbsolute()) { // LOG4J2-704 avoid confusing error message thrown by uri.toURL()
265                LOGGER.error("File not found in file system or classpath: {}", configLocation.toString());
266                return null;
267            }
268            try {
269                return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
270            } catch (final MalformedURLException ex) {
271                LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
272            } catch (final Exception ex) {
273                LOGGER.error("Unable to access {}", configLocation.toString(), ex);
274            }
275            return null;
276        }
277    
278        /**
279         * Load the configuration from the location represented by the String.
280         * @param config The configuration location.
281         * @param loader The default ClassLoader to use.
282         * @return The InputSource to use to read the configuration.
283         */
284        protected ConfigurationSource getInputFromString(final String config, final ClassLoader loader) {
285            try {
286                final URL url = new URL(config);
287                return new ConfigurationSource(url.openStream(), FileUtils.fileFromUri(url.toURI()));
288            } catch (final Exception ex) {
289                final ConfigurationSource source = getInputFromResource(config, loader);
290                if (source == null) {
291                    try {
292                        final File file = new File(config);
293                        return new ConfigurationSource(new FileInputStream(file), file);
294                    } catch (final FileNotFoundException fnfe) {
295                        // Ignore the exception
296                        LOGGER.catching(Level.DEBUG, fnfe);
297                    }
298                }
299                return source;
300            }
301        }
302    
303        /**
304         * Retrieve the configuration via the ClassLoader.
305         * @param resource The resource to load.
306         * @param loader The default ClassLoader to use.
307         * @return The ConfigurationSource for the configuration.
308         */
309        protected ConfigurationSource getInputFromResource(final String resource, final ClassLoader loader) {
310            final URL url = Loader.getResource(resource, loader);
311            if (url == null) {
312                return null;
313            }
314            InputStream is = null;
315            try {
316                is = url.openStream();
317            } catch (final IOException ioe) {
318                LOGGER.catching(Level.DEBUG, ioe);
319                return null;
320            }
321            if (is == null) {
322                return null;
323            }
324    
325            if (FileUtils.isFile(url)) {
326                try {
327                    return new ConfigurationSource(is, FileUtils.fileFromUri(url.toURI()));
328                } catch (final URISyntaxException ex) {
329                    // Just ignore the exception.
330                    LOGGER.catching(Level.DEBUG, ex);
331                }
332            }
333            return new ConfigurationSource(is, url);
334        }
335    
336        /**
337         * Factory that chooses a ConfigurationFactory based on weighting.
338         */
339        private static class WeightedFactory implements Comparable<WeightedFactory> {
340            private final int weight;
341            private final Class<ConfigurationFactory> factoryClass;
342    
343            /**
344             * Constructor.
345             * @param weight The weight.
346             * @param clazz The class.
347             */
348            public WeightedFactory(final int weight, final Class<ConfigurationFactory> clazz) {
349                this.weight = weight;
350                this.factoryClass = clazz;
351            }
352    
353            @Override
354            public int compareTo(final WeightedFactory wf) {
355                final int w = wf.weight;
356                if (weight == w) {
357                    return 0;
358                } else if (weight > w) {
359                    return -1;
360                } else {
361                    return 1;
362                }
363            }
364        }
365    
366        /**
367         * Default Factory.
368         */
369        private static class Factory extends ConfigurationFactory {
370    
371            /**
372             * Default Factory Constructor.
373             * @param name The configuration name.
374             * @param configLocation The configuration location.
375             * @return The Configuration.
376             */
377            @Override
378            public Configuration getConfiguration(final String name, final URI configLocation) {
379    
380                if (configLocation == null) {
381                    final String config = this.substitutor.replace(
382                        PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
383                    if (config != null) {
384                        ConfigurationSource source = null;
385                        try {
386                            source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
387                        } catch (final Exception ex) {
388                            // Ignore the error and try as a String.
389                            LOGGER.catching(Level.DEBUG, ex);
390                        }
391                        if (source == null) {
392                            final ClassLoader loader = this.getClass().getClassLoader();
393                            source = getInputFromString(config, loader);
394                        }
395                        if (source != null) {
396                            for (final ConfigurationFactory factory : factories) {
397                                final String[] types = factory.getSupportedTypes();
398                                if (types != null) {
399                                    for (final String type : types) {
400                                        if (type.equals("*") || config.endsWith(type)) {
401                                            final Configuration c = factory.getConfiguration(source);
402                                            if (c != null) {
403                                                return c;
404                                            }
405                                        }
406                                    }
407                                }
408                            }
409                        }
410                    }
411                } else {
412                    for (final ConfigurationFactory factory : factories) {
413                        final String[] types = factory.getSupportedTypes();
414                        if (types != null) {
415                            for (final String type : types) {
416                                if (type.equals("*") || configLocation.toString().endsWith(type)) {
417                                    final Configuration config = factory.getConfiguration(name, configLocation);
418                                    if (config != null) {
419                                        return config;
420                                    }
421                                }
422                            }
423                        }
424                    }
425                }
426    
427                Configuration config = getConfiguration(true, name);
428                if (config == null) {
429                    config = getConfiguration(true, null);
430                    if (config == null) {
431                        config = getConfiguration(false, name);
432                        if (config == null) {
433                            config = getConfiguration(false, null);
434                        }
435                    }
436                }
437                if (config != null) {
438                    return config;
439                }
440                LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
441                return new DefaultConfiguration();
442            }
443    
444            private Configuration getConfiguration(final boolean isTest, final String name) {
445                final boolean named = name != null && name.length() > 0;
446                final ClassLoader loader = this.getClass().getClassLoader();
447                for (final ConfigurationFactory factory : factories) {
448                    String configName;
449                    final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
450                    final String [] types = factory.getSupportedTypes();
451                    if (types == null) {
452                        continue;
453                    }
454    
455                    for (final String suffix : types) {
456                        if (suffix.equals("*")) {
457                            continue;
458                        }
459                        configName = named ? prefix + name + suffix : prefix + suffix;
460    
461                        final ConfigurationSource source = getInputFromResource(configName, loader);
462                        if (source != null) {
463                            return factory.getConfiguration(source);
464                        }
465                    }
466                }
467                return null;
468            }
469    
470            @Override
471            public String[] getSupportedTypes() {
472                return null;
473            }
474    
475            @Override
476            public Configuration getConfiguration(final ConfigurationSource source) {
477                if (source != null) {
478                    final String config = source.getLocation();
479                    for (final ConfigurationFactory factory : factories) {
480                        final String[] types = factory.getSupportedTypes();
481                        if (types != null) {
482                            for (final String type : types) {
483                                if (type.equals("*") || config != null && config.endsWith(type)) {
484                                    final Configuration c = factory.getConfiguration(source);
485                                    if (c != null) {
486                                        LOGGER.debug("Loaded configuration from {}", source);
487                                        return c;
488                                    }
489                                    LOGGER.error("Cannot determine the ConfigurationFactory to use for {}", config);
490                                    return null;
491                                }
492                            }
493                        }
494                    }
495                }
496                LOGGER.error("Cannot process configuration, input source is null");
497                return null;
498            }
499        }
500    }