001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 018package org.apache.logging.log4j.core.config.properties; 019 020import java.util.Map; 021import java.util.Properties; 022import java.util.concurrent.TimeUnit; 023 024import org.apache.logging.log4j.Level; 025import org.apache.logging.log4j.core.Appender; 026import org.apache.logging.log4j.core.LoggerContext; 027import org.apache.logging.log4j.core.config.ConfigurationException; 028import org.apache.logging.log4j.core.config.ConfigurationSource; 029import org.apache.logging.log4j.core.config.LoggerConfig; 030import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; 031import org.apache.logging.log4j.core.config.builder.api.AppenderRefComponentBuilder; 032import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; 033import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; 034import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; 035import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder; 036import org.apache.logging.log4j.core.config.builder.api.FilterableComponentBuilder; 037import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; 038import org.apache.logging.log4j.core.config.builder.api.LoggableComponentBuilder; 039import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder; 040import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; 041import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder; 042import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder; 043import org.apache.logging.log4j.core.filter.AbstractFilter.AbstractFilterBuilder; 044import org.apache.logging.log4j.core.util.Builder; 045import org.apache.logging.log4j.util.PropertiesUtil; 046import org.apache.logging.log4j.util.Strings; 047 048/** 049 * Helper builder for parsing properties files into a PropertiesConfiguration. 050 * 051 * @since 2.6 052 */ 053public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory 054 implements Builder<PropertiesConfiguration> { 055 056 private static final String ADVERTISER_KEY = "advertiser"; 057 private static final String STATUS_KEY = "status"; 058 private static final String SHUTDOWN_HOOK = "shutdownHook"; 059 private static final String SHUTDOWN_TIMEOUT = "shutdownTimeout"; 060 private static final String VERBOSE = "verbose"; 061 private static final String DEST = "dest"; 062 private static final String PACKAGES = "packages"; 063 private static final String CONFIG_NAME = "name"; 064 private static final String MONITOR_INTERVAL = "monitorInterval"; 065 private static final String CONFIG_TYPE = "type"; 066 067 private final ConfigurationBuilder<PropertiesConfiguration> builder; 068 private LoggerContext loggerContext; 069 private Properties rootProperties; 070 071 public PropertiesConfigurationBuilder() { 072 this.builder = newConfigurationBuilder(PropertiesConfiguration.class); 073 } 074 075 public PropertiesConfigurationBuilder setRootProperties(final Properties rootProperties) { 076 this.rootProperties = rootProperties; 077 return this; 078 } 079 080 public PropertiesConfigurationBuilder setConfigurationSource(final ConfigurationSource source) { 081 builder.setConfigurationSource(source); 082 return this; 083 } 084 085 @Override 086 public PropertiesConfiguration build() { 087 for (final String key : rootProperties.stringPropertyNames()) { 088 if (!key.contains(".")) { 089 builder.addRootProperty(key, rootProperties.getProperty(key)); 090 } 091 } 092 builder 093 .setStatusLevel(Level.toLevel(rootProperties.getProperty(STATUS_KEY), Level.ERROR)) 094 .setShutdownHook(rootProperties.getProperty(SHUTDOWN_HOOK)) 095 .setShutdownTimeout(Long.parseLong(rootProperties.getProperty(SHUTDOWN_TIMEOUT, "0")), TimeUnit.MILLISECONDS) 096 .setVerbosity(rootProperties.getProperty(VERBOSE)) 097 .setDestination(rootProperties.getProperty(DEST)) 098 .setPackages(rootProperties.getProperty(PACKAGES)) 099 .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 Filter " + 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 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 addLoggersToComponent(loggerBuilder, properties); 280 addFiltersToComponent(loggerBuilder, properties); 281 final String additivity = (String) properties.remove("additivity"); 282 if (!Strings.isEmpty(additivity)) { 283 loggerBuilder.addAttribute("additivity", additivity); 284 } 285 return loggerBuilder; 286 } 287 288 private RootLoggerComponentBuilder createRootLogger(final Properties properties) { 289 final String level = Strings.trimToNull((String) properties.remove("level")); 290 final String type = (String) properties.remove(CONFIG_TYPE); 291 final String location = (String) properties.remove("includeLocation"); 292 final boolean includeLocation; 293 final RootLoggerComponentBuilder loggerBuilder; 294 if (type != null) { 295 if (type.equalsIgnoreCase("asyncRoot")) { 296 if (location != null) { 297 includeLocation = Boolean.parseBoolean(location); 298 loggerBuilder = builder.newAsyncRootLogger(level, includeLocation); 299 } else { 300 loggerBuilder = builder.newAsyncRootLogger(level); 301 } 302 } else { 303 throw new ConfigurationException("Unknown Logger type for root logger" + type); 304 } 305 } else if (location != null) { 306 includeLocation = Boolean.parseBoolean(location); 307 loggerBuilder = builder.newRootLogger(level, includeLocation); 308 } else { 309 loggerBuilder = builder.newRootLogger(level); 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}