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