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