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.Map;
21  import java.util.Properties;
22  import java.util.concurrent.TimeUnit;
23  
24  import org.apache.logging.log4j.Level;
25  import org.apache.logging.log4j.core.Appender;
26  import org.apache.logging.log4j.core.LoggerContext;
27  import org.apache.logging.log4j.core.config.ConfigurationException;
28  import org.apache.logging.log4j.core.config.ConfigurationSource;
29  import org.apache.logging.log4j.core.config.LoggerConfig;
30  import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
31  import org.apache.logging.log4j.core.config.builder.api.AppenderRefComponentBuilder;
32  import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder;
33  import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
34  import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
35  import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder;
36  import org.apache.logging.log4j.core.config.builder.api.FilterableComponentBuilder;
37  import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder;
38  import org.apache.logging.log4j.core.config.builder.api.LoggableComponentBuilder;
39  import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder;
40  import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder;
41  import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder;
42  import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder;
43  import org.apache.logging.log4j.core.util.Builder;
44  import org.apache.logging.log4j.util.PropertiesUtil;
45  import org.apache.logging.log4j.util.Strings;
46  
47  /**
48   * Helper builder for parsing properties files into a PropertiesConfiguration.
49   *
50   * @since 2.6
51   */
52  public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory
53      implements Builder<PropertiesConfiguration> {
54  
55      private static final String ADVERTISER_KEY = "advertiser";
56      private static final String STATUS_KEY = "status";
57      private static final String SHUTDOWN_HOOK = "shutdownHook";
58      private static final String SHUTDOWN_TIMEOUT = "shutdownTimeout";
59      private static final String VERBOSE = "verbose";
60      private static final String DEST = "dest";
61      private static final String PACKAGES = "packages";
62      private static final String CONFIG_NAME = "name";
63      private static final String MONITOR_INTERVAL = "monitorInterval";
64      private static final String CONFIG_TYPE = "type";
65  
66      private final ConfigurationBuilder<PropertiesConfiguration> builder;
67      private LoggerContext loggerContext;
68      private Properties rootProperties;
69  
70      public PropertiesConfigurationBuilder() {
71          this.builder = newConfigurationBuilder(PropertiesConfiguration.class);
72      }
73  
74      public PropertiesConfigurationBuilder setRootProperties(final Properties rootProperties) {
75          this.rootProperties = rootProperties;
76          return this;
77      }
78  
79      public PropertiesConfigurationBuilder setConfigurationSource(final ConfigurationSource source) {
80          builder.setConfigurationSource(source);
81          return this;
82      }
83  
84      @Override
85      public PropertiesConfiguration build() {
86          for (final String key : rootProperties.stringPropertyNames()) {
87              if (!key.contains(".")) {
88                  builder.addRootProperty(key, rootProperties.getProperty(key));
89              }
90          }
91          builder
92              .setStatusLevel(Level.toLevel(rootProperties.getProperty(STATUS_KEY), Level.ERROR))
93              .setShutdownHook(rootProperties.getProperty(SHUTDOWN_HOOK))
94              .setShutdownTimeout(Long.parseLong(rootProperties.getProperty(SHUTDOWN_TIMEOUT, "0")), TimeUnit.MILLISECONDS)
95              .setVerbosity(rootProperties.getProperty(VERBOSE))
96              .setDestination(rootProperties.getProperty(DEST))
97              .setPackages(rootProperties.getProperty(PACKAGES))
98              .setConfigurationName(rootProperties.getProperty(CONFIG_NAME))
99              .setMonitorInterval(rootProperties.getProperty(MONITOR_INTERVAL, "0"))
100             .setAdvertiser(rootProperties.getProperty(ADVERTISER_KEY));
101 
102         final Properties propertyPlaceholders = PropertiesUtil.extractSubset(rootProperties, "property");
103         for (final String key : propertyPlaceholders.stringPropertyNames()) {
104             builder.addProperty(key, propertyPlaceholders.getProperty(key));
105         }
106 
107         final Map<String, Properties> scripts = PropertiesUtil.partitionOnCommonPrefixes(
108             PropertiesUtil.extractSubset(rootProperties, "script"));
109         for (final Map.Entry<String, Properties> entry : scripts.entrySet()) {
110             final Properties scriptProps = entry.getValue();
111             final String type = (String) scriptProps.remove("type");
112             if (type == null) {
113                 throw new ConfigurationException("No type provided for script - must be Script or ScriptFile");
114             }
115             if (type.equalsIgnoreCase("script")) {
116                 builder.add(createScript(scriptProps));
117             } else {
118                 builder.add(createScriptFile(scriptProps));
119             }
120         }
121 
122         final Properties levelProps = PropertiesUtil.extractSubset(rootProperties, "customLevel");
123         if (levelProps.size() > 0) {
124             for (final String key : levelProps.stringPropertyNames()) {
125                 builder.add(builder.newCustomLevel(key, Integer.parseInt(levelProps.getProperty(key))));
126             }
127         }
128 
129         final String filterProp = rootProperties.getProperty("filters");
130         if (filterProp != null) {
131             final String[] filterNames = filterProp.split(",");
132             for (final String filterName : filterNames) {
133                 final String name = filterName.trim();
134                 builder.add(createFilter(name, PropertiesUtil.extractSubset(rootProperties, "filter." + name)));
135             }
136         } else {
137 
138             final Map<String, Properties> filters = PropertiesUtil
139                     .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "filter"));
140             for (final Map.Entry<String, Properties> entry : filters.entrySet()) {
141                 builder.add(createFilter(entry.getKey().trim(), entry.getValue()));
142             }
143         }
144 
145         final String appenderProp = rootProperties.getProperty("appenders");
146         if (appenderProp != null) {
147             final String[] appenderNames = appenderProp.split(",");
148             for (final String appenderName : appenderNames) {
149                 final String name = appenderName.trim();
150                 builder.add(createAppender(appenderName.trim(),
151                         PropertiesUtil.extractSubset(rootProperties, "appender." + name)));
152             }
153         } else {
154             final Map<String, Properties> appenders = PropertiesUtil
155                     .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, Appender.ELEMENT_TYPE));
156             for (final Map.Entry<String, Properties> entry : appenders.entrySet()) {
157                 builder.add(createAppender(entry.getKey().trim(), entry.getValue()));
158             }
159         }
160 
161         final String loggerProp = rootProperties.getProperty("loggers");
162         if (loggerProp != null) {
163             final String[] loggerNames = loggerProp.split(",");
164             for (final String loggerName : loggerNames) {
165                 final String name = loggerName.trim();
166                 if (!name.equals(LoggerConfig.ROOT)) {
167                     builder.add(createLogger(name, PropertiesUtil.extractSubset(rootProperties, "logger." +
168                             name)));
169                 }
170             }
171         } else {
172             final Map<String, Properties> loggers = PropertiesUtil
173                     .partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, "logger"));
174             for (final Map.Entry<String, Properties> entry : loggers.entrySet()) {
175                 final String name = entry.getKey().trim();
176                 if (!name.equals(LoggerConfig.ROOT)) {
177                     builder.add(createLogger(name, entry.getValue()));
178                 }
179             }
180         }
181 
182         final Properties props = PropertiesUtil.extractSubset(rootProperties, "rootLogger");
183         if (props.size() > 0) {
184             builder.add(createRootLogger(props));
185         }
186         
187         builder.setLoggerContext(loggerContext);
188         
189         return builder.build(false);
190     }
191 
192     private ScriptComponentBuilder createScript(final Properties properties) {
193         final String name = (String) properties.remove("name");
194         final String language = (String) properties.remove("language");
195         final String text = (String) properties.remove("text");
196         final ScriptComponentBuilder scriptBuilder = builder.newScript(name, language, text);
197         return processRemainingProperties(scriptBuilder, properties);
198     }
199 
200 
201     private ScriptFileComponentBuilder createScriptFile(final Properties properties) {
202         final String name = (String) properties.remove("name");
203         final String path = (String) properties.remove("path");
204         final ScriptFileComponentBuilder scriptFileBuilder = builder.newScriptFile(name, path);
205         return processRemainingProperties(scriptFileBuilder, properties);
206     }
207 
208     private AppenderComponentBuilder createAppender(final String key, final Properties properties) {
209         final String name = (String) properties.remove(CONFIG_NAME);
210         if (Strings.isEmpty(name)) {
211             throw new ConfigurationException("No name attribute provided for Appender " + key);
212         }
213         final String type = (String) properties.remove(CONFIG_TYPE);
214         if (Strings.isEmpty(type)) {
215             throw new ConfigurationException("No type attribute provided for Appender " + key);
216         }
217         final AppenderComponentBuilder appenderBuilder = builder.newAppender(name, type);
218         addFiltersToComponent(appenderBuilder, properties);
219         final Properties layoutProps = PropertiesUtil.extractSubset(properties, "layout");
220         if (layoutProps.size() > 0) {
221             appenderBuilder.add(createLayout(name, layoutProps));
222         }
223 
224         return processRemainingProperties(appenderBuilder, properties);
225     }
226 
227     private FilterComponentBuilder createFilter(final String key, final Properties properties) {
228         final String type = (String) properties.remove(CONFIG_TYPE);
229         if (Strings.isEmpty(type)) {
230             throw new ConfigurationException("No type attribute provided for Appender " + key);
231         }
232         final String onMatch = (String) properties.remove("onMatch");
233         final String onMisMatch = (String) properties.remove("onMisMatch");
234         final FilterComponentBuilder filterBuilder = builder.newFilter(type, onMatch, onMisMatch);
235         return processRemainingProperties(filterBuilder, properties);
236     }
237 
238     private AppenderRefComponentBuilder createAppenderRef(final String key, final Properties properties) {
239         final String ref = (String) properties.remove("ref");
240         if (Strings.isEmpty(ref)) {
241             throw new ConfigurationException("No ref attribute provided for AppenderRef " + key);
242         }
243         final AppenderRefComponentBuilder appenderRefBuilder = builder.newAppenderRef(ref);
244         final String level = Strings.trimToNull((String) properties.remove("level"));
245         if (!Strings.isEmpty(level)) {
246             appenderRefBuilder.addAttribute("level", level);
247         }
248         return addFiltersToComponent(appenderRefBuilder, properties);
249     }
250 
251     private LoggerComponentBuilder createLogger(final String key, final Properties properties) {
252         final String name = (String) properties.remove(CONFIG_NAME);
253         final String location = (String) properties.remove("includeLocation");
254         if (Strings.isEmpty(name)) {
255             throw new ConfigurationException("No name attribute provided for Logger " + key);
256         }
257         final String level = Strings.trimToNull((String) properties.remove("level"));
258         final String type = (String) properties.remove(CONFIG_TYPE);
259         final LoggerComponentBuilder loggerBuilder;
260         boolean includeLocation;
261         if (type != null) {
262             if (type.equalsIgnoreCase("asyncLogger")) {
263                 if (location != null) {
264                     includeLocation = Boolean.parseBoolean(location);
265                     loggerBuilder = builder.newAsyncLogger(name, level, includeLocation);
266                 } else {
267                     loggerBuilder = builder.newAsyncLogger(name, level);
268                 }
269             } else {
270                 throw new ConfigurationException("Unknown Logger type " + type + " for Logger " + name);
271             }
272         } else {
273             if (location != null) {
274                 includeLocation = Boolean.parseBoolean(location);
275                 loggerBuilder = builder.newLogger(name, level, includeLocation);
276             } else {
277                 loggerBuilder = builder.newLogger(name, level);
278             }
279         }
280         addLoggersToComponent(loggerBuilder, properties);
281         addFiltersToComponent(loggerBuilder, properties);
282         final String additivity = (String) properties.remove("additivity");
283         if (!Strings.isEmpty(additivity)) {
284             loggerBuilder.addAttribute("additivity", additivity);
285         }
286         return loggerBuilder;
287     }
288 
289     private RootLoggerComponentBuilder createRootLogger(final Properties properties) {
290         final String level = Strings.trimToNull((String) properties.remove("level"));
291         final String type = (String) properties.remove(CONFIG_TYPE);
292         final String location = (String) properties.remove("includeLocation");
293         final boolean includeLocation;
294         final RootLoggerComponentBuilder loggerBuilder;
295         if (type != null) {
296             if (type.equalsIgnoreCase("asyncRoot")) {
297                 if (location != null) {
298                     includeLocation = Boolean.parseBoolean(location);
299                     loggerBuilder = builder.newAsyncRootLogger(level, includeLocation);
300                 } else {
301                     loggerBuilder = builder.newAsyncRootLogger(level);
302                 }
303             } else {
304                 throw new ConfigurationException("Unknown Logger type for root logger" + type);
305             }
306         } else {
307             if (location != null) {
308                 includeLocation = Boolean.parseBoolean(location);
309                 loggerBuilder = builder.newRootLogger(level, includeLocation);
310             } else {
311                 loggerBuilder = builder.newRootLogger(level);
312             }
313         }
314         addLoggersToComponent(loggerBuilder, properties);
315         return addFiltersToComponent(loggerBuilder, properties);
316     }
317 
318     private LayoutComponentBuilder createLayout(final String appenderName, final Properties properties) {
319         final String type = (String) properties.remove(CONFIG_TYPE);
320         if (Strings.isEmpty(type)) {
321             throw new ConfigurationException("No type attribute provided for Layout on Appender " + appenderName);
322         }
323         final LayoutComponentBuilder layoutBuilder = builder.newLayout(type);
324         return processRemainingProperties(layoutBuilder, properties);
325     }
326 
327     private static <B extends ComponentBuilder<B>> ComponentBuilder<B> createComponent(final ComponentBuilder<?> parent,
328                                                                                        final String key,
329                                                                                        final Properties properties) {
330         final String name = (String) properties.remove(CONFIG_NAME);
331         final String type = (String) properties.remove(CONFIG_TYPE);
332         if (Strings.isEmpty(type)) {
333             throw new ConfigurationException("No type attribute provided for component " + key);
334         }
335         final ComponentBuilder<B> componentBuilder = parent.getBuilder().newComponent(name, type);
336         return processRemainingProperties(componentBuilder, properties);
337     }
338 
339     private static <B extends ComponentBuilder<?>> B processRemainingProperties(final B builder,
340                                                                                 final Properties properties) {
341         while (properties.size() > 0) {
342             final String propertyName = properties.stringPropertyNames().iterator().next();
343             final int index = propertyName.indexOf('.');
344             if (index > 0) {
345                 final String prefix = propertyName.substring(0, index);
346                 final Properties componentProperties = PropertiesUtil.extractSubset(properties, prefix);
347                 builder.addComponent(createComponent(builder, prefix, componentProperties));
348             } else {
349                 builder.addAttribute(propertyName, properties.getProperty(propertyName));
350                 properties.remove(propertyName);
351             }
352         }
353         return builder;
354     }
355 
356     private <B extends FilterableComponentBuilder<? extends ComponentBuilder<?>>> B addFiltersToComponent(
357         final B componentBuilder, final Properties properties) {
358         final Map<String, Properties> filters = PropertiesUtil.partitionOnCommonPrefixes(
359             PropertiesUtil.extractSubset(properties, "filter"));
360         for (final Map.Entry<String, Properties> entry : filters.entrySet()) {
361             componentBuilder.add(createFilter(entry.getKey().trim(), entry.getValue()));
362         }
363         return componentBuilder;
364     }
365 
366     private <B extends LoggableComponentBuilder<? extends ComponentBuilder<?>>> B addLoggersToComponent(
367         final B loggerBuilder, final Properties properties) {
368         final Map<String, Properties> appenderRefs = PropertiesUtil.partitionOnCommonPrefixes(
369             PropertiesUtil.extractSubset(properties, "appenderRef"));
370         for (final Map.Entry<String, Properties> entry : appenderRefs.entrySet()) {
371             loggerBuilder.add(createAppenderRef(entry.getKey().trim(), entry.getValue()));
372         }
373         return loggerBuilder;
374     }
375 
376     public PropertiesConfigurationBuilder setLoggerContext(final LoggerContext loggerContext) {
377         this.loggerContext = loggerContext;
378         return this;
379     }
380 
381     public LoggerContext getLoggerContext() {
382         return loggerContext;
383     }
384 }