View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Builder class to instantiate and configure a Plugin object using a PluginFactory method or PluginBuilderFactory
44   * builder class.
45   *
46   * @param <T> type of Plugin class.
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       * Constructs a PluginBuilder for a given PluginType.
61       *
62       * @param pluginType type of plugin to configure
63       */
64      public PluginBuilder(final PluginType<T> pluginType) {
65          this.pluginType = pluginType;
66          this.clazz = pluginType.getPluginClass();
67      }
68  
69      /**
70       * Specifies the Configuration to use for constructing the plugin instance.
71       *
72       * @param configuration the configuration to use.
73       * @return {@code this}
74       */
75      public PluginBuilder<T> withConfiguration(final Configuration configuration) {
76          this.configuration = configuration;
77          return this;
78      }
79  
80      /**
81       * Specifies the Node corresponding to the plugin object that will be created.
82       *
83       * @param node the plugin configuration node to use.
84       * @return {@code this}
85       */
86      public PluginBuilder<T> withConfigurationNode(final Node node) {
87          this.node = node;
88          return this;
89      }
90  
91      /**
92       * Specifies the LogEvent that may be used to provide extra context for string substitutions.
93       *
94       * @param event the event to use for extra information.
95       * @return {@code this}
96       */
97      public PluginBuilder<T> forLogEvent(final LogEvent event) {
98          this.event = event;
99          return this;
100     }
101 
102     /**
103      * Builds the plugin object.
104      *
105      * @return the plugin object or {@code null} if there was a problem creating it.
106      */
107     @Override
108     public T build() {
109         verify();
110         // first try to use a builder class if one is available
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         // or fall back to factory method if no builder class is available
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; // already processed
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                     // don't overwrite default values if the visitor gives us no value to inject
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; // already processed
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 }