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 static java.nio.charset.StandardCharsets.UTF_8;
23  
24  import java.io.File;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.OutputStreamWriter;
28  import java.io.Writer;
29  import java.util.LinkedHashMap;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import org.apache.maven.plugin.descriptor.DuplicateMojoDescriptorException;
35  import org.apache.maven.plugin.descriptor.MojoDescriptor;
36  import org.apache.maven.plugin.descriptor.Parameter;
37  import org.apache.maven.plugin.descriptor.PluginDescriptor;
38  import org.apache.maven.plugin.descriptor.Requirement;
39  import org.apache.maven.plugin.logging.Log;
40  import org.apache.maven.project.MavenProject;
41  import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
42  import org.apache.maven.tools.plugin.PluginToolsRequest;
43  import org.apache.maven.tools.plugin.util.PluginUtils;
44  import org.codehaus.plexus.util.StringUtils;
45  import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
46  import org.codehaus.plexus.util.xml.XMLWriter;
47  
48  /**
49   * Generate a <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a> and
50   * corresponding <code>plugin-help.xml</code> help content for {@link PluginHelpGenerator}.
51   *
52   */
53  public class PluginDescriptorGenerator
54      implements Generator
55  {
56  
57      private final Log log;
58  
59      public PluginDescriptorGenerator( Log log )
60      {
61          this.log = log;
62      }
63  
64      @Override
65      public void execute( File destinationDirectory, PluginToolsRequest request )
66          throws GeneratorException
67      {
68          // eventually rewrite help mojo class to match actual package name
69          PluginHelpGenerator.rewriteHelpMojo( request, log );
70  
71          try
72          {
73              // write complete plugin.xml descriptor
74              File f = new File( destinationDirectory, "plugin.xml" );
75              writeDescriptor( f, request, false );
76  
77              // write plugin-help.xml help-descriptor
78              MavenProject mavenProject = request.getProject();
79  
80              f = new File( destinationDirectory,
81                            PluginHelpGenerator.getPluginHelpPath( mavenProject ) );
82  
83              writeDescriptor( f, request, true );
84          }
85          catch ( IOException e )
86          {
87              throw new GeneratorException( e.getMessage(), e );
88          }
89          catch ( DuplicateMojoDescriptorException e )
90          {
91              throw new GeneratorException( e.getMessage(), e );
92          }
93      }
94  
95      private String getVersion()
96      {
97          Package p = this.getClass().getPackage();
98          String version = ( p == null ) ? null : p.getSpecificationVersion();
99          return ( version == null ) ? "SNAPSHOT" : version;
100     }
101 
102     public void writeDescriptor( File destinationFile, PluginToolsRequest request, boolean helpDescriptor )
103         throws IOException, DuplicateMojoDescriptorException
104     {
105         PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
106 
107         if ( destinationFile.exists() )
108         {
109             destinationFile.delete();
110         }
111         else if ( !destinationFile.getParentFile().exists() )
112         {
113             destinationFile.getParentFile().mkdirs();
114         }
115 
116         try ( Writer writer = new OutputStreamWriter( new FileOutputStream( destinationFile ), UTF_8 ) )
117         {
118             XMLWriter w = new PrettyPrintXMLWriter( writer, UTF_8.name(), null );
119 
120             w.writeMarkup( "\n<!-- Generated by maven-plugin-tools " + getVersion() + " -->\n\n" );
121 
122             w.startElement( "plugin" );
123 
124             GeneratorUtils.element( w, "name", pluginDescriptor.getName() );
125 
126             GeneratorUtils.element( w, "description", pluginDescriptor.getDescription(), helpDescriptor );
127 
128             GeneratorUtils.element( w, "groupId", pluginDescriptor.getGroupId() );
129 
130             GeneratorUtils.element( w, "artifactId", pluginDescriptor.getArtifactId() );
131 
132             GeneratorUtils.element( w, "version", pluginDescriptor.getVersion() );
133 
134             GeneratorUtils.element( w, "goalPrefix", pluginDescriptor.getGoalPrefix() );
135 
136             if ( !helpDescriptor )
137             {
138                 GeneratorUtils.element( w, "isolatedRealm", String.valueOf( pluginDescriptor.isIsolatedRealm() ) );
139 
140                 GeneratorUtils.element( w, "inheritedByDefault",
141                                         String.valueOf( pluginDescriptor.isInheritedByDefault() ) );
142             }
143 
144             w.startElement( "mojos" );
145 
146             if ( pluginDescriptor.getMojos() != null )
147             {
148                 List<MojoDescriptor> descriptors = pluginDescriptor.getMojos();
149 
150                 PluginUtils.sortMojos( descriptors );
151 
152                 for ( MojoDescriptor descriptor : descriptors )
153                 {
154                     processMojoDescriptor( descriptor, w, helpDescriptor );
155                 }
156             }
157 
158             w.endElement();
159 
160             if ( !helpDescriptor )
161             {
162                 GeneratorUtils.writeDependencies( w, pluginDescriptor );
163             }
164 
165             w.endElement();
166 
167             writer.flush();
168 
169         }
170     }
171 
172     protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w )
173     {
174         processMojoDescriptor( mojoDescriptor, w, false );
175     }
176 
177     /**
178      * @param mojoDescriptor not null
179      * @param w              not null
180      * @param helpDescriptor will clean html content from description fields
181      */
182     @SuppressWarnings( "deprecation" )
183     protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor )
184     {
185         w.startElement( "mojo" );
186 
187         // ----------------------------------------------------------------------
188         //
189         // ----------------------------------------------------------------------
190 
191         w.startElement( "goal" );
192         w.writeText( mojoDescriptor.getGoal() );
193         w.endElement();
194 
195         // ----------------------------------------------------------------------
196         //
197         // ----------------------------------------------------------------------
198 
199         String description = mojoDescriptor.getDescription();
200 
201         if ( StringUtils.isNotEmpty( description ) )
202         {
203             w.startElement( "description" );
204             if ( helpDescriptor )
205             {
206                 w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) );
207             }
208             else
209             {
210                 w.writeText( mojoDescriptor.getDescription() );
211             }
212             w.endElement();
213         }
214 
215         // ----------------------------------------------------------------------
216         //
217         // ----------------------------------------------------------------------
218 
219         if ( StringUtils.isNotEmpty( mojoDescriptor.isDependencyResolutionRequired() ) )
220         {
221             GeneratorUtils.element( w, "requiresDependencyResolution",
222                                     mojoDescriptor.isDependencyResolutionRequired() );
223         }
224 
225         // ----------------------------------------------------------------------
226         //
227         // ----------------------------------------------------------------------
228 
229         GeneratorUtils.element( w, "requiresDirectInvocation",
230                                 String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) );
231 
232         // ----------------------------------------------------------------------
233         //
234         // ----------------------------------------------------------------------
235 
236         GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) );
237 
238         // ----------------------------------------------------------------------
239         //
240         // ----------------------------------------------------------------------
241 
242         GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) );
243 
244         // ----------------------------------------------------------------------
245         //
246         // ----------------------------------------------------------------------
247 
248         GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) );
249 
250         // ----------------------------------------------------------------------
251         //
252         // ----------------------------------------------------------------------
253 
254         GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) );
255 
256         // ----------------------------------------------------------------------
257         //
258         // ----------------------------------------------------------------------
259 
260         GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) );
261 
262         // ----------------------------------------------------------------------
263         //
264         // ----------------------------------------------------------------------
265 
266         if ( StringUtils.isNotEmpty( mojoDescriptor.getPhase() ) )
267         {
268             GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() );
269         }
270 
271         // ----------------------------------------------------------------------
272         //
273         // ----------------------------------------------------------------------
274 
275         if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) )
276         {
277             GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() );
278         }
279 
280         if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteGoal() ) )
281         {
282             GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() );
283         }
284 
285         if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteLifecycle() ) )
286         {
287             GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() );
288         }
289 
290         // ----------------------------------------------------------------------
291         //
292         // ----------------------------------------------------------------------
293 
294         w.startElement( "implementation" );
295         w.writeText( mojoDescriptor.getImplementation() );
296         w.endElement();
297 
298         // ----------------------------------------------------------------------
299         //
300         // ----------------------------------------------------------------------
301 
302         w.startElement( "language" );
303         w.writeText( mojoDescriptor.getLanguage() );
304         w.endElement();
305 
306         // ----------------------------------------------------------------------
307         //
308         // ----------------------------------------------------------------------
309 
310         if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentConfigurator() ) )
311         {
312             w.startElement( "configurator" );
313             w.writeText( mojoDescriptor.getComponentConfigurator() );
314             w.endElement();
315         }
316 
317         // ----------------------------------------------------------------------
318         //
319         // ----------------------------------------------------------------------
320 
321         if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentComposer() ) )
322         {
323             w.startElement( "composer" );
324             w.writeText( mojoDescriptor.getComponentComposer() );
325             w.endElement();
326         }
327 
328         // ----------------------------------------------------------------------
329         //
330         // ----------------------------------------------------------------------
331 
332         w.startElement( "instantiationStrategy" );
333         w.writeText( mojoDescriptor.getInstantiationStrategy() );
334         w.endElement();
335 
336         // ----------------------------------------------------------------------
337         // Strategy for handling repeated reference to mojo in
338         // the calculated (decorated, resolved) execution stack
339         // ----------------------------------------------------------------------
340         w.startElement( "executionStrategy" );
341         w.writeText( mojoDescriptor.getExecutionStrategy() );
342         w.endElement();
343 
344         // ----------------------------------------------------------------------
345         //
346         // ----------------------------------------------------------------------
347 
348         if ( mojoDescriptor.getSince() != null )
349         {
350             w.startElement( "since" );
351 
352             if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) )
353             {
354                 w.writeText( "No version given" );
355             }
356             else
357             {
358                 w.writeText( mojoDescriptor.getSince() );
359             }
360 
361             w.endElement();
362         }
363 
364         // ----------------------------------------------------------------------
365         //
366         // ----------------------------------------------------------------------
367 
368         if ( mojoDescriptor.getDeprecated() != null )
369         {
370             w.startElement( "deprecated" );
371 
372             if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) )
373             {
374                 w.writeText( "No reason given" );
375             }
376             else
377             {
378                 w.writeText( mojoDescriptor.getDeprecated() );
379             }
380 
381             w.endElement();
382         }
383 
384         // ----------------------------------------------------------------------
385         // Extended (3.0) descriptor
386         // ----------------------------------------------------------------------
387 
388         if ( mojoDescriptor instanceof ExtendedMojoDescriptor )
389         {
390             ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
391             if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null )
392             {
393                 GeneratorUtils.element( w, "requiresDependencyCollection",
394                                         extendedMojoDescriptor.getDependencyCollectionRequired() );
395             }
396 
397             GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) );
398         }
399 
400         // ----------------------------------------------------------------------
401         // Parameters
402         // ----------------------------------------------------------------------
403 
404         List<Parameter> parameters = mojoDescriptor.getParameters();
405 
406         w.startElement( "parameters" );
407 
408         Map<String, Requirement> requirements = new LinkedHashMap<>();
409 
410         Set<Parameter> configuration = new LinkedHashSet<>();
411 
412         if ( parameters != null )
413         {
414             if ( helpDescriptor )
415             {
416                 PluginUtils.sortMojoParameters( parameters );
417             }
418 
419             for ( Parameter parameter : parameters )
420             {
421                 String expression = getExpression( parameter );
422 
423                 if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
424                 {
425                     // treat it as a component...a requirement, in other words.
426 
427                     // remove "component." plus expression delimiters
428                     String role = expression.substring( "${component.".length(), expression.length() - 1 );
429 
430                     String roleHint = null;
431 
432                     int posRoleHintSeparator = role.indexOf( '#' );
433                     if ( posRoleHintSeparator > 0 )
434                     {
435                         roleHint = role.substring( posRoleHintSeparator + 1 );
436 
437                         role = role.substring( 0, posRoleHintSeparator );
438                     }
439 
440                     // TODO: remove deprecated expression
441                     requirements.put( parameter.getName(), new Requirement( role, roleHint ) );
442                 }
443                 else if ( parameter.getRequirement() != null )
444                 {
445                     requirements.put( parameter.getName(), parameter.getRequirement() );
446                 }
447                 else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help
448                 {
449                     // treat it as a normal parameter.
450 
451                     w.startElement( "parameter" );
452 
453                     GeneratorUtils.element( w, "name", parameter.getName() );
454 
455                     if ( parameter.getAlias() != null )
456                     {
457                         GeneratorUtils.element( w, "alias", parameter.getAlias() );
458                     }
459 
460                     GeneratorUtils.element( w, "type", parameter.getType() );
461 
462                     if ( parameter.getSince() != null )
463                     {
464                         w.startElement( "since" );
465 
466                         if ( StringUtils.isEmpty( parameter.getSince() ) )
467                         {
468                             w.writeText( "No version given" );
469                         }
470                         else
471                         {
472                             w.writeText( parameter.getSince() );
473                         }
474 
475                         w.endElement();
476                     }
477 
478                     if ( parameter.getDeprecated() != null )
479                     {
480                         if ( StringUtils.isEmpty( parameter.getDeprecated() ) )
481                         {
482                             GeneratorUtils.element( w, "deprecated", "No reason given" );
483                         }
484                         else
485                         {
486                             GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() );
487                         }
488                     }
489 
490                     if ( parameter.getImplementation() != null )
491                     {
492                         GeneratorUtils.element( w, "implementation", parameter.getImplementation() );
493                     }
494 
495                     GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) );
496 
497                     GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) );
498 
499                     GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor );
500 
501                     if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) || StringUtils.isNotEmpty(
502                         parameter.getExpression() ) )
503                     {
504                         configuration.add( parameter );
505                     }
506 
507                     w.endElement();
508                 }
509 
510             }
511         }
512 
513         w.endElement();
514 
515         // ----------------------------------------------------------------------
516         // Configuration
517         // ----------------------------------------------------------------------
518 
519         if ( !configuration.isEmpty() )
520         {
521             w.startElement( "configuration" );
522 
523             for ( Parameter parameter : configuration )
524             {
525                 if ( helpDescriptor && !parameter.isEditable() )
526                 {
527                     // don't show readonly parameters in help
528                     continue;
529                 }
530 
531                 w.startElement( parameter.getName() );
532 
533                 String type = parameter.getType();
534                 if ( StringUtils.isNotEmpty( type ) )
535                 {
536                     w.addAttribute( "implementation", type );
537                 }
538 
539                 if ( parameter.getDefaultValue() != null )
540                 {
541                     w.addAttribute( "default-value", parameter.getDefaultValue() );
542                 }
543 
544                 if ( StringUtils.isNotEmpty( parameter.getExpression() ) )
545                 {
546                     w.writeText( parameter.getExpression() );
547                 }
548 
549                 w.endElement();
550             }
551 
552             w.endElement();
553         }
554 
555         // ----------------------------------------------------------------------
556         // Requirements
557         // ----------------------------------------------------------------------
558 
559         if ( !requirements.isEmpty() && !helpDescriptor )
560         {
561             w.startElement( "requirements" );
562 
563             for ( Map.Entry<String, Requirement> entry : requirements.entrySet() )
564             {
565                 String key = entry.getKey();
566                 Requirement requirement = entry.getValue();
567 
568                 w.startElement( "requirement" );
569 
570                 GeneratorUtils.element( w, "role", requirement.getRole() );
571 
572                 if ( StringUtils.isNotEmpty( requirement.getRoleHint() ) )
573                 {
574                     GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() );
575                 }
576 
577                 GeneratorUtils.element( w, "field-name", key );
578 
579                 w.endElement();
580             }
581 
582             w.endElement();
583         }
584 
585         w.endElement();
586     }
587 
588     /**
589      * Get the expression value, eventually surrounding it with <code>${ }</code>.
590      *
591      * @param parameter the parameter
592      * @return the expression value
593      */
594     private String getExpression( Parameter parameter )
595     {
596         String expression = parameter.getExpression();
597         if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) )
598         {
599             expression = "${" + expression.trim() + "}";
600             parameter.setExpression( expression );
601         }
602         return expression;
603     }
604 }