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.prefix.internal;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.stream.Collectors;
29  import java.util.stream.Stream;
30  
31  import org.apache.maven.artifact.repository.metadata.Metadata;
32  import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
33  import org.apache.maven.model.Build;
34  import org.apache.maven.model.Model;
35  import org.apache.maven.model.Plugin;
36  import org.apache.maven.model.PluginManagement;
37  import org.apache.maven.plugin.BuildPluginManager;
38  import org.apache.maven.plugin.descriptor.PluginDescriptor;
39  import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException;
40  import org.apache.maven.plugin.prefix.PluginPrefixRequest;
41  import org.apache.maven.plugin.prefix.PluginPrefixResolver;
42  import org.apache.maven.plugin.prefix.PluginPrefixResult;
43  import org.codehaus.plexus.component.annotations.Component;
44  import org.codehaus.plexus.component.annotations.Requirement;
45  import org.codehaus.plexus.logging.Logger;
46  import org.eclipse.aether.DefaultRepositorySystemSession;
47  import org.eclipse.aether.RepositoryEvent;
48  import org.eclipse.aether.RepositoryEvent.EventType;
49  import org.eclipse.aether.RepositoryListener;
50  import org.eclipse.aether.RepositorySystem;
51  import org.eclipse.aether.RepositorySystemSession;
52  import org.eclipse.aether.RequestTrace;
53  import org.eclipse.aether.metadata.DefaultMetadata;
54  import org.eclipse.aether.repository.ArtifactRepository;
55  import org.eclipse.aether.repository.RemoteRepository;
56  import org.eclipse.aether.repository.RepositoryPolicy;
57  import org.eclipse.aether.resolution.MetadataRequest;
58  import org.eclipse.aether.resolution.MetadataResult;
59  
60  /**
61   * Resolves a plugin prefix.
62   *
63   * @since 3.0
64   * @author Benjamin Bentmann
65   */
66  @Component(role = PluginPrefixResolver.class)
67  public class DefaultPluginPrefixResolver implements PluginPrefixResolver {
68  
69      private static final String REPOSITORY_CONTEXT = "plugin";
70  
71      @Requirement
72      private Logger logger;
73  
74      @Requirement
75      private BuildPluginManager pluginManager;
76  
77      @Requirement
78      private RepositorySystem repositorySystem;
79  
80      @Requirement
81      private MetadataReader metadataReader;
82  
83      public PluginPrefixResult resolve(PluginPrefixRequest request) throws NoPluginFoundForPrefixException {
84          logger.debug("Resolving plugin prefix " + request.getPrefix() + " from " + request.getPluginGroups());
85  
86          Model pom = request.getPom();
87          Build build = pom != null ? pom.getBuild() : null;
88          PluginManagement management = build != null ? build.getPluginManagement() : null;
89  
90          // map of groupId -> Set(artifactId) plugin candidates:
91          // if value is null, keys are coming from settings, and no artifactId filtering is applied
92          // if value is non-null: we allow only plugins that have enlisted artifactId only
93          // ---
94          // end game is: settings enlisted groupIds are obeying order and are "free for all" (artifactId)
95          // while POM enlisted plugins coming from non-enlisted settings groupIds (ie conflict of prefixes)
96          // will prevail/win.
97          LinkedHashMap<String, Set<String>> candidates = Stream.of(build, management)
98                  .flatMap(container -> container != null ? container.getPlugins().stream() : Stream.empty())
99                  .filter(p -> !request.getPluginGroups().contains(p.getGroupId()))
100                 .collect(Collectors.groupingBy(
101                         Plugin::getGroupId,
102                         LinkedHashMap::new,
103                         Collectors.mapping(Plugin::getArtifactId, Collectors.toSet())));
104         request.getPluginGroups().forEach(g -> candidates.put(g, null));
105         PluginPrefixResult result = resolveFromRepository(request, candidates);
106 
107         // If we haven't been able to resolve the plugin from the repository,
108         // as a last resort, we go through all declared plugins, load them
109         // one by one, and try to find a matching prefix.
110         if (result == null && build != null) {
111             result = resolveFromProject(request, build.getPlugins());
112             if (result == null && management != null) {
113                 result = resolveFromProject(request, management.getPlugins());
114             }
115         }
116 
117         if (result == null) {
118             throw new NoPluginFoundForPrefixException(
119                     request.getPrefix(),
120                     new ArrayList<>(candidates.keySet()),
121                     request.getRepositorySession().getLocalRepository(),
122                     request.getRepositories());
123         } else {
124             logger.debug("Resolved plugin prefix " + request.getPrefix() + " to " + result.getGroupId() + ":"
125                     + result.getArtifactId() + " from repository "
126                     + (result.getRepository() != null ? result.getRepository().getId() : "null"));
127         }
128 
129         return result;
130     }
131 
132     private PluginPrefixResult resolveFromProject(PluginPrefixRequest request, List<Plugin> plugins) {
133         for (Plugin plugin : plugins) {
134             try {
135                 PluginDescriptor pluginDescriptor =
136                         pluginManager.loadPlugin(plugin, request.getRepositories(), request.getRepositorySession());
137 
138                 if (request.getPrefix().equals(pluginDescriptor.getGoalPrefix())) {
139                     return new DefaultPluginPrefixResult(plugin);
140                 }
141             } catch (Exception e) {
142                 if (logger.isDebugEnabled()) {
143                     logger.warn(
144                             "Failed to retrieve plugin descriptor for " + plugin.getId() + ": " + e.getMessage(), e);
145                 } else {
146                     logger.warn("Failed to retrieve plugin descriptor for " + plugin.getId() + ": " + e.getMessage());
147                 }
148             }
149         }
150 
151         return null;
152     }
153 
154     private PluginPrefixResult resolveFromRepository(
155             PluginPrefixRequest request, LinkedHashMap<String, Set<String>> candidates) {
156         RequestTrace trace = RequestTrace.newChild(null, request);
157 
158         List<MetadataRequest> requests = new ArrayList<>();
159 
160         for (String pluginGroup : candidates.keySet()) {
161             org.eclipse.aether.metadata.Metadata metadata =
162                     new DefaultMetadata(pluginGroup, "maven-metadata.xml", DefaultMetadata.Nature.RELEASE_OR_SNAPSHOT);
163 
164             requests.add(new MetadataRequest(metadata, null, REPOSITORY_CONTEXT).setTrace(trace));
165 
166             for (RemoteRepository repository : request.getRepositories()) {
167                 requests.add(new MetadataRequest(metadata, repository, REPOSITORY_CONTEXT).setTrace(trace));
168             }
169         }
170 
171         // initial try, use locally cached metadata
172 
173         List<MetadataResult> results = repositorySystem.resolveMetadata(request.getRepositorySession(), requests);
174         requests.clear();
175 
176         PluginPrefixResult result = processResults(request, trace, results, requests, candidates);
177 
178         if (result != null) {
179             return result;
180         }
181 
182         // second try, refetch all (possibly outdated) metadata that wasn't updated in the first attempt
183 
184         if (!request.getRepositorySession().isOffline() && !requests.isEmpty()) {
185             DefaultRepositorySystemSession session = new DefaultRepositorySystemSession(request.getRepositorySession());
186             session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
187 
188             results = repositorySystem.resolveMetadata(session, requests);
189 
190             return processResults(request, trace, results, null, candidates);
191         }
192 
193         return null;
194     }
195 
196     private PluginPrefixResult processResults(
197             PluginPrefixRequest request,
198             RequestTrace trace,
199             List<MetadataResult> results,
200             List<MetadataRequest> requests,
201             LinkedHashMap<String, Set<String>> candidates) {
202         for (MetadataResult res : results) {
203             org.eclipse.aether.metadata.Metadata metadata = res.getMetadata();
204 
205             if (metadata != null) {
206                 ArtifactRepository repository = res.getRequest().getRepository();
207                 if (repository == null) {
208                     repository = request.getRepositorySession().getLocalRepository();
209                 }
210 
211                 PluginPrefixResult result =
212                         resolveFromRepository(request, trace, metadata.getGroupId(), metadata, repository, candidates);
213 
214                 if (result != null) {
215                     return result;
216                 }
217             }
218 
219             if (requests != null && !res.isUpdated()) {
220                 requests.add(res.getRequest());
221             }
222         }
223 
224         return null;
225     }
226 
227     private PluginPrefixResult resolveFromRepository(
228             PluginPrefixRequest request,
229             RequestTrace trace,
230             String pluginGroup,
231             org.eclipse.aether.metadata.Metadata metadata,
232             ArtifactRepository repository,
233             LinkedHashMap<String, Set<String>> candidates) {
234         if (metadata != null && metadata.getFile() != null && metadata.getFile().isFile()) {
235             try {
236                 Map<String, ?> options = Collections.singletonMap(MetadataReader.IS_STRICT, Boolean.FALSE);
237 
238                 Metadata pluginGroupMetadata = metadataReader.read(metadata.getFile(), options);
239 
240                 List<org.apache.maven.artifact.repository.metadata.Plugin> plugins = pluginGroupMetadata.getPlugins();
241 
242                 if (plugins != null) {
243                     for (org.apache.maven.artifact.repository.metadata.Plugin plugin : plugins) {
244                         if (request.getPrefix().equals(plugin.getPrefix())
245                                 && (candidates.get(pluginGroup) == null
246                                         || candidates.get(pluginGroup).contains(plugin.getArtifactId()))) {
247                             return new DefaultPluginPrefixResult(pluginGroup, plugin.getArtifactId(), repository);
248                         }
249                     }
250                 }
251             } catch (IOException e) {
252                 invalidMetadata(request.getRepositorySession(), trace, metadata, repository, e);
253             }
254         }
255 
256         return null;
257     }
258 
259     private void invalidMetadata(
260             RepositorySystemSession session,
261             RequestTrace trace,
262             org.eclipse.aether.metadata.Metadata metadata,
263             ArtifactRepository repository,
264             Exception exception) {
265         RepositoryListener listener = session.getRepositoryListener();
266         if (listener != null) {
267             RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
268             event.setTrace(trace);
269             event.setMetadata(metadata);
270             event.setException(exception);
271             event.setRepository(repository);
272             listener.metadataInvalid(event.build());
273         }
274     }
275 }