1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugin.version.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.Objects;
28 import java.util.TreeSet;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31
32 import org.apache.maven.artifact.repository.metadata.Metadata;
33 import org.apache.maven.artifact.repository.metadata.Versioning;
34 import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
35 import org.apache.maven.model.Build;
36 import org.apache.maven.model.Plugin;
37 import org.apache.maven.plugin.MavenPluginManager;
38 import org.apache.maven.plugin.PluginIncompatibleException;
39 import org.apache.maven.plugin.PluginResolutionException;
40 import org.apache.maven.plugin.descriptor.PluginDescriptor;
41 import org.apache.maven.plugin.version.PluginVersionRequest;
42 import org.apache.maven.plugin.version.PluginVersionResolutionException;
43 import org.apache.maven.plugin.version.PluginVersionResolver;
44 import org.apache.maven.plugin.version.PluginVersionResult;
45 import org.codehaus.plexus.component.annotations.Component;
46 import org.codehaus.plexus.component.annotations.Requirement;
47 import org.codehaus.plexus.logging.Logger;
48 import org.codehaus.plexus.util.StringUtils;
49 import org.eclipse.aether.RepositoryEvent;
50 import org.eclipse.aether.RepositoryEvent.EventType;
51 import org.eclipse.aether.RepositoryListener;
52 import org.eclipse.aether.RepositorySystem;
53 import org.eclipse.aether.RepositorySystemSession;
54 import org.eclipse.aether.RequestTrace;
55 import org.eclipse.aether.SessionData;
56 import org.eclipse.aether.metadata.DefaultMetadata;
57 import org.eclipse.aether.repository.ArtifactRepository;
58 import org.eclipse.aether.repository.RemoteRepository;
59 import org.eclipse.aether.resolution.MetadataRequest;
60 import org.eclipse.aether.resolution.MetadataResult;
61 import org.eclipse.aether.util.version.GenericVersionScheme;
62 import org.eclipse.aether.version.InvalidVersionSpecificationException;
63 import org.eclipse.aether.version.Version;
64 import org.eclipse.aether.version.VersionScheme;
65
66
67
68
69
70
71
72 @Component(role = PluginVersionResolver.class)
73 public class DefaultPluginVersionResolver implements PluginVersionResolver {
74
75 private static final String REPOSITORY_CONTEXT = "plugin";
76
77 private static final Object CACHE_KEY = new Object();
78
79 @Requirement
80 private Logger logger;
81
82 @Requirement
83 private RepositorySystem repositorySystem;
84
85 @Requirement
86 private MetadataReader metadataReader;
87
88 @Requirement
89 private MavenPluginManager pluginManager;
90
91 public PluginVersionResult resolve(PluginVersionRequest request) throws PluginVersionResolutionException {
92 PluginVersionResult result = resolveFromProject(request);
93
94 if (result == null) {
95 ConcurrentMap<Key, PluginVersionResult> cache = getCache(request);
96 Key key = getKey(request);
97 result = cache.get(key);
98
99 if (result == null) {
100 result = resolveFromRepository(request);
101
102 if (logger.isDebugEnabled()) {
103 logger.debug("Resolved plugin version for " + request.getGroupId() + ":" + request.getArtifactId()
104 + " to " + result.getVersion() + " from repository " + result.getRepository());
105 }
106
107 cache.putIfAbsent(key, result);
108 } else if (logger.isDebugEnabled()) {
109 logger.debug("Reusing cached resolved plugin version for " + request.getGroupId() + ":"
110 + request.getArtifactId() + " to " + result.getVersion() + " from POM " + request.getPom());
111 }
112 } else if (logger.isDebugEnabled()) {
113 logger.debug("Resolved plugin version for " + request.getGroupId() + ":" + request.getArtifactId() + " to "
114 + result.getVersion() + " from POM " + request.getPom());
115 }
116
117 return result;
118 }
119
120 private PluginVersionResult resolveFromRepository(PluginVersionRequest request)
121 throws PluginVersionResolutionException {
122 RequestTrace trace = RequestTrace.newChild(null, request);
123
124 DefaultPluginVersionResult result = new DefaultPluginVersionResult();
125
126 org.eclipse.aether.metadata.Metadata metadata = new DefaultMetadata(
127 request.getGroupId(),
128 request.getArtifactId(),
129 "maven-metadata.xml",
130 DefaultMetadata.Nature.RELEASE_OR_SNAPSHOT);
131
132 List<MetadataRequest> requests = new ArrayList<>();
133
134 requests.add(new MetadataRequest(metadata, null, REPOSITORY_CONTEXT).setTrace(trace));
135
136 for (RemoteRepository repository : request.getRepositories()) {
137 requests.add(new MetadataRequest(metadata, repository, REPOSITORY_CONTEXT).setTrace(trace));
138 }
139
140 List<MetadataResult> results = repositorySystem.resolveMetadata(request.getRepositorySession(), requests);
141
142 Versions versions = new Versions();
143
144 for (MetadataResult res : results) {
145 ArtifactRepository repository = res.getRequest().getRepository();
146 if (repository == null) {
147 repository = request.getRepositorySession().getLocalRepository();
148 }
149
150 mergeMetadata(request.getRepositorySession(), trace, versions, res.getMetadata(), repository);
151 }
152
153 selectVersion(result, request, versions);
154
155 return result;
156 }
157
158 private void selectVersion(DefaultPluginVersionResult result, PluginVersionRequest request, Versions versions)
159 throws PluginVersionResolutionException {
160 String version = null;
161 ArtifactRepository repo = null;
162 boolean resolvedPluginVersions = !versions.versions.isEmpty();
163 boolean searchPerformed = false;
164
165 if (StringUtils.isNotEmpty(versions.releaseVersion)) {
166 version = versions.releaseVersion;
167 repo = versions.releaseRepository;
168 } else if (StringUtils.isNotEmpty(versions.latestVersion)) {
169 version = versions.latestVersion;
170 repo = versions.latestRepository;
171 }
172 if (version != null && !isCompatible(request, version)) {
173 logger.info("Latest version of plugin " + request.getGroupId() + ":" + request.getArtifactId()
174 + " failed compatibility check");
175 versions.versions.remove(version);
176 version = null;
177 searchPerformed = true;
178 }
179
180 if (version == null) {
181 VersionScheme versionScheme = new GenericVersionScheme();
182
183 TreeSet<Version> releases = new TreeSet<>(Collections.reverseOrder());
184 TreeSet<Version> snapshots = new TreeSet<>(Collections.reverseOrder());
185
186 for (String ver : versions.versions.keySet()) {
187 try {
188 Version v = versionScheme.parseVersion(ver);
189
190 if (ver.endsWith("-SNAPSHOT")) {
191 snapshots.add(v);
192 } else {
193 releases.add(v);
194 }
195 } catch (InvalidVersionSpecificationException e) {
196
197 }
198 }
199
200 if (!releases.isEmpty()) {
201 logger.info("Looking for compatible RELEASE version of plugin "
202 + request.getGroupId()
203 + ":"
204 + request.getArtifactId());
205 for (Version v : releases) {
206 String ver = v.toString();
207 if (isCompatible(request, ver)) {
208 version = ver;
209 repo = versions.versions.get(version);
210 break;
211 }
212 }
213 }
214
215 if (version == null && !snapshots.isEmpty()) {
216 logger.info("Looking for compatible SNAPSHOT version of plugin "
217 + request.getGroupId()
218 + ":"
219 + request.getArtifactId());
220 for (Version v : snapshots) {
221 String ver = v.toString();
222 if (isCompatible(request, ver)) {
223 version = ver;
224 repo = versions.versions.get(version);
225 break;
226 }
227 }
228 }
229 }
230
231 if (version != null) {
232
233 if (searchPerformed) {
234 logger.info("Selected plugin " + request.getGroupId() + ":" + request.getArtifactId() + ":" + version);
235 }
236 result.setVersion(version);
237 result.setRepository(repo);
238 } else {
239 logger.warn((resolvedPluginVersions
240 ? "Could not find compatible version of plugin in any plugin repository: "
241 : "Plugin not found in any plugin repository: ")
242 + request.getGroupId()
243 + ":"
244 + request.getArtifactId());
245 throw new PluginVersionResolutionException(
246 request.getGroupId(),
247 request.getArtifactId(),
248 request.getRepositorySession().getLocalRepository(),
249 request.getRepositories(),
250 resolvedPluginVersions
251 ? "Could not find compatible plugin version in any plugin repository"
252 : "Plugin not found in any plugin repository");
253 }
254 }
255
256 private boolean isCompatible(PluginVersionRequest request, String version) {
257 Plugin plugin = new Plugin();
258 plugin.setGroupId(request.getGroupId());
259 plugin.setArtifactId(request.getArtifactId());
260 plugin.setVersion(version);
261
262 PluginDescriptor pluginDescriptor;
263
264 try {
265 pluginDescriptor = pluginManager.getPluginDescriptor(
266 plugin, request.getRepositories(), request.getRepositorySession());
267 } catch (PluginResolutionException e) {
268 logger.debug("Ignoring unresolvable plugin version " + version, e);
269 return false;
270 } catch (Exception e) {
271
272 return true;
273 }
274
275 try {
276 pluginManager.checkRequiredMavenVersion(pluginDescriptor);
277 } catch (PluginIncompatibleException e) {
278 if (logger.isDebugEnabled()) {
279 logger.warn("Ignoring incompatible plugin version " + version, e);
280 } else {
281 logger.warn("Ignoring incompatible plugin version " + version + ": " + e.getMessage());
282 }
283 return false;
284 }
285
286 return true;
287 }
288
289 private void mergeMetadata(
290 RepositorySystemSession session,
291 RequestTrace trace,
292 Versions versions,
293 org.eclipse.aether.metadata.Metadata metadata,
294 ArtifactRepository repository) {
295 if (metadata != null && metadata.getFile() != null && metadata.getFile().isFile()) {
296 try {
297 Map<String, ?> options = Collections.singletonMap(MetadataReader.IS_STRICT, Boolean.FALSE);
298
299 Metadata repoMetadata = metadataReader.read(metadata.getFile(), options);
300
301 mergeMetadata(versions, repoMetadata, repository);
302 } catch (IOException e) {
303 invalidMetadata(session, trace, metadata, repository, e);
304 }
305 }
306 }
307
308 private void invalidMetadata(
309 RepositorySystemSession session,
310 RequestTrace trace,
311 org.eclipse.aether.metadata.Metadata metadata,
312 ArtifactRepository repository,
313 Exception exception) {
314 RepositoryListener listener = session.getRepositoryListener();
315 if (listener != null) {
316 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
317 event.setTrace(trace);
318 event.setMetadata(metadata);
319 event.setException(exception);
320 event.setRepository(repository);
321 listener.metadataInvalid(event.build());
322 }
323 }
324
325 private void mergeMetadata(Versions versions, Metadata source, ArtifactRepository repository) {
326 Versioning versioning = source.getVersioning();
327 if (versioning != null) {
328 String timestamp = StringUtils.clean(versioning.getLastUpdated());
329
330 if (StringUtils.isNotEmpty(versioning.getRelease()) && timestamp.compareTo(versions.releaseTimestamp) > 0) {
331 versions.releaseVersion = versioning.getRelease();
332 versions.releaseTimestamp = timestamp;
333 versions.releaseRepository = repository;
334 }
335
336 if (StringUtils.isNotEmpty(versioning.getLatest()) && timestamp.compareTo(versions.latestTimestamp) > 0) {
337 versions.latestVersion = versioning.getLatest();
338 versions.latestTimestamp = timestamp;
339 versions.latestRepository = repository;
340 }
341
342 for (String version : versioning.getVersions()) {
343 if (!versions.versions.containsKey(version)) {
344 versions.versions.put(version, repository);
345 }
346 }
347 }
348 }
349
350 private PluginVersionResult resolveFromProject(PluginVersionRequest request) {
351 PluginVersionResult result = null;
352
353 if (request.getPom() != null && request.getPom().getBuild() != null) {
354 Build build = request.getPom().getBuild();
355
356 result = resolveFromProject(request, build.getPlugins());
357
358 if (result == null && build.getPluginManagement() != null) {
359 result = resolveFromProject(request, build.getPluginManagement().getPlugins());
360 }
361 }
362
363 return result;
364 }
365
366 private PluginVersionResult resolveFromProject(PluginVersionRequest request, List<Plugin> plugins) {
367 for (Plugin plugin : plugins) {
368 if (request.getGroupId().equals(plugin.getGroupId())
369 && request.getArtifactId().equals(plugin.getArtifactId())) {
370 if (plugin.getVersion() != null) {
371 return new DefaultPluginVersionResult(plugin.getVersion());
372 } else {
373 return null;
374 }
375 }
376 }
377 return null;
378 }
379
380 @SuppressWarnings("unchecked")
381 private ConcurrentMap<Key, PluginVersionResult> getCache(PluginVersionRequest request) {
382 SessionData data = request.getRepositorySession().getData();
383 return (ConcurrentMap<Key, PluginVersionResult>)
384 data.computeIfAbsent(CACHE_KEY, () -> new ConcurrentHashMap<>(256));
385 }
386
387 private static Key getKey(PluginVersionRequest request) {
388 return new Key(request.getGroupId(), request.getArtifactId(), request.getRepositories());
389 }
390
391 static class Key {
392 final String groupId;
393 final String artifactId;
394 final List<RemoteRepository> repositories;
395 final int hash;
396
397 Key(String groupId, String artifactId, List<RemoteRepository> repositories) {
398 this.groupId = groupId;
399 this.artifactId = artifactId;
400 this.repositories = repositories;
401 this.hash = Objects.hash(groupId, artifactId, repositories);
402 }
403
404 @Override
405 public boolean equals(Object o) {
406 if (this == o) {
407 return true;
408 }
409 if (o == null || getClass() != o.getClass()) {
410 return false;
411 }
412 Key key = (Key) o;
413 return groupId.equals(key.groupId)
414 && artifactId.equals(key.artifactId)
415 && repositories.equals(key.repositories);
416 }
417
418 @Override
419 public int hashCode() {
420 return hash;
421 }
422 }
423
424 static class Versions {
425
426 String releaseVersion = "";
427
428 String releaseTimestamp = "";
429
430 ArtifactRepository releaseRepository;
431
432 String latestVersion = "";
433
434 String latestTimestamp = "";
435
436 ArtifactRepository latestRepository;
437
438 Map<String, ArtifactRepository> versions = new LinkedHashMap<>();
439 }
440 }