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 import java.util.Objects;
30
31 import org.apache.logging.log4j.Logger;
32 import org.apache.logging.log4j.core.LogEvent;
33 import org.apache.logging.log4j.core.config.Configuration;
34 import org.apache.logging.log4j.core.config.ConfigurationException;
35 import org.apache.logging.log4j.core.config.Node;
36 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
37 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
38 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
39 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
40 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators;
41 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
42 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors;
43 import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor;
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={}].", 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) {
126 LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e);
127 return null;
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
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;
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
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
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;
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
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 }