1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.component.annotation

File ComponentAnnotationLoader.java

 

Coverage histogram

../../../../img/srcFileCovDistChart8.png
56% of files have more coverage

Code metrics

60
149
14
1
549
321
57
0.38
10.64
14
4.07

Classes

Class Line # Actions
ComponentAnnotationLoader 58 149 0% 57 51
0.7713004477.1%
 

Contributing tests

This file is covered by 37 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.component.annotation;
21   
22    import java.io.BufferedReader;
23    import java.io.IOException;
24    import java.io.InputStream;
25    import java.io.InputStreamReader;
26    import java.lang.annotation.Annotation;
27    import java.lang.reflect.ParameterizedType;
28    import java.lang.reflect.Type;
29    import java.net.URL;
30    import java.util.ArrayList;
31    import java.util.Arrays;
32    import java.util.Enumeration;
33    import java.util.HashMap;
34    import java.util.LinkedHashSet;
35    import java.util.List;
36    import java.util.Map;
37    import java.util.Set;
38    import java.util.zip.ZipEntry;
39    import java.util.zip.ZipInputStream;
40   
41    import javax.inject.Provider;
42   
43    import org.slf4j.Logger;
44    import org.slf4j.LoggerFactory;
45    import org.xwiki.component.descriptor.ComponentDescriptor;
46    import org.xwiki.component.descriptor.DefaultComponentDescriptor;
47    import org.xwiki.component.internal.RoleHint;
48    import org.xwiki.component.manager.ComponentManager;
49    import org.xwiki.component.util.DefaultParameterizedType;
50    import org.xwiki.component.util.ReflectionUtils;
51   
52    /**
53    * Dynamically loads all components defined using Annotations and declared in META-INF/components.txt files.
54    *
55    * @version $Id: 085feb66781cccbfbc9d96c07bb9faf58acdf263 $
56    * @since 1.8.1
57    */
 
58    public class ComponentAnnotationLoader
59    {
60    /**
61    * Location in the classloader of the file defining the list of component implementation class to parser for
62    * annotations.
63    */
64    public static final String COMPONENT_LIST = "META-INF/components.txt";
65   
66    /**
67    * Location in the classloader of the file specifying which component implementation to use when several components
68    * with the same role/hint are found.
69    *
70    * @deprecated starting with 3.3M1 use the notion of priorities instead (see {@link ComponentDeclaration}).
71    */
72    @Deprecated
73    public static final String COMPONENT_OVERRIDE_LIST = "META-INF/component-overrides.txt";
74   
75    /**
76    * The encoding used to parse component list files.
77    */
78    private static final String COMPONENT_LIST_ENCODING = "UTF-8";
79   
80    /**
81    * Logger to use for logging...
82    */
83    private static final Logger LOGGER = LoggerFactory.getLogger(ComponentAnnotationLoader.class);
84   
85    /**
86    * Factory to create a Component Descriptor from an annotated class.
87    */
88    private ComponentDescriptorFactory factory = new ComponentDescriptorFactory();
89   
90    /**
91    * Loads all components defined using annotations.
92    *
93    * @param manager the component manager to use to dynamically register components
94    * @param classLoader the classloader to use to look for the Component list declaration file (
95    * {@code META-INF/components.txt})
96    */
 
97  2504 toggle public void initialize(ComponentManager manager, ClassLoader classLoader)
98    {
99  2504 try {
100    // Find all declared components by retrieving the list defined in COMPONENT_LIST.
101  2504 List<ComponentDeclaration> componentDeclarations = getDeclaredComponents(classLoader, COMPONENT_LIST);
102   
103    // Find all the Component overrides and adds them to the bottom of the list as component declarations with
104    // the highest priority of 0. This is purely for backward compatibility since the override files is now
105    // deprecated.
106  2504 List<ComponentDeclaration> componentOverrideDeclarations =
107    getDeclaredComponents(classLoader, COMPONENT_OVERRIDE_LIST);
108  2504 for (ComponentDeclaration componentOverrideDeclaration : componentOverrideDeclarations) {
109    // Since the old way to declare an override was to define it in both a component.txt and a
110    // component-overrides.txt file we first need to remove the override component declaration stored in
111    // componentDeclarations.
112  3 componentDeclarations.remove(componentOverrideDeclaration);
113    // Add it to the end of the list with the highest priority.
114  3 componentDeclarations.add(new ComponentDeclaration(componentOverrideDeclaration
115    .getImplementationClassName(), 0));
116    }
117   
118  2504 initialize(manager, classLoader, componentDeclarations);
119    } catch (Exception e) {
120    // Make sure we make the calling code fail in order to fail fast and prevent the application to start
121    // if something is amiss.
122  0 throw new RuntimeException("Failed to get the list of components to load", e);
123    }
124    }
125   
126    /**
127    * @param manager the component manager to use to dynamically register components
128    * @param classLoader the classloader to use to look for the Component list declaration file (
129    * {@code META-INF/components.txt})
130    * @param componentDeclarations the declarations of components to register
131    * @since 3.3M1
132    */
 
133  3849 toggle public void initialize(ComponentManager manager, ClassLoader classLoader,
134    List<ComponentDeclaration> componentDeclarations)
135    {
136  3849 register(manager, classLoader, componentDeclarations);
137    }
138   
139    /**
140    * @param manager the component manager to use to dynamically register components
141    * @param classLoader the classloader to use to look for the Component list declaration file (
142    * {@code META-INF/components.txt})
143    * @param componentDeclarations the declarations of components to register
144    * @since 4.0M1
145    */
 
146  3849 toggle public void register(ComponentManager manager, ClassLoader classLoader,
147    List<ComponentDeclaration> componentDeclarations)
148    {
149  3849 try {
150    // 2) For each component class name found, load its class and use introspection to find the necessary
151    // annotations required to create a Component Descriptor.
152  3849 Map<RoleHint<?>, ComponentDescriptor<?>> descriptorMap =
153    new HashMap<RoleHint<?>, ComponentDescriptor<?>>();
154  3849 Map<RoleHint<?>, Integer> priorityMap = new HashMap<RoleHint<?>, Integer>();
155   
156  3849 for (ComponentDeclaration componentDeclaration : componentDeclarations) {
157  769534 Class<?> componentClass;
158  769534 try {
159  769534 componentClass = classLoader.loadClass(componentDeclaration.getImplementationClassName());
160    } catch (Exception e) {
161  0 throw new RuntimeException(
162    String.format("Failed to load component class [%s] for annotation parsing",
163    componentDeclaration.getImplementationClassName()), e);
164    }
165   
166    // Look for ComponentRole annotations and register one component per ComponentRole found
167  769534 for (Type componentRoleType : findComponentRoleTypes(componentClass)) {
168  814731 for (ComponentDescriptor<?> componentDescriptor : this.factory.createComponentDescriptors(
169    componentClass, componentRoleType)) {
170    // If there's already a existing role/hint in the list of descriptors then decide which one
171    // to keep by looking at their priorities. Highest priority wins (i.e. lowest integer value).
172  831944 RoleHint<?> roleHint =
173    new RoleHint(componentDescriptor.getRoleType(), componentDescriptor.getRoleHint());
174   
175  831944 addComponent(descriptorMap, priorityMap, roleHint, componentDescriptor, componentDeclaration,
176    true);
177    }
178    }
179    }
180   
181    // 3) Activate all component descriptors
182  3849 for (ComponentDescriptor<?> descriptor : descriptorMap.values()) {
183  827064 manager.registerComponent(descriptor);
184    }
185    } catch (Exception e) {
186    // Make sure we make the calling code fail in order to fail fast and prevent the application to start
187    // if something is amiss.
188  0 throw new RuntimeException("Failed to dynamically load components with annotations", e);
189    }
190    }
191   
 
192  831944 toggle private void addComponent(Map<RoleHint<?>, ComponentDescriptor<?>> descriptorMap,
193    Map<RoleHint<?>, Integer> priorityMap, RoleHint<?> roleHint, ComponentDescriptor<?> componentDescriptor,
194    ComponentDeclaration componentDeclaration, boolean warn)
195    {
196  831944 if (descriptorMap.containsKey(roleHint)) {
197    // Compare priorities
198  4880 int currentPriority = priorityMap.get(roleHint);
199  4880 if (componentDeclaration.getPriority() < currentPriority) {
200    // Override!
201  2334 descriptorMap.put(roleHint, componentDescriptor);
202  2334 priorityMap.put(roleHint, componentDeclaration.getPriority());
203  2546 } else if (componentDeclaration.getPriority() == currentPriority) {
204  0 if (warn) {
205    // Warning that we're not overwriting since they have the same priorities
206  0 getLogger().warn(
207    "Component [{}] which implements [{}] tried to overwrite component "
208    + "[{}]. However, no action was taken since both components have the same priority "
209    + "level of [{}].",
210    new Object[] { componentDeclaration.getImplementationClassName(), roleHint,
211    descriptorMap.get(roleHint).getImplementation().getName(), currentPriority });
212    }
213    } else {
214  2546 getLogger().debug(
215    "Ignored component [{}] since its priority level of [{}] is lower "
216    + "than the currently registered component [{}] which has a priority of [{}]",
217    new Object[] { componentDeclaration.getImplementationClassName(),
218    componentDeclaration.getPriority(), currentPriority });
219    }
220    } else {
221  827064 descriptorMap.put(roleHint, componentDescriptor);
222  827064 priorityMap.put(roleHint, componentDeclaration.getPriority());
223    }
224    }
225   
226    /**
227    * @param manager the component manager to use to dynamically register components
228    * @param classLoader the classloader to use to look for the Component list declaration file (
229    * {@code META-INF/components.txt})
230    * @param componentDeclarations the declarations of components to register
231    * @since 4.0M1
232    */
 
233  76 toggle public void unregister(ComponentManager manager, ClassLoader classLoader,
234    List<ComponentDeclaration> componentDeclarations)
235    {
236  76 for (ComponentDeclaration componentDeclaration : componentDeclarations) {
237  120 Class<?> componentClass = null;
238  120 try {
239  120 componentClass = classLoader.loadClass(componentDeclaration.getImplementationClassName());
240    } catch (ClassNotFoundException e) {
241  0 getLogger().warn("Can't find any existing component with class [{}]. Ignoring it.",
242    componentDeclaration.getImplementationClassName());
243    } catch (Exception e) {
244  0 getLogger().warn("Fail to load component implementation class [{}]. Ignoring it.",
245    componentDeclaration.getImplementationClassName(), e);
246    }
247   
248  120 if (componentClass != null) {
249  120 for (ComponentDescriptor<?> componentDescriptor : getComponentsDescriptors(componentClass)) {
250  120 manager.unregisterComponent(componentDescriptor);
251   
252  120 if (componentDescriptor.getRoleType() instanceof ParameterizedType) {
253  44 Class roleClass = ReflectionUtils.getTypeClass(componentDescriptor.getRoleType());
254   
255  44 DefaultComponentDescriptor<?> classComponentDescriptor =
256    new DefaultComponentDescriptor(componentDescriptor);
257  44 classComponentDescriptor.setRoleType(roleClass);
258   
259  44 manager.unregisterComponent(classComponentDescriptor);
260    }
261    }
262    }
263    }
264    }
265   
 
266  1003 toggle public List<ComponentDescriptor> getComponentsDescriptors(Class<?> componentClass)
267    {
268  1003 List<ComponentDescriptor> descriptors = new ArrayList<ComponentDescriptor>();
269   
270    // Look for ComponentRole annotations and register one component per ComponentRole found
271  1003 for (Type componentRoleType : findComponentRoleTypes(componentClass)) {
272  1017 descriptors.addAll(this.factory.createComponentDescriptors(componentClass, componentRoleType));
273    }
274   
275  1003 return descriptors;
276    }
277   
 
278  772916 toggle public Set<Type> findComponentRoleTypes(Class<?> componentClass)
279    {
280  772916 return findComponentRoleTypes(componentClass, null);
281    }
282   
 
283  3005658 toggle public Set<Type> findComponentRoleTypes(Class<?> componentClass, Type[] parameters)
284    {
285    // Note: We use a Set to ensure that we don't register duplicate roles.
286  3005658 Set<Type> types = new LinkedHashSet<Type>();
287   
288  3005658 Component component = componentClass.getAnnotation(Component.class);
289   
290    // If the roles are specified by the user then don't auto-discover roles!
291  3005658 if (component != null && component.roles().length > 0) {
292  21354 types.addAll(Arrays.asList(component.roles()));
293    } else {
294    // Auto-discover roles by looking for a @Role annotation or a @Provider one in both the superclass
295    // and implemented interfaces.
296  2984304 for (Type interfaceType : getGenericInterfaces(componentClass)) {
297  1571567 Class<?> interfaceClass;
298  1571567 Type[] interfaceParameters;
299   
300  1571567 if (interfaceType instanceof ParameterizedType) {
301  223803 ParameterizedType interfaceParameterizedType = (ParameterizedType) interfaceType;
302   
303  223803 interfaceClass = ReflectionUtils.getTypeClass(interfaceType);
304  223803 Type[] variableParameters = interfaceParameterizedType.getActualTypeArguments();
305   
306  223803 interfaceParameters =
307    ReflectionUtils.resolveSuperArguments(variableParameters, componentClass, parameters);
308   
309  223803 if (interfaceParameters == null) {
310  1390 interfaceType = interfaceClass;
311  222413 } else if (interfaceParameters != variableParameters) {
312  92564 interfaceType =
313    new DefaultParameterizedType(interfaceParameterizedType.getOwnerType(), interfaceClass,
314    interfaceParameters);
315    }
316  1347764 } else if (interfaceType instanceof Class) {
317  1347764 interfaceClass = (Class<?>) interfaceType;
318  1347764 interfaceParameters = null;
319    } else {
320  0 continue;
321    }
322   
323    // Handle superclass of interfaces
324  1571567 types.addAll(findComponentRoleTypes(interfaceClass, interfaceParameters));
325   
326    // Handle interfaces directly declared in the passed component class
327  1571567 if (ReflectionUtils.getDirectAnnotation(Role.class, interfaceClass) != null) {
328  780754 types.add(interfaceType);
329    }
330   
331    // Handle javax.inject.Provider
332  1571567 if (Provider.class.isAssignableFrom(interfaceClass)) {
333  22007 types.add(interfaceType);
334    }
335   
336    // Handle ComponentRole (retro-compatibility since 4.0M1)
337  1571567 if (ReflectionUtils.getDirectAnnotation(ComponentRole.class, interfaceClass) != null) {
338  12952 types.add(interfaceClass);
339    }
340    }
341   
342    // Note that we need to look into the superclass since the super class can itself implements an interface
343    // that has the @Role annotation.
344  2984304 Type superType = componentClass.getGenericSuperclass();
345  2984304 if (superType != null && superType != Object.class) {
346  661175 if (superType instanceof ParameterizedType) {
347  182274 ParameterizedType superParameterizedType = (ParameterizedType) superType;
348  182274 types.addAll(findComponentRoleTypes((Class) superParameterizedType.getRawType(), ReflectionUtils
349    .resolveSuperArguments(superParameterizedType.getActualTypeArguments(), componentClass,
350    parameters)));
351  478901 } else if (superType instanceof Class) {
352  478901 types.addAll(findComponentRoleTypes((Class) superType, null));
353    }
354    }
355    }
356   
357  3005658 return types;
358    }
359   
360    /**
361    * Helper method that generate a {@link RuntimeException} in case of a reflection error.
362    *
363    * @param componentClass the component for which to return the interface types
364    * @return the Types representing the interfaces directly implemented by the class or interface represented by this
365    * object
366    * @throws RuntimeException in case of a reflection error such as
367    * {@link java.lang.reflect.MalformedParameterizedTypeException}
368    */
 
369  2984304 toggle private Type[] getGenericInterfaces(Class<?> componentClass)
370    {
371  2984304 Type[] interfaceTypes;
372  2984304 try {
373  2984304 interfaceTypes = componentClass.getGenericInterfaces();
374    } catch (Exception e) {
375  0 throw new RuntimeException(String.format("Failed to get interface for [%s]", componentClass.getName()), e);
376    }
377  2984304 return interfaceTypes;
378    }
379   
380    /**
381    * Finds the interfaces that implement component roles by looking recursively in all interfaces of the passed
382    * component implementation class. If the roles annotation value is specified then use the specified list instead of
383    * doing auto-discovery. Also note that we support component classes implementing JSR 330's
384    * {@link javax.inject.Provider} (and thus without a component role annotation).
385    *
386    * @param componentClass the component implementation class for which to find the component roles it implements
387    * @return the list of component role classes implemented
388    * @deprecated since 4.0M1 use {@link #findComponentRoleTypes(Class)} instead
389    */
 
390  0 toggle @Deprecated
391    public Set<Class<?>> findComponentRoleClasses(Class<?> componentClass)
392    {
393    // Note: We use a Set to ensure that we don't register duplicate roles.
394  0 Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
395   
396  0 Component component = componentClass.getAnnotation(Component.class);
397  0 if (component != null && component.roles().length > 0) {
398  0 classes.addAll(Arrays.asList(component.roles()));
399    } else {
400    // Look in both superclass and interfaces for @Role or javax.inject.Provider
401  0 for (Class<?> interfaceClass : componentClass.getInterfaces()) {
402    // Handle superclass of interfaces
403  0 classes.addAll(findComponentRoleClasses(interfaceClass));
404   
405    // Handle interfaces directly declared in the passed component class
406  0 for (Annotation annotation : interfaceClass.getDeclaredAnnotations()) {
407  0 if (annotation.annotationType() == ComponentRole.class) {
408  0 classes.add(interfaceClass);
409    }
410    }
411   
412    // Handle javax.inject.Provider
413  0 if (Provider.class.isAssignableFrom(interfaceClass)) {
414  0 classes.add(interfaceClass);
415    }
416    }
417   
418    // Note that we need to look into the superclass since the super class can itself implements an interface
419    // that has the @Role annotation.
420  0 Class<?> superClass = componentClass.getSuperclass();
421  0 if (superClass != null && superClass != Object.class) {
422  0 classes.addAll(findComponentRoleClasses(superClass));
423    }
424    }
425   
426  0 return classes;
427    }
428   
429    /**
430    * Get all components listed in the passed resource file.
431    *
432    * @param classLoader the classloader to use to find the resources
433    * @param location the name of the resources to look for
434    * @return the list of component implementation class names
435    * @throws IOException in case of an error loading the component list resource
436    * @since 3.3M1
437    */
 
438  5008 toggle private List<ComponentDeclaration> getDeclaredComponents(ClassLoader classLoader, String location)
439    throws IOException
440    {
441  5008 List<ComponentDeclaration> annotatedClassNames = new ArrayList<ComponentDeclaration>();
442  5008 Enumeration<URL> urls = classLoader.getResources(location);
443  94088 while (urls.hasMoreElements()) {
444  89080 URL url = urls.nextElement();
445   
446  89080 LOGGER.debug("Loading declared component definitions from [{}]", url);
447   
448  89080 InputStream componentListStream = url.openStream();
449   
450  89080 try {
451  89080 annotatedClassNames.addAll(getDeclaredComponents(componentListStream));
452    } finally {
453  89080 componentListStream.close();
454    }
455    }
456   
457  5008 return annotatedClassNames;
458    }
459   
460    /**
461    * Get all components listed in the passed resource stream. The format is:
462    * {@code (priority level):(fully qualified component implementation name)}.
463    *
464    * @param componentListStream the stream to parse
465    * @return the list of component declaration (implementation class names and priorities)
466    * @throws IOException in case of an error loading the component list resource
467    * @since 3.3M1
468    */
 
469  89323 toggle public List<ComponentDeclaration> getDeclaredComponents(InputStream componentListStream) throws IOException
470    {
471  89323 List<ComponentDeclaration> annotatedClassNames = new ArrayList<ComponentDeclaration>();
472   
473    // Read all components definition from the URL
474    // Always force UTF-8 as the encoding, since these files are read from the official jars, and those are
475    // generated on an 8-bit system.
476  89323 BufferedReader in = new BufferedReader(new InputStreamReader(componentListStream, COMPONENT_LIST_ENCODING));
477  89323 String inputLine;
478  ? while ((inputLine = in.readLine()) != null) {
479    // Make sure we don't add empty lines
480  745377 if (inputLine.trim().length() > 0) {
481  742663 try {
482  742663 String[] chunks = inputLine.split(":");
483  742663 ComponentDeclaration componentDeclaration;
484  742663 if (chunks.length > 1) {
485  5041 componentDeclaration = new ComponentDeclaration(chunks[1], Integer.parseInt(chunks[0]));
486    } else {
487  737622 componentDeclaration = new ComponentDeclaration(chunks[0]);
488    }
489  742663 LOGGER.debug(" - Adding component definition [{}] with priority [{}]",
490    componentDeclaration.getImplementationClassName(), componentDeclaration.getPriority());
491  742663 annotatedClassNames.add(componentDeclaration);
492    } catch (Exception e) {
493  0 getLogger().error("Failed to parse component declaration from [{}]", inputLine, e);
494    }
495    }
496    }
497   
498  89323 return annotatedClassNames;
499    }
500   
501    /**
502    * Get all components listed in a JAR file.
503    *
504    * @param jarFile the JAR file to parse
505    * @return the list of component declaration (implementation class names and priorities)
506    * @throws IOException in case of an error loading the component list resource
507    */
 
508  302 toggle public List<ComponentDeclaration> getDeclaredComponentsFromJAR(InputStream jarFile) throws IOException
509    {
510  302 ZipInputStream zis = new ZipInputStream(jarFile);
511   
512  302 List<ComponentDeclaration> componentDeclarations = null;
513  302 List<ComponentDeclaration> componentOverrideDeclarations = null;
514   
515  28213 for (ZipEntry entry = zis.getNextEntry(); entry != null
516    && (componentDeclarations == null || componentOverrideDeclarations == null); entry = zis.getNextEntry()) {
517  27911 if (entry.getName().equals(ComponentAnnotationLoader.COMPONENT_LIST)) {
518  243 componentDeclarations = getDeclaredComponents(zis);
519  27668 } else if (entry.getName().equals(ComponentAnnotationLoader.COMPONENT_OVERRIDE_LIST)) {
520  0 componentOverrideDeclarations = getDeclaredComponents(zis);
521    }
522    }
523   
524    // Merge all overrides found with a priority of 0. This is purely for backward compatibility since the
525    // override files is now deprecated.
526  302 if (componentOverrideDeclarations != null) {
527  0 if (componentDeclarations == null) {
528  0 componentDeclarations = new ArrayList<ComponentDeclaration>();
529    }
530  0 for (ComponentDeclaration componentOverrideDeclaration : componentOverrideDeclarations) {
531  0 componentDeclarations.add(new ComponentDeclaration(componentOverrideDeclaration
532    .getImplementationClassName(), 0));
533    }
534    }
535   
536  302 return componentDeclarations;
537    }
538   
539    /**
540    * Useful for unit tests that need to capture logs; they can return a mock logger instead of the real logger and
541    * thus assert what's been logged.
542    *
543    * @return the Logger instance to use to log
544    */
 
545  2546 toggle protected Logger getLogger()
546    {
547  2546 return LOGGER;
548    }
549    }