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