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