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 ConfigurationException e) {
125 LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e);
126 return null;
127 } catch (final Exception e) {
128 LOGGER.error("Could not create plugin of type {} for element {}: {}",
129 this.clazz, node.getName(),
130 (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e);
131 }
132
133 try {
134 final Method factory = findFactoryMethod(this.clazz);
135 final Object[] params = generateParameters(factory);
136 return factory.invoke(null, params);
137 } catch (final Exception e) {
138 LOGGER.error("Unable to invoke factory method in {} for element {}: {}",
139 this.clazz, this.node.getName(),
140 (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e);
141 return null;
142 }
143 }
144
145 private void verify() {
146 Objects.requireNonNull(this.configuration, "No Configuration object was set.");
147 Objects.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.getReturnType())) {
156 ReflectionUtil.makeAccessible(method);
157 return (Builder<?>) method.invoke(null);
158 }
159 }
160 return null;
161 }
162
163 private void injectFields(final Builder<?> builder) throws IllegalAccessException {
164 final List<Field> fields = TypeUtil.getAllDeclaredFields(builder.getClass());
165 AccessibleObject.setAccessible(fields.toArray(new Field[] {}), true);
166 final StringBuilder log = new StringBuilder();
167 boolean invalid = false;
168 String reason = "";
169 for (final Field field : fields) {
170 log.append(log.length() == 0 ? simpleName(builder) + "(" : ", ");
171 final Annotation[] annotations = field.getDeclaredAnnotations();
172 final String[] aliases = extractPluginAliases(annotations);
173 for (final Annotation a : annotations) {
174 if (a instanceof PluginAliases) {
175 continue;
176 }
177 final PluginVisitor<? extends Annotation> visitor =
178 PluginVisitors.findVisitor(a.annotationType());
179 if (visitor != null) {
180 final Object value = visitor.setAliases(aliases)
181 .setAnnotation(a)
182 .setConversionType(field.getType())
183 .setStrSubstitutor(configuration.getStrSubstitutor())
184 .setMember(field)
185 .visit(configuration, node, event, log);
186
187 if (value != null) {
188 field.set(builder, value);
189 }
190 }
191 }
192 final Collection<ConstraintValidator<?>> validators =
193 ConstraintValidators.findValidators(annotations);
194 final Object value = field.get(builder);
195 for (final ConstraintValidator<?> validator : validators) {
196 if (!validator.isValid(field.getName(), value)) {
197 invalid = true;
198 if (!reason.isEmpty()) {
199 reason += ", ";
200 }
201 reason += "field '" + field.getName() + "' has invalid value '" + value + "'";
202 }
203 }
204 }
205 log.append(log.length() == 0 ? builder.getClass().getSimpleName() + "()" : ")");
206 LOGGER.debug(log.toString());
207 if (invalid) {
208 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid: " + reason);
209 }
210 checkForRemainingAttributes();
211 verifyNodeChildrenUsed();
212 }
213
214
215
216
217 private static String simpleName(final Object object) {
218 if (object == null) {
219 return "null";
220 }
221 final String cls = object.getClass().getName();
222 final int index = cls.lastIndexOf('.');
223 return index < 0 ? cls : cls.substring(index + 1);
224 }
225
226 private static Method findFactoryMethod(final Class<?> clazz) {
227 for (final Method method : clazz.getDeclaredMethods()) {
228 if (method.isAnnotationPresent(PluginFactory.class) &&
229 Modifier.isStatic(method.getModifiers())) {
230 ReflectionUtil.makeAccessible(method);
231 return method;
232 }
233 }
234 throw new IllegalStateException("No factory method found for class " + clazz.getName());
235 }
236
237 private Object[] generateParameters(final Method factory) {
238 final StringBuilder log = new StringBuilder();
239 final Class<?>[] types = factory.getParameterTypes();
240 final Annotation[][] annotations = factory.getParameterAnnotations();
241 final Object[] args = new Object[annotations.length];
242 boolean invalid = false;
243 for (int i = 0; i < annotations.length; i++) {
244 log.append(log.length() == 0 ? factory.getName() + "(" : ", ");
245 final String[] aliases = extractPluginAliases(annotations[i]);
246 for (final Annotation a : annotations[i]) {
247 if (a instanceof PluginAliases) {
248 continue;
249 }
250 final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor(
251 a.annotationType());
252 if (visitor != null) {
253 final Object value = visitor.setAliases(aliases)
254 .setAnnotation(a)
255 .setConversionType(types[i])
256 .setStrSubstitutor(configuration.getStrSubstitutor())
257 .setMember(factory)
258 .visit(configuration, node, event, log);
259
260 if (value != null) {
261 args[i] = value;
262 }
263 }
264 }
265 final Collection<ConstraintValidator<?>> validators =
266 ConstraintValidators.findValidators(annotations[i]);
267 final Object value = args[i];
268 final String argName = "arg[" + i + "](" + simpleName(value) + ")";
269 for (final ConstraintValidator<?> validator : validators) {
270 if (!validator.isValid(argName, value)) {
271 invalid = true;
272 }
273 }
274 }
275 log.append(log.length() == 0 ? factory.getName() + "()" : ")");
276 checkForRemainingAttributes();
277 verifyNodeChildrenUsed();
278 LOGGER.debug(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 LOGGER.error(sb.toString());
314 }
315 }
316
317 private void verifyNodeChildrenUsed() {
318 final List<Node> children = node.getChildren();
319 if (!(pluginType.isDeferChildren() || children.isEmpty())) {
320 for (final Node child : children) {
321 final String nodeType = node.getType().getElementName();
322 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + ' ' + node.getName();
323 LOGGER.error("{} has no parameter that matches element {}", start, child.getName());
324 }
325 }
326 }
327 }