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