View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugin.descriptor;
20  
21  import java.util.ArrayList;
22  import java.util.LinkedHashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.stream.Collectors;
27  
28  import org.apache.maven.plugin.Mojo;
29  import org.codehaus.plexus.component.repository.ComponentDescriptor;
30  import org.codehaus.plexus.configuration.PlexusConfiguration;
31  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
32  
33  /**
34   * The bean containing the Mojo descriptor.<br>
35   * For more information about the usage tag, have a look to:
36   * <a href="https://maven.apache.org/developers/mojo-api-specification.html">
37   * https://maven.apache.org/developers/mojo-api-specification.html</a>
38   *
39   * TODO is there a need for the delegation of MavenMojoDescriptor to this?
40   * Why not just extend ComponentDescriptor here?
41   */
42  public class MojoDescriptor extends ComponentDescriptor<Mojo> implements Cloneable {
43      /** The Plexus component type */
44      public static final String MAVEN_PLUGIN = "maven-plugin";
45  
46      /** "once-per-session" execution strategy */
47      public static final String SINGLE_PASS_EXEC_STRATEGY = "once-per-session";
48  
49      /** "always" execution strategy */
50      public static final String MULTI_PASS_EXEC_STRATEGY = "always";
51  
52      private static final String DEFAULT_INSTANTIATION_STRATEGY = "per-lookup";
53  
54      private static final String DEFAULT_LANGUAGE = "java";
55  
56      private final ArrayList<Parameter> parameters;
57  
58      /** By default, the execution strategy is "once-per-session" */
59      private String executionStrategy = SINGLE_PASS_EXEC_STRATEGY;
60  
61      /**
62       * The goal name for the Mojo, that users will reference from the command line to execute the Mojo directly, or
63       * inside a POM in order to provide Mojo-specific configuration.
64       */
65      private String goal;
66  
67      /**
68       * Defines a default phase to bind a mojo execution to if the user does not explicitly set a phase in the POM.
69       * <i>Note:</i> This will not automagically make a mojo run when the plugin declaration is added to the POM. It
70       * merely enables the user to omit the <code>&lt;phase&gt;</code> element from the surrounding
71       * <code>&lt;execution&gt;</code> element.
72       */
73      private String phase;
74  
75      /** Specify the version when the Mojo was added to the API. Similar to Javadoc since. */
76      private String since;
77  
78      /** Reference the invocation phase of the Mojo. */
79      private String executePhase;
80  
81      /** Reference the invocation goal of the Mojo. */
82      private String executeGoal;
83  
84      /** Reference the invocation lifecycle of the Mojo. */
85      private String executeLifecycle;
86  
87      /**
88       * Description with reason of Mojo deprecation. Similar to Javadoc {@code @deprecated}.
89       * This will trigger a warning when a user tries to use a Mojo marked as deprecated.
90       */
91      private String deprecated;
92  
93      /**
94       * Flags this Mojo to run it in a multi-module way, i.e. aggregate the build with the set of projects listed as
95       * modules. By default, no need to aggregate the Maven project and its child modules
96       */
97      private boolean aggregator = false;
98  
99      // ----------------------------------------------------------------------
100     //
101     // ----------------------------------------------------------------------
102 
103     /** Specify the required dependencies in a specified scope */
104     private String dependencyResolutionRequired = null;
105 
106     /**
107      * The scope of (transitive) dependencies that should be collected but not resolved.
108      * @since 3.0-alpha-3
109      */
110     private String dependencyCollectionRequired;
111 
112     /**  By default, the Mojo needs a Maven project to be executed */
113     private boolean projectRequired = true;
114 
115     /**  By default, the Mojo is assumed to work offline as well */
116     private boolean onlineRequired = false;
117 
118     /**  Plugin configuration */
119     private PlexusConfiguration mojoConfiguration;
120 
121     /**  Plugin descriptor */
122     private PluginDescriptor pluginDescriptor;
123 
124     /**  By default, the Mojo is inherited */
125     private boolean inheritedByDefault = true;
126 
127     /**  By default, the Mojo cannot be invoked directly */
128     private boolean directInvocationOnly = false;
129 
130     /**  By default, the Mojo don't need reports to run */
131     private boolean requiresReports = false;
132 
133     /**
134      * By default, mojos are not threadsafe
135      * @since 3.0-beta-2
136      */
137     private boolean threadSafe = false;
138 
139     private boolean v4Api = false;
140 
141     /**
142      * Default constructor.
143      */
144     public MojoDescriptor() {
145         this.parameters = new ArrayList<>();
146         setInstantiationStrategy(DEFAULT_INSTANTIATION_STRATEGY);
147         setComponentFactory(DEFAULT_LANGUAGE);
148     }
149 
150     public MojoDescriptor(PluginDescriptor pd, org.apache.maven.api.plugin.descriptor.MojoDescriptor md) {
151         this();
152         this.setPluginDescriptor(pd);
153         this.setGoal(md.getGoal());
154         this.setExecuteGoal(md.getExecuteGoal());
155         this.setExecuteLifecycle(md.getExecuteLifecycle());
156         this.setExecutePhase(md.getExecutePhase());
157         this.setDeprecated(md.getDeprecated());
158         this.setLanguage(md.getLanguage());
159         this.setAggregator(md.isAggregator());
160         this.setDependencyCollectionRequired(md.getDependencyCollection());
161         this.setDependencyResolutionRequired(md.getDependencyResolution());
162         this.setComponentConfigurator(md.getConfigurator());
163         this.setInheritedByDefault(md.isInheritedByDefault());
164         this.setPhase(md.getPhase());
165         this.setOnlineRequired(md.isOnlineRequired());
166         this.setProjectRequired(md.isProjectRequired());
167         this.setSince(md.getSince());
168         this.setThreadSafe(true);
169         this.setImplementation(md.getImplementation());
170         try {
171             this.setParameters(md.getParameters().stream().map(Parameter::new).collect(Collectors.toList()));
172         } catch (DuplicateParameterException e) {
173             throw new IllegalArgumentException(e);
174         }
175         this.mojoDescriptorV4 = md;
176         this.v4Api = true;
177     }
178     // ----------------------------------------------------------------------
179     //
180     // ----------------------------------------------------------------------
181 
182     /**
183      * @return the language of this Mojo, i.e. <code>java</code>
184      */
185     public String getLanguage() {
186         return getComponentFactory();
187     }
188 
189     /**
190      * @param language the new language
191      */
192     public void setLanguage(String language) {
193         setComponentFactory(language);
194     }
195 
196     /**
197      * @return Description with reason of a Mojo deprecation.
198      */
199     public String getDeprecated() {
200         return deprecated;
201     }
202 
203     /**
204      * @param deprecated Description with reason of a Mojo deprecation.
205      */
206     public void setDeprecated(String deprecated) {
207         this.deprecated = deprecated;
208     }
209 
210     /**
211      * @return the list of parameters copy. Any change to returned list is NOT reflected on this instance. To add
212      * parameters, use {@link #addParameter(Parameter)} method.
213      */
214     public List<Parameter> getParameters() {
215         return new ArrayList<>(parameters);
216     }
217 
218     /**
219      * @param parameters the new list of parameters
220      * @throws DuplicateParameterException if any
221      */
222     public void setParameters(List<Parameter> parameters) throws DuplicateParameterException {
223         this.parameters.clear();
224         for (Parameter parameter : parameters) {
225             addParameter(parameter);
226         }
227     }
228 
229     /**
230      * @param parameter add a new parameter
231      * @throws DuplicateParameterException if any
232      */
233     public void addParameter(Parameter parameter) throws DuplicateParameterException {
234         if (parameters.contains(parameter)) {
235             throw new DuplicateParameterException(parameter.getName()
236                     + " has been declared multiple times in mojo with goal: " + getGoal() + " (implementation: "
237                     + getImplementation() + ")");
238         }
239 
240         parameters.add(parameter);
241     }
242 
243     /**
244      * @return the list parameters as a Map (keyed by {@link Parameter#getName()}) that is built from
245      * {@link #parameters} list on each call. In other words, the map returned is built on fly and is a copy.
246      * Any change to this map is NOT reflected on list and other way around!
247      */
248     public Map<String, Parameter> getParameterMap() {
249         LinkedHashMap<String, Parameter> parameterMap = new LinkedHashMap<>();
250 
251         for (Parameter pd : parameters) {
252             parameterMap.put(pd.getName(), pd);
253         }
254 
255         return parameterMap;
256     }
257 
258     // ----------------------------------------------------------------------
259     // Dependency requirement
260     // ----------------------------------------------------------------------
261 
262     /**
263      * @param requiresDependencyResolution the new required dependencies in a specified scope
264      */
265     public void setDependencyResolutionRequired(String requiresDependencyResolution) {
266         this.dependencyResolutionRequired = requiresDependencyResolution;
267     }
268 
269     public String getDependencyResolutionRequired() {
270         return dependencyResolutionRequired;
271     }
272 
273     /**
274      * @return the required dependencies in a specified scope
275      * TODO the name is not intelligible
276      */
277     @Deprecated
278     public String isDependencyResolutionRequired() {
279         return dependencyResolutionRequired;
280     }
281 
282     /**
283      * @since 3.0-alpha-3
284      */
285     public void setDependencyCollectionRequired(String requiresDependencyCollection) {
286         this.dependencyCollectionRequired = requiresDependencyCollection;
287     }
288 
289     /**
290      * Gets the scope of (transitive) dependencies that should be collected. Dependency collection refers to the process
291      * of calculating the complete dependency tree in terms of artifact coordinates. In contrast to dependency
292      * resolution, this does not include the download of the files for the dependency artifacts. It is meant for mojos
293      * that only want to analyze the set of transitive dependencies, in particular during early lifecycle phases where
294      * full dependency resolution might fail due to projects which haven't been built yet.
295      *
296      * @return The scope of (transitive) dependencies that should be collected or {@code null} if none.
297      * @since 3.0-alpha-3
298      */
299     public String getDependencyCollectionRequired() {
300         return dependencyCollectionRequired;
301     }
302 
303     // ----------------------------------------------------------------------
304     // Project requirement
305     // ----------------------------------------------------------------------
306 
307     /**
308      * @param requiresProject <code>true</code> if the Mojo needs a Maven project to be executed, <code>false</code>
309      * otherwise.
310      */
311     public void setProjectRequired(boolean requiresProject) {
312         this.projectRequired = requiresProject;
313     }
314 
315     /**
316      * @return <code>true</code> if the Mojo needs a Maven project to be executed, <code>false</code> otherwise.
317      */
318     public boolean isProjectRequired() {
319         return projectRequired;
320     }
321 
322     // ----------------------------------------------------------------------
323     // Online vs. Offline requirement
324     // ----------------------------------------------------------------------
325 
326     /**
327      * @param requiresOnline <code>true</code> if the Mojo is online, <code>false</code> otherwise.
328      */
329     public void setOnlineRequired(boolean requiresOnline) {
330         this.onlineRequired = requiresOnline;
331     }
332 
333     /**
334      * @return <code>true</code> if the Mojo is online, <code>false</code> otherwise.
335      */
336     // blech! this isn't even intelligible as a method name. provided for
337     // consistency...
338     public boolean isOnlineRequired() {
339         return onlineRequired;
340     }
341 
342     /**
343      * @return <code>true</code> if the Mojo is online, <code>false</code> otherwise.
344      */
345     // more english-friendly method...keep the code clean! :)
346     public boolean requiresOnline() {
347         return onlineRequired;
348     }
349 
350     /**
351      * @return the bound phase name of the Mojo
352      */
353     public String getPhase() {
354         return phase;
355     }
356 
357     /**
358      * @param phase the new bound phase name of the Mojo
359      */
360     public void setPhase(String phase) {
361         this.phase = phase;
362     }
363 
364     /**
365      * @return the version when the Mojo was added to the API
366      */
367     public String getSince() {
368         return since;
369     }
370 
371     /**
372      * @param since the new version when the Mojo was added to the API
373      */
374     public void setSince(String since) {
375         this.since = since;
376     }
377 
378     /**
379      * @return The goal name of the Mojo
380      */
381     public String getGoal() {
382         return goal;
383     }
384 
385     /**
386      * @param goal The new goal name of the Mojo
387      */
388     public void setGoal(String goal) {
389         this.goal = goal;
390     }
391 
392     /**
393      * @return the invocation phase of the Mojo
394      */
395     public String getExecutePhase() {
396         return executePhase;
397     }
398 
399     /**
400      * @param executePhase the new invocation phase of the Mojo
401      */
402     public void setExecutePhase(String executePhase) {
403         this.executePhase = executePhase;
404     }
405 
406     /**
407      * @return <code>true</code> if the Mojo uses <code>always</code> for the <code>executionStrategy</code>
408      */
409     public boolean alwaysExecute() {
410         return MULTI_PASS_EXEC_STRATEGY.equals(executionStrategy);
411     }
412 
413     /**
414      * @return the execution strategy
415      */
416     public String getExecutionStrategy() {
417         return executionStrategy;
418     }
419 
420     /**
421      * @param executionStrategy the new execution strategy
422      */
423     public void setExecutionStrategy(String executionStrategy) {
424         this.executionStrategy = executionStrategy;
425     }
426 
427     /**
428      * @return the mojo configuration
429      */
430     public PlexusConfiguration getMojoConfiguration() {
431         if (mojoConfiguration == null) {
432             mojoConfiguration = new XmlPlexusConfiguration("configuration");
433         }
434         return mojoConfiguration;
435     }
436 
437     /**
438      * @param mojoConfiguration a new mojo configuration
439      */
440     public void setMojoConfiguration(PlexusConfiguration mojoConfiguration) {
441         this.mojoConfiguration = mojoConfiguration;
442     }
443 
444     /** {@inheritDoc} */
445     public String getRole() {
446         return isV4Api() ? "org.apache.maven.api.plugin.Mojo" : Mojo.ROLE;
447     }
448 
449     /** {@inheritDoc} */
450     public String getRoleHint() {
451         return getId();
452     }
453 
454     /**
455      * @return the id of the mojo, based on the goal name
456      */
457     public String getId() {
458         return getPluginDescriptor().getId() + ":" + getGoal();
459     }
460 
461     /**
462      * @return the full goal name
463      * @see PluginDescriptor#getGoalPrefix()
464      * @see #getGoal()
465      */
466     public String getFullGoalName() {
467         return getPluginDescriptor().getGoalPrefix() + ":" + getGoal();
468     }
469 
470     /** {@inheritDoc} */
471     public String getComponentType() {
472         return MAVEN_PLUGIN;
473     }
474 
475     /**
476      * @return the plugin descriptor
477      */
478     public PluginDescriptor getPluginDescriptor() {
479         return pluginDescriptor;
480     }
481 
482     /**
483      * @param pluginDescriptor the new plugin descriptor
484      */
485     public void setPluginDescriptor(PluginDescriptor pluginDescriptor) {
486         this.pluginDescriptor = pluginDescriptor;
487     }
488 
489     /**
490      * @return <code>true</code> if the Mojo is inherited, <code>false</code> otherwise.
491      */
492     public boolean isInheritedByDefault() {
493         return inheritedByDefault;
494     }
495 
496     /**
497      * @param inheritedByDefault <code>true</code> if the Mojo is inherited, <code>false</code> otherwise.
498      */
499     public void setInheritedByDefault(boolean inheritedByDefault) {
500         this.inheritedByDefault = inheritedByDefault;
501     }
502 
503     /** {@inheritDoc} */
504     public boolean equals(Object object) {
505         if (this == object) {
506             return true;
507         }
508 
509         if (object instanceof MojoDescriptor) {
510             MojoDescriptor other = (MojoDescriptor) object;
511 
512             return Objects.equals(getPluginDescriptor(), other.getPluginDescriptor())
513                     && Objects.equals(getGoal(), other.getGoal());
514         }
515 
516         return false;
517     }
518 
519     /** {@inheritDoc} */
520     public int hashCode() {
521         return Objects.hash(getGoal(), getPluginDescriptor());
522     }
523 
524     /**
525      * @return the invocation lifecycle of the Mojo
526      */
527     public String getExecuteLifecycle() {
528         return executeLifecycle;
529     }
530 
531     /**
532      * @param executeLifecycle the new invocation lifecycle of the Mojo
533      */
534     public void setExecuteLifecycle(String executeLifecycle) {
535         this.executeLifecycle = executeLifecycle;
536     }
537 
538     /**
539      * @param aggregator <code>true</code> if the Mojo uses the Maven project and its child modules,
540      * <code>false</code> otherwise.
541      */
542     public void setAggregator(boolean aggregator) {
543         this.aggregator = aggregator;
544     }
545 
546     /**
547      * @return <code>true</code> if the Mojo uses the Maven project and its child modules,
548      * <code>false</code> otherwise.
549      */
550     public boolean isAggregator() {
551         return aggregator;
552     }
553 
554     /**
555      * @return <code>true</code> if the Mojo cannot be invoked directly, <code>false</code> otherwise.
556      */
557     public boolean isDirectInvocationOnly() {
558         return directInvocationOnly;
559     }
560 
561     /**
562      * @param directInvocationOnly <code>true</code> if the Mojo cannot be invoked directly,
563      * <code>false</code> otherwise.
564      */
565     public void setDirectInvocationOnly(boolean directInvocationOnly) {
566         this.directInvocationOnly = directInvocationOnly;
567     }
568 
569     /**
570      * @return <code>true</code> if the Mojo needs reports to run, <code>false</code> otherwise.
571      */
572     public boolean isRequiresReports() {
573         return requiresReports;
574     }
575 
576     /**
577      * @param requiresReports <code>true</code> if the Mojo needs reports to run, <code>false</code> otherwise.
578      */
579     public void setRequiresReports(boolean requiresReports) {
580         this.requiresReports = requiresReports;
581     }
582 
583     /**
584      * @param executeGoal the new invocation goal of the Mojo
585      */
586     public void setExecuteGoal(String executeGoal) {
587         this.executeGoal = executeGoal;
588     }
589 
590     /**
591      * @return the invocation goal of the Mojo
592      */
593     public String getExecuteGoal() {
594         return executeGoal;
595     }
596 
597     /**
598      * @return True if the <code>Mojo</code> is thread-safe and can be run safely in parallel
599      * @since 3.0-beta-2
600      */
601     public boolean isThreadSafe() {
602         return threadSafe;
603     }
604 
605     /**
606      * @param threadSafe indicates that the mojo is thread-safe and can be run safely in parallel
607      * @since 3.0-beta-2
608      */
609     public void setThreadSafe(boolean threadSafe) {
610         this.threadSafe = threadSafe;
611     }
612 
613     /**
614      * @return {@code true} if this mojo forks either a goal or the lifecycle, {@code false} otherwise.
615      */
616     public boolean isForking() {
617         return (getExecuteGoal() != null && !getExecuteGoal().isEmpty())
618                 || (getExecutePhase() != null && !getExecutePhase().isEmpty());
619     }
620 
621     public boolean isV4Api() {
622         return v4Api;
623     }
624 
625     /**
626      * Creates a shallow copy of this mojo descriptor.
627      */
628     @Override
629     public MojoDescriptor clone() {
630         try {
631             return (MojoDescriptor) super.clone();
632         } catch (CloneNotSupportedException e) {
633             throw new UnsupportedOperationException(e);
634         }
635     }
636 
637     private volatile org.apache.maven.api.plugin.descriptor.MojoDescriptor mojoDescriptorV4;
638 
639     public org.apache.maven.api.plugin.descriptor.MojoDescriptor getMojoDescriptorV4() {
640         if (mojoDescriptorV4 == null) {
641             synchronized (this) {
642                 if (mojoDescriptorV4 == null) {
643                     mojoDescriptorV4 = org.apache.maven.api.plugin.descriptor.MojoDescriptor.newBuilder()
644                             .goal(goal)
645                             .description(getDescription())
646                             .implementation(getImplementation())
647                             .language(getLanguage())
648                             .phase(phase)
649                             .executeGoal(executeGoal)
650                             .executeLifecycle(executeLifecycle)
651                             .executePhase(executePhase)
652                             .aggregator(aggregator)
653                             .dependencyResolution(dependencyResolutionRequired)
654                             .dependencyCollection(dependencyCollectionRequired)
655                             .projectRequired(projectRequired)
656                             .onlineRequired(onlineRequired)
657                             .inheritedByDefault(inheritedByDefault)
658                             .since(since)
659                             .deprecated(deprecated)
660                             .configurator(getComponentConfigurator())
661                             .parameters(getParameters().stream()
662                                     .filter(p -> p.getRequirement() == null)
663                                     .map(Parameter::getParameterV4)
664                                     .collect(Collectors.toList()))
665                             .id(getId())
666                             .fullGoalName(getFullGoalName())
667                             .build();
668                 }
669             }
670         }
671         return mojoDescriptorV4;
672     }
673 }