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