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.util.Builder; 044import org.apache.logging.log4j.util.PropertiesUtil; 045import org.apache.logging.log4j.util.Strings; 046 047/** 048 * Helper builder for parsing properties files into a PropertiesConfiguration. 049 * 050 * @since 2.6 051 */ 052public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory 053 implements Builder<PropertiesConfiguration> { 054 055 private static final String ADVERTISER_KEY = "advertiser"; 056 private static final String STATUS_KEY = "status"; 057 private static final String SHUTDOWN_HOOK = "shutdownHook"; 058 private static final String SHUTDOWN_TIMEOUT = "shutdownTimeout"; 059 private static final String VERBOSE = "verbose"; 060 private static final String DEST = "dest"; 061 private static final String PACKAGES = "packages"; 062 private static final String CONFIG_NAME = "name"; 063 private static final String MONITOR_INTERVAL = "monitorInterval"; 064 private static final String CONFIG_TYPE = "type"; 065 066 private final ConfigurationBuilder<PropertiesConfiguration> builder; 067 private LoggerContext loggerContext; 068 private Properties rootProperties; 069 070 public PropertiesConfigurationBuilder() { 071 this.builder = newConfigurationBuilder(PropertiesConfiguration.class); 072 } 073 074 public PropertiesConfigurationBuilder setRootProperties(final Properties rootProperties) { 075 this.rootProperties = rootProperties; 076 return this; 077 } 078 079 public PropertiesConfigurationBuilder setConfigurationSource(final ConfigurationSource source) { 080 builder.setConfigurationSource(source); 081 return this; 082 } 083 084 @Override 085 public PropertiesConfiguration build() { 086 for (final String key : rootProperties.stringPropertyNames()) { 087 if (!key.contains(".")) { 088 builder.addRootProperty(key, rootProperties.getProperty(key)); 089 } 090 } 091 builder 092 .setStatusLevel(Level.toLevel(rootProperties.getProperty(STATUS_KEY), Level.ERROR)) 093 .setShutdownHook(rootProperties.getProperty(SHUTDOWN_HOOK)) 094 .setShutdownTimeout(Long.parseLong(rootProperties.getProperty(SHUTDOWN_TIMEOUT, "0")), TimeUnit.MILLISECONDS) 095 .setVerbosity(rootProperties.getProperty(VERBOSE)) 096 .setDestination(rootProperties.getProperty(DEST)) 097 .setPackages(rootProperties.getProperty(PACKAGES)) 098 .setConfigurationName(rootProperties.getProperty(CONFIG_NAME)) 099 .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}