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