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 018 package org.apache.logging.log4j.core.config.plugins.util; 019 020 import java.lang.annotation.Annotation; 021 import java.lang.reflect.AccessibleObject; 022 import java.lang.reflect.Field; 023 import java.lang.reflect.InvocationTargetException; 024 import java.lang.reflect.Method; 025 import java.lang.reflect.Modifier; 026 import java.util.Collection; 027 import java.util.List; 028 import java.util.Map; 029 030 import org.apache.logging.log4j.Logger; 031 import org.apache.logging.log4j.core.LogEvent; 032 import org.apache.logging.log4j.core.config.Configuration; 033 import org.apache.logging.log4j.core.config.ConfigurationException; 034 import org.apache.logging.log4j.core.config.Node; 035 import org.apache.logging.log4j.core.config.plugins.PluginAliases; 036 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 037 import org.apache.logging.log4j.core.config.plugins.PluginFactory; 038 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator; 039 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators; 040 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor; 041 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors; 042 import org.apache.logging.log4j.core.util.Assert; 043 import org.apache.logging.log4j.core.util.Builder; 044 import org.apache.logging.log4j.core.util.ReflectionUtil; 045 import org.apache.logging.log4j.core.util.TypeUtil; 046 import org.apache.logging.log4j.status.StatusLogger; 047 import org.apache.logging.log4j.util.Chars; 048 import org.apache.logging.log4j.util.StringBuilders; 049 050 /** 051 * Builder class to instantiate and configure a Plugin object using a PluginFactory method or PluginBuilderFactory 052 * builder class. 053 */ 054 public class PluginBuilder implements Builder<Object> { 055 056 private static final Logger LOGGER = StatusLogger.getLogger(); 057 058 private final PluginType<?> pluginType; 059 private final Class<?> clazz; 060 061 private Configuration configuration; 062 private Node node; 063 private LogEvent event; 064 065 /** 066 * Constructs a PluginBuilder for a given PluginType. 067 * 068 * @param pluginType type of plugin to configure 069 */ 070 public PluginBuilder(final PluginType<?> pluginType) { 071 this.pluginType = pluginType; 072 this.clazz = pluginType.getPluginClass(); 073 } 074 075 /** 076 * Specifies the Configuration to use for constructing the plugin instance. 077 * 078 * @param configuration the configuration to use. 079 * @return {@code this} 080 */ 081 public PluginBuilder withConfiguration(final Configuration configuration) { 082 this.configuration = configuration; 083 return this; 084 } 085 086 /** 087 * Specifies the Node corresponding to the plugin object that will be created. 088 * 089 * @param node the plugin configuration node to use. 090 * @return {@code this} 091 */ 092 public PluginBuilder withConfigurationNode(final Node node) { 093 this.node = node; 094 return this; 095 } 096 097 /** 098 * Specifies the LogEvent that may be used to provide extra context for string substitutions. 099 * 100 * @param event the event to use for extra information. 101 * @return {@code this} 102 */ 103 public PluginBuilder forLogEvent(final LogEvent event) { 104 this.event = event; 105 return this; 106 } 107 108 /** 109 * Builds the plugin object. 110 * 111 * @return the plugin object or {@code null} if there was a problem creating it. 112 */ 113 @Override 114 public Object build() { 115 verify(); 116 // first try to use a builder class if one is available 117 try { 118 LOGGER.debug("Building Plugin[name={}, class={}]. Searching for builder factory method...", pluginType.getElementName(), 119 pluginType.getPluginClass().getName()); 120 final Builder<?> builder = createBuilder(this.clazz); 121 if (builder != null) { 122 injectFields(builder); 123 final Object result = builder.build(); 124 LOGGER.debug("Built Plugin[name={}] OK from builder factory method.", pluginType.getElementName()); 125 return result; 126 } 127 } catch (final Exception e) { 128 LOGGER.error("Unable to inject fields into builder class for plugin type {}, element {}.", this.clazz, 129 node.getName(), e); 130 } 131 // or fall back to factory method if no builder class is available 132 try { 133 LOGGER.debug("Still building Plugin[name={}, class={}]. Searching for factory method...", 134 pluginType.getElementName(), pluginType.getPluginClass().getName()); 135 final Method factory = findFactoryMethod(this.clazz); 136 final Object[] params = generateParameters(factory); 137 final Object plugin = factory.invoke(null, params); 138 LOGGER.debug("Built Plugin[name={}] OK from factory method.", pluginType.getElementName()); 139 return plugin; 140 } catch (final Exception e) { 141 LOGGER.error("Unable to invoke factory method in class {} for element {}.", this.clazz, this.node.getName(), 142 e); 143 return null; 144 } 145 } 146 147 private void verify() { 148 Assert.requireNonNull(this.configuration, "No Configuration object was set."); 149 Assert.requireNonNull(this.node, "No Node object was set."); 150 } 151 152 private static Builder<?> createBuilder(final Class<?> clazz) 153 throws InvocationTargetException, IllegalAccessException { 154 for (final Method method : clazz.getDeclaredMethods()) { 155 if (method.isAnnotationPresent(PluginBuilderFactory.class) && 156 Modifier.isStatic(method.getModifiers()) && 157 TypeUtil.isAssignable(Builder.class, method.getGenericReturnType())) { 158 ReflectionUtil.makeAccessible(method); 159 @SuppressWarnings("unchecked") 160 final Builder<?> builder = (Builder<?>) method.invoke(null); 161 LOGGER.debug("Found builder factory method [{}]: {}.", method.getName(), method); 162 return builder; 163 } 164 } 165 LOGGER.debug("No builder factory method found in class {}. Going to try finding a factory method instead.", 166 clazz.getName()); 167 return null; 168 } 169 170 private void injectFields(final Builder<?> builder) throws IllegalAccessException { 171 final Field[] fields = builder.getClass().getDeclaredFields(); 172 AccessibleObject.setAccessible(fields, true); 173 final StringBuilder log = new StringBuilder(); 174 boolean invalid = false; 175 for (final Field field : fields) { 176 log.append(log.length() == 0 ? "with params(" : ", "); 177 final Annotation[] annotations = field.getDeclaredAnnotations(); 178 final String[] aliases = extractPluginAliases(annotations); 179 for (final Annotation a : annotations) { 180 if (a instanceof PluginAliases) { 181 continue; // already processed 182 } 183 final PluginVisitor<? extends Annotation> visitor = 184 PluginVisitors.findVisitor(a.annotationType()); 185 if (visitor != null) { 186 final Object value = visitor.setAliases(aliases) 187 .setAnnotation(a) 188 .setConversionType(field.getType()) 189 .setStrSubstitutor(configuration.getStrSubstitutor()) 190 .setMember(field) 191 .visit(configuration, node, event, log); 192 // don't overwrite default values if the visitor gives us no value to inject 193 if (value != null) { 194 field.set(builder, value); 195 } 196 } 197 } 198 final Collection<ConstraintValidator<?>> validators = 199 ConstraintValidators.findValidators(annotations); 200 final Object value = field.get(builder); 201 for (final ConstraintValidator<?> validator : validators) { 202 if (!validator.isValid(value)) { 203 invalid = true; 204 } 205 } 206 } 207 if (log.length() > 0) { 208 log.append(')'); 209 } 210 LOGGER.debug("Calling build() on class {} for element {} {}", builder.getClass(), node.getName(), 211 log.toString()); 212 if (invalid) { 213 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid"); 214 } 215 checkForRemainingAttributes(); 216 verifyNodeChildrenUsed(); 217 } 218 219 private static Method findFactoryMethod(final Class<?> clazz) { 220 for (final Method method : clazz.getDeclaredMethods()) { 221 if (method.isAnnotationPresent(PluginFactory.class) && 222 Modifier.isStatic(method.getModifiers())) { 223 LOGGER.debug("Found factory method [{}]: {}.", method.getName(), method); 224 ReflectionUtil.makeAccessible(method); 225 return method; 226 } 227 } 228 throw new IllegalStateException("No factory method found for class " + clazz.getName()); 229 } 230 231 private Object[] generateParameters(final Method factory) { 232 final StringBuilder log = new StringBuilder(); 233 final Class<?>[] types = factory.getParameterTypes(); 234 final Annotation[][] annotations = factory.getParameterAnnotations(); 235 final Object[] args = new Object[annotations.length]; 236 boolean invalid = false; 237 for (int i = 0; i < annotations.length; i++) { 238 log.append(log.length() == 0 ? "with params(" : ", "); 239 final String[] aliases = extractPluginAliases(annotations[i]); 240 for (final Annotation a : annotations[i]) { 241 if (a instanceof PluginAliases) { 242 continue; // already processed 243 } 244 final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor( 245 a.annotationType()); 246 if (visitor != null) { 247 final Object value = visitor.setAliases(aliases) 248 .setAnnotation(a) 249 .setConversionType(types[i]) 250 .setStrSubstitutor(configuration.getStrSubstitutor()) 251 .setMember(factory) 252 .visit(configuration, node, event, log); 253 // don't overwrite existing values if the visitor gives us no value to inject 254 if (value != null) { 255 args[i] = value; 256 } 257 } 258 } 259 final Collection<ConstraintValidator<?>> validators = 260 ConstraintValidators.findValidators(annotations[i]); 261 final Object value = args[i]; 262 for (final ConstraintValidator<?> validator : validators) { 263 if (!validator.isValid(value)) { 264 invalid = true; 265 } 266 } 267 } 268 if (log.length() > 0) { 269 log.append(')'); 270 } 271 checkForRemainingAttributes(); 272 verifyNodeChildrenUsed(); 273 LOGGER.debug("Calling {} on class {} for element {} {}", factory.getName(), clazz.getName(), node.getName(), 274 log.toString()); 275 if (invalid) { 276 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid"); 277 } 278 return args; 279 } 280 281 private static String[] extractPluginAliases(final Annotation... parmTypes) { 282 String[] aliases = null; 283 for (final Annotation a : parmTypes) { 284 if (a instanceof PluginAliases) { 285 aliases = ((PluginAliases) a).value(); 286 } 287 } 288 return aliases; 289 } 290 291 private void checkForRemainingAttributes() { 292 final Map<String, String> attrs = node.getAttributes(); 293 if (!attrs.isEmpty()) { 294 final StringBuilder sb = new StringBuilder(); 295 for (final String key : attrs.keySet()) { 296 if (sb.length() == 0) { 297 sb.append(node.getName()); 298 sb.append(" contains "); 299 if (attrs.size() == 1) { 300 sb.append("an invalid element or attribute "); 301 } else { 302 sb.append("invalid attributes "); 303 } 304 } else { 305 sb.append(", "); 306 } 307 StringBuilders.appendDqValue(sb, key); 308 309 } 310 LOGGER.error(sb.toString()); 311 } 312 } 313 314 private void verifyNodeChildrenUsed() { 315 final List<Node> children = node.getChildren(); 316 if (!(pluginType.isDeferChildren() || children.isEmpty())) { 317 for (final Node child : children) { 318 final String nodeType = node.getType().getElementName(); 319 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + ' ' + node.getName(); 320 LOGGER.error("{} has no parameter that matches element {}", start, child.getName()); 321 } 322 } 323 } 324 }