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 javax.xml.stream.XMLStreamException;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  import java.nio.file.Files;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.regex.Pattern;
36  import java.util.stream.Collectors;
37  
38  import org.apache.maven.api.plugin.descriptor.lifecycle.Lifecycle;
39  import org.apache.maven.api.plugin.descriptor.lifecycle.LifecycleConfiguration;
40  import org.apache.maven.artifact.Artifact;
41  import org.apache.maven.artifact.ArtifactUtils;
42  import org.apache.maven.model.Plugin;
43  import org.apache.maven.plugin.lifecycle.io.LifecycleStaxReader;
44  import org.codehaus.plexus.classworlds.realm.ClassRealm;
45  import org.codehaus.plexus.component.repository.ComponentDescriptor;
46  import org.codehaus.plexus.component.repository.ComponentSetDescriptor;
47  import org.eclipse.aether.graph.DependencyNode;
48  
49  /**
50   */
51  public class PluginDescriptor extends ComponentSetDescriptor implements Cloneable {
52  
53      private static final String LIFECYCLE_DESCRIPTOR = "META-INF/maven/lifecycle.xml";
54  
55      private static final Pattern PATTERN_FILTER_1 = Pattern.compile("-?(maven|plugin)-?");
56  
57      private String groupId;
58  
59      private String artifactId;
60  
61      private String version;
62  
63      private String goalPrefix;
64  
65      private String source;
66  
67      private boolean inheritedByDefault = true;
68  
69      private List<Artifact> artifacts;
70  
71      private DependencyNode dependencyNode;
72  
73      private ClassRealm classRealm;
74  
75      // calculated on-demand.
76      private Map<String, Artifact> artifactMap;
77  
78      private Set<Artifact> introducedDependencyArtifacts;
79  
80      private String name;
81  
82      private String description;
83  
84      private String requiredMavenVersion;
85  
86      private String requiredJavaVersion;
87  
88      private Plugin plugin;
89  
90      private Artifact pluginArtifact;
91  
92      private Map<String, Lifecycle> lifecycleMappings;
93  
94      // ----------------------------------------------------------------------
95      //
96      // ----------------------------------------------------------------------
97  
98      public PluginDescriptor() {}
99  
100     public PluginDescriptor(PluginDescriptor original) {
101         this.setGroupId(original.getGroupId());
102         this.setArtifactId(original.getArtifactId());
103         this.setVersion(original.getVersion());
104         this.setGoalPrefix(original.getGoalPrefix());
105         this.setInheritedByDefault(original.isInheritedByDefault());
106         this.setName(original.getName());
107         this.setDescription(original.getDescription());
108         this.setRequiredMavenVersion(original.getRequiredMavenVersion());
109         this.setRequiredJavaVersion(original.getRequiredJavaVersion());
110         this.setPluginArtifact(ArtifactUtils.copyArtifactSafe(original.getPluginArtifact()));
111         this.setComponents(clone(original.getMojos(), this));
112         this.setId(original.getId());
113         this.setIsolatedRealm(original.isIsolatedRealm());
114         this.setSource(original.getSource());
115         this.setDependencies(original.getDependencies());
116         this.setDependencyNode(original.getDependencyNode());
117     }
118 
119     private static List<ComponentDescriptor<?>> clone(List<MojoDescriptor> mojos, PluginDescriptor pluginDescriptor) {
120         List<ComponentDescriptor<?>> clones = null;
121         if (mojos != null) {
122             clones = new ArrayList<>(mojos.size());
123             for (MojoDescriptor mojo : mojos) {
124                 MojoDescriptor clone = mojo.clone();
125                 clone.setPluginDescriptor(pluginDescriptor);
126                 clones.add(clone);
127             }
128         }
129         return clones;
130     }
131 
132     public PluginDescriptor(org.apache.maven.api.plugin.descriptor.PluginDescriptor original) {
133         this.setGroupId(original.getGroupId());
134         this.setArtifactId(original.getArtifactId());
135         this.setVersion(original.getVersion());
136         this.setGoalPrefix(original.getGoalPrefix());
137         this.setInheritedByDefault(original.isInheritedByDefault());
138         this.setName(original.getName());
139         this.setDescription(original.getDescription());
140         this.setRequiredMavenVersion(original.getRequiredMavenVersion());
141         this.setRequiredJavaVersion(original.getRequiredJavaVersion());
142         this.setPluginArtifact(null); // TODO: v4
143         this.setComponents(original.getMojos().stream()
144                 .map(m -> new MojoDescriptor(this, m))
145                 .collect(Collectors.toList()));
146         this.setId(original.getId());
147         this.setIsolatedRealm(original.isIsolatedRealm());
148         this.setSource(null);
149         this.setDependencies(Collections.emptyList()); // TODO: v4
150         this.setDependencyNode(null); // TODO: v4
151         this.pluginDescriptorV4 = original;
152     }
153 
154     // ----------------------------------------------------------------------
155     //
156     // ----------------------------------------------------------------------
157 
158     @SuppressWarnings({"unchecked", "rawtypes"})
159     public List<MojoDescriptor> getMojos() {
160         return (List) getComponents();
161     }
162 
163     public void addMojo(MojoDescriptor mojoDescriptor) throws DuplicateMojoDescriptorException {
164         MojoDescriptor existing = null;
165         // this relies heavily on the equals() and hashCode() for ComponentDescriptor,
166         // which uses role:roleHint for identity...and roleHint == goalPrefix:goal.
167         // role does not vary for Mojos.
168         List<MojoDescriptor> mojos = getMojos();
169 
170         if (mojos != null && mojos.contains(mojoDescriptor)) {
171             int indexOf = mojos.indexOf(mojoDescriptor);
172 
173             existing = mojos.get(indexOf);
174         }
175 
176         if (existing != null) {
177             throw new DuplicateMojoDescriptorException(
178                     getGoalPrefix(),
179                     mojoDescriptor.getGoal(),
180                     existing.getImplementation(),
181                     mojoDescriptor.getImplementation());
182         } else {
183             addComponentDescriptor(mojoDescriptor);
184         }
185     }
186 
187     public String getGroupId() {
188         return groupId;
189     }
190 
191     public void setGroupId(String groupId) {
192         this.groupId = groupId;
193     }
194 
195     public String getArtifactId() {
196         return artifactId;
197     }
198 
199     public void setArtifactId(String artifactId) {
200         this.artifactId = artifactId;
201     }
202 
203     // ----------------------------------------------------------------------
204     // Dependencies
205     // ----------------------------------------------------------------------
206 
207     public static String constructPluginKey(String groupId, String artifactId, String version) {
208         return groupId + ":" + artifactId + ":" + version;
209     }
210 
211     public String getPluginLookupKey() {
212         return groupId + ":" + artifactId;
213     }
214 
215     public String getId() {
216         return constructPluginKey(groupId, artifactId, version);
217     }
218 
219     public static String getDefaultPluginArtifactId(String id) {
220         return "maven-" + id + "-plugin";
221     }
222 
223     public static String getDefaultPluginGroupId() {
224         return "org.apache.maven.plugins";
225     }
226 
227     /**
228      * Parse maven-...-plugin.
229      *
230      * TODO move to plugin-tools-api as a default only
231      */
232     public static String getGoalPrefixFromArtifactId(String artifactId) {
233         if ("maven-plugin-plugin".equals(artifactId)) {
234             return "plugin";
235         } else {
236             return PATTERN_FILTER_1.matcher(artifactId).replaceAll("");
237         }
238     }
239 
240     public String getGoalPrefix() {
241         return goalPrefix;
242     }
243 
244     public void setGoalPrefix(String goalPrefix) {
245         this.goalPrefix = goalPrefix;
246     }
247 
248     public void setVersion(String version) {
249         this.version = version;
250     }
251 
252     public String getVersion() {
253         return version;
254     }
255 
256     public void setSource(String source) {
257         this.source = source;
258     }
259 
260     public String getSource() {
261         return source;
262     }
263 
264     public boolean isInheritedByDefault() {
265         return inheritedByDefault;
266     }
267 
268     public void setInheritedByDefault(boolean inheritedByDefault) {
269         this.inheritedByDefault = inheritedByDefault;
270     }
271 
272     /**
273      * Gets the artifacts that make up the plugin's class realm, excluding artifacts shadowed by the Maven core realm
274      * like {@code maven-project}.
275      *
276      * @return The plugin artifacts, never {@code null}.
277      */
278     public List<Artifact> getArtifacts() {
279         return artifacts;
280     }
281 
282     public void setArtifacts(List<Artifact> artifacts) {
283         this.artifacts = artifacts;
284 
285         // clear the calculated artifactMap
286         artifactMap = null;
287     }
288 
289     public DependencyNode getDependencyNode() {
290         return dependencyNode;
291     }
292 
293     public void setDependencyNode(DependencyNode dependencyNode) {
294         this.dependencyNode = dependencyNode;
295     }
296 
297     /**
298      * The map of artifacts accessible by the versionlessKey, i.e. groupId:artifactId
299      *
300      * @return a Map of artifacts, never {@code null}
301      * @see #getArtifacts()
302      */
303     public Map<String, Artifact> getArtifactMap() {
304         if (artifactMap == null) {
305             artifactMap = ArtifactUtils.artifactMapByVersionlessId(getArtifacts());
306         }
307 
308         return artifactMap;
309     }
310 
311     @Override
312     public boolean equals(Object object) {
313         if (this == object) {
314             return true;
315         }
316 
317         return object instanceof PluginDescriptor pluginDescriptor && getId().equals(pluginDescriptor.getId());
318     }
319 
320     @Override
321     public int hashCode() {
322         return 10 + getId().hashCode();
323     }
324 
325     public MojoDescriptor getMojo(String goal) {
326         if (getMojos() == null) {
327             return null; // no mojo in this POM
328         }
329 
330         // TODO could we use a map? Maybe if the parent did that for components too, as this is too vulnerable to
331         // changes above not being propagated to the map
332         for (MojoDescriptor desc : getMojos()) {
333             if (goal.equals(desc.getGoal())) {
334                 return desc;
335             }
336         }
337         return null;
338     }
339 
340     public void setClassRealm(ClassRealm classRealm) {
341         this.classRealm = classRealm;
342     }
343 
344     public ClassRealm getClassRealm() {
345         return classRealm;
346     }
347 
348     public void setIntroducedDependencyArtifacts(Set<Artifact> introducedDependencyArtifacts) {
349         this.introducedDependencyArtifacts = introducedDependencyArtifacts;
350     }
351 
352     public Set<Artifact> getIntroducedDependencyArtifacts() {
353         return (introducedDependencyArtifacts != null) ? introducedDependencyArtifacts : Collections.emptySet();
354     }
355 
356     public void setName(String name) {
357         this.name = name;
358     }
359 
360     public String getName() {
361         return name;
362     }
363 
364     public void setDescription(String description) {
365         this.description = description;
366     }
367 
368     public String getDescription() {
369         return description;
370     }
371 
372     /**
373      * Set required Maven version, as defined in plugin's pom.xml since 3.0.2,
374      * as defined in plugin.xml since 4.0.0-alpha-3.
375      *
376      * @param requiredMavenVersion Maven version required by the plugin
377      * @since 3.0.2
378      */
379     // used by maven-core's org.apache.maven.plugin.internal.DefaultMavenPluginManager#getPluginDescriptor(...)
380     // and PluginDescriptorBuilder since 4.0.0-alpha-3
381     public void setRequiredMavenVersion(String requiredMavenVersion) {
382         this.requiredMavenVersion = requiredMavenVersion;
383     }
384 
385     /**
386      * Get required Maven version, as defined in plugin's pom.xml since 3.0.2,
387      * as defined in plugin.xml since 4.0.0-alpha-3.
388      *
389      * @return the Maven version required by the plugin
390      * @since 3.0.2
391      */
392     public String getRequiredMavenVersion() {
393         return requiredMavenVersion;
394     }
395 
396     public void setRequiredJavaVersion(String requiredJavaVersion) {
397         this.requiredJavaVersion = requiredJavaVersion;
398     }
399 
400     public String getRequiredJavaVersion() {
401         return requiredJavaVersion;
402     }
403 
404     public void setPlugin(Plugin plugin) {
405         this.plugin = plugin;
406     }
407 
408     public Plugin getPlugin() {
409         return plugin;
410     }
411 
412     public Artifact getPluginArtifact() {
413         return pluginArtifact;
414     }
415 
416     public void setPluginArtifact(Artifact pluginArtifact) {
417         this.pluginArtifact = pluginArtifact;
418     }
419 
420     public Lifecycle getLifecycleMapping(String lifecycleId) throws IOException, XMLStreamException {
421         return getLifecycleMappings().get(lifecycleId);
422     }
423 
424     public Map<String, Lifecycle> getLifecycleMappings() throws IOException, XMLStreamException {
425         if (lifecycleMappings == null) {
426             LifecycleConfiguration lifecycleConfiguration;
427 
428             try (InputStream input = getDescriptorStream(LIFECYCLE_DESCRIPTOR)) {
429                 lifecycleConfiguration = new LifecycleStaxReader().read(input);
430             }
431 
432             lifecycleMappings = new HashMap<>();
433 
434             for (Lifecycle lifecycle : lifecycleConfiguration.getLifecycles()) {
435                 lifecycleMappings.put(lifecycle.getId(), lifecycle);
436             }
437         }
438         return lifecycleMappings;
439     }
440 
441     private InputStream getDescriptorStream(String descriptor) throws IOException {
442         File pluginFile = (pluginArtifact != null) ? pluginArtifact.getFile() : null;
443         if (pluginFile == null) {
444             throw new IllegalStateException("plugin main artifact has not been resolved for " + getId());
445         }
446 
447         if (pluginFile.isFile()) {
448             try {
449                 return new URL("jar:" + pluginFile.toURI() + "!/" + descriptor).openStream();
450             } catch (MalformedURLException e) {
451                 throw new IllegalStateException(e);
452             }
453         } else {
454             return Files.newInputStream(new File(pluginFile, descriptor).toPath());
455         }
456     }
457 
458     /**
459      * Creates a shallow copy of this plugin descriptor.
460      */
461     @Override
462     public PluginDescriptor clone() {
463         try {
464             return (PluginDescriptor) super.clone();
465         } catch (CloneNotSupportedException e) {
466             throw new UnsupportedOperationException(e);
467         }
468     }
469 
470     public void addMojos(List<MojoDescriptor> mojos) throws DuplicateMojoDescriptorException {
471         for (MojoDescriptor mojoDescriptor : mojos) {
472             addMojo(mojoDescriptor);
473         }
474     }
475 
476     private volatile org.apache.maven.api.plugin.descriptor.PluginDescriptor pluginDescriptorV4;
477 
478     public org.apache.maven.api.plugin.descriptor.PluginDescriptor getPluginDescriptorV4() {
479         if (pluginDescriptorV4 == null) {
480             synchronized (this) {
481                 if (pluginDescriptorV4 == null) {
482                     pluginDescriptorV4 = org.apache.maven.api.plugin.descriptor.PluginDescriptor.newBuilder()
483                             .namespaceUri(null)
484                             .modelEncoding(null)
485                             .name(name)
486                             .description(description)
487                             .groupId(groupId)
488                             .artifactId(artifactId)
489                             .version(version)
490                             .goalPrefix(goalPrefix)
491                             .isolatedRealm(isIsolatedRealm())
492                             .inheritedByDefault(inheritedByDefault)
493                             .requiredJavaVersion(requiredJavaVersion)
494                             .requiredMavenVersion(requiredMavenVersion)
495                             .mojos(getMojos().stream()
496                                     .map(MojoDescriptor::getMojoDescriptorV4)
497                                     .collect(Collectors.toList()))
498                             .build();
499                 }
500             }
501         }
502         return pluginDescriptorV4;
503     }
504 }