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.repository.internal;
20  
21  import java.io.InputStream;
22  import java.nio.file.Files;
23  import java.nio.file.Path;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Date;
27  import java.util.Iterator;
28  import java.util.LinkedHashMap;
29  import java.util.Map;
30  import java.util.Objects;
31  import java.util.jar.JarFile;
32  import java.util.zip.ZipEntry;
33  
34  import org.apache.maven.api.xml.XmlNode;
35  import org.apache.maven.internal.xml.XmlNodeBuilder;
36  import org.apache.maven.repository.internal.PluginsMetadata.PluginInfo;
37  import org.eclipse.aether.RepositorySystemSession;
38  import org.eclipse.aether.artifact.Artifact;
39  import org.eclipse.aether.deployment.DeployRequest;
40  import org.eclipse.aether.impl.MetadataGenerator;
41  import org.eclipse.aether.installation.InstallRequest;
42  import org.eclipse.aether.metadata.Metadata;
43  import org.eclipse.aether.util.ConfigUtils;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  /**
48   * Maven G level metadata generator.
49   * <p>
50   * Plugin metadata contains G level list of "prefix" to A mapping for plugins present under this G.
51   *
52   * @deprecated since 4.0.0, use {@code maven-api-impl} jar instead
53   */
54  @Deprecated(since = "4.0.0")
55  class PluginsMetadataGenerator implements MetadataGenerator {
56      private static final String PLUGIN_DESCRIPTOR_LOCATION = "META-INF/maven/plugin.xml";
57  
58      private final Logger logger = LoggerFactory.getLogger(getClass());
59  
60      private final Map<Object, PluginsMetadata> processedPlugins;
61  
62      private final Date timestamp;
63  
64      PluginsMetadataGenerator(RepositorySystemSession session, InstallRequest request) {
65          this(session, request.getMetadata());
66      }
67  
68      PluginsMetadataGenerator(RepositorySystemSession session, DeployRequest request) {
69          this(session, request.getMetadata());
70      }
71  
72      private PluginsMetadataGenerator(RepositorySystemSession session, Collection<? extends Metadata> metadatas) {
73          this.processedPlugins = new LinkedHashMap<>();
74          this.timestamp = (Date) ConfigUtils.getObject(session, new Date(), "maven.startTime");
75  
76          /*
77           * NOTE: This should be considered a quirk to support interop with Maven's legacy ArtifactDeployer which
78           * processes one artifact at a time and hence cannot associate the artifacts from the same project to use the
79           * same version index. Allowing the caller to pass in metadata from a previous deployment allows to re-establish
80           * the association between the artifacts of the same project.
81           */
82          for (Iterator<? extends Metadata> it = metadatas.iterator(); it.hasNext(); ) {
83              Metadata metadata = it.next();
84              if (metadata instanceof PluginsMetadata) {
85                  it.remove();
86                  PluginsMetadata pluginMetadata = (PluginsMetadata) metadata;
87                  processedPlugins.put(pluginMetadata.getGroupId(), pluginMetadata);
88              }
89          }
90      }
91  
92      @Override
93      public Collection<? extends Metadata> prepare(Collection<? extends Artifact> artifacts) {
94          return Collections.emptyList();
95      }
96  
97      @Override
98      public Artifact transformArtifact(Artifact artifact) {
99          return artifact;
100     }
101 
102     @Override
103     public Collection<? extends Metadata> finish(Collection<? extends Artifact> artifacts) {
104         LinkedHashMap<String, PluginsMetadata> plugins = new LinkedHashMap<>();
105         for (Artifact artifact : artifacts) {
106             PluginInfo pluginInfo = extractPluginInfo(artifact);
107             if (pluginInfo != null) {
108                 String key = pluginInfo.groupId;
109                 if (processedPlugins.get(key) == null) {
110                     PluginsMetadata pluginMetadata = plugins.get(key);
111                     if (pluginMetadata == null) {
112                         pluginMetadata = new PluginsMetadata(pluginInfo, timestamp);
113                         plugins.put(key, pluginMetadata);
114                     }
115                 }
116             }
117         }
118         return plugins.values();
119     }
120 
121     private PluginInfo extractPluginInfo(Artifact artifact) {
122         // sanity: jar, no classifier and file exists
123         if (artifact != null
124                 && "jar".equals(artifact.getExtension())
125                 && "".equals(artifact.getClassifier())
126                 && artifact.getPath() != null) {
127             Path artifactPath = artifact.getPath();
128             if (Files.isRegularFile(artifactPath)) {
129                 try (JarFile artifactJar = new JarFile(artifactPath.toFile(), false)) {
130                     ZipEntry pluginDescriptorEntry = artifactJar.getEntry(PLUGIN_DESCRIPTOR_LOCATION);
131 
132                     if (pluginDescriptorEntry != null) {
133                         try (InputStream is = artifactJar.getInputStream(pluginDescriptorEntry)) {
134                             // Note: using DOM instead of use of
135                             // org.apache.maven.plugin.descriptor.PluginDescriptor
136                             // as it would pull in dependency on:
137                             // - maven-plugin-api (for model)
138                             // - Plexus Container (for model supporting classes and exceptions)
139                             XmlNode root = XmlNodeBuilder.build(is, null);
140                             String groupId = mayGetChild(root, "groupId");
141                             String artifactId = mayGetChild(root, "artifactId");
142                             String goalPrefix = mayGetChild(root, "goalPrefix");
143                             String name = mayGetChild(root, "name");
144                             // sanity check: plugin descriptor extracted from artifact must have same GA
145                             if (Objects.equals(artifact.getGroupId(), groupId)
146                                     && Objects.equals(artifact.getArtifactId(), artifactId)) {
147                                 // here groupId and artifactId cannot be null
148                                 return new PluginInfo(groupId, artifactId, goalPrefix, name);
149                             } else {
150                                 logger.warn(
151                                         "Artifact {}:{}"
152                                                 + " JAR (about to be installed/deployed) contains Maven Plugin metadata for"
153                                                 + " conflicting coordinates: {}:{}."
154                                                 + " Your JAR contains rogue Maven Plugin metadata."
155                                                 + " Possible causes may be: shaded into this JAR some Maven Plugin or some rogue resource.",
156                                         artifact.getGroupId(),
157                                         artifact.getArtifactId(),
158                                         groupId,
159                                         artifactId);
160                             }
161                         }
162                     }
163                 } catch (Exception e) {
164                     // here we can have: IO. ZIP or Plexus Conf Ex: but we should not interfere with user intent
165                 }
166             }
167         }
168         return null;
169     }
170 
171     private static String mayGetChild(XmlNode node, String child) {
172         XmlNode c = node.getChild(child);
173         if (c != null) {
174             return c.getValue();
175         }
176         return null;
177     }
178 }