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