001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.logging.log4j.core.config.plugins.util;
019
020 import java.lang.annotation.Annotation;
021 import java.lang.reflect.AccessibleObject;
022 import java.lang.reflect.Field;
023 import java.lang.reflect.InvocationTargetException;
024 import java.lang.reflect.Method;
025 import java.lang.reflect.Modifier;
026 import java.util.Collection;
027 import java.util.List;
028 import java.util.Map;
029
030 import org.apache.logging.log4j.Logger;
031 import org.apache.logging.log4j.core.LogEvent;
032 import org.apache.logging.log4j.core.config.Configuration;
033 import org.apache.logging.log4j.core.config.ConfigurationException;
034 import org.apache.logging.log4j.core.config.Node;
035 import org.apache.logging.log4j.core.config.plugins.PluginAliases;
036 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
037 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
038 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
039 import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators;
040 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
041 import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors;
042 import org.apache.logging.log4j.core.util.Assert;
043 import org.apache.logging.log4j.core.util.Builder;
044 import org.apache.logging.log4j.core.util.ReflectionUtil;
045 import org.apache.logging.log4j.core.util.TypeUtil;
046 import org.apache.logging.log4j.status.StatusLogger;
047 import org.apache.logging.log4j.util.Chars;
048 import org.apache.logging.log4j.util.StringBuilders;
049
050 /**
051 * Builder class to instantiate and configure a Plugin object using a PluginFactory method or PluginBuilderFactory
052 * builder class.
053 */
054 public class PluginBuilder implements Builder<Object> {
055
056 private static final Logger LOGGER = StatusLogger.getLogger();
057
058 private final PluginType<?> pluginType;
059 private final Class<?> clazz;
060
061 private Configuration configuration;
062 private Node node;
063 private LogEvent event;
064
065 /**
066 * Constructs a PluginBuilder for a given PluginType.
067 *
068 * @param pluginType type of plugin to configure
069 */
070 public PluginBuilder(final PluginType<?> pluginType) {
071 this.pluginType = pluginType;
072 this.clazz = pluginType.getPluginClass();
073 }
074
075 /**
076 * Specifies the Configuration to use for constructing the plugin instance.
077 *
078 * @param configuration the configuration to use.
079 * @return {@code this}
080 */
081 public PluginBuilder withConfiguration(final Configuration configuration) {
082 this.configuration = configuration;
083 return this;
084 }
085
086 /**
087 * Specifies the Node corresponding to the plugin object that will be created.
088 *
089 * @param node the plugin configuration node to use.
090 * @return {@code this}
091 */
092 public PluginBuilder withConfigurationNode(final Node node) {
093 this.node = node;
094 return this;
095 }
096
097 /**
098 * Specifies the LogEvent that may be used to provide extra context for string substitutions.
099 *
100 * @param event the event to use for extra information.
101 * @return {@code this}
102 */
103 public PluginBuilder forLogEvent(final LogEvent event) {
104 this.event = event;
105 return this;
106 }
107
108 /**
109 * Builds the plugin object.
110 *
111 * @return the plugin object or {@code null} if there was a problem creating it.
112 */
113 @Override
114 public Object build() {
115 verify();
116 // first try to use a builder class if one is available
117 try {
118 LOGGER.debug("Building Plugin[name={}, class={}]. Searching for builder factory method...", pluginType.getElementName(),
119 pluginType.getPluginClass().getName());
120 final Builder<?> builder = createBuilder(this.clazz);
121 if (builder != null) {
122 injectFields(builder);
123 final Object result = builder.build();
124 LOGGER.debug("Built Plugin[name={}] OK from builder factory method.", pluginType.getElementName());
125 return result;
126 }
127 } catch (final Exception e) {
128 LOGGER.error("Unable to inject fields into builder class for plugin type {}, element {}.", this.clazz,
129 node.getName(), e);
130 }
131 // or fall back to factory method if no builder class is available
132 try {
133 LOGGER.debug("Still building Plugin[name={}, class={}]. Searching for factory method...",
134 pluginType.getElementName(), pluginType.getPluginClass().getName());
135 final Method factory = findFactoryMethod(this.clazz);
136 final Object[] params = generateParameters(factory);
137 final Object plugin = factory.invoke(null, params);
138 LOGGER.debug("Built Plugin[name={}] OK from factory method.", pluginType.getElementName());
139 return plugin;
140 } catch (final Exception e) {
141 LOGGER.error("Unable to invoke factory method in class {} for element {}.", this.clazz, this.node.getName(),
142 e);
143 return null;
144 }
145 }
146
147 private void verify() {
148 Assert.requireNonNull(this.configuration, "No Configuration object was set.");
149 Assert.requireNonNull(this.node, "No Node object was set.");
150 }
151
152 private static Builder<?> createBuilder(final Class<?> clazz)
153 throws InvocationTargetException, IllegalAccessException {
154 for (final Method method : clazz.getDeclaredMethods()) {
155 if (method.isAnnotationPresent(PluginBuilderFactory.class) &&
156 Modifier.isStatic(method.getModifiers()) &&
157 TypeUtil.isAssignable(Builder.class, method.getGenericReturnType())) {
158 ReflectionUtil.makeAccessible(method);
159 @SuppressWarnings("unchecked")
160 final Builder<?> builder = (Builder<?>) method.invoke(null);
161 LOGGER.debug("Found builder factory method [{}]: {}.", method.getName(), method);
162 return builder;
163 }
164 }
165 LOGGER.debug("No builder factory method found in class {}. Going to try finding a factory method instead.",
166 clazz.getName());
167 return null;
168 }
169
170 private void injectFields(final Builder<?> builder) throws IllegalAccessException {
171 final Field[] fields = builder.getClass().getDeclaredFields();
172 AccessibleObject.setAccessible(fields, true);
173 final StringBuilder log = new StringBuilder();
174 boolean invalid = false;
175 for (final Field field : fields) {
176 log.append(log.length() == 0 ? "with params(" : ", ");
177 final Annotation[] annotations = field.getDeclaredAnnotations();
178 final String[] aliases = extractPluginAliases(annotations);
179 for (final Annotation a : annotations) {
180 if (a instanceof PluginAliases) {
181 continue; // already processed
182 }
183 final PluginVisitor<? extends Annotation> visitor =
184 PluginVisitors.findVisitor(a.annotationType());
185 if (visitor != null) {
186 final Object value = visitor.setAliases(aliases)
187 .setAnnotation(a)
188 .setConversionType(field.getType())
189 .setStrSubstitutor(configuration.getStrSubstitutor())
190 .setMember(field)
191 .visit(configuration, node, event, log);
192 // don't overwrite default values if the visitor gives us no value to inject
193 if (value != null) {
194 field.set(builder, value);
195 }
196 }
197 }
198 final Collection<ConstraintValidator<?>> validators =
199 ConstraintValidators.findValidators(annotations);
200 final Object value = field.get(builder);
201 for (final ConstraintValidator<?> validator : validators) {
202 if (!validator.isValid(value)) {
203 invalid = true;
204 }
205 }
206 }
207 if (log.length() > 0) {
208 log.append(')');
209 }
210 LOGGER.debug("Calling build() on class {} for element {} {}", builder.getClass(), node.getName(),
211 log.toString());
212 if (invalid) {
213 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
214 }
215 checkForRemainingAttributes();
216 verifyNodeChildrenUsed();
217 }
218
219 private static Method findFactoryMethod(final Class<?> clazz) {
220 for (final Method method : clazz.getDeclaredMethods()) {
221 if (method.isAnnotationPresent(PluginFactory.class) &&
222 Modifier.isStatic(method.getModifiers())) {
223 LOGGER.debug("Found factory method [{}]: {}.", method.getName(), method);
224 ReflectionUtil.makeAccessible(method);
225 return method;
226 }
227 }
228 throw new IllegalStateException("No factory method found for class " + clazz.getName());
229 }
230
231 private Object[] generateParameters(final Method factory) {
232 final StringBuilder log = new StringBuilder();
233 final Class<?>[] types = factory.getParameterTypes();
234 final Annotation[][] annotations = factory.getParameterAnnotations();
235 final Object[] args = new Object[annotations.length];
236 boolean invalid = false;
237 for (int i = 0; i < annotations.length; i++) {
238 log.append(log.length() == 0 ? "with params(" : ", ");
239 final String[] aliases = extractPluginAliases(annotations[i]);
240 for (final Annotation a : annotations[i]) {
241 if (a instanceof PluginAliases) {
242 continue; // already processed
243 }
244 final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor(
245 a.annotationType());
246 if (visitor != null) {
247 final Object value = visitor.setAliases(aliases)
248 .setAnnotation(a)
249 .setConversionType(types[i])
250 .setStrSubstitutor(configuration.getStrSubstitutor())
251 .setMember(factory)
252 .visit(configuration, node, event, log);
253 // don't overwrite existing values if the visitor gives us no value to inject
254 if (value != null) {
255 args[i] = value;
256 }
257 }
258 }
259 final Collection<ConstraintValidator<?>> validators =
260 ConstraintValidators.findValidators(annotations[i]);
261 final Object value = args[i];
262 for (final ConstraintValidator<?> validator : validators) {
263 if (!validator.isValid(value)) {
264 invalid = true;
265 }
266 }
267 }
268 if (log.length() > 0) {
269 log.append(')');
270 }
271 checkForRemainingAttributes();
272 verifyNodeChildrenUsed();
273 LOGGER.debug("Calling {} on class {} for element {} {}", factory.getName(), clazz.getName(), node.getName(),
274 log.toString());
275 if (invalid) {
276 throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
277 }
278 return args;
279 }
280
281 private static String[] extractPluginAliases(final Annotation... parmTypes) {
282 String[] aliases = null;
283 for (final Annotation a : parmTypes) {
284 if (a instanceof PluginAliases) {
285 aliases = ((PluginAliases) a).value();
286 }
287 }
288 return aliases;
289 }
290
291 private void checkForRemainingAttributes() {
292 final Map<String, String> attrs = node.getAttributes();
293 if (!attrs.isEmpty()) {
294 final StringBuilder sb = new StringBuilder();
295 for (final String key : attrs.keySet()) {
296 if (sb.length() == 0) {
297 sb.append(node.getName());
298 sb.append(" contains ");
299 if (attrs.size() == 1) {
300 sb.append("an invalid element or attribute ");
301 } else {
302 sb.append("invalid attributes ");
303 }
304 } else {
305 sb.append(", ");
306 }
307 StringBuilders.appendDqValue(sb, key);
308
309 }
310 LOGGER.error(sb.toString());
311 }
312 }
313
314 private void verifyNodeChildrenUsed() {
315 final List<Node> children = node.getChildren();
316 if (!(pluginType.isDeferChildren() || children.isEmpty())) {
317 for (final Node child : children) {
318 final String nodeType = node.getType().getElementName();
319 final String start = nodeType.equals(node.getName()) ? node.getName() : nodeType + ' ' + node.getName();
320 LOGGER.error("{} has no parameter that matches element {}", start, child.getName());
321 }
322 }
323 }
324 }