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    }