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
018package org.apache.logging.log4j.core.config.properties;
019
020import java.util.HashMap;
021import java.util.Map;
022import java.util.Properties;
023
024import org.apache.logging.log4j.Level;
025import org.apache.logging.log4j.core.config.ConfigurationException;
026import org.apache.logging.log4j.core.config.ConfigurationSource;
027import org.apache.logging.log4j.core.config.LoggerConfig;
028import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
029import org.apache.logging.log4j.core.config.builder.api.AppenderRefComponentBuilder;
030import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
031import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
032import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
033import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder;
034import org.apache.logging.log4j.core.config.builder.api.FilterableComponentBuilder;
035import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
036import org.apache.logging.log4j.core.config.builder.api.LoggableComponentBuilder;
037import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
038import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
039import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder;
040import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder;
041import org.apache.logging.log4j.core.util.Builder;
042import org.apache.logging.log4j.util.PropertiesUtil;
043import org.apache.logging.log4j.util.Strings;
044
045/**
046 * Helper builder for parsing properties files into a PropertiesConfiguration.
047 *
048 * @since 2.6
049 */
050public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory
051    implements Builder<PropertiesConfiguration> {
052
053    private static final String ADVERTISER_KEY = "advertiser";
054    private static final String STATUS_KEY = "status";
055    private static final String SHUTDOWN_HOOK = "shutdownHook";
056    private static final String VERBOSE = "verbose";
057    private static final String PACKAGES = "packages";
058    private static final String CONFIG_NAME = "name";
059    private static final String MONITOR_INTERVAL = "monitorInterval";
060    private static final String CONFIG_TYPE = "type";
061
062    private final ConfigurationBuilder<PropertiesConfiguration> builder;
063    private Properties rootProperties;
064
065    public PropertiesConfigurationBuilder() {
066        this.builder = newConfigurationBuilder(PropertiesConfiguration.class);
067    }
068
069    public PropertiesConfigurationBuilder setRootProperties(final Properties rootProperties) {
070        this.rootProperties = rootProperties;
071        return this;
072    }
073
074    public PropertiesConfigurationBuilder setConfigurationSource(final ConfigurationSource source) {
075        builder.setConfigurationSource(source);
076        return this;
077    }
078
079    @Override
080    public PropertiesConfiguration build() {
081        final Map<String, String> rootProps = new HashMap<>();
082        for (final String key : rootProperties.stringPropertyNames()) {
083            if (!key.contains(".")) {
084                builder.addRootProperty(key, rootProperties.getProperty(key));
085            }
086        }
087        builder
088            .setStatusLevel(Level.toLevel(rootProperties.getProperty(STATUS_KEY), Level.ERROR))
089            .setShutdownHook(rootProperties.getProperty(SHUTDOWN_HOOK))
090            .setVerbosity(rootProperties.getProperty(VERBOSE))
091            .setPackages(rootProperties.getProperty(PACKAGES))
092            .setConfigurationName(rootProperties.getProperty(CONFIG_NAME))
093            .setMonitorInterval(rootProperties.getProperty(MONITOR_INTERVAL, "0"))
094            .setAdvertiser(rootProperties.getProperty(ADVERTISER_KEY));
095
096        final Properties propertyPlaceholders = PropertiesUtil.extractSubset(rootProperties, "property");
097        for (final String key : propertyPlaceholders.stringPropertyNames()) {
098            builder.addProperty(key, propertyPlaceholders.getProperty(key));
099        }
100
101        final Map<String, Properties> scripts = PropertiesUtil.partitionOnCommonPrefixes(
102            PropertiesUtil.extractSubset(rootProperties, "script"));
103        for (final Map.Entry<String, Properties> entry : scripts.entrySet()) {
104            final Properties scriptProps = entry.getValue();
105            final String type = (String) scriptProps.remove("type");
106            if (type == null) {
107                throw new ConfigurationException("No type provided for script - must be Script or ScriptFile");
108            }
109            if (type.equalsIgnoreCase("script")) {
110                builder.add(createScript(scriptProps));
111            } else {
112                builder.add(createScriptFile(scriptProps));
113            }
114        }
115
116        final Properties levelProps = PropertiesUtil.extractSubset(rootProperties, "customLevel");
117        if (levelProps.size() > 0) {
118            for (final String key : levelProps.stringPropertyNames()) {
119                builder.add(builder.newCustomLevel(key, Integer.parseInt(levelProps.getProperty(key))));
120            }
121        }
122
123        final String filterProp = rootProperties.getProperty("filters");
124        if (filterProp != null) {
125            final String[] filterNames = filterProp.split(",");
126            for (final String filterName : filterNames) {
127                final String name = filterName.trim();
128                builder.add(createFilter(name, PropertiesUtil.extractSubset(rootProperties, "filter." + name)));
129            }
130        } else {
131
132            final Map<String, Properties> filters = PropertiesUtil
133                    .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "filter"));
134            for (final Map.Entry<String, Properties> entry : filters.entrySet()) {
135                builder.add(createFilter(entry.getKey().trim(), entry.getValue()));
136            }
137        }
138
139        final String appenderProp = rootProperties.getProperty("appenders");
140        if (appenderProp != null) {
141            final String[] appenderNames = appenderProp.split(",");
142            for (final String appenderName : appenderNames) {
143                final String name = appenderName.trim();
144                builder.add(createAppender(appenderName.trim(),
145                        PropertiesUtil.extractSubset(rootProperties, "appender." + name)));
146            }
147        } else {
148            final Map<String, Properties> appenders = PropertiesUtil
149                    .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "appender"));
150            for (final Map.Entry<String, Properties> entry : appenders.entrySet()) {
151                builder.add(createAppender(entry.getKey().trim(), entry.getValue()));
152            }
153        }
154
155        final String loggerProp = rootProperties.getProperty("loggers");
156        if (loggerProp != null) {
157            final String[] loggerNames = loggerProp.split(",");
158            for (final String loggerName : loggerNames) {
159                final String name = loggerName.trim();
160                if (!name.equals(LoggerConfig.ROOT)) {
161                    builder.add(createLogger(name, PropertiesUtil.extractSubset(rootProperties, "logger." +
162                            name)));
163                }
164            }
165        } else {
166            final Map<String, Properties> loggers = PropertiesUtil
167                    .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "logger"));
168            for (final Map.Entry<String, Properties> entry : loggers.entrySet()) {
169                final String name = entry.getKey().trim();
170                if (!name.equals(LoggerConfig.ROOT)) {
171                    builder.add(createLogger(name, entry.getValue()));
172                }
173            }
174        }
175
176        final Properties props = PropertiesUtil.extractSubset(rootProperties, "rootLogger");
177        if (props.size() > 0) {
178            builder.add(createRootLogger(props));
179        }
180
181        return builder.build(false);
182    }
183
184    private ScriptComponentBuilder createScript(final Properties properties) {
185        final String name = (String) properties.remove("name");
186        final String language = (String) properties.remove("language");
187        final String text = (String) properties.remove("text");
188        final ScriptComponentBuilder scriptBuilder = builder.newScript(name, language, text);
189        return processRemainingProperties(scriptBuilder, properties);
190    }
191
192
193    private ScriptFileComponentBuilder createScriptFile(final Properties properties) {
194        final String name = (String) properties.remove("name");
195        final String path = (String) properties.remove("path");
196        final ScriptFileComponentBuilder scriptFileBuilder = builder.newScriptFile(name, path);
197        return processRemainingProperties(scriptFileBuilder, properties);
198    }
199
200    private AppenderComponentBuilder createAppender(final String key, final Properties properties) {
201        final String name = (String) properties.remove(CONFIG_NAME);
202        if (Strings.isEmpty(name)) {
203            throw new ConfigurationException("No name attribute provided for Appender " + key);
204        }
205        final String type = (String) properties.remove(CONFIG_TYPE);
206        if (Strings.isEmpty(type)) {
207            throw new ConfigurationException("No type attribute provided for Appender " + key);
208        }
209        final AppenderComponentBuilder appenderBuilder = builder.newAppender(name, type);
210        addFiltersToComponent(appenderBuilder, properties);
211        final Properties layoutProps = PropertiesUtil.extractSubset(properties, "layout");
212        if (layoutProps.size() > 0) {
213            appenderBuilder.add(createLayout(name, layoutProps));
214        }
215
216        return processRemainingProperties(appenderBuilder, properties);
217    }
218
219    private FilterComponentBuilder createFilter(final String key, final Properties properties) {
220        final String type = (String) properties.remove(CONFIG_TYPE);
221        if (Strings.isEmpty(type)) {
222            throw new ConfigurationException("No type attribute provided for Appender " + key);
223        }
224        final String onMatch = (String) properties.remove("onMatch");
225        final String onMisMatch = (String) properties.remove("onMisMatch");
226        final FilterComponentBuilder filterBuilder = builder.newFilter(type, onMatch, onMisMatch);
227        return processRemainingProperties(filterBuilder, properties);
228    }
229
230    private AppenderRefComponentBuilder createAppenderRef(final String key, final Properties properties) {
231        final String ref = (String) properties.remove("ref");
232        if (Strings.isEmpty(ref)) {
233            throw new ConfigurationException("No ref attribute provided for AppenderRef " + key);
234        }
235        final AppenderRefComponentBuilder appenderRefBuilder = builder.newAppenderRef(ref);
236        final String level = (String) properties.remove("level");
237        if (!Strings.isEmpty(level)) {
238            appenderRefBuilder.addAttribute("level", level);
239        }
240        return addFiltersToComponent(appenderRefBuilder, properties);
241    }
242
243    private LoggerComponentBuilder createLogger(final String key, final Properties properties) {
244        final String name = (String) properties.remove(CONFIG_NAME);
245        final String location = (String) properties.remove("includeLocation");
246        if (Strings.isEmpty(name)) {
247            throw new ConfigurationException("No name attribute provided for Logger " + key);
248        }
249        final String level = (String) properties.remove("level");
250        final String type = (String) properties.remove(CONFIG_TYPE);
251        final LoggerComponentBuilder loggerBuilder;
252        boolean includeLocation;
253        if (type != null) {
254            if (type.equalsIgnoreCase("asyncLogger")) {
255                if (location != null) {
256                    includeLocation = Boolean.parseBoolean(location);
257                    loggerBuilder = builder.newAsyncLogger(name, level, includeLocation);
258                } else {
259                    loggerBuilder = builder.newAsyncLogger(name, level);
260                }
261            } else {
262                throw new ConfigurationException("Unknown Logger type " + type + " for Logger " + name);
263            }
264        } else {
265            if (location != null) {
266                includeLocation = Boolean.parseBoolean(location);
267                loggerBuilder = builder.newLogger(name, level, includeLocation);
268            } else {
269                loggerBuilder = builder.newLogger(name, level);
270            }
271        }
272        addLoggersToComponent(loggerBuilder, properties);
273        addFiltersToComponent(loggerBuilder, properties);
274        final String additivity = (String) properties.remove("additivity");
275        if (!Strings.isEmpty(additivity)) {
276            loggerBuilder.addAttribute("additivity", additivity);
277        }
278        return loggerBuilder;
279    }
280
281    private RootLoggerComponentBuilder createRootLogger(final Properties properties) {
282        final String level = (String) properties.remove("level");
283        final String type = (String) properties.remove(CONFIG_TYPE);
284        final String location = (String) properties.remove("includeLocation");
285        final boolean includeLocation;
286        final RootLoggerComponentBuilder loggerBuilder;
287        if (type != null) {
288            if (type.equalsIgnoreCase("asyncRoot")) {
289                if (location != null) {
290                    includeLocation = Boolean.parseBoolean(location);
291                    loggerBuilder = builder.newAsyncRootLogger(level, includeLocation);
292                } else {
293                    loggerBuilder = builder.newAsyncRootLogger(level);
294                }
295            } else {
296                throw new ConfigurationException("Unknown Logger type for root logger" + type);
297            }
298        } else {
299            if (location != null) {
300                includeLocation = Boolean.parseBoolean(location);
301                loggerBuilder = builder.newRootLogger(level, includeLocation);
302            } else {
303                loggerBuilder = builder.newRootLogger(level);
304            }
305        }
306        addLoggersToComponent(loggerBuilder, properties);
307        return addFiltersToComponent(loggerBuilder, properties);
308    }
309
310    private LayoutComponentBuilder createLayout(final String appenderName, final Properties properties) {
311        final String type = (String) properties.remove(CONFIG_TYPE);
312        if (Strings.isEmpty(type)) {
313            throw new ConfigurationException("No type attribute provided for Layout on Appender " + appenderName);
314        }
315        final LayoutComponentBuilder layoutBuilder = builder.newLayout(type);
316        return processRemainingProperties(layoutBuilder, properties);
317    }
318
319    private static <B extends ComponentBuilder<B>> ComponentBuilder<B> createComponent(final ComponentBuilder<?> parent,
320                                                                                       final String key,
321                                                                                       final Properties properties) {
322        final String name = (String) properties.remove(CONFIG_NAME);
323        final String type = (String) properties.remove(CONFIG_TYPE);
324        if (Strings.isEmpty(type)) {
325            throw new ConfigurationException("No type attribute provided for component " + key);
326        }
327        final ComponentBuilder<B> componentBuilder = parent.getBuilder().newComponent(name, type);
328        return processRemainingProperties(componentBuilder, properties);
329    }
330
331    private static <B extends ComponentBuilder<?>> B processRemainingProperties(final B builder,
332                                                                                final Properties properties) {
333        while (properties.size() > 0) {
334            final String propertyName = properties.stringPropertyNames().iterator().next();
335            final int index = propertyName.indexOf('.');
336            if (index > 0) {
337                final String prefix = propertyName.substring(0, index);
338                final Properties componentProperties = PropertiesUtil.extractSubset(properties, prefix);
339                builder.addComponent(createComponent(builder, prefix, componentProperties));
340            } else {
341                builder.addAttribute(propertyName, properties.getProperty(propertyName));
342                properties.remove(propertyName);
343            }
344        }
345        return builder;
346    }
347
348    private <B extends FilterableComponentBuilder<? extends ComponentBuilder<?>>> B addFiltersToComponent(
349        final B componentBuilder, final Properties properties) {
350        final Map<String, Properties> filters = PropertiesUtil.partitionOnCommonPrefixes(
351            PropertiesUtil.extractSubset(properties, "filter"));
352        for (final Map.Entry<String, Properties> entry : filters.entrySet()) {
353            componentBuilder.add(createFilter(entry.getKey().trim(), entry.getValue()));
354        }
355        return componentBuilder;
356    }
357
358    private <B extends LoggableComponentBuilder<? extends ComponentBuilder<?>>> B addLoggersToComponent(
359        final B loggerBuilder, final Properties properties) {
360        final Map<String, Properties> appenderRefs = PropertiesUtil.partitionOnCommonPrefixes(
361            PropertiesUtil.extractSubset(properties, "appenderRef"));
362        for (final Map.Entry<String, Properties> entry : appenderRefs.entrySet()) {
363            loggerBuilder.add(createAppenderRef(entry.getKey().trim(), entry.getValue()));
364        }
365        return loggerBuilder;
366    }
367}