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.HashMap; 021import java.util.Map; 022import java.util.Properties; 023 024import org.apache.logging.log4j.Level; 025import org.apache.logging.log4j.core.config.ConfigurationException; 026import org.apache.logging.log4j.core.config.ConfigurationSource; 027import org.apache.logging.log4j.core.config.LoggerConfig; 028import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder; 029import org.apache.logging.log4j.core.config.builder.api.AppenderRefComponentBuilder; 030import org.apache.logging.log4j.core.config.builder.api.ComponentBuilder; 031import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; 032import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; 033import org.apache.logging.log4j.core.config.builder.api.FilterComponentBuilder; 034import org.apache.logging.log4j.core.config.builder.api.FilterableComponentBuilder; 035import org.apache.logging.log4j.core.config.builder.api.LayoutComponentBuilder; 036import org.apache.logging.log4j.core.config.builder.api.LoggableComponentBuilder; 037import org.apache.logging.log4j.core.config.builder.api.LoggerComponentBuilder; 038import org.apache.logging.log4j.core.config.builder.api.RootLoggerComponentBuilder; 039import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder; 040import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder; 041import org.apache.logging.log4j.core.util.Builder; 042import org.apache.logging.log4j.util.PropertiesUtil; 043import org.apache.logging.log4j.util.Strings; 044 045/** 046 * Helper builder for parsing properties files into a PropertiesConfiguration. 047 * 048 * @since 2.6 049 */ 050public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory 051 implements Builder<PropertiesConfiguration> { 052 053 private static final String ADVERTISER_KEY = "advertiser"; 054 private static final String STATUS_KEY = "status"; 055 private static final String SHUTDOWN_HOOK = "shutdownHook"; 056 private static final String VERBOSE = "verbose"; 057 private static final String PACKAGES = "packages"; 058 private static final String CONFIG_NAME = "name"; 059 private static final String MONITOR_INTERVAL = "monitorInterval"; 060 private static final String CONFIG_TYPE = "type"; 061 062 private final ConfigurationBuilder<PropertiesConfiguration> builder; 063 private Properties rootProperties; 064 065 public PropertiesConfigurationBuilder() { 066 this.builder = newConfigurationBuilder(PropertiesConfiguration.class); 067 } 068 069 public PropertiesConfigurationBuilder setRootProperties(final Properties rootProperties) { 070 this.rootProperties = rootProperties; 071 return this; 072 } 073 074 public PropertiesConfigurationBuilder setConfigurationSource(ConfigurationSource source) { 075 builder.setConfigurationSource(source); 076 return this; 077 } 078 079 @Override 080 public PropertiesConfiguration build() { 081 Map<String, String> rootProps = new HashMap<>(); 082 for (String key : rootProperties.stringPropertyNames()) { 083 if (!key.contains(".")) { 084 builder.addRootProperty(key, rootProperties.getProperty(key)); 085 } 086 } 087 builder 088 .setStatusLevel(Level.toLevel(rootProperties.getProperty(STATUS_KEY), Level.ERROR)) 089 .setShutdownHook(rootProperties.getProperty(SHUTDOWN_HOOK)) 090 .setVerbosity(rootProperties.getProperty(VERBOSE)) 091 .setPackages(rootProperties.getProperty(PACKAGES)) 092 .setConfigurationName(rootProperties.getProperty(CONFIG_NAME)) 093 .setMonitorInterval(rootProperties.getProperty(MONITOR_INTERVAL, "0")) 094 .setAdvertiser(rootProperties.getProperty(ADVERTISER_KEY)); 095 096 final Properties propertyPlaceholders = PropertiesUtil.extractSubset(rootProperties, "property"); 097 for (final String key : propertyPlaceholders.stringPropertyNames()) { 098 builder.addProperty(key, propertyPlaceholders.getProperty(key)); 099 } 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}