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 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}