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(ConfigurationSource source) {
75          builder.setConfigurationSource(source);
76          return this;
77      }
78  
79      @Override
80      public PropertiesConfiguration build() {
81          Map<String, String> rootProps = new HashMap<>();
82          for (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 Map<String, Properties> filters = PropertiesUtil.partitionOnCommonPrefixes(
124             PropertiesUtil.extractSubset(rootProperties, "filter"));
125         for (final Map.Entry<String, Properties> entry : filters.entrySet()) {
126             builder.add(createFilter(entry.getKey().trim(), entry.getValue()));
127         }
128 
129         final Map<String, Properties> appenders = PropertiesUtil.partitionOnCommonPrefixes(
130             PropertiesUtil.extractSubset(rootProperties, "appender"));
131         for (final Map.Entry<String, Properties> entry : appenders.entrySet()) {
132             builder.add(createAppender(entry.getKey().trim(), entry.getValue()));
133         }
134 
135         final Map<String, Properties> loggers = PropertiesUtil.partitionOnCommonPrefixes(
136             PropertiesUtil.extractSubset(rootProperties, "logger"));
137         for (final Map.Entry<String, Properties> entry : loggers.entrySet()) {
138             final String name = entry.getKey().trim();
139             if (!name.equals(LoggerConfig.ROOT)) {
140                 builder.add(createLogger(name, entry.getValue()));
141             }
142         }
143 
144         final Properties props = PropertiesUtil.extractSubset(rootProperties, "rootLogger");
145         if (props.size() > 0) {
146             builder.add(createRootLogger(props));
147         }
148 
149         return builder.build(false);
150     }
151 
152     private ScriptComponentBuilder createScript(final Properties properties) {
153         final String name = (String) properties.remove("name");
154         final String language = (String) properties.remove("language");
155         final String text = (String) properties.remove("text");
156         final ScriptComponentBuilder scriptBuilder = builder.newScript(name, language, text);
157         return processRemainingProperties(scriptBuilder, properties);
158     }
159 
160 
161     private ScriptFileComponentBuilder createScriptFile(final Properties properties) {
162         final String name = (String) properties.remove("name");
163         final String path = (String) properties.remove("path");
164         final ScriptFileComponentBuilder scriptFileBuilder = builder.newScriptFile(name, path);
165         return processRemainingProperties(scriptFileBuilder, properties);
166     }
167 
168     private AppenderComponentBuilder createAppender(final String key, final Properties properties) {
169         final String name = (String) properties.remove(CONFIG_NAME);
170         if (Strings.isEmpty(name)) {
171             throw new ConfigurationException("No name attribute provided for Appender " + key);
172         }
173         final String type = (String) properties.remove(CONFIG_TYPE);
174         if (Strings.isEmpty(type)) {
175             throw new ConfigurationException("No type attribute provided for Appender " + key);
176         }
177         final AppenderComponentBuilder appenderBuilder = builder.newAppender(name, type);
178         addFiltersToComponent(appenderBuilder, properties);
179         final Properties layoutProps = PropertiesUtil.extractSubset(properties, "layout");
180         if (layoutProps.size() > 0) {
181             appenderBuilder.add(createLayout(name, layoutProps));
182         }
183 
184         return processRemainingProperties(appenderBuilder, properties);
185     }
186 
187     private FilterComponentBuilder createFilter(final String key, final Properties properties) {
188         final String type = (String) properties.remove(CONFIG_TYPE);
189         if (Strings.isEmpty(type)) {
190             throw new ConfigurationException("No type attribute provided for Appender " + key);
191         }
192         final String onMatch = (String) properties.remove("onMatch");
193         final String onMisMatch = (String) properties.remove("onMisMatch");
194         final FilterComponentBuilder filterBuilder = builder.newFilter(type, onMatch, onMisMatch);
195         return processRemainingProperties(filterBuilder, properties);
196     }
197 
198     private AppenderRefComponentBuilder createAppenderRef(final String key, final Properties properties) {
199         final String ref = (String) properties.remove("ref");
200         if (Strings.isEmpty(ref)) {
201             throw new ConfigurationException("No ref attribute provided for AppenderRef " + key);
202         }
203         final AppenderRefComponentBuilder appenderRefBuilder = builder.newAppenderRef(ref);
204         final String level = (String) properties.remove("level");
205         if (!Strings.isEmpty(level)) {
206             appenderRefBuilder.addAttribute("level", level);
207         }
208         return addFiltersToComponent(appenderRefBuilder, properties);
209     }
210 
211     private LoggerComponentBuilder createLogger(final String key, final Properties properties) {
212         final String name = (String) properties.remove(CONFIG_NAME);
213         final String location = (String) properties.remove("includeLocation");
214         if (Strings.isEmpty(name)) {
215             throw new ConfigurationException("No name attribute provided for Logger " + key);
216         }
217         final String level = (String) properties.remove("level");
218         final String type = (String) properties.remove(CONFIG_TYPE);
219         final LoggerComponentBuilder loggerBuilder;
220         boolean includeLocation;
221         if (type != null) {
222             if (type.equalsIgnoreCase("asyncLogger")) {
223                 if (location != null) {
224                     includeLocation = Boolean.parseBoolean(location);
225                     loggerBuilder = builder.newAsyncLogger(name, level, includeLocation);
226                 } else {
227                     loggerBuilder = builder.newAsyncLogger(name, level);
228                 }
229             } else {
230                 throw new ConfigurationException("Unknown Logger type " + type + " for Logger " + name);
231             }
232         } else {
233             if (location != null) {
234                 includeLocation = Boolean.parseBoolean(location);
235                 loggerBuilder = builder.newLogger(name, level, includeLocation);
236             } else {
237                 loggerBuilder = builder.newLogger(name, level);
238             }
239         }
240         addLoggersToComponent(loggerBuilder, properties);
241         addFiltersToComponent(loggerBuilder, properties);
242         final String additivity = (String) properties.remove("additivity");
243         if (!Strings.isEmpty(additivity)) {
244             loggerBuilder.addAttribute("additivity", additivity);
245         }
246         return loggerBuilder;
247     }
248 
249     private RootLoggerComponentBuilder createRootLogger(final Properties properties) {
250         final String level = (String) properties.remove("level");
251         final String type = (String) properties.remove(CONFIG_TYPE);
252         final String location = (String) properties.remove("includeLocation");
253         final boolean includeLocation;
254         final RootLoggerComponentBuilder loggerBuilder;
255         if (type != null) {
256             if (type.equalsIgnoreCase("asyncRoot")) {
257                 if (location != null) {
258                     includeLocation = Boolean.parseBoolean(location);
259                     loggerBuilder = builder.newAsyncRootLogger(level, includeLocation);
260                 } else {
261                     loggerBuilder = builder.newAsyncRootLogger(level);
262                 }
263             } else {
264                 throw new ConfigurationException("Unknown Logger type for root logger" + type);
265             }
266         } else {
267             if (location != null) {
268                 includeLocation = Boolean.parseBoolean(location);
269                 loggerBuilder = builder.newRootLogger(level, includeLocation);
270             } else {
271                 loggerBuilder = builder.newRootLogger(level);
272             }
273         }
274         addLoggersToComponent(loggerBuilder, properties);
275         return addFiltersToComponent(loggerBuilder, properties);
276     }
277 
278     private LayoutComponentBuilder createLayout(final String appenderName, final Properties properties) {
279         final String type = (String) properties.remove(CONFIG_TYPE);
280         if (Strings.isEmpty(type)) {
281             throw new ConfigurationException("No type attribute provided for Layout on Appender " + appenderName);
282         }
283         final LayoutComponentBuilder layoutBuilder = builder.newLayout(type);
284         return processRemainingProperties(layoutBuilder, properties);
285     }
286 
287     private static <B extends ComponentBuilder<B>> ComponentBuilder<B> createComponent(final ComponentBuilder<?> parent,
288                                                                                        final String key,
289                                                                                        final Properties properties) {
290         final String name = (String) properties.remove(CONFIG_NAME);
291         final String type = (String) properties.remove(CONFIG_TYPE);
292         if (Strings.isEmpty(type)) {
293             throw new ConfigurationException("No type attribute provided for component " + key);
294         }
295         final ComponentBuilder<B> componentBuilder = parent.getBuilder().newComponent(name, type);
296         return processRemainingProperties(componentBuilder, properties);
297     }
298 
299     private static <B extends ComponentBuilder<?>> B processRemainingProperties(final B builder,
300                                                                                 final Properties properties) {
301         while (properties.size() > 0) {
302             final String propertyName = properties.stringPropertyNames().iterator().next();
303             int index = propertyName.indexOf('.');
304             if (index > 0) {
305                 final String prefix = propertyName.substring(0, index);
306                 final Properties componentProperties = PropertiesUtil.extractSubset(properties, prefix);
307                 builder.addComponent(createComponent(builder, prefix, componentProperties));
308             } else {
309                 builder.addAttribute(propertyName, properties.getProperty(propertyName));
310                 properties.remove(propertyName);
311             }
312         }
313         return builder;
314     }
315 
316     private <B extends FilterableComponentBuilder<? extends ComponentBuilder<?>>> B addFiltersToComponent(
317         final B componentBuilder, final Properties properties) {
318         final Map<String, Properties> filters = PropertiesUtil.partitionOnCommonPrefixes(
319             PropertiesUtil.extractSubset(properties, "filter"));
320         for (final Map.Entry<String, Properties> entry : filters.entrySet()) {
321             componentBuilder.add(createFilter(entry.getKey().trim(), entry.getValue()));
322         }
323         return componentBuilder;
324     }
325 
326     private <B extends LoggableComponentBuilder<? extends ComponentBuilder<?>>> B addLoggersToComponent(
327         final B loggerBuilder, final Properties properties) {
328         final Map<String, Properties> appenderRefs = PropertiesUtil.partitionOnCommonPrefixes(
329             PropertiesUtil.extractSubset(properties, "appenderRef"));
330         for (final Map.Entry<String, Properties> entry : appenderRefs.entrySet()) {
331             loggerBuilder.add(createAppenderRef(entry.getKey().trim(), entry.getValue()));
332         }
333         return loggerBuilder;
334     }
335 }