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