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