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={}]. Searching for builder factory method...", pluginType.getElementName(),
118 pluginType.getPluginClass().getName());
119 final Builder<?> builder = createBuilder(this.clazz);
120 if (builder != null) {
121 injectFields(builder);
122 final Object result = builder.build();
123 LOGGER.debug("Built Plugin[name={}] OK from builder factory method.", pluginType.getElementName());
124 return result;
125 }
126 } catch (final Exception e) {
127 LOGGER.error("Unable to inject fields into builder class for plugin type {}, element {}.", this.clazz,
128 node.getName(), e);
129 }
130
131 try {
132 LOGGER.debug("Still building Plugin[name={}, class={}]. Searching for factory method...",
133 pluginType.getElementName(), pluginType.getPluginClass().getName());
134 final Method factory = findFactoryMethod(this.clazz);
135 final Object[] params = generateParameters(factory);
136 final Object plugin = factory.invoke(null, params);
137 LOGGER.debug("Built Plugin[name={}] OK from factory method.", pluginType.getElementName());
138 return plugin;
139 } catch (final Exception e) {
140 LOGGER.error("Unable to invoke factory method in class {} for element {}.", this.clazz, this.node.getName(),
141 e);
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.getGenericReturnType())) {
157 ReflectionUtil.makeAccessible(method);
158 @SuppressWarnings("unchecked")
159 final Builder<?> builder = (Builder<?>) method.invoke(null);
160 LOGGER.debug("Found builder factory method [{}]: {}.", method.getName(), method);
161 return builder;
162 }
163 }
164 LOGGER.debug("No builder factory method found in class {}. Going to try finding a factory method instead.",
165 clazz.getName());
166 return null;
167 }
168
169 private void injectFields(final Builder<?> builder) throws IllegalAccessException {
170 final Field[] fields = builder.getClass().getDeclaredFields();
171 AccessibleObject.setAccessible(fields, true);
172 final StringBuilder log = new StringBuilder();
173 boolean invalid = false;
174 for (final Field field : fields) {
175 log.append(log.length() == 0 ? "with params(" : ", ");
176 final Annotation[] annotations = field.getDeclaredAnnotations();
177 final String[] aliases = extractPluginAliases(annotations);
178 for (final Annotation a : annotations) {
179 if (a instanceof PluginAliases) {
180 continue;
181 }
182 final PluginVisitor<? extends Annotation> visitor =
183 PluginVisitors.findVisitor(a.annotationType());
184 if (visitor != null) {
185 final Object value = visitor.setAliases(aliases)
186 .setAnnotation(a)
187 .setConversionType(field.getType())
188 .setStrSubstitutor(configuration.getStrSubstitutor())
189 .setMember(field)
190 .visit(configuration, node, event, log);
191
192 if (value != null) {
193 field.set(builder, value);
194 }
195 }
196 }
197 final Collection<ConstraintValidator<?>> validators =
198 ConstraintValidators.findValidators(annotations);
199 final Object value = field.get(builder);
200 for (final ConstraintValidator<?> validator : validators) {
201 if (!validator.isValid(value)) {
202 invalid = true;
203 }
204 }
205 }
206 if (log.length() > 0) {
207 log.append(')');
208 }
209 LOGGER.debug("Calling build() on class {} for element {} {}", builder.getClass(), node.getName(),
210 log.toString());
211 if (invalid) {
212 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
213 }
214 checkForRemainingAttributes();
215 verifyNodeChildrenUsed();
216 }
217
218 private static Method findFactoryMethod(final Class<?> clazz) {
219 for (final Method method : clazz.getDeclaredMethods()) {
220 if (method.isAnnotationPresent(PluginFactory.class) &&
221 Modifier.isStatic(method.getModifiers())) {
222 LOGGER.debug("Found factory method [{}]: {}.", method.getName(), method);
223 ReflectionUtil.makeAccessible(method);
224 return method;
225 }
226 }
227 throw new IllegalStateException("No factory method found for class " + clazz.getName());
228 }
229
230 private Object[] generateParameters(final Method factory) {
231 final StringBuilder log = new StringBuilder();
232 final Class<?>[] types = factory.getParameterTypes();
233 final Annotation[][] annotations = factory.getParameterAnnotations();
234 final Object[] args = new Object[annotations.length];
235 boolean invalid = false;
236 for (int i = 0; i < annotations.length; i++) {
237 log.append(log.length() == 0 ? "with params(" : ", ");
238 final String[] aliases = extractPluginAliases(annotations[i]);
239 for (final Annotation a : annotations[i]) {
240 if (a instanceof PluginAliases) {
241 continue;
242 }
243 final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor(
244 a.annotationType());
245 if (visitor != null) {
246 final Object value = visitor.setAliases(aliases)
247 .setAnnotation(a)
248 .setConversionType(types[i])
249 .setStrSubstitutor(configuration.getStrSubstitutor())
250 .setMember(factory)
251 .visit(configuration, node, event, log);
252
253 if (value != null) {
254 args[i] = value;
255 }
256 }
257 }
258 final Collection<ConstraintValidator<?>> validators =
259 ConstraintValidators.findValidators(annotations[i]);
260 final Object value = args[i];
261 for (final ConstraintValidator<?> validator : validators) {
262 if (!validator.isValid(value)) {
263 invalid = true;
264 }
265 }
266 }
267 if (log.length() > 0) {
268 log.append(')');
269 }
270 checkForRemainingAttributes();
271 verifyNodeChildrenUsed();
272 LOGGER.debug("Calling {} on class {} for element {} {}", factory.getName(), clazz.getName(), node.getName(),
273 log.toString());
274 if (invalid) {
275 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
276 }
277 return args;
278 }
279
280 private static String[] extractPluginAliases(final Annotation... parmTypes) {
281 String[] aliases = null;
282 for (final Annotation a : parmTypes) {
283 if (a instanceof PluginAliases) {
284 aliases = ((PluginAliases) a).value();
285 }
286 }
287 return aliases;
288 }
289
290 private void checkForRemainingAttributes() {
291 final Map<String, String> attrs = node.getAttributes();
292 if (!attrs.isEmpty()) {
293 final StringBuilder sb = new StringBuilder();
294 for (final String key : attrs.keySet()) {
295 if (sb.length() == 0) {
296 sb.append(node.getName());
297 sb.append(" contains ");
298 if (attrs.size() == 1) {
299 sb.append("an invalid element or attribute ");
300 } else {
301 sb.append("invalid attributes ");
302 }
303 } else {
304 sb.append(", ");
305 }
306 StringBuilders.appendDqValue(sb, key);
307 }
308 LOGGER.error(sb.toString());
309 }
310 }
311
312 private void verifyNodeChildrenUsed() {
313 final List<Node> children = node.getChildren();
314 if (!(pluginType.isDeferChildren() || children.isEmpty())) {
315 for (final Node child : children) {
316 final String nodeType = node.getType().getElementName();
317 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + ' ' + node.getName();
318 LOGGER.error("{} has no parameter that matches element {}", start, child.getName());
319 }
320 }
321 }
322 }