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.plugins.util; 019 020import java.lang.annotation.Annotation; 021import java.lang.reflect.AccessibleObject; 022import java.lang.reflect.Field; 023import java.lang.reflect.InvocationTargetException; 024import java.lang.reflect.Method; 025import java.lang.reflect.Modifier; 026import java.util.Collection; 027import java.util.List; 028import java.util.Map; 029import java.util.Objects; 030 031import org.apache.logging.log4j.Logger; 032import org.apache.logging.log4j.core.LogEvent; 033import org.apache.logging.log4j.core.config.Configuration; 034import org.apache.logging.log4j.core.config.ConfigurationException; 035import org.apache.logging.log4j.core.config.Node; 036import org.apache.logging.log4j.core.config.plugins.PluginAliases; 037import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; 038import org.apache.logging.log4j.core.config.plugins.PluginFactory; 039import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator; 040import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators; 041import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor; 042import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors; 043import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor; 044import org.apache.logging.log4j.core.util.Builder; 045import org.apache.logging.log4j.core.util.ReflectionUtil; 046import org.apache.logging.log4j.core.util.TypeUtil; 047import org.apache.logging.log4j.status.StatusLogger; 048import 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 */ 054public 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={}].", pluginType.getElementName(), 119 pluginType.getPluginClass().getName()); 120 final Builder<?> builder = createBuilder(this.clazz); 121 if (builder != null) { 122 injectFields(builder); 123 return builder.build(); 124 } 125 } catch (final ConfigurationException e) { // LOG4J2-1908 126 LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e); 127 return null; // no point in trying the factory method 128 } catch (final Throwable t) { 129 LOGGER.error("Could not create plugin of type {} for element {}: {}", 130 this.clazz, node.getName(), 131 (t instanceof InvocationTargetException ? ((InvocationTargetException) t).getCause() : t).toString(), t); 132 } 133 // or fall back to factory method if no builder class is available 134 try { 135 final Method factory = findFactoryMethod(this.clazz); 136 final Object[] params = generateParameters(factory); 137 return factory.invoke(null, params); 138 } catch (final Throwable t) { 139 LOGGER.error("Unable to invoke factory method in {} for element {}: {}", 140 this.clazz, this.node.getName(), 141 (t instanceof InvocationTargetException ? ((InvocationTargetException) t).getCause() : t).toString(), t); 142 return null; 143 } 144 } 145 146 private void verify() { 147 Objects.requireNonNull(this.configuration, "No Configuration object was set."); 148 Objects.requireNonNull(this.node, "No Node object was set."); 149 } 150 151 private static Builder<?> createBuilder(final Class<?> clazz) 152 throws InvocationTargetException, IllegalAccessException { 153 for (final Method method : clazz.getDeclaredMethods()) { 154 if (method.isAnnotationPresent(PluginBuilderFactory.class) && 155 Modifier.isStatic(method.getModifiers()) && 156 TypeUtil.isAssignable(Builder.class, method.getReturnType())) { 157 ReflectionUtil.makeAccessible(method); 158 return (Builder<?>) method.invoke(null); 159 } 160 } 161 return null; 162 } 163 164 private void injectFields(final Builder<?> builder) throws IllegalAccessException { 165 final List<Field> fields = TypeUtil.getAllDeclaredFields(builder.getClass()); 166 AccessibleObject.setAccessible(fields.toArray(new Field[] {}), true); 167 final StringBuilder log = new StringBuilder(); 168 boolean invalid = false; 169 String reason = ""; 170 for (final Field field : fields) { 171 log.append(log.length() == 0 ? simpleName(builder) + "(" : ", "); 172 final Annotation[] annotations = field.getDeclaredAnnotations(); 173 final String[] aliases = extractPluginAliases(annotations); 174 for (final Annotation a : annotations) { 175 if (a instanceof PluginAliases) { 176 continue; // already processed 177 } 178 final PluginVisitor<? extends Annotation> visitor = 179 PluginVisitors.findVisitor(a.annotationType()); 180 if (visitor != null) { 181 final Object value = visitor.setAliases(aliases) 182 .setAnnotation(a) 183 .setConversionType(field.getType()) 184 .setStrSubstitutor(event == null 185 ? new ConfigurationStrSubstitutor(configuration.getStrSubstitutor()) 186 : configuration.getStrSubstitutor()) 187 .setMember(field) 188 .visit(configuration, node, event, log); 189 // don't overwrite default values if the visitor gives us no value to inject 190 if (value != null) { 191 field.set(builder, value); 192 } 193 } 194 } 195 final Collection<ConstraintValidator<?>> validators = 196 ConstraintValidators.findValidators(annotations); 197 final Object value = field.get(builder); 198 for (final ConstraintValidator<?> validator : validators) { 199 if (!validator.isValid(field.getName(), value)) { 200 invalid = true; 201 if (!reason.isEmpty()) { 202 reason += ", "; 203 } 204 reason += "field '" + field.getName() + "' has invalid value '" + value + "'"; 205 } 206 } 207 } 208 log.append(log.length() == 0 ? builder.getClass().getSimpleName() + "()" : ")"); 209 LOGGER.debug(log.toString()); 210 if (invalid) { 211 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid: " + reason); 212 } 213 checkForRemainingAttributes(); 214 verifyNodeChildrenUsed(); 215 } 216 217 /** 218 * {@code object.getClass().getSimpleName()} returns {@code Builder}, when we want {@code PatternLayout$Builder}. 219 */ 220 private static String simpleName(final Object object) { 221 if (object == null) { 222 return "null"; 223 } 224 final String cls = object.getClass().getName(); 225 final int index = cls.lastIndexOf('.'); 226 return index < 0 ? cls : cls.substring(index + 1); 227 } 228 229 private static Method findFactoryMethod(final Class<?> clazz) { 230 for (final Method method : clazz.getDeclaredMethods()) { 231 if (method.isAnnotationPresent(PluginFactory.class) && 232 Modifier.isStatic(method.getModifiers())) { 233 ReflectionUtil.makeAccessible(method); 234 return method; 235 } 236 } 237 throw new IllegalStateException("No factory method found for class " + clazz.getName()); 238 } 239 240 private Object[] generateParameters(final Method factory) { 241 final StringBuilder log = new StringBuilder(); 242 final Class<?>[] types = factory.getParameterTypes(); 243 final Annotation[][] annotations = factory.getParameterAnnotations(); 244 final Object[] args = new Object[annotations.length]; 245 boolean invalid = false; 246 for (int i = 0; i < annotations.length; i++) { 247 log.append(log.length() == 0 ? factory.getName() + "(" : ", "); 248 final String[] aliases = extractPluginAliases(annotations[i]); 249 for (final Annotation a : annotations[i]) { 250 if (a instanceof PluginAliases) { 251 continue; // already processed 252 } 253 final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor( 254 a.annotationType()); 255 if (visitor != null) { 256 final Object value = visitor.setAliases(aliases) 257 .setAnnotation(a) 258 .setConversionType(types[i]) 259 .setStrSubstitutor(event == null 260 ? new ConfigurationStrSubstitutor(configuration.getStrSubstitutor()) 261 : configuration.getStrSubstitutor()) 262 .setMember(factory) 263 .visit(configuration, node, event, log); 264 // don't overwrite existing values if the visitor gives us no value to inject 265 if (value != null) { 266 args[i] = value; 267 } 268 } 269 } 270 final Collection<ConstraintValidator<?>> validators = 271 ConstraintValidators.findValidators(annotations[i]); 272 final Object value = args[i]; 273 final String argName = "arg[" + i + "](" + simpleName(value) + ")"; 274 for (final ConstraintValidator<?> validator : validators) { 275 if (!validator.isValid(argName, value)) { 276 invalid = true; 277 } 278 } 279 } 280 log.append(log.length() == 0 ? factory.getName() + "()" : ")"); 281 checkForRemainingAttributes(); 282 verifyNodeChildrenUsed(); 283 LOGGER.debug(log.toString()); 284 if (invalid) { 285 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid"); 286 } 287 return args; 288 } 289 290 private static String[] extractPluginAliases(final Annotation... parmTypes) { 291 String[] aliases = null; 292 for (final Annotation a : parmTypes) { 293 if (a instanceof PluginAliases) { 294 aliases = ((PluginAliases) a).value(); 295 } 296 } 297 return aliases; 298 } 299 300 private void checkForRemainingAttributes() { 301 final Map<String, String> attrs = node.getAttributes(); 302 if (!attrs.isEmpty()) { 303 final StringBuilder sb = new StringBuilder(); 304 for (final String key : attrs.keySet()) { 305 if (sb.length() == 0) { 306 sb.append(node.getName()); 307 sb.append(" contains "); 308 if (attrs.size() == 1) { 309 sb.append("an invalid element or attribute "); 310 } else { 311 sb.append("invalid attributes "); 312 } 313 } else { 314 sb.append(", "); 315 } 316 StringBuilders.appendDqValue(sb, key); 317 } 318 LOGGER.error(sb.toString()); 319 } 320 } 321 322 private void verifyNodeChildrenUsed() { 323 final List<Node> children = node.getChildren(); 324 if (!(pluginType.isDeferChildren() || children.isEmpty())) { 325 for (final Node child : children) { 326 final String nodeType = node.getType().getElementName(); 327 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + ' ' + node.getName(); 328 LOGGER.error("{} has no parameter that matches element {}", start, child.getName()); 329 } 330 } 331 } 332}