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