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