View Javadoc

1   package org.apache.maven.tools.plugin.generator;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.plugin.descriptor.DuplicateMojoDescriptorException;
23  import org.apache.maven.plugin.descriptor.MojoDescriptor;
24  import org.apache.maven.plugin.descriptor.Parameter;
25  import org.apache.maven.plugin.descriptor.PluginDescriptor;
26  import org.apache.maven.plugin.descriptor.Requirement;
27  import org.apache.maven.project.MavenProject;
28  import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
29  import org.apache.maven.tools.plugin.PluginToolsRequest;
30  import org.apache.maven.tools.plugin.util.PluginUtils;
31  import org.codehaus.plexus.util.IOUtil;
32  import org.codehaus.plexus.util.PropertyUtils;
33  import org.codehaus.plexus.util.StringUtils;
34  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
35  import org.codehaus.plexus.util.xml.XMLWriter;
36  import org.objectweb.asm.ClassReader;
37  import org.objectweb.asm.ClassVisitor;
38  import org.objectweb.asm.ClassWriter;
39  import org.objectweb.asm.commons.RemappingClassAdapter;
40  import org.objectweb.asm.commons.SimpleRemapper;
41  
42  import java.io.File;
43  import java.io.FileInputStream;
44  import java.io.FileOutputStream;
45  import java.io.IOException;
46  import java.io.OutputStreamWriter;
47  import java.io.Writer;
48  import java.text.SimpleDateFormat;
49  import java.util.Date;
50  import java.util.LinkedHashMap;
51  import java.util.LinkedHashSet;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.Properties;
55  import java.util.Set;
56  
57  /**
58   * Generate a <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a> and
59   * corresponding help content.
60   *
61   * @version $Id: PluginDescriptorGenerator.java 1343173 2012-05-28 09:30:23Z hboutemy $
62   * @todo add example usage tag that can be shown in the doco
63   * @todo need to add validation directives so that systems embedding maven2 can
64   * get validation directives to help users in IDEs.
65   */
66  public class PluginDescriptorGenerator
67      implements Generator
68  {
69  
70      /**
71       * {@inheritDoc}
72       */
73      public void execute( File destinationDirectory, PluginToolsRequest request )
74          throws GeneratorException
75      {
76  
77          File tmpPropertiesFile =
78              new File( request.getProject().getBuild().getDirectory(), "maven-plugin-help.properties" );
79  
80          if ( tmpPropertiesFile.exists() )
81          {
82              Properties properties = PropertyUtils.loadProperties( tmpPropertiesFile );
83  
84              String helpPackageName = properties.getProperty( "helpPackageName" );
85  
86              // if helpPackageName property is empty we have to rewrite the class with a better package name than empty
87              if ( StringUtils.isEmpty( helpPackageName ) )
88              {
89                  String helpMojoImplementation = rewriteHelpClassToMojoPackage( request );
90                  if ( helpMojoImplementation != null )
91                  {
92                      // rewrite plugin descriptor with new HelpMojo implementation class
93                      rewriteDescriptor( request.getPluginDescriptor(), helpMojoImplementation );
94                  }
95  
96              }
97          }
98  
99          try
100         {
101             // write complete plugin.xml descriptor
102             File f = new File( destinationDirectory, "plugin.xml" );
103             writeDescriptor( f, request, false );
104 
105             // write plugin-help.xml help-descriptor
106             MavenProject mavenProject = request.getProject();
107             String pluginHelpFilePath =
108                 "META-INF/maven/" + mavenProject.getGroupId() + "/" + mavenProject.getArtifactId()
109                     + "/plugin-help.xml";
110             f = new File( request.getProject().getBuild().getOutputDirectory(), pluginHelpFilePath );
111             writeDescriptor( f, request, true );
112         }
113         catch ( IOException e )
114         {
115             throw new GeneratorException( e.getMessage(), e );
116         }
117         catch ( DuplicateMojoDescriptorException e )
118         {
119             throw new GeneratorException( e.getMessage(), e );
120         }
121     }
122 
123     private String getVersion()
124     {
125         Package p = this.getClass().getPackage();
126         String version = ( p == null ) ? null : p.getSpecificationVersion();
127         return ( version == null ) ? "SNAPSHOT" : version;
128     }
129 
130     public void writeDescriptor( File destinationFile, PluginToolsRequest request, boolean helpDescriptor )
131         throws IOException, DuplicateMojoDescriptorException
132     {
133         PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
134 
135         if ( destinationFile.exists() )
136         {
137             destinationFile.delete();
138         }
139         else
140         {
141             if ( !destinationFile.getParentFile().exists() )
142             {
143                 destinationFile.getParentFile().mkdirs();
144             }
145         }
146 
147         String encoding = "UTF-8";
148 
149         Writer writer = null;
150         try
151         {
152             writer = new OutputStreamWriter( new FileOutputStream( destinationFile ), encoding );
153 
154             XMLWriter w = new PrettyPrintXMLWriter( writer, encoding, null );
155 
156             w.writeMarkup( "\n<!-- Generated by maven-plugin-tools " + getVersion() + " on "
157                 + new SimpleDateFormat( "yyyy-MM-dd" ).format( new Date() ) + " -->\n\n" );
158 
159             w.startElement( "plugin" );
160 
161             GeneratorUtils.element( w, "name", pluginDescriptor.getName() );
162 
163             GeneratorUtils.element( w, "description", pluginDescriptor.getDescription(), helpDescriptor );
164 
165             GeneratorUtils.element( w, "groupId", pluginDescriptor.getGroupId() );
166 
167             GeneratorUtils.element( w, "artifactId", pluginDescriptor.getArtifactId() );
168 
169             GeneratorUtils.element( w, "version", pluginDescriptor.getVersion() );
170 
171             GeneratorUtils.element( w, "goalPrefix", pluginDescriptor.getGoalPrefix() );
172 
173             if ( !helpDescriptor )
174             {
175                 GeneratorUtils.element( w, "isolatedRealm", String.valueOf( pluginDescriptor.isIsolatedRealm() ) );
176 
177                 GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( pluginDescriptor.isInheritedByDefault() ) );
178             }
179 
180             w.startElement( "mojos" );
181 
182             if ( pluginDescriptor.getMojos() != null )
183             {
184                 @SuppressWarnings( "unchecked" ) List<MojoDescriptor> descriptors = pluginDescriptor.getMojos();
185 
186                 if ( helpDescriptor )
187                 {
188                     PluginUtils.sortMojos( descriptors );
189                 }
190 
191                 for ( MojoDescriptor descriptor : descriptors )
192                 {
193                     processMojoDescriptor( descriptor, w, helpDescriptor );
194                 }
195             }
196 
197             w.endElement();
198 
199             if ( !helpDescriptor )
200             {
201                 GeneratorUtils.writeDependencies( w, pluginDescriptor );
202             }
203 
204             w.endElement();
205 
206             writer.flush();
207 
208         }
209         finally
210         {
211             IOUtil.close( writer );
212         }
213     }
214 
215     protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w )
216     {
217         processMojoDescriptor( mojoDescriptor, w, false );
218     }
219 
220     /**
221      * @param mojoDescriptor   not null
222      * @param w                not null
223      * @param helpDescriptor will clean html content from description fields
224      */
225     protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor )
226     {
227         w.startElement( "mojo" );
228 
229         // ----------------------------------------------------------------------
230         //
231         // ----------------------------------------------------------------------
232 
233         w.startElement( "goal" );
234         w.writeText( mojoDescriptor.getGoal() );
235         w.endElement();
236 
237         // ----------------------------------------------------------------------
238         //
239         // ----------------------------------------------------------------------
240 
241         String description = mojoDescriptor.getDescription();
242 
243         if ( description != null )
244         {
245             w.startElement( "description" );
246             if ( helpDescriptor )
247             {
248                 w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) );
249             }
250             else
251             {
252                 w.writeText( mojoDescriptor.getDescription() );
253             }
254             w.endElement();
255         }
256 
257         // ----------------------------------------------------------------------
258         //
259         // ----------------------------------------------------------------------
260 
261         if ( mojoDescriptor.isDependencyResolutionRequired() != null )
262         {
263             GeneratorUtils.element( w, "requiresDependencyResolution", mojoDescriptor.isDependencyResolutionRequired() );
264         }
265 
266         // ----------------------------------------------------------------------
267         //
268         // ----------------------------------------------------------------------
269 
270         GeneratorUtils.element( w, "requiresDirectInvocation", String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) );
271 
272         // ----------------------------------------------------------------------
273         //
274         // ----------------------------------------------------------------------
275 
276         GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) );
277 
278         // ----------------------------------------------------------------------
279         //
280         // ----------------------------------------------------------------------
281 
282         GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) );
283 
284         // ----------------------------------------------------------------------
285         //
286         // ----------------------------------------------------------------------
287 
288         GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) );
289 
290         // ----------------------------------------------------------------------
291         //
292         // ----------------------------------------------------------------------
293 
294         GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) );
295 
296         // ----------------------------------------------------------------------
297         //
298         // ----------------------------------------------------------------------
299 
300         GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) );
301 
302         // ----------------------------------------------------------------------
303         //
304         // ----------------------------------------------------------------------
305 
306         if ( mojoDescriptor.getPhase() != null )
307         {
308             GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() );
309         }
310 
311         // ----------------------------------------------------------------------
312         //
313         // ----------------------------------------------------------------------
314 
315         if ( mojoDescriptor.getExecutePhase() != null )
316         {
317             GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() );
318         }
319 
320         if ( mojoDescriptor.getExecuteGoal() != null )
321         {
322             GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() );
323         }
324 
325         if ( mojoDescriptor.getExecuteLifecycle() != null )
326         {
327             GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() );
328         }
329 
330         // ----------------------------------------------------------------------
331         //
332         // ----------------------------------------------------------------------
333 
334         w.startElement( "implementation" );
335         w.writeText( mojoDescriptor.getImplementation() );
336         w.endElement();
337 
338         // ----------------------------------------------------------------------
339         //
340         // ----------------------------------------------------------------------
341 
342         w.startElement( "language" );
343         w.writeText( mojoDescriptor.getLanguage() );
344         w.endElement();
345 
346         // ----------------------------------------------------------------------
347         //
348         // ----------------------------------------------------------------------
349 
350         if ( mojoDescriptor.getComponentConfigurator() != null )
351         {
352             w.startElement( "configurator" );
353             w.writeText( mojoDescriptor.getComponentConfigurator() );
354             w.endElement();
355         }
356 
357         // ----------------------------------------------------------------------
358         //
359         // ----------------------------------------------------------------------
360 
361         if ( mojoDescriptor.getComponentComposer() != null )
362         {
363             w.startElement( "composer" );
364             w.writeText( mojoDescriptor.getComponentComposer() );
365             w.endElement();
366         }
367 
368         // ----------------------------------------------------------------------
369         //
370         // ----------------------------------------------------------------------
371 
372         w.startElement( "instantiationStrategy" );
373         w.writeText( mojoDescriptor.getInstantiationStrategy() );
374         w.endElement();
375 
376         // ----------------------------------------------------------------------
377         // Strategy for handling repeated reference to mojo in
378         // the calculated (decorated, resolved) execution stack
379         // ----------------------------------------------------------------------
380         w.startElement( "executionStrategy" );
381         w.writeText( mojoDescriptor.getExecutionStrategy() );
382         w.endElement();
383 
384         // ----------------------------------------------------------------------
385         //
386         // ----------------------------------------------------------------------
387 
388         if ( mojoDescriptor.getSince() != null )
389         {
390             w.startElement( "since" );
391 
392             if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) )
393             {
394                 w.writeText( "No version given" );
395             }
396             else
397             {
398                 w.writeText( mojoDescriptor.getSince() );
399             }
400 
401             w.endElement();
402         }
403 
404         // ----------------------------------------------------------------------
405         //
406         // ----------------------------------------------------------------------
407 
408         if ( mojoDescriptor.getDeprecated() != null )
409         {
410             w.startElement( "deprecated" );
411 
412             if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) )
413             {
414                 w.writeText( "No reason given" );
415             }
416             else
417             {
418                 w.writeText( mojoDescriptor.getDeprecated() );
419             }
420 
421             w.endElement();
422         }
423 
424         // ----------------------------------------------------------------------
425         // Extended (3.0) descriptor
426         // ----------------------------------------------------------------------
427 
428         if ( mojoDescriptor instanceof ExtendedMojoDescriptor )
429         {
430             ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
431             if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null )
432             {
433                 GeneratorUtils.element( w, "requiresDependencyCollection",
434                                      extendedMojoDescriptor.getDependencyCollectionRequired() );
435             }
436 
437             GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) );
438         }
439 
440         // ----------------------------------------------------------------------
441         // Parameters
442         // ----------------------------------------------------------------------
443 
444         @SuppressWarnings( "unchecked" ) List<Parameter> parameters = mojoDescriptor.getParameters();
445 
446         w.startElement( "parameters" );
447 
448         Map<String, Requirement> requirements = new LinkedHashMap<String, Requirement>();
449 
450         Set<Parameter> configuration = new LinkedHashSet<Parameter>();
451 
452         if ( parameters != null )
453         {
454             if ( helpDescriptor )
455             {
456                 PluginUtils.sortMojoParameters( parameters );
457             }
458 
459             for ( Parameter parameter : parameters )
460             {
461                 String expression = getExpression( parameter );
462 
463                 if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
464                 {
465                     // treat it as a component...a requirement, in other words.
466 
467                     // remove "component." plus expression delimiters
468                     String role = expression.substring( "${component.".length(), expression.length() - 1 );
469 
470                     String roleHint = null;
471 
472                     int posRoleHintSeparator = role.indexOf( "#" );
473                     if ( posRoleHintSeparator > 0 )
474                     {
475                         roleHint = role.substring( posRoleHintSeparator + 1 );
476 
477                         role = role.substring( 0, posRoleHintSeparator );
478                     }
479 
480                     // TODO: remove deprecated expression
481                     requirements.put( parameter.getName(), new Requirement( role, roleHint ) );
482                 }
483                 else if ( parameter.getRequirement() != null )
484                 {
485                     requirements.put( parameter.getName(), parameter.getRequirement() );
486                 }
487                 else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help
488                 {
489                     // treat it as a normal parameter.
490 
491                     w.startElement( "parameter" );
492 
493                     GeneratorUtils.element( w, "name", parameter.getName() );
494 
495                     if ( parameter.getAlias() != null )
496                     {
497                         GeneratorUtils.element( w, "alias", parameter.getAlias() );
498                     }
499 
500                     GeneratorUtils.element( w, "type", parameter.getType() );
501 
502                     if ( parameter.getSince() != null )
503                     {
504                         w.startElement( "since" );
505 
506                         if ( StringUtils.isEmpty( parameter.getSince() ) )
507                         {
508                             w.writeText( "No version given" );
509                         }
510                         else
511                         {
512                             w.writeText( parameter.getSince() );
513                         }
514 
515                         w.endElement();
516                     }
517 
518                     if ( parameter.getDeprecated() != null )
519                     {
520                         if ( StringUtils.isEmpty( parameter.getDeprecated() ) )
521                         {
522                             GeneratorUtils.element( w, "deprecated", "No reason given" );
523                         }
524                         else
525                         {
526                             GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() );
527                         }
528                     }
529 
530                     if ( parameter.getImplementation() != null )
531                     {
532                         GeneratorUtils.element( w, "implementation", parameter.getImplementation() );
533                     }
534 
535                     GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) );
536 
537                     GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) );
538 
539                     GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor );
540 
541                     if ( StringUtils.isNotEmpty( parameter.getDefaultValue() )
542                         || StringUtils.isNotEmpty( parameter.getExpression() ) )
543                     {
544                         configuration.add( parameter );
545                     }
546 
547                     w.endElement();
548                 }
549 
550             }
551         }
552 
553         w.endElement();
554 
555         // ----------------------------------------------------------------------
556         // Configuration
557         // ----------------------------------------------------------------------
558 
559         if ( !configuration.isEmpty() )
560         {
561             w.startElement( "configuration" );
562 
563             for ( Parameter parameter : configuration )
564             {
565                 if ( helpDescriptor && !parameter.isEditable() )
566                 {
567                     // don't show readonly parameters in help
568                     continue;
569                 }
570 
571                 w.startElement( parameter.getName() );
572 
573                 String type = parameter.getType();
574                 if ( type != null )
575                 {
576                     w.addAttribute( "implementation", type );
577                 }
578 
579                 if ( parameter.getDefaultValue() != null )
580                 {
581                     w.addAttribute( "default-value", parameter.getDefaultValue() );
582                 }
583 
584                 if ( parameter.getExpression() != null )
585                 {
586                     w.writeText( parameter.getExpression() );
587                 }
588 
589                 w.endElement();
590             }
591 
592             w.endElement();
593         }
594 
595         // ----------------------------------------------------------------------
596         // Requirements
597         // ----------------------------------------------------------------------
598 
599         if ( !requirements.isEmpty() && !helpDescriptor )
600         {
601             w.startElement( "requirements" );
602 
603             for ( Map.Entry<String, Requirement> entry : requirements.entrySet() )
604             {
605                 String key = entry.getKey();
606                 Requirement requirement = entry.getValue();
607 
608                 w.startElement( "requirement" );
609 
610                 GeneratorUtils.element( w, "role", requirement.getRole() );
611 
612                 if ( requirement.getRoleHint() != null )
613                 {
614                     GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() );
615                 }
616 
617                 GeneratorUtils.element( w, "field-name", key );
618 
619                 w.endElement();
620             }
621 
622             w.endElement();
623         }
624 
625         w.endElement();
626     }
627 
628     /**
629      * Get the expression value, eventually surrounding it with <code>${ }</code>.
630      * 
631      * @param parameter the parameter
632      * @return the expression value
633      */
634     private String getExpression( Parameter parameter )
635     {
636         String expression = parameter.getExpression();
637         if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) )
638         {
639             expression = "${" + expression.trim() + "}";
640             parameter.setExpression( expression );
641         }
642         return expression;
643     }
644 
645     protected String rewriteHelpClassToMojoPackage( PluginToolsRequest request )
646         throws GeneratorException
647     {
648         String destinationPackage = GeneratorUtils.discoverPackageName( request.getPluginDescriptor() );
649         if ( StringUtils.isEmpty( destinationPackage ) )
650         {
651             return null;
652         }
653         File helpClassFile = new File( request.getProject().getBuild().getOutputDirectory(), "HelpMojo.class" );
654         if ( !helpClassFile.exists() )
655         {
656             return null;
657         }
658         File rewriteHelpClassFile = new File(
659             request.getProject().getBuild().getOutputDirectory() + "/" + StringUtils.replace( destinationPackage, ".",
660                                                                                               "/" ), "HelpMojo.class" );
661         if ( !rewriteHelpClassFile.getParentFile().exists() )
662         {
663             rewriteHelpClassFile.getParentFile().mkdirs();
664         }
665 
666         ClassReader cr = null;
667         try
668         {
669             cr = new ClassReader( new FileInputStream( helpClassFile ) );
670         }
671         catch ( IOException e )
672         {
673             throw new GeneratorException( e.getMessage(), e );
674         }
675 
676         ClassWriter cw = new ClassWriter( 0 );
677 
678         ClassVisitor cv = new RemappingClassAdapter( cw, new SimpleRemapper( "HelpMojo",
679                                                                              StringUtils.replace( destinationPackage,
680                                                                                                   ".", "/" )
681                                                                                  + "/HelpMojo" ) );
682 
683         try
684         {
685             cr.accept( cv, ClassReader.EXPAND_FRAMES );
686         }
687         catch ( Throwable e )
688         {
689             throw new GeneratorException( "ASM issue processing classFile " + helpClassFile.getPath(), e );
690         }
691 
692         byte[] renamedClass = cw.toByteArray();
693         FileOutputStream fos = null;
694         try
695         {
696             fos = new FileOutputStream( rewriteHelpClassFile );
697             fos.write( renamedClass );
698         }
699         catch ( IOException e )
700         {
701             throw new GeneratorException( "Error rewriting help class: " + e.getMessage(), e );
702         }
703         finally
704         {
705             IOUtil.close( fos );
706         }
707         helpClassFile.delete();
708         return destinationPackage + ".HelpMojo";
709     }
710 
711 
712     private void rewriteDescriptor( PluginDescriptor pluginDescriptor, String helpMojoImplementation )
713     {
714         MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo( "help" );
715         if ( mojoDescriptor != null )
716         {
717             mojoDescriptor.setImplementation( helpMojoImplementation );
718         }
719     }
720 }