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