001 package org.apache.maven.tools.plugin.generator;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import org.apache.maven.plugin.descriptor.DuplicateMojoDescriptorException;
023 import org.apache.maven.plugin.descriptor.MojoDescriptor;
024 import org.apache.maven.plugin.descriptor.Parameter;
025 import org.apache.maven.plugin.descriptor.PluginDescriptor;
026 import org.apache.maven.plugin.descriptor.Requirement;
027 import org.apache.maven.project.MavenProject;
028 import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
029 import org.apache.maven.tools.plugin.PluginToolsRequest;
030 import org.apache.maven.tools.plugin.util.PluginUtils;
031 import org.codehaus.plexus.util.IOUtil;
032 import org.codehaus.plexus.util.PropertyUtils;
033 import org.codehaus.plexus.util.StringUtils;
034 import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
035 import org.codehaus.plexus.util.xml.XMLWriter;
036 import org.objectweb.asm.ClassReader;
037 import org.objectweb.asm.ClassVisitor;
038 import org.objectweb.asm.ClassWriter;
039 import org.objectweb.asm.commons.RemappingClassAdapter;
040 import org.objectweb.asm.commons.SimpleRemapper;
041
042 import java.io.File;
043 import java.io.FileInputStream;
044 import java.io.FileOutputStream;
045 import java.io.IOException;
046 import java.io.OutputStreamWriter;
047 import java.io.Writer;
048 import java.text.SimpleDateFormat;
049 import java.util.Date;
050 import java.util.LinkedHashMap;
051 import java.util.LinkedHashSet;
052 import java.util.List;
053 import java.util.Map;
054 import java.util.Properties;
055 import java.util.Set;
056
057 /**
058 * Generate a <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a> and
059 * corresponding help content.
060 *
061 * @version $Id: PluginDescriptorGenerator.java 1343173 2012-05-28 09:30:23Z hboutemy $
062 * @todo add example usage tag that can be shown in the doco
063 * @todo need to add validation directives so that systems embedding maven2 can
064 * get validation directives to help users in IDEs.
065 */
066 public class PluginDescriptorGenerator
067 implements Generator
068 {
069
070 /**
071 * {@inheritDoc}
072 */
073 public void execute( File destinationDirectory, PluginToolsRequest request )
074 throws GeneratorException
075 {
076
077 File tmpPropertiesFile =
078 new File( request.getProject().getBuild().getDirectory(), "maven-plugin-help.properties" );
079
080 if ( tmpPropertiesFile.exists() )
081 {
082 Properties properties = PropertyUtils.loadProperties( tmpPropertiesFile );
083
084 String helpPackageName = properties.getProperty( "helpPackageName" );
085
086 // if helpPackageName property is empty we have to rewrite the class with a better package name than empty
087 if ( StringUtils.isEmpty( helpPackageName ) )
088 {
089 String helpMojoImplementation = rewriteHelpClassToMojoPackage( request );
090 if ( helpMojoImplementation != null )
091 {
092 // rewrite plugin descriptor with new HelpMojo implementation class
093 rewriteDescriptor( request.getPluginDescriptor(), helpMojoImplementation );
094 }
095
096 }
097 }
098
099 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 }