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