1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.logging.log4j.core.config.plugins.util;
19
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.AccessibleObject;
22 import java.lang.reflect.Field;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Modifier;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Map;
29
30 import org.apache.logging.log4j.Logger;
31 import org.apache.logging.log4j.core.LogEvent;
32 import org.apache.logging.log4j.core.config.Configuration;
33 import org.apache.logging.log4j.core.config.ConfigurationException;
34 import org.apache.logging.log4j.core.config.Node;
35 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
36 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
37 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
38 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
39 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators;
40 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
41 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors;
42 import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
43 import org.apache.logging.log4j.core.util.Assert;
44 import org.apache.logging.log4j.core.util.Builder;
45 import org.apache.logging.log4j.core.util.ReflectionUtil;
46 import org.apache.logging.log4j.core.util.TypeUtil;
47 import org.apache.logging.log4j.status.StatusLogger;
48 import org.apache.logging.log4j.util.StringBuilders;
49
50
51
52
53
54 public class PluginBuilder implements Builder<Object> {
55
56 private static final Logger LOGGER = StatusLogger.getLogger();
57
58 private final PluginType<?> pluginType;
59 private final Class<?> clazz;
60
61 private Configuration configuration;
62 private Node node;
63 private LogEvent event;
64
65
66
67
68
69
70 public PluginBuilder(final PluginType<?> pluginType) {
71 this.pluginType = pluginType;
72 this.clazz = pluginType.getPluginClass();
73 }
74
75
76
77
78
79
80
81 public PluginBuilder withConfiguration(final Configuration configuration) {
82 this.configuration = configuration;
83 return this;
84 }
85
86
87
88
89
90
91
92 public PluginBuilder withConfigurationNode(final Node node) {
93 this.node = node;
94 return this;
95 }
96
97
98
99
100
101
102
103 public PluginBuilder forLogEvent(final LogEvent event) {
104 this.event = event;
105 return this;
106 }
107
108
109
110
111
112
113 @Override
114 public Object build() {
115 verify();
116
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
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;
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(event == null
190 ? new ConfigurationStrSubstitutor(configuration.getStrSubstitutor())
191 : configuration.getStrSubstitutor())
192 .setMember(field)
193 .visit(configuration, node, event, log);
194
195 if (value != null) {
196 field.set(builder, value);
197 }
198 }
199 }
200 final Collection<ConstraintValidator<?>> validators =
201 ConstraintValidators.findValidators(annotations);
202 final Object value = field.get(builder);
203 for (final ConstraintValidator<?> validator : validators) {
204 if (!validator.isValid(value)) {
205 invalid = true;
206 }
207 }
208 }
209 if (log.length() > 0) {
210 log.append(')');
211 }
212 LOGGER.debug("Calling build() on class {} for element {} {}", builder.getClass(), node.getName(),
213 log.toString());
214 if (invalid) {
215 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
216 }
217 checkForRemainingAttributes();
218 verifyNodeChildrenUsed();
219 }
220
221 private static Method findFactoryMethod(final Class<?> clazz) {
222 for (final Method method : clazz.getDeclaredMethods()) {
223 if (method.isAnnotationPresent(PluginFactory.class) &&
224 Modifier.isStatic(method.getModifiers())) {
225 LOGGER.debug("Found factory method [{}]: {}.", method.getName(), method);
226 ReflectionUtil.makeAccessible(method);
227 return method;
228 }
229 }
230 throw new IllegalStateException("No factory method found for class " + clazz.getName());
231 }
232
233 private Object[] generateParameters(final Method factory) {
234 final StringBuilder log = new StringBuilder();
235 final Class<?>[] types = factory.getParameterTypes();
236 final Annotation[][] annotations = factory.getParameterAnnotations();
237 final Object[] args = new Object[annotations.length];
238 boolean invalid = false;
239 for (int i = 0; i < annotations.length; i++) {
240 log.append(log.length() == 0 ? "with params(" : ", ");
241 final String[] aliases = extractPluginAliases(annotations[i]);
242 for (final Annotation a : annotations[i]) {
243 if (a instanceof PluginAliases) {
244 continue;
245 }
246 final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor(
247 a.annotationType());
248 if (visitor != null) {
249 final Object value = visitor.setAliases(aliases)
250 .setAnnotation(a)
251 .setConversionType(types[i])
252 .setStrSubstitutor(event == null
253 ? new ConfigurationStrSubstitutor(configuration.getStrSubstitutor())
254 : configuration.getStrSubstitutor())
255 .setMember(factory)
256 .visit(configuration, node, event, log);
257
258 if (value != null) {
259 args[i] = value;
260 }
261 }
262 }
263 final Collection<ConstraintValidator<?>> validators =
264 ConstraintValidators.findValidators(annotations[i]);
265 final Object value = args[i];
266 for (final ConstraintValidator<?> validator : validators) {
267 if (!validator.isValid(value)) {
268 invalid = true;
269 }
270 }
271 }
272 if (log.length() > 0) {
273 log.append(')');
274 }
275 checkForRemainingAttributes();
276 verifyNodeChildrenUsed();
277 LOGGER.debug("Calling {} on class {} for element {} {}", factory.getName(), clazz.getName(), node.getName(),
278 log.toString());
279 if (invalid) {
280 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
281 }
282 return args;
283 }
284
285 private static String[] extractPluginAliases(final Annotation... parmTypes) {
286 String[] aliases = null;
287 for (final Annotation a : parmTypes) {
288 if (a instanceof PluginAliases) {
289 aliases = ((PluginAliases) a).value();
290 }
291 }
292 return aliases;
293 }
294
295 private void checkForRemainingAttributes() {
296 final Map<String, String> attrs = node.getAttributes();
297 if (!attrs.isEmpty()) {
298 final StringBuilder sb = new StringBuilder();
299 for (final String key : attrs.keySet()) {
300 if (sb.length() == 0) {
301 sb.append(node.getName());
302 sb.append(" contains ");
303 if (attrs.size() == 1) {
304 sb.append("an invalid element or attribute ");
305 } else {
306 sb.append("invalid attributes ");
307 }
308 } else {
309 sb.append(", ");
310 }
311 StringBuilders.appendDqValue(sb, key);
312
313 }
314 LOGGER.error(sb.toString());
315 }
316 }
317
318 private void verifyNodeChildrenUsed() {
319 final List<Node> children = node.getChildren();
320 if (!(pluginType.isDeferChildren() || children.isEmpty())) {
321 for (final Node child : children) {
322 final String nodeType = node.getType().getElementName();
323 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + ' ' + node.getName();
324 LOGGER.error("{} has no parameter that matches element {}", start, child.getName());
325 }
326 }
327 }
328 }