View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  
18  package org.apache.logging.log4j.core.config.properties;
19  
20  import java.util.HashMap;
21  import java.util.Map;
22  import java.util.Properties;
23  
24  import org.apache.logging.log4j.Level;
25  import org.apache.logging.log4j.core.config.ConfigurationException;
26  import org.apache.logging.log4j.core.config.ConfigurationSource;
27  import org.apache.logging.log4j.core.config.LoggerConfig;
28  import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
29  import org.apache.logging.log4j.core.config.builder.api.AppenderRefComponentBuilder;
30  import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
31  import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
32  import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
33  import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder;
34  import org.apache.logging.log4j.core.config.builder.api.FilterableComponentBuilder;
35  import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
36  import org.apache.logging.log4j.core.config.builder.api.LoggableComponentBuilder;
37  import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
38  import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
39  import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder;
40  import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder;
41  import org.apache.logging.log4j.core.util.Builder;
42  import org.apache.logging.log4j.util.PropertiesUtil;
43  import org.apache.logging.log4j.util.Strings;
44  
45  /**
46   * Helper builder for parsing properties files into a PropertiesConfiguration.
47   *
48   * @since 2.6
49   */
50  public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory
51      implements Builder<PropertiesConfiguration> {
52  
53      private static final String ADVERTISER_KEY = "advertiser";
54      private static final String STATUS_KEY = "status";
55      private static final String SHUTDOWN_HOOK = "shutdownHook";
56      private static final String VERBOSE = "verbose";
57      private static final String PACKAGES = "packages";
58      private static final String CONFIG_NAME = "name";
59      private static final String MONITOR_INTERVAL = "monitorInterval";
60      private static final String CONFIG_TYPE = "type";
61  
62      private final ConfigurationBuilder<PropertiesConfiguration> builder;
63      private Properties rootProperties;
64  
65      public PropertiesConfigurationBuilder() {
66          this.builder = newConfigurationBuilder(PropertiesConfiguration.class);
67      }
68  
69      public PropertiesConfigurationBuilder setRootProperties(final Properties rootProperties) {
70          this.rootProperties = rootProperties;
71          return this;
72      }
73  
74      public PropertiesConfigurationBuilder setConfigurationSource(final ConfigurationSource source) {
75          builder.setConfigurationSource(source);
76          return this;
77      }
78  
79      @Override
80      public PropertiesConfiguration build() {
81          final Map<String, String> rootProps = new HashMap<>();
82          for (final String key : rootProperties.stringPropertyNames()) {
83              if (!key.contains(".")) {
84                  builder.addRootProperty(key, rootProperties.getProperty(key));
85              }
86          }
87          builder
88              .setStatusLevel(Level.toLevel(rootProperties.getProperty(STATUS_KEY), Level.ERROR))
89              .setShutdownHook(rootProperties.getProperty(SHUTDOWN_HOOK))
90              .setVerbosity(rootProperties.getProperty(VERBOSE))
91              .setPackages(rootProperties.getProperty(PACKAGES))
92              .setConfigurationName(rootProperties.getProperty(CONFIG_NAME))
93              .setMonitorInterval(rootProperties.getProperty(MONITOR_INTERVAL, "0"))
94              .setAdvertiser(rootProperties.getProperty(ADVERTISER_KEY));
95  
96          final Properties propertyPlaceholders = PropertiesUtil.extractSubset(rootProperties, "property");
97          for (final String key : propertyPlaceholders.stringPropertyNames()) {
98              builder.addProperty(key, propertyPlaceholders.getProperty(key));
99          }
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 }