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