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