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 java.io.File;
023    import java.io.FileOutputStream;
024    import java.io.IOException;
025    import java.io.OutputStreamWriter;
026    import java.io.Writer;
027    import java.text.SimpleDateFormat;
028    import java.util.Date;
029    import java.util.LinkedHashMap;
030    import java.util.LinkedHashSet;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Set;
034    import org.apache.maven.plugin.descriptor.DuplicateMojoDescriptorException;
035    import org.apache.maven.plugin.descriptor.MojoDescriptor;
036    import org.apache.maven.plugin.descriptor.Parameter;
037    import org.apache.maven.plugin.descriptor.PluginDescriptor;
038    import org.apache.maven.plugin.descriptor.Requirement;
039    import org.apache.maven.plugin.logging.Log;
040    import org.apache.maven.project.MavenProject;
041    import org.apache.maven.tools.plugin.ExtendedMojoDescriptor;
042    import org.apache.maven.tools.plugin.PluginToolsRequest;
043    import org.apache.maven.tools.plugin.util.PluginUtils;
044    import org.codehaus.plexus.logging.Logger;
045    import org.codehaus.plexus.util.IOUtil;
046    import org.codehaus.plexus.util.StringUtils;
047    import org.codehaus.plexus.util.xml.PrettyPrintXMLWriter;
048    import org.codehaus.plexus.util.xml.XMLWriter;
049    
050    /**
051     * Generate a <a href="/ref/current/maven-plugin-api/plugin.html">Maven Plugin Descriptor XML file</a> and
052     * corresponding <code>plugin-help.xml</code> help content for {@link PluginHelpGenerator}.
053     *
054     * @version $Id: PluginDescriptorGenerator.java 1406615 2012-11-07 13:26:25Z krosenvold $
055     * @todo add example usage tag that can be shown in the doco
056     * @todo need to add validation directives so that systems embedding maven2 can
057     * get validation directives to help users in IDEs.
058     */
059    public class PluginDescriptorGenerator
060        implements Generator
061    {
062    
063        private final Log log;
064    
065        public PluginDescriptorGenerator( Log log )
066        {
067            this.log = log;
068        }
069    
070        /**
071         * {@inheritDoc}
072         */
073        public void execute( File destinationDirectory, PluginToolsRequest request )
074            throws GeneratorException
075        {
076            // eventually rewrite help mojo class to match actual package name
077            PluginHelpGenerator.rewriteHelpMojo( request, log );
078    
079            try
080            {
081                // write complete plugin.xml descriptor
082                File f = new File( destinationDirectory, "plugin.xml" );
083                writeDescriptor( f, request, false );
084    
085                // write plugin-help.xml help-descriptor
086                MavenProject mavenProject = request.getProject();
087    
088                f = new File( mavenProject.getBuild().getOutputDirectory(),
089                              PluginHelpGenerator.getPluginHelpPath( mavenProject ) );
090    
091                writeDescriptor( f, request, true );
092            }
093            catch ( IOException e )
094            {
095                throw new GeneratorException( e.getMessage(), e );
096            }
097            catch ( DuplicateMojoDescriptorException e )
098            {
099                throw new GeneratorException( e.getMessage(), e );
100            }
101        }
102    
103        private String getVersion()
104        {
105            Package p = this.getClass().getPackage();
106            String version = ( p == null ) ? null : p.getSpecificationVersion();
107            return ( version == null ) ? "SNAPSHOT" : version;
108        }
109    
110        public void writeDescriptor( File destinationFile, PluginToolsRequest request, boolean helpDescriptor )
111            throws IOException, DuplicateMojoDescriptorException
112        {
113            PluginDescriptor pluginDescriptor = request.getPluginDescriptor();
114    
115            if ( destinationFile.exists() )
116            {
117                destinationFile.delete();
118            }
119            else
120            {
121                if ( !destinationFile.getParentFile().exists() )
122                {
123                    destinationFile.getParentFile().mkdirs();
124                }
125            }
126    
127            String encoding = "UTF-8";
128    
129            Writer writer = null;
130            try
131            {
132                writer = new OutputStreamWriter( new FileOutputStream( destinationFile ), encoding );
133    
134                XMLWriter w = new PrettyPrintXMLWriter( writer, encoding, null );
135    
136                w.writeMarkup( "\n<!-- Generated by maven-plugin-tools " + getVersion() + " on " + new SimpleDateFormat(
137                    "yyyy-MM-dd" ).format( new Date() ) + " -->\n\n" );
138    
139                w.startElement( "plugin" );
140    
141                GeneratorUtils.element( w, "name", pluginDescriptor.getName() );
142    
143                GeneratorUtils.element( w, "description", pluginDescriptor.getDescription(), helpDescriptor );
144    
145                GeneratorUtils.element( w, "groupId", pluginDescriptor.getGroupId() );
146    
147                GeneratorUtils.element( w, "artifactId", pluginDescriptor.getArtifactId() );
148    
149                GeneratorUtils.element( w, "version", pluginDescriptor.getVersion() );
150    
151                GeneratorUtils.element( w, "goalPrefix", pluginDescriptor.getGoalPrefix() );
152    
153                if ( !helpDescriptor )
154                {
155                    GeneratorUtils.element( w, "isolatedRealm", String.valueOf( pluginDescriptor.isIsolatedRealm() ) );
156    
157                    GeneratorUtils.element( w, "inheritedByDefault",
158                                            String.valueOf( pluginDescriptor.isInheritedByDefault() ) );
159                }
160    
161                w.startElement( "mojos" );
162    
163                if ( pluginDescriptor.getMojos() != null )
164                {
165                    @SuppressWarnings( "unchecked" ) List<MojoDescriptor> descriptors = pluginDescriptor.getMojos();
166    
167                    if ( helpDescriptor )
168                    {
169                        PluginUtils.sortMojos( descriptors );
170                    }
171    
172                    for ( MojoDescriptor descriptor : descriptors )
173                    {
174                        processMojoDescriptor( descriptor, w, helpDescriptor );
175                    }
176                }
177    
178                w.endElement();
179    
180                if ( !helpDescriptor )
181                {
182                    GeneratorUtils.writeDependencies( w, pluginDescriptor );
183                }
184    
185                w.endElement();
186    
187                writer.flush();
188    
189            }
190            finally
191            {
192                IOUtil.close( writer );
193            }
194        }
195    
196        protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w )
197        {
198            processMojoDescriptor( mojoDescriptor, w, false );
199        }
200    
201        /**
202         * @param mojoDescriptor not null
203         * @param w              not null
204         * @param helpDescriptor will clean html content from description fields
205         */
206        protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor )
207        {
208            w.startElement( "mojo" );
209    
210            // ----------------------------------------------------------------------
211            //
212            // ----------------------------------------------------------------------
213    
214            w.startElement( "goal" );
215            w.writeText( mojoDescriptor.getGoal() );
216            w.endElement();
217    
218            // ----------------------------------------------------------------------
219            //
220            // ----------------------------------------------------------------------
221    
222            String description = mojoDescriptor.getDescription();
223    
224            if ( StringUtils.isNotEmpty( description ) )
225            {
226                w.startElement( "description" );
227                if ( helpDescriptor )
228                {
229                    w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) );
230                }
231                else
232                {
233                    w.writeText( mojoDescriptor.getDescription() );
234                }
235                w.endElement();
236            }
237    
238            // ----------------------------------------------------------------------
239            //
240            // ----------------------------------------------------------------------
241    
242            if ( StringUtils.isNotEmpty( mojoDescriptor.isDependencyResolutionRequired() ) )
243            {
244                GeneratorUtils.element( w, "requiresDependencyResolution",
245                                        mojoDescriptor.isDependencyResolutionRequired() );
246            }
247    
248            // ----------------------------------------------------------------------
249            //
250            // ----------------------------------------------------------------------
251    
252            GeneratorUtils.element( w, "requiresDirectInvocation",
253                                    String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) );
254    
255            // ----------------------------------------------------------------------
256            //
257            // ----------------------------------------------------------------------
258    
259            GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) );
260    
261            // ----------------------------------------------------------------------
262            //
263            // ----------------------------------------------------------------------
264    
265            GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) );
266    
267            // ----------------------------------------------------------------------
268            //
269            // ----------------------------------------------------------------------
270    
271            GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) );
272    
273            // ----------------------------------------------------------------------
274            //
275            // ----------------------------------------------------------------------
276    
277            GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) );
278    
279            // ----------------------------------------------------------------------
280            //
281            // ----------------------------------------------------------------------
282    
283            GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) );
284    
285            // ----------------------------------------------------------------------
286            //
287            // ----------------------------------------------------------------------
288    
289            if ( StringUtils.isNotEmpty( mojoDescriptor.getPhase() ) )
290            {
291                GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() );
292            }
293    
294            // ----------------------------------------------------------------------
295            //
296            // ----------------------------------------------------------------------
297    
298            if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) )
299            {
300                GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() );
301            }
302    
303            if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteGoal() ) )
304            {
305                GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() );
306            }
307    
308            if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteLifecycle() ) )
309            {
310                GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() );
311            }
312    
313            // ----------------------------------------------------------------------
314            //
315            // ----------------------------------------------------------------------
316    
317            w.startElement( "implementation" );
318            w.writeText( mojoDescriptor.getImplementation() );
319            w.endElement();
320    
321            // ----------------------------------------------------------------------
322            //
323            // ----------------------------------------------------------------------
324    
325            w.startElement( "language" );
326            w.writeText( mojoDescriptor.getLanguage() );
327            w.endElement();
328    
329            // ----------------------------------------------------------------------
330            //
331            // ----------------------------------------------------------------------
332    
333            if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentConfigurator() ) )
334            {
335                w.startElement( "configurator" );
336                w.writeText( mojoDescriptor.getComponentConfigurator() );
337                w.endElement();
338            }
339    
340            // ----------------------------------------------------------------------
341            //
342            // ----------------------------------------------------------------------
343    
344            if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentComposer() ) )
345            {
346                w.startElement( "composer" );
347                w.writeText( mojoDescriptor.getComponentComposer() );
348                w.endElement();
349            }
350    
351            // ----------------------------------------------------------------------
352            //
353            // ----------------------------------------------------------------------
354    
355            w.startElement( "instantiationStrategy" );
356            w.writeText( mojoDescriptor.getInstantiationStrategy() );
357            w.endElement();
358    
359            // ----------------------------------------------------------------------
360            // Strategy for handling repeated reference to mojo in
361            // the calculated (decorated, resolved) execution stack
362            // ----------------------------------------------------------------------
363            w.startElement( "executionStrategy" );
364            w.writeText( mojoDescriptor.getExecutionStrategy() );
365            w.endElement();
366    
367            // ----------------------------------------------------------------------
368            //
369            // ----------------------------------------------------------------------
370    
371            if ( mojoDescriptor.getSince() != null )
372            {
373                w.startElement( "since" );
374    
375                if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) )
376                {
377                    w.writeText( "No version given" );
378                }
379                else
380                {
381                    w.writeText( mojoDescriptor.getSince() );
382                }
383    
384                w.endElement();
385            }
386    
387            // ----------------------------------------------------------------------
388            //
389            // ----------------------------------------------------------------------
390    
391            if ( mojoDescriptor.getDeprecated() != null )
392            {
393                w.startElement( "deprecated" );
394    
395                if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) )
396                {
397                    w.writeText( "No reason given" );
398                }
399                else
400                {
401                    w.writeText( mojoDescriptor.getDeprecated() );
402                }
403    
404                w.endElement();
405            }
406    
407            // ----------------------------------------------------------------------
408            // Extended (3.0) descriptor
409            // ----------------------------------------------------------------------
410    
411            if ( mojoDescriptor instanceof ExtendedMojoDescriptor )
412            {
413                ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
414                if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null )
415                {
416                    GeneratorUtils.element( w, "requiresDependencyCollection",
417                                            extendedMojoDescriptor.getDependencyCollectionRequired() );
418                }
419    
420                GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) );
421            }
422    
423            // ----------------------------------------------------------------------
424            // Parameters
425            // ----------------------------------------------------------------------
426    
427            @SuppressWarnings( "unchecked" ) List<Parameter> parameters = mojoDescriptor.getParameters();
428    
429            w.startElement( "parameters" );
430    
431            Map<String, Requirement> requirements = new LinkedHashMap<String, Requirement>();
432    
433            Set<Parameter> configuration = new LinkedHashSet<Parameter>();
434    
435            if ( parameters != null )
436            {
437                if ( helpDescriptor )
438                {
439                    PluginUtils.sortMojoParameters( parameters );
440                }
441    
442                for ( Parameter parameter : parameters )
443                {
444                    String expression = getExpression( parameter );
445    
446                    if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
447                    {
448                        // treat it as a component...a requirement, in other words.
449    
450                        // remove "component." plus expression delimiters
451                        String role = expression.substring( "${component.".length(), expression.length() - 1 );
452    
453                        String roleHint = null;
454    
455                        int posRoleHintSeparator = role.indexOf( '#' );
456                        if ( posRoleHintSeparator > 0 )
457                        {
458                            roleHint = role.substring( posRoleHintSeparator + 1 );
459    
460                            role = role.substring( 0, posRoleHintSeparator );
461                        }
462    
463                        // TODO: remove deprecated expression
464                        requirements.put( parameter.getName(), new Requirement( role, roleHint ) );
465                    }
466                    else if ( parameter.getRequirement() != null )
467                    {
468                        requirements.put( parameter.getName(), parameter.getRequirement() );
469                    }
470                    else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help
471                    {
472                        // treat it as a normal parameter.
473    
474                        w.startElement( "parameter" );
475    
476                        GeneratorUtils.element( w, "name", parameter.getName() );
477    
478                        if ( parameter.getAlias() != null )
479                        {
480                            GeneratorUtils.element( w, "alias", parameter.getAlias() );
481                        }
482    
483                        GeneratorUtils.element( w, "type", parameter.getType() );
484    
485                        if ( parameter.getSince() != null )
486                        {
487                            w.startElement( "since" );
488    
489                            if ( StringUtils.isEmpty( parameter.getSince() ) )
490                            {
491                                w.writeText( "No version given" );
492                            }
493                            else
494                            {
495                                w.writeText( parameter.getSince() );
496                            }
497    
498                            w.endElement();
499                        }
500    
501                        if ( parameter.getDeprecated() != null )
502                        {
503                            if ( StringUtils.isEmpty( parameter.getDeprecated() ) )
504                            {
505                                GeneratorUtils.element( w, "deprecated", "No reason given" );
506                            }
507                            else
508                            {
509                                GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() );
510                            }
511                        }
512    
513                        if ( parameter.getImplementation() != null )
514                        {
515                            GeneratorUtils.element( w, "implementation", parameter.getImplementation() );
516                        }
517    
518                        GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) );
519    
520                        GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) );
521    
522                        GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor );
523    
524                        if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) || StringUtils.isNotEmpty(
525                            parameter.getExpression() ) )
526                        {
527                            configuration.add( parameter );
528                        }
529    
530                        w.endElement();
531                    }
532    
533                }
534            }
535    
536            w.endElement();
537    
538            // ----------------------------------------------------------------------
539            // Configuration
540            // ----------------------------------------------------------------------
541    
542            if ( !configuration.isEmpty() )
543            {
544                w.startElement( "configuration" );
545    
546                for ( Parameter parameter : configuration )
547                {
548                    if ( helpDescriptor && !parameter.isEditable() )
549                    {
550                        // don't show readonly parameters in help
551                        continue;
552                    }
553    
554                    w.startElement( parameter.getName() );
555    
556                    String type = parameter.getType();
557                    if ( StringUtils.isNotEmpty( type ) )
558                    {
559                        w.addAttribute( "implementation", type );
560                    }
561    
562                    if ( parameter.getDefaultValue() != null )
563                    {
564                        w.addAttribute( "default-value", parameter.getDefaultValue() );
565                    }
566    
567                    if ( StringUtils.isNotEmpty( parameter.getExpression() ) )
568                    {
569                        w.writeText( parameter.getExpression() );
570                    }
571    
572                    w.endElement();
573                }
574    
575                w.endElement();
576            }
577    
578            // ----------------------------------------------------------------------
579            // Requirements
580            // ----------------------------------------------------------------------
581    
582            if ( !requirements.isEmpty() && !helpDescriptor )
583            {
584                w.startElement( "requirements" );
585    
586                for ( Map.Entry<String, Requirement> entry : requirements.entrySet() )
587                {
588                    String key = entry.getKey();
589                    Requirement requirement = entry.getValue();
590    
591                    w.startElement( "requirement" );
592    
593                    GeneratorUtils.element( w, "role", requirement.getRole() );
594    
595                    if ( StringUtils.isNotEmpty( requirement.getRoleHint() ) )
596                    {
597                        GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() );
598                    }
599    
600                    GeneratorUtils.element( w, "field-name", key );
601    
602                    w.endElement();
603                }
604    
605                w.endElement();
606            }
607    
608            w.endElement();
609        }
610    
611        /**
612         * Get the expression value, eventually surrounding it with <code>${ }</code>.
613         *
614         * @param parameter the parameter
615         * @return the expression value
616         */
617        private String getExpression( Parameter parameter )
618        {
619            String expression = parameter.getExpression();
620            if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) )
621            {
622                expression = "${" + expression.trim() + "}";
623                parameter.setExpression( expression );
624            }
625            return expression;
626        }
627    }