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