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     protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor )
183     {
184         w.startElement( "mojo" );
185 
186         // ----------------------------------------------------------------------
187         //
188         // ----------------------------------------------------------------------
189 
190         w.startElement( "goal" );
191         w.writeText( mojoDescriptor.getGoal() );
192         w.endElement();
193 
194         // ----------------------------------------------------------------------
195         //
196         // ----------------------------------------------------------------------
197 
198         String description = mojoDescriptor.getDescription();
199 
200         if ( StringUtils.isNotEmpty( description ) )
201         {
202             w.startElement( "description" );
203             if ( helpDescriptor )
204             {
205                 w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) );
206             }
207             else
208             {
209                 w.writeText( mojoDescriptor.getDescription() );
210             }
211             w.endElement();
212         }
213 
214         // ----------------------------------------------------------------------
215         //
216         // ----------------------------------------------------------------------
217 
218         if ( StringUtils.isNotEmpty( mojoDescriptor.isDependencyResolutionRequired() ) )
219         {
220             GeneratorUtils.element( w, "requiresDependencyResolution",
221                                     mojoDescriptor.isDependencyResolutionRequired() );
222         }
223 
224         // ----------------------------------------------------------------------
225         //
226         // ----------------------------------------------------------------------
227 
228         GeneratorUtils.element( w, "requiresDirectInvocation",
229                                 String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) );
230 
231         // ----------------------------------------------------------------------
232         //
233         // ----------------------------------------------------------------------
234 
235         GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) );
236 
237         // ----------------------------------------------------------------------
238         //
239         // ----------------------------------------------------------------------
240 
241         GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) );
242 
243         // ----------------------------------------------------------------------
244         //
245         // ----------------------------------------------------------------------
246 
247         GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) );
248 
249         // ----------------------------------------------------------------------
250         //
251         // ----------------------------------------------------------------------
252 
253         GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) );
254 
255         // ----------------------------------------------------------------------
256         //
257         // ----------------------------------------------------------------------
258 
259         GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) );
260 
261         // ----------------------------------------------------------------------
262         //
263         // ----------------------------------------------------------------------
264 
265         if ( StringUtils.isNotEmpty( mojoDescriptor.getPhase() ) )
266         {
267             GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() );
268         }
269 
270         // ----------------------------------------------------------------------
271         //
272         // ----------------------------------------------------------------------
273 
274         if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) )
275         {
276             GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() );
277         }
278 
279         if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteGoal() ) )
280         {
281             GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() );
282         }
283 
284         if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteLifecycle() ) )
285         {
286             GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() );
287         }
288 
289         // ----------------------------------------------------------------------
290         //
291         // ----------------------------------------------------------------------
292 
293         w.startElement( "implementation" );
294         w.writeText( mojoDescriptor.getImplementation() );
295         w.endElement();
296 
297         // ----------------------------------------------------------------------
298         //
299         // ----------------------------------------------------------------------
300 
301         w.startElement( "language" );
302         w.writeText( mojoDescriptor.getLanguage() );
303         w.endElement();
304 
305         // ----------------------------------------------------------------------
306         //
307         // ----------------------------------------------------------------------
308 
309         if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentConfigurator() ) )
310         {
311             w.startElement( "configurator" );
312             w.writeText( mojoDescriptor.getComponentConfigurator() );
313             w.endElement();
314         }
315 
316         // ----------------------------------------------------------------------
317         //
318         // ----------------------------------------------------------------------
319 
320         if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentComposer() ) )
321         {
322             w.startElement( "composer" );
323             w.writeText( mojoDescriptor.getComponentComposer() );
324             w.endElement();
325         }
326 
327         // ----------------------------------------------------------------------
328         //
329         // ----------------------------------------------------------------------
330 
331         w.startElement( "instantiationStrategy" );
332         w.writeText( mojoDescriptor.getInstantiationStrategy() );
333         w.endElement();
334 
335         // ----------------------------------------------------------------------
336         // Strategy for handling repeated reference to mojo in
337         // the calculated (decorated, resolved) execution stack
338         // ----------------------------------------------------------------------
339         w.startElement( "executionStrategy" );
340         w.writeText( mojoDescriptor.getExecutionStrategy() );
341         w.endElement();
342 
343         // ----------------------------------------------------------------------
344         //
345         // ----------------------------------------------------------------------
346 
347         if ( mojoDescriptor.getSince() != null )
348         {
349             w.startElement( "since" );
350 
351             if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) )
352             {
353                 w.writeText( "No version given" );
354             }
355             else
356             {
357                 w.writeText( mojoDescriptor.getSince() );
358             }
359 
360             w.endElement();
361         }
362 
363         // ----------------------------------------------------------------------
364         //
365         // ----------------------------------------------------------------------
366 
367         if ( mojoDescriptor.getDeprecated() != null )
368         {
369             w.startElement( "deprecated" );
370 
371             if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) )
372             {
373                 w.writeText( "No reason given" );
374             }
375             else
376             {
377                 w.writeText( mojoDescriptor.getDeprecated() );
378             }
379 
380             w.endElement();
381         }
382 
383         // ----------------------------------------------------------------------
384         // Extended (3.0) descriptor
385         // ----------------------------------------------------------------------
386 
387         if ( mojoDescriptor instanceof ExtendedMojoDescriptor )
388         {
389             ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
390             if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null )
391             {
392                 GeneratorUtils.element( w, "requiresDependencyCollection",
393                                         extendedMojoDescriptor.getDependencyCollectionRequired() );
394             }
395 
396             GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) );
397         }
398 
399         // ----------------------------------------------------------------------
400         // Parameters
401         // ----------------------------------------------------------------------
402 
403         List<Parameter> parameters = mojoDescriptor.getParameters();
404 
405         w.startElement( "parameters" );
406 
407         Map<String, Requirement> requirements = new LinkedHashMap<>();
408 
409         Set<Parameter> configuration = new LinkedHashSet<>();
410 
411         if ( parameters != null )
412         {
413             if ( helpDescriptor )
414             {
415                 PluginUtils.sortMojoParameters( parameters );
416             }
417 
418             for ( Parameter parameter : parameters )
419             {
420                 String expression = getExpression( parameter );
421 
422                 if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
423                 {
424                     // treat it as a component...a requirement, in other words.
425 
426                     // remove "component." plus expression delimiters
427                     String role = expression.substring( "${component.".length(), expression.length() - 1 );
428 
429                     String roleHint = null;
430 
431                     int posRoleHintSeparator = role.indexOf( '#' );
432                     if ( posRoleHintSeparator > 0 )
433                     {
434                         roleHint = role.substring( posRoleHintSeparator + 1 );
435 
436                         role = role.substring( 0, posRoleHintSeparator );
437                     }
438 
439                     // TODO: remove deprecated expression
440                     requirements.put( parameter.getName(), new Requirement( role, roleHint ) );
441                 }
442                 else if ( parameter.getRequirement() != null )
443                 {
444                     requirements.put( parameter.getName(), parameter.getRequirement() );
445                 }
446                 else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help
447                 {
448                     // treat it as a normal parameter.
449 
450                     w.startElement( "parameter" );
451 
452                     GeneratorUtils.element( w, "name", parameter.getName() );
453 
454                     if ( parameter.getAlias() != null )
455                     {
456                         GeneratorUtils.element( w, "alias", parameter.getAlias() );
457                     }
458 
459                     GeneratorUtils.element( w, "type", parameter.getType() );
460 
461                     if ( parameter.getSince() != null )
462                     {
463                         w.startElement( "since" );
464 
465                         if ( StringUtils.isEmpty( parameter.getSince() ) )
466                         {
467                             w.writeText( "No version given" );
468                         }
469                         else
470                         {
471                             w.writeText( parameter.getSince() );
472                         }
473 
474                         w.endElement();
475                     }
476 
477                     if ( parameter.getDeprecated() != null )
478                     {
479                         if ( StringUtils.isEmpty( parameter.getDeprecated() ) )
480                         {
481                             GeneratorUtils.element( w, "deprecated", "No reason given" );
482                         }
483                         else
484                         {
485                             GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() );
486                         }
487                     }
488 
489                     if ( parameter.getImplementation() != null )
490                     {
491                         GeneratorUtils.element( w, "implementation", parameter.getImplementation() );
492                     }
493 
494                     GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) );
495 
496                     GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) );
497 
498                     GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor );
499 
500                     if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) || StringUtils.isNotEmpty(
501                         parameter.getExpression() ) )
502                     {
503                         configuration.add( parameter );
504                     }
505 
506                     w.endElement();
507                 }
508 
509             }
510         }
511 
512         w.endElement();
513 
514         // ----------------------------------------------------------------------
515         // Configuration
516         // ----------------------------------------------------------------------
517 
518         if ( !configuration.isEmpty() )
519         {
520             w.startElement( "configuration" );
521 
522             for ( Parameter parameter : configuration )
523             {
524                 if ( helpDescriptor && !parameter.isEditable() )
525                 {
526                     // don't show readonly parameters in help
527                     continue;
528                 }
529 
530                 w.startElement( parameter.getName() );
531 
532                 String type = parameter.getType();
533                 if ( StringUtils.isNotEmpty( type ) )
534                 {
535                     w.addAttribute( "implementation", type );
536                 }
537 
538                 if ( parameter.getDefaultValue() != null )
539                 {
540                     w.addAttribute( "default-value", parameter.getDefaultValue() );
541                 }
542 
543                 if ( StringUtils.isNotEmpty( parameter.getExpression() ) )
544                 {
545                     w.writeText( parameter.getExpression() );
546                 }
547 
548                 w.endElement();
549             }
550 
551             w.endElement();
552         }
553 
554         // ----------------------------------------------------------------------
555         // Requirements
556         // ----------------------------------------------------------------------
557 
558         if ( !requirements.isEmpty() && !helpDescriptor )
559         {
560             w.startElement( "requirements" );
561 
562             for ( Map.Entry<String, Requirement> entry : requirements.entrySet() )
563             {
564                 String key = entry.getKey();
565                 Requirement requirement = entry.getValue();
566 
567                 w.startElement( "requirement" );
568 
569                 GeneratorUtils.element( w, "role", requirement.getRole() );
570 
571                 if ( StringUtils.isNotEmpty( requirement.getRoleHint() ) )
572                 {
573                     GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() );
574                 }
575 
576                 GeneratorUtils.element( w, "field-name", key );
577 
578                 w.endElement();
579             }
580 
581             w.endElement();
582         }
583 
584         w.endElement();
585     }
586 
587     /**
588      * Get the expression value, eventually surrounding it with <code>${ }</code>.
589      *
590      * @param parameter the parameter
591      * @return the expression value
592      */
593     private String getExpression( Parameter parameter )
594     {
595         String expression = parameter.getExpression();
596         if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) )
597         {
598             expression = "${" + expression.trim() + "}";
599             parameter.setExpression( expression );
600         }
601         return expression;
602     }
603 }