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     public boolean equals(Object object) {
312         if (this == object) {
313             return true;
314         }
315 
316         return object instanceof PluginDescriptor && getId().equals(((PluginDescriptor) object).getId());
317     }
318 
319     public int hashCode() {
320         return 10 + getId().hashCode();
321     }
322 
323     public MojoDescriptor getMojo(String goal) {
324         if (getMojos() == null) {
325             return null; // no mojo in this POM
326         }
327 
328         // TODO could we use a map? Maybe if the parent did that for components too, as this is too vulnerable to
329         // changes above not being propagated to the map
330         for (MojoDescriptor desc : getMojos()) {
331             if (goal.equals(desc.getGoal())) {
332                 return desc;
333             }
334         }
335         return null;
336     }
337 
338     public void setClassRealm(ClassRealm classRealm) {
339         this.classRealm = classRealm;
340     }
341 
342     public ClassRealm getClassRealm() {
343         return classRealm;
344     }
345 
346     public void setIntroducedDependencyArtifacts(Set<Artifact> introducedDependencyArtifacts) {
347         this.introducedDependencyArtifacts = introducedDependencyArtifacts;
348     }
349 
350     public Set<Artifact> getIntroducedDependencyArtifacts() {
351         return (introducedDependencyArtifacts != null) ? introducedDependencyArtifacts : Collections.emptySet();
352     }
353 
354     public void setName(String name) {
355         this.name = name;
356     }
357 
358     public String getName() {
359         return name;
360     }
361 
362     public void setDescription(String description) {
363         this.description = description;
364     }
365 
366     public String getDescription() {
367         return description;
368     }
369 
370     /**
371      * Set required Maven version, as defined in plugin's pom.xml since 3.0.2,
372      * as defined in plugin.xml since 4.0.0-alpha-3.
373      *
374      * @param requiredMavenVersion Maven version required by the plugin
375      * @since 3.0.2
376      */
377     // used by maven-core's org.apache.maven.plugin.internal.DefaultMavenPluginManager#getPluginDescriptor(...)
378     // and PluginDescriptorBuilder since 4.0.0-alpha-3
379     public void setRequiredMavenVersion(String requiredMavenVersion) {
380         this.requiredMavenVersion = requiredMavenVersion;
381     }
382 
383     /**
384      * Get required Maven version, as defined in plugin's pom.xml since 3.0.2,
385      * as defined in plugin.xml since 4.0.0-alpha-3.
386      *
387      * @return the Maven version required by the plugin
388      * @since 3.0.2
389      */
390     public String getRequiredMavenVersion() {
391         return requiredMavenVersion;
392     }
393 
394     public void setRequiredJavaVersion(String requiredJavaVersion) {
395         this.requiredJavaVersion = requiredJavaVersion;
396     }
397 
398     public String getRequiredJavaVersion() {
399         return requiredJavaVersion;
400     }
401 
402     public void setPlugin(Plugin plugin) {
403         this.plugin = plugin;
404     }
405 
406     public Plugin getPlugin() {
407         return plugin;
408     }
409 
410     public Artifact getPluginArtifact() {
411         return pluginArtifact;
412     }
413 
414     public void setPluginArtifact(Artifact pluginArtifact) {
415         this.pluginArtifact = pluginArtifact;
416     }
417 
418     public Lifecycle getLifecycleMapping(String lifecycleId) throws IOException, XMLStreamException {
419         return getLifecycleMappings().get(lifecycleId);
420     }
421 
422     public Map<String, Lifecycle> getLifecycleMappings() throws IOException, XMLStreamException {
423         if (lifecycleMappings == null) {
424             LifecycleConfiguration lifecycleConfiguration;
425 
426             try (InputStream input = getDescriptorStream(LIFECYCLE_DESCRIPTOR)) {
427                 lifecycleConfiguration = new LifecycleStaxReader().read(input);
428             }
429 
430             lifecycleMappings = new HashMap<>();
431 
432             for (Lifecycle lifecycle : lifecycleConfiguration.getLifecycles()) {
433                 lifecycleMappings.put(lifecycle.getId(), lifecycle);
434             }
435         }
436         return lifecycleMappings;
437     }
438 
439     private InputStream getDescriptorStream(String descriptor) throws IOException {
440         File pluginFile = (pluginArtifact != null) ? pluginArtifact.getFile() : null;
441         if (pluginFile == null) {
442             throw new IllegalStateException("plugin main artifact has not been resolved for " + getId());
443         }
444 
445         if (pluginFile.isFile()) {
446             try {
447                 return new URL("jar:" + pluginFile.toURI() + "!/" + descriptor).openStream();
448             } catch (MalformedURLException e) {
449                 throw new IllegalStateException(e);
450             }
451         } else {
452             return Files.newInputStream(new File(pluginFile, descriptor).toPath());
453         }
454     }
455 
456     /**
457      * Creates a shallow copy of this plugin descriptor.
458      */
459     @Override
460     public PluginDescriptor clone() {
461         try {
462             return (PluginDescriptor) super.clone();
463         } catch (CloneNotSupportedException e) {
464             throw new UnsupportedOperationException(e);
465         }
466     }
467 
468     public void addMojos(List<MojoDescriptor> mojos) throws DuplicateMojoDescriptorException {
469         for (MojoDescriptor mojoDescriptor : mojos) {
470             addMojo(mojoDescriptor);
471         }
472     }
473 
474     private volatile org.apache.maven.api.plugin.descriptor.PluginDescriptor pluginDescriptorV4;
475 
476     public org.apache.maven.api.plugin.descriptor.PluginDescriptor getPluginDescriptorV4() {
477         if (pluginDescriptorV4 == null) {
478             synchronized (this) {
479                 if (pluginDescriptorV4 == null) {
480                     pluginDescriptorV4 = org.apache.maven.api.plugin.descriptor.PluginDescriptor.newBuilder()
481                             .namespaceUri(null)
482                             .modelEncoding(null)
483                             .name(name)
484                             .description(description)
485                             .groupId(groupId)
486                             .artifactId(artifactId)
487                             .version(version)
488                             .goalPrefix(goalPrefix)
489                             .isolatedRealm(isIsolatedRealm())
490                             .inheritedByDefault(inheritedByDefault)
491                             .requiredJavaVersion(requiredJavaVersion)
492                             .requiredMavenVersion(requiredMavenVersion)
493                             .mojos(getMojos().stream()
494                                     .map(MojoDescriptor::getMojoDescriptorV4)
495                                     .collect(Collectors.toList()))
496                             .build();
497                 }
498             }
499         }
500         return pluginDescriptorV4;
501     }
502 }