View Javadoc
1   /*
2    * Copyright (C) 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.codehaus.plexus.metadata.gleaner;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.lang.reflect.Modifier;
22  import java.net.URL;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Enumeration;
26  import java.util.List;
27  
28  import org.codehaus.plexus.component.annotations.Component;
29  import org.codehaus.plexus.component.annotations.Configuration;
30  import org.codehaus.plexus.component.annotations.Requirement;
31  import org.codehaus.plexus.component.repository.ComponentDescriptor;
32  import org.codehaus.plexus.component.repository.ComponentRequirement;
33  import org.codehaus.plexus.component.repository.ComponentRequirementList;
34  import org.codehaus.plexus.configuration.PlexusConfiguration;
35  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
36  import org.codehaus.plexus.metadata.ann.AnnClass;
37  import org.codehaus.plexus.metadata.ann.AnnField;
38  import org.codehaus.plexus.metadata.ann.AnnReader;
39  import org.codehaus.plexus.util.IOUtil;
40  
41  /**
42   * A class component gleaner which inspects each type for {@code org.codehaus.plexus.component.annotations.*} annotations
43   * and when found translates them into a {@link ComponentDescriptor}.
44   *
45   */
46  public class AnnotationComponentGleaner extends ComponentGleanerSupport implements ClassComponentGleaner {
47      private static final String OBJECT_SLASHED_NAME = Object.class.getName().replace('.', '/');
48  
49      public ComponentDescriptor<?> glean(String className, ClassLoader cl) throws ComponentGleanerException {
50          assert className != null;
51          assert cl != null;
52  
53          AnnClass annClass = readClass(className.replace('.', '/'), cl);
54  
55          // Skip abstract classes
56          if (Modifier.isAbstract(annClass.getAccess())) {
57              return null;
58          }
59  
60          Component anno = annClass.getAnnotation(Component.class);
61  
62          if (anno == null) {
63              return null;
64          }
65  
66          ComponentDescriptor<?> component = new ComponentDescriptor<Object>();
67  
68          component.setRole(anno.role().getName());
69  
70          component.setRoleHint(filterEmptyAsNull(anno.hint()));
71  
72          component.setImplementation(className);
73  
74          component.setVersion(filterEmptyAsNull(anno.version()));
75  
76          component.setComponentType(filterEmptyAsNull(anno.type()));
77  
78          component.setInstantiationStrategy(filterEmptyAsNull(anno.instantiationStrategy()));
79  
80          component.setLifecycleHandler(filterEmptyAsNull(anno.lifecycleHandler()));
81  
82          component.setComponentProfile(filterEmptyAsNull(anno.profile()));
83  
84          component.setComponentComposer(filterEmptyAsNull(anno.composer()));
85  
86          component.setComponentConfigurator(filterEmptyAsNull(anno.configurator()));
87  
88          component.setComponentFactory(filterEmptyAsNull(anno.factory()));
89  
90          component.setDescription(filterEmptyAsNull(anno.description()));
91  
92          component.setAlias(filterEmptyAsNull(anno.alias()));
93  
94          component.setIsolatedRealm(anno.isolatedRealm());
95  
96          for (AnnClass c : getClasses(annClass, cl)) {
97              for (AnnField field : c.getFields().values()) {
98                  ComponentRequirement requirement = findRequirement(field, c, cl);
99  
100                 if (requirement != null) {
101                     component.addRequirement(requirement);
102                 }
103 
104                 PlexusConfiguration config = findConfiguration(field, c, cl);
105 
106                 if (config != null) {
107                     addChildConfiguration(component, config);
108                 }
109             }
110 
111             //
112             // TODO: Inspect methods?
113             //
114         }
115 
116         return component;
117     }
118 
119     private AnnClass readClass(String className, ClassLoader cl) throws ComponentGleanerException {
120         InputStream is = null;
121 
122         try {
123             // only read annotation from project classes (not jars)
124             Enumeration<URL> en = cl.getResources(className + ".class");
125             while (en.hasMoreElements()) {
126                 URL url = en.nextElement();
127                 if (url.toString().startsWith("file:")) {
128                     is = url.openStream();
129                     return AnnReader.read(is, cl);
130                 }
131             }
132             throw new ComponentGleanerException("Can't find class " + className);
133         } catch (IOException ex) {
134             throw new ComponentGleanerException("Can't read class " + className, ex);
135         } finally {
136             IOUtil.close(is);
137         }
138     }
139 
140     private AnnClass readClass2(String className, ClassLoader cl) throws ComponentGleanerException {
141         InputStream is = null;
142         try {
143             is = cl.getResourceAsStream(className + ".class");
144             return AnnReader.read(is, cl);
145         } catch (IOException ex) {
146             throw new ComponentGleanerException("Can't read class " + className, ex);
147         } finally {
148             IOUtil.close(is);
149         }
150     }
151 
152     /**
153      * Returns a list of all of the classes which the given type inherits from.
154      */
155     private List<AnnClass> getClasses(AnnClass annClass, ClassLoader cl) throws ComponentGleanerException {
156         assert annClass != null;
157 
158         List<AnnClass> classes = new ArrayList<AnnClass>();
159 
160         while (annClass != null) {
161             classes.add(annClass);
162             String superName = annClass.getSuperName();
163             if (superName != null && !superName.equals(OBJECT_SLASHED_NAME)) {
164                 annClass = readClass2(superName, cl);
165             } else {
166                 break;
167             }
168 
169             //
170             // TODO: See if we need to include interfaces here too?
171             //
172         }
173 
174         return classes;
175     }
176 
177     private ComponentRequirement findRequirement(final AnnField field, AnnClass annClass, ClassLoader cl)
178             throws ComponentGleanerException {
179         assert field != null;
180 
181         Requirement anno = field.getAnnotation(Requirement.class);
182 
183         if (anno == null) {
184             return null;
185         }
186 
187         String fieldType = field.getType();
188 
189         // TODO implement type resolution without loading classes
190         Class<?> type;
191         try {
192             type = Class.forName(fieldType, false, cl);
193         } catch (ClassNotFoundException ex) {
194             // TODO Auto-generated catch block
195             throw new ComponentGleanerException("Can't load class " + fieldType);
196         }
197 
198         ComponentRequirement requirement;
199 
200         if (isRequirementListType(type)) {
201             requirement = new ComponentRequirementList();
202 
203             String[] hints = anno.hints();
204 
205             if (hints != null && hints.length > 0) {
206                 ((ComponentRequirementList) requirement).setRoleHints(Arrays.asList(hints));
207             }
208 
209             //
210             // TODO: See if we can glean any type details out of any generic information from the map or collection
211             //
212         } else {
213             requirement = new ComponentRequirement();
214 
215             requirement.setRoleHint(filterEmptyAsNull(anno.hint()));
216         }
217 
218         // TODO need to read default annotation values
219         // if (anno.role()==null || anno.role().isAssignableFrom(Object.class)) {
220         if (anno.role().isAssignableFrom(Object.class)) {
221             requirement.setRole(type.getName());
222         } else {
223             requirement.setRole(anno.role().getName());
224         }
225 
226         requirement.setFieldName(field.getName());
227 
228         requirement.setFieldMappingType(type.getName());
229 
230         requirement.setOptional(anno.optional());
231 
232         return requirement;
233     }
234 
235     private PlexusConfiguration findConfiguration(AnnField field, AnnClass c, ClassLoader cl) {
236         assert field != null;
237 
238         Configuration anno = field.getAnnotation(Configuration.class);
239 
240         if (anno == null) {
241             return null;
242         }
243 
244         String name = filterEmptyAsNull(anno.name());
245         if (name == null) {
246             name = field.getName();
247         }
248         name = deHump(name);
249 
250         XmlPlexusConfiguration config = new XmlPlexusConfiguration(name);
251 
252         String value = filterEmptyAsNull(anno.value());
253         if (value != null) {
254             config.setValue(value);
255         }
256 
257         return config;
258     }
259 }