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.PluginResolutionException;
39 import org.apache.maven.plugin.descriptor.PluginDescriptor;
40 import org.apache.maven.plugin.version.PluginVersionRequest;
41 import org.apache.maven.plugin.version.PluginVersionResolutionException;
42 import org.apache.maven.plugin.version.PluginVersionResolver;
43 import org.apache.maven.plugin.version.PluginVersionResult;
44 import org.codehaus.plexus.component.annotations.Component;
45 import org.codehaus.plexus.component.annotations.Requirement;
46 import org.codehaus.plexus.logging.Logger;
47 import org.codehaus.plexus.util.StringUtils;
48 import org.eclipse.aether.RepositoryEvent;
49 import org.eclipse.aether.RepositoryEvent.EventType;
50 import org.eclipse.aether.RepositoryListener;
51 import org.eclipse.aether.RepositorySystem;
52 import org.eclipse.aether.RepositorySystemSession;
53 import org.eclipse.aether.RequestTrace;
54 import org.eclipse.aether.SessionData;
55 import org.eclipse.aether.metadata.DefaultMetadata;
56 import org.eclipse.aether.repository.ArtifactRepository;
57 import org.eclipse.aether.repository.RemoteRepository;
58 import org.eclipse.aether.resolution.MetadataRequest;
59 import org.eclipse.aether.resolution.MetadataResult;
60 import org.eclipse.aether.util.version.GenericVersionScheme;
61 import org.eclipse.aether.version.InvalidVersionSpecificationException;
62 import org.eclipse.aether.version.Version;
63 import org.eclipse.aether.version.VersionScheme;
64
65
66
67
68
69
70
71 @Component(role = PluginVersionResolver.class)
72 public class DefaultPluginVersionResolver implements PluginVersionResolver {
73
74 private static final String REPOSITORY_CONTEXT = "plugin";
75
76 private static final Object CACHE_KEY = new Object();
77
78 @Requirement
79 private Logger logger;
80
81 @Requirement
82 private RepositorySystem repositorySystem;
83
84 @Requirement
85 private MetadataReader metadataReader;
86
87 @Requirement
88 private MavenPluginManager pluginManager;
89
90 public PluginVersionResult resolve(PluginVersionRequest request) throws PluginVersionResolutionException {
91 PluginVersionResult result = resolveFromProject(request);
92
93 if (result == null) {
94 ConcurrentMap<Key, PluginVersionResult> cache =
95 getCache(request.getRepositorySession().getData());
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
163 if (StringUtils.isNotEmpty(versions.releaseVersion)) {
164 version = versions.releaseVersion;
165 repo = versions.releaseRepository;
166 } else if (StringUtils.isNotEmpty(versions.latestVersion)) {
167 version = versions.latestVersion;
168 repo = versions.latestRepository;
169 }
170 if (version != null && !isCompatible(request, version)) {
171 versions.versions.remove(version);
172 version = null;
173 }
174
175 if (version == null) {
176 VersionScheme versionScheme = new GenericVersionScheme();
177
178 TreeSet<Version> releases = new TreeSet<>(Collections.reverseOrder());
179 TreeSet<Version> snapshots = new TreeSet<>(Collections.reverseOrder());
180
181 for (String ver : versions.versions.keySet()) {
182 try {
183 Version v = versionScheme.parseVersion(ver);
184
185 if (ver.endsWith("-SNAPSHOT")) {
186 snapshots.add(v);
187 } else {
188 releases.add(v);
189 }
190 } catch (InvalidVersionSpecificationException e) {
191
192 }
193 }
194
195 for (Version v : releases) {
196 String ver = v.toString();
197 if (isCompatible(request, ver)) {
198 version = ver;
199 repo = versions.versions.get(version);
200 break;
201 }
202 }
203
204 if (version == null) {
205 for (Version v : snapshots) {
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
216 if (version != null) {
217 result.setVersion(version);
218 result.setRepository(repo);
219 } else {
220 throw new PluginVersionResolutionException(
221 request.getGroupId(),
222 request.getArtifactId(),
223 request.getRepositorySession().getLocalRepository(),
224 request.getRepositories(),
225 "Plugin not found in any plugin repository");
226 }
227 }
228
229 private boolean isCompatible(PluginVersionRequest request, String version) {
230 Plugin plugin = new Plugin();
231 plugin.setGroupId(request.getGroupId());
232 plugin.setArtifactId(request.getArtifactId());
233 plugin.setVersion(version);
234
235 PluginDescriptor pluginDescriptor;
236
237 try {
238 pluginDescriptor = pluginManager.getPluginDescriptor(
239 plugin, request.getRepositories(), request.getRepositorySession());
240 } catch (PluginResolutionException e) {
241 logger.debug("Ignoring unresolvable plugin version " + version, e);
242 return false;
243 } catch (Exception e) {
244
245 return true;
246 }
247
248 try {
249 pluginManager.checkRequiredMavenVersion(pluginDescriptor);
250 } catch (Exception e) {
251 logger.debug("Ignoring incompatible plugin version " + version + ": " + e.getMessage());
252 return false;
253 }
254
255 return true;
256 }
257
258 private void mergeMetadata(
259 RepositorySystemSession session,
260 RequestTrace trace,
261 Versions versions,
262 org.eclipse.aether.metadata.Metadata metadata,
263 ArtifactRepository repository) {
264 if (metadata != null && metadata.getFile() != null && metadata.getFile().isFile()) {
265 try {
266 Map<String, ?> options = Collections.singletonMap(MetadataReader.IS_STRICT, Boolean.FALSE);
267
268 Metadata repoMetadata = metadataReader.read(metadata.getFile(), options);
269
270 mergeMetadata(versions, repoMetadata, repository);
271 } catch (IOException e) {
272 invalidMetadata(session, trace, metadata, repository, e);
273 }
274 }
275 }
276
277 private void invalidMetadata(
278 RepositorySystemSession session,
279 RequestTrace trace,
280 org.eclipse.aether.metadata.Metadata metadata,
281 ArtifactRepository repository,
282 Exception exception) {
283 RepositoryListener listener = session.getRepositoryListener();
284 if (listener != null) {
285 RepositoryEvent.Builder event = new RepositoryEvent.Builder(session, EventType.METADATA_INVALID);
286 event.setTrace(trace);
287 event.setMetadata(metadata);
288 event.setException(exception);
289 event.setRepository(repository);
290 listener.metadataInvalid(event.build());
291 }
292 }
293
294 private void mergeMetadata(Versions versions, Metadata source, ArtifactRepository repository) {
295 Versioning versioning = source.getVersioning();
296 if (versioning != null) {
297 String timestamp = StringUtils.clean(versioning.getLastUpdated());
298
299 if (StringUtils.isNotEmpty(versioning.getRelease()) && timestamp.compareTo(versions.releaseTimestamp) > 0) {
300 versions.releaseVersion = versioning.getRelease();
301 versions.releaseTimestamp = timestamp;
302 versions.releaseRepository = repository;
303 }
304
305 if (StringUtils.isNotEmpty(versioning.getLatest()) && timestamp.compareTo(versions.latestTimestamp) > 0) {
306 versions.latestVersion = versioning.getLatest();
307 versions.latestTimestamp = timestamp;
308 versions.latestRepository = repository;
309 }
310
311 for (String version : versioning.getVersions()) {
312 if (!versions.versions.containsKey(version)) {
313 versions.versions.put(version, repository);
314 }
315 }
316 }
317 }
318
319 private PluginVersionResult resolveFromProject(PluginVersionRequest request) {
320 PluginVersionResult result = null;
321
322 if (request.getPom() != null && request.getPom().getBuild() != null) {
323 Build build = request.getPom().getBuild();
324
325 result = resolveFromProject(request, build.getPlugins());
326
327 if (result == null && build.getPluginManagement() != null) {
328 result = resolveFromProject(request, build.getPluginManagement().getPlugins());
329 }
330 }
331
332 return result;
333 }
334
335 private PluginVersionResult resolveFromProject(PluginVersionRequest request, List<Plugin> plugins) {
336 for (Plugin plugin : plugins) {
337 if (request.getGroupId().equals(plugin.getGroupId())
338 && request.getArtifactId().equals(plugin.getArtifactId())) {
339 if (plugin.getVersion() != null) {
340 return new DefaultPluginVersionResult(plugin.getVersion());
341 } else {
342 return null;
343 }
344 }
345 }
346 return null;
347 }
348
349 @SuppressWarnings("unchecked")
350 private ConcurrentMap<Key, PluginVersionResult> getCache(SessionData data) {
351 ConcurrentMap<Key, PluginVersionResult> cache = (ConcurrentMap<Key, PluginVersionResult>) data.get(CACHE_KEY);
352 while (cache == null) {
353 cache = new ConcurrentHashMap<>(256);
354 if (data.set(CACHE_KEY, null, cache)) {
355 break;
356 }
357 cache = (ConcurrentMap<Key, PluginVersionResult>) data.get(CACHE_KEY);
358 }
359 return cache;
360 }
361
362 private static Key getKey(PluginVersionRequest request) {
363 return new Key(request.getGroupId(), request.getArtifactId(), request.getRepositories());
364 }
365
366 static class Key {
367 final String groupId;
368 final String artifactId;
369 final List<RemoteRepository> repositories;
370 final int hash;
371
372 Key(String groupId, String artifactId, List<RemoteRepository> repositories) {
373 this.groupId = groupId;
374 this.artifactId = artifactId;
375 this.repositories = repositories;
376 this.hash = Objects.hash(groupId, artifactId, repositories);
377 }
378
379 @Override
380 public boolean equals(Object o) {
381 if (this == o) {
382 return true;
383 }
384 if (o == null || getClass() != o.getClass()) {
385 return false;
386 }
387 Key key = (Key) o;
388 return groupId.equals(key.groupId)
389 && artifactId.equals(key.artifactId)
390 && repositories.equals(key.repositories);
391 }
392
393 @Override
394 public int hashCode() {
395 return hash;
396 }
397 }
398
399 static class Versions {
400
401 String releaseVersion = "";
402
403 String releaseTimestamp = "";
404
405 ArtifactRepository releaseRepository;
406
407 String latestVersion = "";
408
409 String latestTimestamp = "";
410
411 ArtifactRepository latestRepository;
412
413 Map<String, ArtifactRepository> versions = new LinkedHashMap<>();
414 }
415 }