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 <tt>org.codehaus.plexus.component.annotations.*</tt> annotations
43   * and when found translates them into a {@link ComponentDescriptor}.
44   *
45   * @version $Id$
46   */
47  public class AnnotationComponentGleaner
48      extends ComponentGleanerSupport
49      implements ClassComponentGleaner
50  {
51      private static final String OBJECT_SLASHED_NAME = Object.class.getName().replace('.', '/');
52  
53      public ComponentDescriptor<?> glean(String className, ClassLoader cl) throws ComponentGleanerException 
54      {
55          assert className != null;
56          assert cl != null;
57  
58          AnnClass annClass = readClass(className.replace('.', '/'), cl);
59          
60          // Skip abstract classes
61          if (Modifier.isAbstract(annClass.getAccess())) {
62              return null;
63          }
64          
65          
66          Component anno = annClass.getAnnotation(Component.class);
67  
68          if (anno == null) {
69              return null;
70          }
71  
72          ComponentDescriptor<?> component = new ComponentDescriptor<Object>();
73          
74          component.setRole(anno.role().getName());
75  
76          component.setRoleHint(filterEmptyAsNull(anno.hint()));
77  
78          component.setImplementation(className);
79  
80          component.setVersion(filterEmptyAsNull(anno.version()));
81  
82          component.setComponentType(filterEmptyAsNull(anno.type()));
83  
84          component.setInstantiationStrategy(filterEmptyAsNull(anno.instantiationStrategy()));
85  
86          component.setLifecycleHandler(filterEmptyAsNull(anno.lifecycleHandler()));
87  
88          component.setComponentProfile(filterEmptyAsNull(anno.profile()));
89  
90          component.setComponentComposer(filterEmptyAsNull(anno.composer()));
91  
92          component.setComponentConfigurator(filterEmptyAsNull(anno.configurator()));
93  
94          component.setComponentFactory(filterEmptyAsNull(anno.factory()));
95  
96          component.setDescription(filterEmptyAsNull(anno.description()));
97  
98          component.setAlias(filterEmptyAsNull(anno.alias()));
99  
100         component.setIsolatedRealm(anno.isolatedRealm());
101 
102         for (AnnClass c : getClasses(annClass, cl)) {
103             for (AnnField field : c.getFields().values()) {
104                 ComponentRequirement requirement = findRequirement(field, c, cl);
105 
106                 if (requirement != null) {
107                     component.addRequirement(requirement);
108                 }
109 
110                 PlexusConfiguration config = findConfiguration(field, c, cl);
111 
112                 if (config != null) {
113                     addChildConfiguration(component, config);
114                 }
115             }
116 
117             //
118             // TODO: Inspect methods?
119             //
120         }
121 
122         return component;
123     }
124 
125     private AnnClass readClass(String className, ClassLoader cl) throws ComponentGleanerException 
126     {
127     	InputStream is = null;
128     	
129     	try 
130     	{
131     		// only read annotation from project classes (not jars)
132     		Enumeration<URL> en = cl.getResources( className + ".class" );
133     		while ( en.hasMoreElements() ) {
134 				URL url = en.nextElement();
135 				if( url.toString().startsWith( "file:" ) ) 
136 				{
137 					is = url.openStream();
138 					return AnnReader.read( is, cl );
139 				}	
140 			}
141     		throw new ComponentGleanerException("Can't find class " + className);
142         } 
143         catch (IOException ex) 
144         {
145         	throw new ComponentGleanerException("Can't read class " + className, ex);
146         }
147         finally
148         {
149         	IOUtil.close(is);
150         }
151     }
152 
153     private AnnClass readClass2(String className, ClassLoader cl) throws ComponentGleanerException 
154     {
155         InputStream is = null;
156         try 
157         {
158           is = cl.getResourceAsStream(className + ".class");
159           return AnnReader.read(is, cl);
160         } 
161         catch (IOException ex) 
162         {
163           throw new ComponentGleanerException("Can't read class " + className, ex);
164         }
165         finally
166         {
167           IOUtil.close(is);
168         }
169     }
170     
171     /**
172      * Returns a list of all of the classes which the given type inherits from.
173      */
174     private List<AnnClass> getClasses(AnnClass annClass, ClassLoader cl) throws ComponentGleanerException {
175         assert annClass != null;
176 
177         List<AnnClass> classes = new ArrayList<AnnClass>();
178 
179         while(annClass!=null) {
180             classes.add(annClass);
181             String superName = annClass.getSuperName();
182             if(superName!=null && !superName.equals(OBJECT_SLASHED_NAME)) {
183               annClass = readClass2(superName, cl);
184             } else {
185               break;
186             }
187 
188             //
189             // TODO: See if we need to include interfaces here too?
190             //
191         }
192 
193         return classes;
194     }
195 
196     private ComponentRequirement findRequirement(final AnnField field, AnnClass annClass, ClassLoader cl) 
197         throws ComponentGleanerException 
198     {
199         assert field != null;
200 
201         Requirement anno = field.getAnnotation(Requirement.class);
202         
203         if (anno == null) {
204             return null;
205         }
206 
207         String fieldType = field.getType();
208         
209         // TODO implement type resolution without loading classes
210         Class<?> type;
211         try {
212           type = Class.forName(fieldType, false, cl);
213         } catch (ClassNotFoundException ex) {
214           // TODO Auto-generated catch block
215           throw new ComponentGleanerException("Can't load class " + fieldType);
216         }
217 
218         ComponentRequirement requirement;
219 
220         if (isRequirementListType(type)) {
221             requirement = new ComponentRequirementList();
222 
223             String[] hints = anno.hints();
224 
225             if (hints != null && hints.length > 0) {
226                 ((ComponentRequirementList)requirement).setRoleHints(Arrays.asList(hints));
227             }
228 
229             //
230             // TODO: See if we can glean any type details out of any generic information from the map or collection
231             //
232         }
233         else {
234             requirement = new ComponentRequirement();
235 
236             requirement.setRoleHint(filterEmptyAsNull(anno.hint()));
237         }
238 
239         // TODO need to read default annotation values 
240         // if (anno.role()==null || anno.role().isAssignableFrom(Object.class)) {
241         if (anno.role().isAssignableFrom(Object.class)) {
242             requirement.setRole(type.getName());
243         }
244         else {
245             requirement.setRole(anno.role().getName());
246         }
247 
248         requirement.setFieldName(field.getName());
249 
250         requirement.setFieldMappingType(type.getName());
251 
252         requirement.setOptional( anno.optional() );
253 
254         return requirement;
255     }
256 
257     private PlexusConfiguration findConfiguration(AnnField field, AnnClass c, ClassLoader cl) {
258         assert field != null;
259 
260         Configuration anno = field.getAnnotation(Configuration.class);
261 
262         if (anno == null) {
263             return null;
264         }
265 
266         String name = filterEmptyAsNull(anno.name());
267         if (name == null) {
268             name = field.getName();
269         }
270         name = deHump(name);
271         
272         XmlPlexusConfiguration config = new XmlPlexusConfiguration(name);
273 
274         String value = filterEmptyAsNull(anno.value());
275         if (value != null) {
276             config.setValue(value);
277         }
278 
279         return config;
280     }
281 }