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><phase></code> element from the surrounding
71 * <code><execution></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.setV4Api(true);
170 this.setImplementation(md.getImplementation());
171 try {
172 this.setParameters(md.getParameters().stream().map(Parameter::new).collect(Collectors.toList()));
173 } catch (DuplicateParameterException e) {
174 throw new IllegalArgumentException(e);
175 }
176 this.mojoDescriptorV4 = md;
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().length() > 0)
618 || (getExecutePhase() != null && getExecutePhase().length() > 0);
619 }
620
621 public boolean isV4Api() {
622 return v4Api;
623 }
624
625 public void setV4Api(boolean v4Api) {
626 this.v4Api = v4Api;
627 }
628
629 /**
630 * Creates a shallow copy of this mojo descriptor.
631 */
632 @Override
633 public MojoDescriptor clone() {
634 try {
635 return (MojoDescriptor) super.clone();
636 } catch (CloneNotSupportedException e) {
637 throw new UnsupportedOperationException(e);
638 }
639 }
640
641 private volatile org.apache.maven.api.plugin.descriptor.MojoDescriptor mojoDescriptorV4;
642
643 public org.apache.maven.api.plugin.descriptor.MojoDescriptor getMojoDescriptorV4() {
644 if (mojoDescriptorV4 == null) {
645 synchronized (this) {
646 if (mojoDescriptorV4 == null) {
647 mojoDescriptorV4 = org.apache.maven.api.plugin.descriptor.MojoDescriptor.newBuilder()
648 .goal(goal)
649 .description(getDescription())
650 .implementation(getImplementation())
651 .language(getLanguage())
652 .phase(phase)
653 .executeGoal(executeGoal)
654 .executeLifecycle(executeLifecycle)
655 .executePhase(executePhase)
656 .aggregator(aggregator)
657 .dependencyResolution(dependencyResolutionRequired)
658 .dependencyCollection(dependencyCollectionRequired)
659 .projectRequired(projectRequired)
660 .onlineRequired(onlineRequired)
661 .inheritedByDefault(inheritedByDefault)
662 .since(since)
663 .deprecated(deprecated)
664 .configurator(getComponentConfigurator())
665 .parameters(getParameters().stream()
666 .filter(p -> p.getRequirement() == null)
667 .map(Parameter::getParameterV4)
668 .collect(Collectors.toList()))
669 .id(getId())
670 .fullGoalName(getFullGoalName())
671 .build();
672 }
673 }
674 }
675 return mojoDescriptorV4;
676 }
677 }