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    @SuppressWarnings( "deprecation" )
183    protected void processMojoDescriptor( MojoDescriptor mojoDescriptor, XMLWriter w, boolean helpDescriptor )
184    {
185        w.startElement( "mojo" );
186
187        // ----------------------------------------------------------------------
188        //
189        // ----------------------------------------------------------------------
190
191        w.startElement( "goal" );
192        w.writeText( mojoDescriptor.getGoal() );
193        w.endElement();
194
195        // ----------------------------------------------------------------------
196        //
197        // ----------------------------------------------------------------------
198
199        String description = mojoDescriptor.getDescription();
200
201        if ( StringUtils.isNotEmpty( description ) )
202        {
203            w.startElement( "description" );
204            if ( helpDescriptor )
205            {
206                w.writeText( GeneratorUtils.toText( mojoDescriptor.getDescription() ) );
207            }
208            else
209            {
210                w.writeText( mojoDescriptor.getDescription() );
211            }
212            w.endElement();
213        }
214
215        // ----------------------------------------------------------------------
216        //
217        // ----------------------------------------------------------------------
218
219        if ( StringUtils.isNotEmpty( mojoDescriptor.isDependencyResolutionRequired() ) )
220        {
221            GeneratorUtils.element( w, "requiresDependencyResolution",
222                                    mojoDescriptor.isDependencyResolutionRequired() );
223        }
224
225        // ----------------------------------------------------------------------
226        //
227        // ----------------------------------------------------------------------
228
229        GeneratorUtils.element( w, "requiresDirectInvocation",
230                                String.valueOf( mojoDescriptor.isDirectInvocationOnly() ) );
231
232        // ----------------------------------------------------------------------
233        //
234        // ----------------------------------------------------------------------
235
236        GeneratorUtils.element( w, "requiresProject", String.valueOf( mojoDescriptor.isProjectRequired() ) );
237
238        // ----------------------------------------------------------------------
239        //
240        // ----------------------------------------------------------------------
241
242        GeneratorUtils.element( w, "requiresReports", String.valueOf( mojoDescriptor.isRequiresReports() ) );
243
244        // ----------------------------------------------------------------------
245        //
246        // ----------------------------------------------------------------------
247
248        GeneratorUtils.element( w, "aggregator", String.valueOf( mojoDescriptor.isAggregator() ) );
249
250        // ----------------------------------------------------------------------
251        //
252        // ----------------------------------------------------------------------
253
254        GeneratorUtils.element( w, "requiresOnline", String.valueOf( mojoDescriptor.isOnlineRequired() ) );
255
256        // ----------------------------------------------------------------------
257        //
258        // ----------------------------------------------------------------------
259
260        GeneratorUtils.element( w, "inheritedByDefault", String.valueOf( mojoDescriptor.isInheritedByDefault() ) );
261
262        // ----------------------------------------------------------------------
263        //
264        // ----------------------------------------------------------------------
265
266        if ( StringUtils.isNotEmpty( mojoDescriptor.getPhase() ) )
267        {
268            GeneratorUtils.element( w, "phase", mojoDescriptor.getPhase() );
269        }
270
271        // ----------------------------------------------------------------------
272        //
273        // ----------------------------------------------------------------------
274
275        if ( StringUtils.isNotEmpty( mojoDescriptor.getExecutePhase() ) )
276        {
277            GeneratorUtils.element( w, "executePhase", mojoDescriptor.getExecutePhase() );
278        }
279
280        if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteGoal() ) )
281        {
282            GeneratorUtils.element( w, "executeGoal", mojoDescriptor.getExecuteGoal() );
283        }
284
285        if ( StringUtils.isNotEmpty( mojoDescriptor.getExecuteLifecycle() ) )
286        {
287            GeneratorUtils.element( w, "executeLifecycle", mojoDescriptor.getExecuteLifecycle() );
288        }
289
290        // ----------------------------------------------------------------------
291        //
292        // ----------------------------------------------------------------------
293
294        w.startElement( "implementation" );
295        w.writeText( mojoDescriptor.getImplementation() );
296        w.endElement();
297
298        // ----------------------------------------------------------------------
299        //
300        // ----------------------------------------------------------------------
301
302        w.startElement( "language" );
303        w.writeText( mojoDescriptor.getLanguage() );
304        w.endElement();
305
306        // ----------------------------------------------------------------------
307        //
308        // ----------------------------------------------------------------------
309
310        if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentConfigurator() ) )
311        {
312            w.startElement( "configurator" );
313            w.writeText( mojoDescriptor.getComponentConfigurator() );
314            w.endElement();
315        }
316
317        // ----------------------------------------------------------------------
318        //
319        // ----------------------------------------------------------------------
320
321        if ( StringUtils.isNotEmpty( mojoDescriptor.getComponentComposer() ) )
322        {
323            w.startElement( "composer" );
324            w.writeText( mojoDescriptor.getComponentComposer() );
325            w.endElement();
326        }
327
328        // ----------------------------------------------------------------------
329        //
330        // ----------------------------------------------------------------------
331
332        w.startElement( "instantiationStrategy" );
333        w.writeText( mojoDescriptor.getInstantiationStrategy() );
334        w.endElement();
335
336        // ----------------------------------------------------------------------
337        // Strategy for handling repeated reference to mojo in
338        // the calculated (decorated, resolved) execution stack
339        // ----------------------------------------------------------------------
340        w.startElement( "executionStrategy" );
341        w.writeText( mojoDescriptor.getExecutionStrategy() );
342        w.endElement();
343
344        // ----------------------------------------------------------------------
345        //
346        // ----------------------------------------------------------------------
347
348        if ( mojoDescriptor.getSince() != null )
349        {
350            w.startElement( "since" );
351
352            if ( StringUtils.isEmpty( mojoDescriptor.getSince() ) )
353            {
354                w.writeText( "No version given" );
355            }
356            else
357            {
358                w.writeText( mojoDescriptor.getSince() );
359            }
360
361            w.endElement();
362        }
363
364        // ----------------------------------------------------------------------
365        //
366        // ----------------------------------------------------------------------
367
368        if ( mojoDescriptor.getDeprecated() != null )
369        {
370            w.startElement( "deprecated" );
371
372            if ( StringUtils.isEmpty( mojoDescriptor.getDeprecated() ) )
373            {
374                w.writeText( "No reason given" );
375            }
376            else
377            {
378                w.writeText( mojoDescriptor.getDeprecated() );
379            }
380
381            w.endElement();
382        }
383
384        // ----------------------------------------------------------------------
385        // Extended (3.0) descriptor
386        // ----------------------------------------------------------------------
387
388        if ( mojoDescriptor instanceof ExtendedMojoDescriptor )
389        {
390            ExtendedMojoDescriptor extendedMojoDescriptor = (ExtendedMojoDescriptor) mojoDescriptor;
391            if ( extendedMojoDescriptor.getDependencyCollectionRequired() != null )
392            {
393                GeneratorUtils.element( w, "requiresDependencyCollection",
394                                        extendedMojoDescriptor.getDependencyCollectionRequired() );
395            }
396
397            GeneratorUtils.element( w, "threadSafe", String.valueOf( extendedMojoDescriptor.isThreadSafe() ) );
398        }
399
400        // ----------------------------------------------------------------------
401        // Parameters
402        // ----------------------------------------------------------------------
403
404        List<Parameter> parameters = mojoDescriptor.getParameters();
405
406        w.startElement( "parameters" );
407
408        Map<String, Requirement> requirements = new LinkedHashMap<>();
409
410        Set<Parameter> configuration = new LinkedHashSet<>();
411
412        if ( parameters != null )
413        {
414            if ( helpDescriptor )
415            {
416                PluginUtils.sortMojoParameters( parameters );
417            }
418
419            for ( Parameter parameter : parameters )
420            {
421                String expression = getExpression( parameter );
422
423                if ( StringUtils.isNotEmpty( expression ) && expression.startsWith( "${component." ) )
424                {
425                    // treat it as a component...a requirement, in other words.
426
427                    // remove "component." plus expression delimiters
428                    String role = expression.substring( "${component.".length(), expression.length() - 1 );
429
430                    String roleHint = null;
431
432                    int posRoleHintSeparator = role.indexOf( '#' );
433                    if ( posRoleHintSeparator > 0 )
434                    {
435                        roleHint = role.substring( posRoleHintSeparator + 1 );
436
437                        role = role.substring( 0, posRoleHintSeparator );
438                    }
439
440                    // TODO: remove deprecated expression
441                    requirements.put( parameter.getName(), new Requirement( role, roleHint ) );
442                }
443                else if ( parameter.getRequirement() != null )
444                {
445                    requirements.put( parameter.getName(), parameter.getRequirement() );
446                }
447                else if ( !helpDescriptor || parameter.isEditable() ) // don't show readonly parameters in help
448                {
449                    // treat it as a normal parameter.
450
451                    w.startElement( "parameter" );
452
453                    GeneratorUtils.element( w, "name", parameter.getName() );
454
455                    if ( parameter.getAlias() != null )
456                    {
457                        GeneratorUtils.element( w, "alias", parameter.getAlias() );
458                    }
459
460                    GeneratorUtils.element( w, "type", parameter.getType() );
461
462                    if ( parameter.getSince() != null )
463                    {
464                        w.startElement( "since" );
465
466                        if ( StringUtils.isEmpty( parameter.getSince() ) )
467                        {
468                            w.writeText( "No version given" );
469                        }
470                        else
471                        {
472                            w.writeText( parameter.getSince() );
473                        }
474
475                        w.endElement();
476                    }
477
478                    if ( parameter.getDeprecated() != null )
479                    {
480                        if ( StringUtils.isEmpty( parameter.getDeprecated() ) )
481                        {
482                            GeneratorUtils.element( w, "deprecated", "No reason given" );
483                        }
484                        else
485                        {
486                            GeneratorUtils.element( w, "deprecated", parameter.getDeprecated() );
487                        }
488                    }
489
490                    if ( parameter.getImplementation() != null )
491                    {
492                        GeneratorUtils.element( w, "implementation", parameter.getImplementation() );
493                    }
494
495                    GeneratorUtils.element( w, "required", Boolean.toString( parameter.isRequired() ) );
496
497                    GeneratorUtils.element( w, "editable", Boolean.toString( parameter.isEditable() ) );
498
499                    GeneratorUtils.element( w, "description", parameter.getDescription(), helpDescriptor );
500
501                    if ( StringUtils.isNotEmpty( parameter.getDefaultValue() ) || StringUtils.isNotEmpty(
502                        parameter.getExpression() ) )
503                    {
504                        configuration.add( parameter );
505                    }
506
507                    w.endElement();
508                }
509
510            }
511        }
512
513        w.endElement();
514
515        // ----------------------------------------------------------------------
516        // Configuration
517        // ----------------------------------------------------------------------
518
519        if ( !configuration.isEmpty() )
520        {
521            w.startElement( "configuration" );
522
523            for ( Parameter parameter : configuration )
524            {
525                if ( helpDescriptor && !parameter.isEditable() )
526                {
527                    // don't show readonly parameters in help
528                    continue;
529                }
530
531                w.startElement( parameter.getName() );
532
533                String type = parameter.getType();
534                if ( StringUtils.isNotEmpty( type ) )
535                {
536                    w.addAttribute( "implementation", type );
537                }
538
539                if ( parameter.getDefaultValue() != null )
540                {
541                    w.addAttribute( "default-value", parameter.getDefaultValue() );
542                }
543
544                if ( StringUtils.isNotEmpty( parameter.getExpression() ) )
545                {
546                    w.writeText( parameter.getExpression() );
547                }
548
549                w.endElement();
550            }
551
552            w.endElement();
553        }
554
555        // ----------------------------------------------------------------------
556        // Requirements
557        // ----------------------------------------------------------------------
558
559        if ( !requirements.isEmpty() && !helpDescriptor )
560        {
561            w.startElement( "requirements" );
562
563            for ( Map.Entry<String, Requirement> entry : requirements.entrySet() )
564            {
565                String key = entry.getKey();
566                Requirement requirement = entry.getValue();
567
568                w.startElement( "requirement" );
569
570                GeneratorUtils.element( w, "role", requirement.getRole() );
571
572                if ( StringUtils.isNotEmpty( requirement.getRoleHint() ) )
573                {
574                    GeneratorUtils.element( w, "role-hint", requirement.getRoleHint() );
575                }
576
577                GeneratorUtils.element( w, "field-name", key );
578
579                w.endElement();
580            }
581
582            w.endElement();
583        }
584
585        w.endElement();
586    }
587
588    /**
589     * Get the expression value, eventually surrounding it with <code>${ }</code>.
590     *
591     * @param parameter the parameter
592     * @return the expression value
593     */
594    private String getExpression( Parameter parameter )
595    {
596        String expression = parameter.getExpression();
597        if ( StringUtils.isNotBlank( expression ) && !expression.contains( "${" ) )
598        {
599            expression = "${" + expression.trim() + "}";
600            parameter.setExpression( expression );
601        }
602        return expression;
603    }
604}