View Javadoc
1   package org.codehaus.plexus.metadata.gleaner;
2   
3   /*
4    * The MIT License
5    *
6    * Copyright (c) 2004, The Codehaus
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   */
26  
27  import java.util.*;
28  
29  import com.thoughtworks.qdox.JavaProjectBuilder;
30  import com.thoughtworks.qdox.model.DocletTag;
31  import com.thoughtworks.qdox.model.JavaClass;
32  import com.thoughtworks.qdox.model.JavaField;
33  import org.codehaus.plexus.component.repository.ComponentDescriptor;
34  import org.codehaus.plexus.component.repository.ComponentRequirement;
35  import org.codehaus.plexus.component.repository.ComponentRequirementList;
36  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
37  import org.codehaus.plexus.logging.LogEnabled;
38  import org.codehaus.plexus.personality.plexus.lifecycle.phase.*;
39  import org.codehaus.plexus.util.StringUtils;
40  
41  /**
42   * A source component gleaner which uses QDox to discover Javadoc annotations.
43   *
44   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
45   */
46  public class QDoxComponentGleaner extends ComponentGleanerSupport implements SourceComponentGleaner {
47      public static final String PLEXUS_COMPONENT_TAG = "plexus.component";
48  
49      public static final String PLEXUS_REQUIREMENT_TAG = "plexus.requirement";
50  
51      public static final String PLEXUS_CONFIGURATION_TAG = "plexus.configuration";
52  
53      public static final String PLEXUS_VERSION_PARAMETER = "version";
54  
55      public static final String PLEXUS_ROLE_PARAMETER = "role";
56  
57      public static final String PLEXUS_ROLE_HINT_PARAMETER = "role-hint";
58  
59      public static final String PLEXUS_ROLE_HINT_LIST_PARAMETER = "role-hints";
60  
61      public static final String PLEXUS_ALIAS_PARAMETER = "alias";
62  
63      public static final String PLEXUS_DEFAULT_VALUE_PARAMETER = "default-value";
64  
65      public static final String PLEXUS_LIFECYCLE_HANDLER_PARAMETER = "lifecycle-handler";
66  
67      public static final String PLEXUS_INSTANTIATION_STARTEGY_PARAMETER = "instantiation-strategy";
68  
69      public static final String PLEXUS_OPTIONAL_PARAMETER = "optional";
70  
71      public static final String PLEXUS_DEFAULT_HINT = "default";
72  
73      // ----------------------------------------------------------------------
74      // ComponentGleaner Implementation
75      // ----------------------------------------------------------------------
76  
77      public ComponentDescriptor<?> glean(JavaProjectBuilder classCache, JavaClass javaClass)
78              throws ComponentGleanerException {
79          DocletTag tag = javaClass.getTagByName(PLEXUS_COMPONENT_TAG);
80  
81          if (tag == null) {
82              return null;
83          }
84  
85          Map<String, String> parameters = tag.getNamedParameterMap();
86  
87          // ----------------------------------------------------------------------
88          //
89          // ----------------------------------------------------------------------
90  
91          String fqn = javaClass.getFullyQualifiedName();
92  
93          // log.debug( "Creating descriptor for component: {}", fqn );
94  
95          ComponentDescriptor<?> componentDescriptor = new ComponentDescriptor<Object>();
96  
97          componentDescriptor.setImplementation(fqn);
98  
99          // ----------------------------------------------------------------------
100         // Role
101         // ----------------------------------------------------------------------
102 
103         String role = getParameter(parameters, PLEXUS_ROLE_PARAMETER);
104 
105         if (role == null) {
106             role = findRole(javaClass);
107 
108             if (role == null) {
109                 /*
110                 log.warn( "Could not figure out a role for the component '" + fqn + "'. " +
111                     "Please specify a role with a parameter '" + PLEXUS_ROLE_PARAMETER + "' " + "on the @" +
112                     PLEXUS_COMPONENT_TAG + " tag." );
113                     */
114 
115                 return null;
116             }
117         }
118 
119         componentDescriptor.setRole(role);
120 
121         // ----------------------------------------------------------------------
122         // Role hint
123         // ----------------------------------------------------------------------
124 
125         String roleHint = getParameter(parameters, PLEXUS_ROLE_HINT_PARAMETER);
126 
127         if (roleHint != null) {
128             // getLogger().debug( " Role hint: " + roleHint );
129         }
130 
131         componentDescriptor.setRoleHint(roleHint);
132 
133         // ----------------------------------------------------------------------
134         // Version
135         // ----------------------------------------------------------------------
136 
137         String version = getParameter(parameters, PLEXUS_VERSION_PARAMETER);
138 
139         componentDescriptor.setVersion(version);
140 
141         // ----------------------------------------------------------------------
142         // Lifecycle handler
143         // ----------------------------------------------------------------------
144 
145         String lifecycleHandler = getParameter(parameters, PLEXUS_LIFECYCLE_HANDLER_PARAMETER);
146 
147         componentDescriptor.setLifecycleHandler(lifecycleHandler);
148 
149         // ----------------------------------------------------------------------
150         // Lifecycle handler
151         // ----------------------------------------------------------------------
152 
153         String instatiationStrategy = getParameter(parameters, PLEXUS_INSTANTIATION_STARTEGY_PARAMETER);
154 
155         componentDescriptor.setInstantiationStrategy(instatiationStrategy);
156 
157         // ----------------------------------------------------------------------
158         // Alias
159         // ----------------------------------------------------------------------
160 
161         componentDescriptor.setAlias(getParameter(parameters, PLEXUS_ALIAS_PARAMETER));
162 
163         // ----------------------------------------------------------------------
164         //
165         // ----------------------------------------------------------------------
166 
167         findExtraParameters(PLEXUS_COMPONENT_TAG, parameters);
168 
169         // ----------------------------------------------------------------------
170         // Requirements
171         // ----------------------------------------------------------------------
172 
173         findRequirements(classCache, componentDescriptor, javaClass);
174 
175         // ----------------------------------------------------------------------
176         // Description
177         // ----------------------------------------------------------------------
178 
179         String comment = javaClass.getComment();
180 
181         if (comment != null) {
182             int i = comment.indexOf('.');
183 
184             if (i > 0) {
185                 comment = comment.substring(0, i + 1); // include the dot
186             }
187         }
188 
189         componentDescriptor.setDescription(comment);
190 
191         // ----------------------------------------------------------------------
192         // Configuration
193         // ----------------------------------------------------------------------
194 
195         XmlPlexusConfiguration configuration = new XmlPlexusConfiguration("configuration");
196 
197         findConfiguration(configuration, javaClass);
198 
199         componentDescriptor.setConfiguration(configuration);
200 
201         return componentDescriptor;
202     }
203 
204     // ----------------------------------------------------------------------
205     //
206     // ----------------------------------------------------------------------
207 
208     private static final List<String> IGNORED_INTERFACES = Collections.unmodifiableList(Arrays.asList(new String[] {
209         LogEnabled.class.getName(),
210         Initializable.class.getName(),
211         Configurable.class.getName(),
212         Contextualizable.class.getName(),
213         Disposable.class.getName(),
214         Startable.class.getName(),
215     }));
216 
217     private static String getPackage(JavaClass javaClass) {
218         if (javaClass.getPackage() != null) {
219             return javaClass.getPackage().getName();
220         } else {
221             return "";
222         }
223     }
224 
225     private String findRole(JavaClass javaClass) {
226         // ----------------------------------------------------------------------
227         // Remove any Plexus specific interfaces from the calculation
228         // ----------------------------------------------------------------------
229 
230         List<JavaClass> interfaces = new ArrayList<JavaClass>(javaClass.getInterfaces());
231 
232         for (Iterator<JavaClass> it = interfaces.iterator(); it.hasNext(); ) {
233             JavaClass ifc = it.next();
234 
235             if (IGNORED_INTERFACES.contains(ifc.getFullyQualifiedName())) {
236                 it.remove();
237             }
238         }
239 
240         // ----------------------------------------------------------------------
241         // For each implemented interface, check to see if it's a candiate
242         // interface
243         // ----------------------------------------------------------------------
244 
245         String role = null;
246 
247         String className = javaClass.getName();
248 
249         for (JavaClass ifc : interfaces) {
250             String fqn = ifc.getFullyQualifiedName();
251 
252             String pkg = getPackage(ifc);
253 
254             if (pkg == null) {
255                 int index = fqn.lastIndexOf('.');
256 
257                 if (index == -1) {
258                     // -----------------------------------------------------------------------
259                     // This is a special case which will happen in two cases:
260                     // 1) The component is in the default/root package
261                     // 2) The interface is in another build, typically in an -api package
262                     // while the code beeing gleaned in in the -impl build.
263                     //
264                     // Since it's most likely in another package than in the default package
265                     // prepend the gleaned class' package
266                     // -----------------------------------------------------------------------
267 
268                     pkg = getPackage(javaClass);
269 
270                     fqn = pkg + "." + fqn;
271                 } else {
272                     pkg = fqn.substring(0, index);
273                 }
274             }
275 
276             if (fqn == null) {
277                 fqn = ifc.getName();
278             }
279 
280             String name = fqn.substring(pkg.length() + 1);
281 
282             if (className.endsWith(name)) {
283                 if (role != null) {
284                     /*
285                     log.warn( "Found several possible roles for component " + "'" +
286                         javaClass.getFullyQualifiedName() + "', " + "will use '" + role + "', found: " + ifc.getName() + "." );
287                         */
288                 }
289 
290                 role = fqn;
291             }
292         }
293 
294         if (role == null) {
295             JavaClass superClass = javaClass.getSuperJavaClass();
296 
297             if (superClass != null) {
298                 role = findRole(superClass);
299             }
300         }
301 
302         return role;
303     }
304 
305     private void findRequirements(
306             JavaProjectBuilder classCache, ComponentDescriptor<?> componentDescriptor, JavaClass javaClass) {
307         List<JavaField> fields = javaClass.getFields();
308 
309         // ----------------------------------------------------------------------
310         // Search the super class for requirements
311         // ----------------------------------------------------------------------
312 
313         if (javaClass.getSuperJavaClass() != null) {
314             findRequirements(classCache, componentDescriptor, javaClass.getSuperJavaClass());
315         }
316 
317         // ----------------------------------------------------------------------
318         // Search the current class for requirements
319         // ----------------------------------------------------------------------
320 
321         for (JavaField field : fields) {
322             DocletTag tag = field.getTagByName(PLEXUS_REQUIREMENT_TAG);
323 
324             if (tag == null) {
325                 continue;
326             }
327 
328             Map<String, String> parameters = new HashMap<String, String>(tag.getNamedParameterMap());
329 
330             // ----------------------------------------------------------------------
331             // Role
332             // ----------------------------------------------------------------------
333 
334             String requirementClass = field.getType().getFullyQualifiedName();
335 
336             boolean isMap =
337                     requirementClass.equals(Map.class.getName()) || requirementClass.equals(Collection.class.getName());
338 
339             try {
340                 isMap = isMap || Collection.class.isAssignableFrom(Class.forName(requirementClass));
341             } catch (ClassNotFoundException e) {
342                 // ignore the assignable Collection test, though this should never happen
343             }
344 
345             boolean isList = requirementClass.equals(List.class.getName());
346 
347             ComponentRequirement cr;
348 
349             String hint = getParameter(parameters, PLEXUS_ROLE_HINT_PARAMETER);
350 
351             if (isMap || isList) {
352                 cr = new ComponentRequirementList();
353 
354                 String hintList = getParameter(parameters, PLEXUS_ROLE_HINT_LIST_PARAMETER);
355 
356                 if (hintList != null) {
357                     String[] hintArr = hintList.split(",");
358 
359                     ((ComponentRequirementList) cr).setRoleHints(Arrays.asList(hintArr));
360                 }
361             } else {
362                 cr = new ComponentRequirement();
363 
364                 cr.setRoleHint(hint);
365             }
366 
367             String role = getParameter(parameters, PLEXUS_ROLE_PARAMETER);
368 
369             if (role == null) {
370                 cr.setRole(requirementClass);
371             } else {
372                 cr.setRole(role);
373             }
374 
375             String optional = getParameter(parameters, PLEXUS_OPTIONAL_PARAMETER);
376 
377             cr.setOptional(Boolean.parseBoolean(optional));
378 
379             cr.setFieldName(field.getName());
380 
381             if (isMap || isList) {
382                 if (hint != null) {
383                     /*
384                     log.warn( "Field: '" + field.getName() + "': A role hint cannot be specified if the " +
385                         "field is a java.util.Map or a java.util.List" );
386                         */
387 
388                     continue;
389                 }
390 
391                 if (role == null) {
392                     /*
393                     log.warn( "Field: '" + field.getName() + "': A java.util.Map or java.util.List " +
394                         "requirement has to specify a '" + PLEXUS_ROLE_PARAMETER + "' parameter on " + "the @" +
395                         PLEXUS_REQUIREMENT_TAG + " tag so Plexus can know which components to " +
396                         "put in the map or list." );
397                         */
398 
399                     continue;
400                 }
401 
402                 JavaClass roleClass = classCache.getClassByName(role);
403 
404                 if (role.indexOf('.') == -1 && StringUtils.isEmpty(getPackage(roleClass))) {
405                     role = getPackage(javaClass) + "." + roleClass.getName();
406                 }
407 
408                 cr.setRole(role);
409 
410                 findExtraParameters(PLEXUS_REQUIREMENT_TAG, parameters);
411             }
412 
413             // ----------------------------------------------------------------------
414             //
415             // ----------------------------------------------------------------------
416 
417             componentDescriptor.addRequirement(cr);
418         }
419     }
420 
421     private void findConfiguration(XmlPlexusConfiguration configuration, JavaClass javaClass)
422             throws ComponentGleanerException {
423         List<JavaField> fields = javaClass.getFields();
424 
425         // ----------------------------------------------------------------------
426         // Search the super class for configurable fields.
427         // ----------------------------------------------------------------------
428 
429         if (javaClass.getSuperJavaClass() != null) {
430             findConfiguration(configuration, javaClass.getSuperJavaClass());
431         }
432 
433         // ----------------------------------------------------------------------
434         // Search the current class for configurable fields.
435         // ----------------------------------------------------------------------
436 
437         for (JavaField field : fields) {
438             DocletTag tag = field.getTagByName(PLEXUS_CONFIGURATION_TAG);
439 
440             if (tag == null) {
441                 continue;
442             }
443 
444             Map<String, String> parameters = new HashMap<String, String>(tag.getNamedParameterMap());
445 
446             /* don't use the getParameter helper as we like empty strings */
447             String defaultValue = parameters.remove(PLEXUS_DEFAULT_VALUE_PARAMETER);
448 
449             if (defaultValue == null) {
450                 /*
451                 log.warn( "Component: " + javaClass.getName() + ", field name: '" + field.getName() + "': " +
452                     "Currently configurable fields will not be written to the descriptor " +
453                     "without a default value." );*/
454 
455                 continue;
456             }
457 
458             String name = deHump(field.getName());
459 
460             XmlPlexusConfiguration c;
461 
462             c = new XmlPlexusConfiguration(name);
463 
464             c.setValue(defaultValue);
465 
466             // log.debug( " Configuration: {}={}", name, defaultValue );
467 
468             configuration.addChild(c);
469 
470             findExtraParameters(PLEXUS_CONFIGURATION_TAG, parameters);
471         }
472     }
473 
474     // ----------------------------------------------------------------------
475     //
476     // ----------------------------------------------------------------------
477 
478     private void findExtraParameters(String tagName, Map<String, String> parameters) {
479         for (String s : parameters.keySet()) {
480             // log.warn( "Extra parameter on the '" + tagName + "' tag: '" + s + "'." );
481         }
482     }
483 
484     private String getParameter(Map<String, String> parameters, String parameter) {
485         String value = parameters.remove(parameter);
486 
487         if (StringUtils.isEmpty(value)) {
488             return null;
489         }
490 
491         return value;
492     }
493 }