1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.filter.internal

File DefaultFilterDescriptorManager.java

 

Coverage histogram

../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

50
97
13
1
372
232
46
0.47
7.46
13
3.54

Classes

Class Line # Actions
DefaultFilterDescriptorManager 62 97 0% 46 17
0.8937589.4%
 

Contributing tests

This file is covered by 688 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.filter.internal;
21   
22    import java.lang.reflect.Method;
23    import java.lang.reflect.Parameter;
24    import java.lang.reflect.Proxy;
25    import java.lang.reflect.Type;
26    import java.util.Collections;
27    import java.util.HashSet;
28    import java.util.List;
29    import java.util.Map;
30    import java.util.Set;
31    import java.util.concurrent.ConcurrentHashMap;
32   
33    import javax.inject.Inject;
34    import javax.inject.Singleton;
35   
36    import org.apache.commons.collections4.IterableUtils;
37    import org.apache.commons.lang3.ClassUtils;
38    import org.apache.commons.lang3.ClassUtils.Interfaces;
39    import org.apache.commons.lang3.reflect.MethodUtils;
40    import org.slf4j.Logger;
41    import org.xwiki.component.annotation.Component;
42    import org.xwiki.component.util.ReflectionMethodUtils;
43    import org.xwiki.component.util.ReflectionUtils;
44    import org.xwiki.filter.FilterDescriptor;
45    import org.xwiki.filter.FilterDescriptorManager;
46    import org.xwiki.filter.FilterElementDescriptor;
47    import org.xwiki.filter.FilterElementParameterDescriptor;
48    import org.xwiki.filter.IncompatibleFilterException;
49    import org.xwiki.filter.annotation.Default;
50    import org.xwiki.filter.annotation.Name;
51    import org.xwiki.properties.ConverterManager;
52    import org.xwiki.properties.converter.ConversionException;
53   
54    /**
55    * Default implementation of {@link FilterDescriptorManager}.
56    *
57    * @version $Id: 28de4cd4f1803dadc6a47f8bdf0bd879b38918cb $
58    * @since 5.2M1
59    */
60    @Component
61    @Singleton
 
62    public class DefaultFilterDescriptorManager implements FilterDescriptorManager
63    {
64    /**
65    * The prefix of the begin events.
66    */
67    public static final String PREFIX_BEGIN = "begin";
68   
69    /**
70    * The prefix of the end events.
71    */
72    public static final String PREFIX_END = "end";
73   
74    /**
75    * The prefix of the on events.
76    */
77    public static final String PREFIX_ON = "on";
78   
79    private static final Class<?>[] CLASS_ARRAY = new Class<?>[0];
80   
81    /**
82    * The descriptors.
83    */
84    private Map<Class<?>, FilterDescriptor> descriptors = new ConcurrentHashMap<Class<?>, FilterDescriptor>();
85   
86    /**
87    * Used to convert default values from {@link String}.
88    */
89    @Inject
90    private ConverterManager converter;
91   
92    @Inject
93    private Logger logger;
94   
 
95  42971 toggle @Override
96    public FilterDescriptor getFilterDescriptor(Class<?>... interfaces)
97    {
98  42972 FilterDescriptor totalDescriptor = null;
99   
100  42972 for (Class<?> i : interfaces) {
101  44436 FilterDescriptor descriptor = this.descriptors.get(i);
102   
103  44436 if (descriptor == null) {
104  1993 try {
105  1993 descriptor = createDescriptor(i);
106    } catch (IncompatibleFilterException e) {
107  0 this.logger.error("Failed to create descriptor for filter [{}]", i, e);
108   
109  0 continue;
110    }
111  1993 this.descriptors.put(i, descriptor);
112    }
113   
114  44436 if (totalDescriptor == null) {
115  42972 totalDescriptor = descriptor;
116    } else {
117  1464 totalDescriptor.add(descriptor);
118    }
119    }
120   
121  42972 return totalDescriptor;
122    }
123   
124    /**
125    * @param method the method
126    * @param searchTopMethod search for top most overridden method
127    * @return the corresponding element name
128    */
 
129  14719 toggle public static String getElementName(Method method, boolean searchTopMethod)
130    {
131  14719 Method topMethod = method;
132   
133  14719 if (searchTopMethod) {
134    // Get top most method declaration
135  14719 Set<Method> hierarchy = MethodUtils.getOverrideHierarchy(method, Interfaces.INCLUDE);
136  14719 topMethod = IterableUtils.get(hierarchy, hierarchy.size() - 1);
137    }
138   
139    // Get element name from method
140  14719 return getElementName(topMethod);
141    }
142   
143    /**
144    * @param method the method
145    * @return the corresponding element name
146    */
 
147  77235 toggle public static String getElementName(Method method)
148    {
149  77236 Name name = method.getAnnotation(Name.class);
150   
151  77235 if (name != null) {
152  147 return name.value();
153    }
154   
155  77089 return getElementName(method.getName());
156    }
157   
158    /**
159    * @param methodName the method name
160    * @return the corresponding element name
161    */
 
162  77088 toggle public static String getElementName(String methodName)
163    {
164  77089 String elementName;
165  77089 if (methodName.startsWith(PREFIX_BEGIN)) {
166  24906 elementName = methodName.substring(PREFIX_BEGIN.length(), methodName.length());
167  52183 } else if (methodName.startsWith(PREFIX_END)) {
168  20988 elementName = methodName.substring(PREFIX_END.length(), methodName.length());
169  31195 } else if (methodName.startsWith(PREFIX_ON)) {
170  21858 elementName = methodName.substring(PREFIX_ON.length(), methodName.length());
171    } else {
172  9337 elementName = null;
173    }
174   
175  77089 if (elementName != null) {
176  67752 elementName = Character.toLowerCase(elementName.charAt(0)) + elementName.substring(1, elementName.length());
177    }
178   
179  77089 return elementName;
180    }
181   
182    /**
183    * @param type the class of the filter
184    * @return the descriptor of the filter
185    * @throws IncompatibleFilterException when several methods/events are incompatibles
186    */
 
187  1992 toggle private FilterDescriptor createDescriptor(Class<?> type) throws IncompatibleFilterException
188    {
189    // Proxy "loose" various reflection informations (like method parameter names)
190  1993 if (Proxy.isProxyClass(type)) {
191  62 return getFilterDescriptor(type.getInterfaces());
192    } else {
193  1931 FilterDescriptor descriptor = new FilterDescriptor();
194   
195  1930 for (Method method : type.getMethods()) {
196    // Get top most method declaration
197  62517 Set<Method> hierarchy = MethodUtils.getOverrideHierarchy(method, Interfaces.INCLUDE);
198  62515 Method topMethod = IterableUtils.get(hierarchy, hierarchy.size() - 1);
199   
200    // Get element name from method
201  62517 String elementName = getElementName(topMethod);
202   
203    // If a name can be found, continue
204  62516 if (elementName != null) {
205  53180 addElement(elementName, descriptor, topMethod);
206    }
207    }
208   
209  1931 return descriptor;
210    }
211    }
212   
213    /**
214    * @param elementName the name of the element
215    * @param descriptor the descriptor in which to add the element
216    * @param method the method associated to the element
217    * @throws IncompatibleFilterException when passed method is not compatible with matching filter(s)
218    */
 
219  53178 toggle private void addElement(String elementName, FilterDescriptor descriptor, Method method)
220    throws IncompatibleFilterException
221    {
222  53178 String lowerElementName = elementName.toLowerCase();
223   
224  53180 FilterElementDescriptor element = descriptor.getElements().get(lowerElementName);
225   
226  53180 Type[] methodTypes = method.getGenericParameterTypes();
227   
228  53179 if (element == null || methodTypes.length > element.getParameters().length) {
229  32017 FilterElementParameterDescriptor<?>[] parameters =
230    new FilterElementParameterDescriptor<?>[methodTypes.length];
231   
232  79217 for (int i = 0; i < methodTypes.length; ++i) {
233  47200 parameters[i] = createFilterElementParameter(method, i, methodTypes[i]);
234    }
235   
236    // Make sure those parameters are compatible with any other matching element
237  32017 if (element != null) {
238  0 checkCompatible(element, parameters);
239    }
240   
241  32017 element = new FilterElementDescriptor(elementName, parameters);
242   
243  32017 descriptor.getElements().put(lowerElementName, element);
244    }
245   
246  53180 addMethod(element, method);
247    }
248   
 
249  0 toggle private void checkCompatible(FilterElementDescriptor element, FilterElementParameterDescriptor<?>[] parameters)
250    throws IncompatibleFilterException
251    {
252  0 for (FilterElementParameterDescriptor<?> parameter : parameters) {
253  0 FilterElementParameterDescriptor<?> elementParameter = element.getParameter(parameter.getName());
254   
255  0 if (elementParameter != null && !elementParameter.getType().equals(parameter.getType())) {
256  0 throw new IncompatibleFilterException("Parameter [" + parameter + "] is not compatible with parameter ["
257    + elementParameter + "] (different types)");
258    }
259    }
260    }
261   
262    /**
263    * @param method the method associated to the element
264    * @param index the method parameter index
265    * @param type the method parameter type
266    * @return the associated {@link FilterElementParameterDescriptor}
267    */
 
268  47199 toggle private FilterElementParameterDescriptor<?> createFilterElementParameter(Method method, int index, Type type)
269    {
270    // @Name
271  47200 List<Name> nameAnnotations =
272    ReflectionMethodUtils.getMethodParameterAnnotations(method, index, Name.class, true);
273   
274  47200 String name;
275  47199 if (!nameAnnotations.isEmpty()) {
276  2910 name = nameAnnotations.get(0).value();
277    } else {
278    // Fallback on reflection to get the parameter name
279  44289 Parameter parameter = method.getParameters()[index];
280  44289 name = parameter.isNamePresent() ? method.getParameters()[index].getName() : null;
281    }
282   
283    // @Default
284  47200 List<Default> defaultAnnotations =
285    ReflectionMethodUtils.getMethodParameterAnnotations(method, index, Default.class, true);
286   
287  47201 Object defaultValue;
288  47201 if (!defaultAnnotations.isEmpty()) {
289  24042 defaultValue = defaultAnnotations.get(0).value();
290   
291  24042 if (defaultValue != null) {
292  24042 try {
293  24042 defaultValue = this.converter.convert(type, defaultValue);
294    } catch (ConversionException e) {
295    // TODO: remove that hack when String -> Map support is added to xwiki-properties
296  16104 if (ReflectionUtils.getTypeClass(type) == Map.class && ((String) defaultValue).isEmpty()) {
297  16104 defaultValue = Collections.EMPTY_MAP;
298    } else {
299  0 throw e;
300    }
301    }
302    }
303    } else {
304  23159 defaultValue = null;
305    }
306   
307  47200 return new FilterElementParameterDescriptor<Object>(index, name, type, defaultValue);
308    }
309   
310    /**
311    * @param element the element
312    * @param method the method to add to the element
313    */
 
314  53180 toggle private void addMethod(FilterElementDescriptor element, Method method)
315    {
316  53180 String methodName = method.getName();
317  53180 Type[] methodTypes = method.getGenericParameterTypes();
318   
319  53180 if (methodName.startsWith(PREFIX_BEGIN)) {
320  21048 if (element.getBeginMethod() == null
321    || element.getBeginMethod().getGenericParameterTypes().length < methodTypes.length) {
322  21014 element.setBeginMethod(method);
323    }
324  32132 } else if (methodName.startsWith(PREFIX_END)) {
325  21048 if (element.getEndMethod() == null
326    || element.getEndMethod().getGenericParameterTypes().length < methodTypes.length) {
327  21014 element.setEndMethod(method);
328    }
329    } else {
330  11084 if (element.getOnMethod() == null
331    || element.getOnMethod().getGenericParameterTypes().length < methodTypes.length) {
332  11084 element.setOnMethod(method);
333    }
334    }
335    }
336   
 
337  41627 toggle @Override
338    public <F> F createFilterProxy(Object targetFilter, Class<?>... interfaces)
339    {
340  41627 return createFilterProxy(targetFilter, Thread.currentThread().getContextClassLoader(), interfaces);
341    }
342   
 
343  41627 toggle @Override
344    public <F> F createFilterProxy(Object targetFilter, ClassLoader loader, Class<?>... interfaces)
345    {
346  41627 for (Class<?> i : interfaces) {
347  41629 if (!i.isInstance(targetFilter)) {
348  41602 return (F) Proxy.newProxyInstance(loader, interfaces,
349    new FilterProxy(targetFilter, getFilterDescriptor(interfaces)));
350    }
351    }
352   
353  25 return (F) targetFilter;
354    }
355   
 
356  44 toggle @Override
357    public <F> F createCompositeFilter(Object... filters)
358    {
359  44 return createCompositeFilter(Thread.currentThread().getContextClassLoader(), filters);
360    }
361   
 
362  44 toggle @Override
363    public <F> F createCompositeFilter(ClassLoader loader, Object... filters)
364    {
365  44 Set<Class<?>> interfaces = new HashSet<Class<?>>();
366  44 for (Object filter : filters) {
367  86 interfaces.addAll(ClassUtils.getAllInterfaces(filter.getClass()));
368    }
369   
370  44 return (F) Proxy.newProxyInstance(loader, interfaces.toArray(CLASS_ARRAY), new CompositeFilter(this, filters));
371    }
372    }