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.util.Assert;
43 import org.apache.logging.log4j.core.util.Builder;
44 import org.apache.logging.log4j.core.util.ReflectionUtil;
45 import org.apache.logging.log4j.core.util.TypeUtil;
46 import org.apache.logging.log4j.status.StatusLogger;
47
48
49
50
51
52 public class PluginBuilder implements Builder<Object> {
53
54 private static final Logger LOGGER = StatusLogger.getLogger();
55
56 private final PluginType<?> pluginType;
57 private final Class<?> clazz;
58
59 private Configuration configuration;
60 private Node node;
61 private LogEvent event;
62
63
64
65
66
67
68 public PluginBuilder(final PluginType<?> pluginType) {
69 this.pluginType = pluginType;
70 this.clazz = pluginType.getPluginClass();
71 }
72
73
74
75
76
77
78
79 public PluginBuilder withConfiguration(final Configuration configuration) {
80 this.configuration = configuration;
81 return this;
82 }
83
84
85
86
87
88
89
90 public PluginBuilder withConfigurationNode(final Node node) {
91 this.node = node;
92 return this;
93 }
94
95
96
97
98
99
100
101 public PluginBuilder forLogEvent(final LogEvent event) {
102 this.event = event;
103 return this;
104 }
105
106
107
108
109
110
111 @Override
112 public Object build() {
113 verify();
114
115 try {
116 LOGGER.debug("Building Plugin[name={}, class={}]. Searching for builder factory method...", pluginType.getElementName(),
117 pluginType.getPluginClass().getName());
118 final Builder<?> builder = createBuilder(this.clazz);
119 if (builder != null) {
120 injectFields(builder);
121 final Object result = builder.build();
122 LOGGER.debug("Built Plugin[name={}] OK from builder factory method.", pluginType.getElementName());
123 return result;
124 }
125 } catch (final Exception e) {
126 LOGGER.error("Unable to inject fields into builder class for plugin type {}, element {}.", this.clazz,
127 node.getName(), e);
128 }
129
130 try {
131 LOGGER.debug("Still building Plugin[name={}, class={}]. Searching for factory method...",
132 pluginType.getElementName(), pluginType.getPluginClass().getName());
133 final Method factory = findFactoryMethod(this.clazz);
134 final Object[] params = generateParameters(factory);
135 final Object plugin = factory.invoke(null, params);
136 LOGGER.debug("Built Plugin[name={}] OK from factory method.", pluginType.getElementName());
137 return plugin;
138 } catch (final Exception e) {
139 LOGGER.error("Unable to invoke factory method in class {} for element {}.", this.clazz, this.node.getName(),
140 e);
141 return null;
142 }
143 }
144
145 private void verify() {
146 Assert.requireNonNull(this.configuration, "No Configuration object was set.");
147 Assert.requireNonNull(this.node, "No Node object was set.");
148 }
149
150 private static Builder<?> createBuilder(final Class<?> clazz)
151 throws InvocationTargetException, IllegalAccessException {
152 for (final Method method : clazz.getDeclaredMethods()) {
153 if (method.isAnnotationPresent(PluginBuilderFactory.class) &&
154 Modifier.isStatic(method.getModifiers()) &&
155 TypeUtil.isAssignable(Builder.class, method.getGenericReturnType())) {
156 ReflectionUtil.makeAccessible(method);
157 @SuppressWarnings("unchecked")
158 final Builder<?> builder = (Builder<?>) method.invoke(null);
159 LOGGER.debug("Found builder factory method [{}]: {}.", method.getName(), method);
160 return builder;
161 }
162 }
163 LOGGER.debug("No builder factory method found in class {}. Going to try finding a factory method instead.",
164 clazz.getName());
165 return null;
166 }
167
168 private void injectFields(final Builder<?> builder) throws IllegalAccessException {
169 final Field[] fields = builder.getClass().getDeclaredFields();
170 AccessibleObject.setAccessible(fields, true);
171 final StringBuilder log = new StringBuilder();
172 boolean invalid = false;
173 for (final Field field : fields) {
174 log.append(log.length() == 0 ? "with params(" : ", ");
175 final Annotation[] annotations = field.getDeclaredAnnotations();
176 final String[] aliases = extractPluginAliases(annotations);
177 for (final Annotation a : annotations) {
178 if (a instanceof PluginAliases) {
179 continue;
180 }
181 final PluginVisitor<? extends Annotation> visitor =
182 PluginVisitors.findVisitor(a.annotationType());
183 if (visitor != null) {
184 final Object value = visitor.setAliases(aliases)
185 .setAnnotation(a)
186 .setConversionType(field.getType())
187 .setStrSubstitutor(configuration.getStrSubstitutor())
188 .setMember(field)
189 .visit(configuration, node, event, log);
190
191 if (value != null) {
192 field.set(builder, value);
193 }
194 }
195 }
196 final Collection<ConstraintValidator<?>> validators =
197 ConstraintValidators.findValidators(annotations);
198 final Object value = field.get(builder);
199 for (final ConstraintValidator<?> validator : validators) {
200 if (!validator.isValid(value)) {
201 invalid = true;
202 }
203 }
204 }
205 if (log.length() > 0) {
206 log.append(')');
207 }
208 LOGGER.debug("Calling build() on class {} for element {} {}", builder.getClass(), node.getName(),
209 log.toString());
210 if (invalid) {
211 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
212 }
213 checkForRemainingAttributes();
214 verifyNodeChildrenUsed();
215 }
216
217 private static Method findFactoryMethod(final Class<?> clazz) {
218 for (final Method method : clazz.getDeclaredMethods()) {
219 if (method.isAnnotationPresent(PluginFactory.class) &&
220 Modifier.isStatic(method.getModifiers())) {
221 LOGGER.debug("Found factory method [{}]: {}.", method.getName(), method);
222 ReflectionUtil.makeAccessible(method);
223 return method;
224 }
225 }
226 throw new IllegalStateException("No factory method found for class " + clazz.getName());
227 }
228
229 private Object[] generateParameters(final Method factory) {
230 final StringBuilder log = new StringBuilder();
231 final Class<?>[] types = factory.getParameterTypes();
232 final Annotation[][] annotations = factory.getParameterAnnotations();
233 final Object[] args = new Object[annotations.length];
234 boolean invalid = false;
235 for (int i = 0; i < annotations.length; i++) {
236 log.append(log.length() == 0 ? "with params(" : ", ");
237 final String[] aliases = extractPluginAliases(annotations[i]);
238 for (final Annotation a : annotations[i]) {
239 if (a instanceof PluginAliases) {
240 continue;
241 }
242 final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor(
243 a.annotationType());
244 if (visitor != null) {
245 final Object value = visitor.setAliases(aliases)
246 .setAnnotation(a)
247 .setConversionType(types[i])
248 .setStrSubstitutor(configuration.getStrSubstitutor())
249 .setMember(factory)
250 .visit(configuration, node, event, log);
251
252 if (value != null) {
253 args[i] = value;
254 }
255 }
256 }
257 final Collection<ConstraintValidator<?>> validators =
258 ConstraintValidators.findValidators(annotations[i]);
259 final Object value = args[i];
260 for (final ConstraintValidator<?> validator : validators) {
261 if (!validator.isValid(value)) {
262 invalid = true;
263 }
264 }
265 }
266 if (log.length() > 0) {
267 log.append(')');
268 }
269 checkForRemainingAttributes();
270 verifyNodeChildrenUsed();
271 LOGGER.debug("Calling {} on class {} for element {} {}", factory.getName(), clazz.getName(), node.getName(),
272 log.toString());
273 if (invalid) {
274 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
275 }
276 return args;
277 }
278
279 private static String[] extractPluginAliases(final Annotation... parmTypes) {
280 String[] aliases = null;
281 for (final Annotation a : parmTypes) {
282 if (a instanceof PluginAliases) {
283 aliases = ((PluginAliases) a).value();
284 }
285 }
286 return aliases;
287 }
288
289 private void checkForRemainingAttributes() {
290 final Map<String, String> attrs = node.getAttributes();
291 if (!attrs.isEmpty()) {
292 final StringBuilder sb = new StringBuilder();
293 for (final String key : attrs.keySet()) {
294 if (sb.length() == 0) {
295 sb.append(node.getName());
296 sb.append(" contains ");
297 if (attrs.size() == 1) {
298 sb.append("an invalid element or attribute ");
299 } else {
300 sb.append("invalid attributes ");
301 }
302 } else {
303 sb.append(", ");
304 }
305 sb.append('"');
306 sb.append(key);
307 sb.append('"');
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 }