001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl.collect; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashSet; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Map; 028 029import org.eclipse.aether.DefaultRepositorySystemSession; 030import org.eclipse.aether.RepositoryException; 031import org.eclipse.aether.RepositorySystemSession; 032import org.eclipse.aether.RequestTrace; 033import org.eclipse.aether.artifact.Artifact; 034import org.eclipse.aether.artifact.ArtifactProperties; 035import org.eclipse.aether.collection.CollectRequest; 036import org.eclipse.aether.collection.CollectResult; 037import org.eclipse.aether.collection.DependencyCollectionException; 038import org.eclipse.aether.collection.DependencyGraphTransformer; 039import org.eclipse.aether.collection.DependencyTraverser; 040import org.eclipse.aether.collection.VersionFilter; 041import org.eclipse.aether.graph.DefaultDependencyNode; 042import org.eclipse.aether.graph.Dependency; 043import org.eclipse.aether.graph.DependencyNode; 044import org.eclipse.aether.impl.ArtifactDescriptorReader; 045import org.eclipse.aether.impl.DependencyCollector; 046import org.eclipse.aether.impl.RemoteRepositoryManager; 047import org.eclipse.aether.impl.VersionRangeResolver; 048import org.eclipse.aether.repository.ArtifactRepository; 049import org.eclipse.aether.repository.RemoteRepository; 050import org.eclipse.aether.resolution.ArtifactDescriptorException; 051import org.eclipse.aether.resolution.ArtifactDescriptorRequest; 052import org.eclipse.aether.resolution.ArtifactDescriptorResult; 053import org.eclipse.aether.resolution.VersionRangeRequest; 054import org.eclipse.aether.resolution.VersionRangeResolutionException; 055import org.eclipse.aether.resolution.VersionRangeResult; 056import org.eclipse.aether.spi.locator.ServiceLocator; 057import org.eclipse.aether.util.ConfigUtils; 058import org.eclipse.aether.util.graph.transformer.TransformationContextKeys; 059import org.eclipse.aether.version.Version; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import static java.util.Objects.requireNonNull; 064 065/** 066 * Helper class for delegate implementations, they MUST subclass this class. 067 * 068 * @since 1.8.0 069 */ 070public abstract class DependencyCollectorDelegate implements DependencyCollector { 071 protected static final String CONFIG_PROP_MAX_EXCEPTIONS = "aether.dependencyCollector.maxExceptions"; 072 073 protected static final int CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT = 50; 074 075 protected static final String CONFIG_PROP_MAX_CYCLES = "aether.dependencyCollector.maxCycles"; 076 077 protected static final int CONFIG_PROP_MAX_CYCLES_DEFAULT = 10; 078 079 protected final Logger logger = LoggerFactory.getLogger(getClass()); 080 081 protected RemoteRepositoryManager remoteRepositoryManager; 082 083 protected ArtifactDescriptorReader descriptorReader; 084 085 protected VersionRangeResolver versionRangeResolver; 086 087 /** 088 * Default ctor for SL. 089 * 090 * @deprecated Will be dropped once SL gone. 091 */ 092 @Deprecated 093 protected DependencyCollectorDelegate() { 094 // enables default constructor 095 } 096 097 protected DependencyCollectorDelegate( 098 RemoteRepositoryManager remoteRepositoryManager, 099 ArtifactDescriptorReader artifactDescriptorReader, 100 VersionRangeResolver versionRangeResolver) { 101 setRemoteRepositoryManager(remoteRepositoryManager); 102 setArtifactDescriptorReader(artifactDescriptorReader); 103 setVersionRangeResolver(versionRangeResolver); 104 } 105 106 public void initService(ServiceLocator locator) { 107 setRemoteRepositoryManager(locator.getService(RemoteRepositoryManager.class)); 108 setArtifactDescriptorReader(locator.getService(ArtifactDescriptorReader.class)); 109 setVersionRangeResolver(locator.getService(VersionRangeResolver.class)); 110 } 111 112 public DependencyCollector setRemoteRepositoryManager(RemoteRepositoryManager remoteRepositoryManager) { 113 this.remoteRepositoryManager = 114 requireNonNull(remoteRepositoryManager, "remote repository manager cannot be null"); 115 return this; 116 } 117 118 public DependencyCollector setArtifactDescriptorReader(ArtifactDescriptorReader artifactDescriptorReader) { 119 descriptorReader = requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null"); 120 return this; 121 } 122 123 public DependencyCollector setVersionRangeResolver(VersionRangeResolver versionRangeResolver) { 124 this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null"); 125 return this; 126 } 127 128 @SuppressWarnings("checkstyle:methodlength") 129 @Override 130 public final CollectResult collectDependencies(RepositorySystemSession session, CollectRequest request) 131 throws DependencyCollectionException { 132 requireNonNull(session, "session cannot be null"); 133 requireNonNull(request, "request cannot be null"); 134 session = optimizeSession(session); 135 136 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 137 138 CollectResult result = new CollectResult(request); 139 140 DependencyTraverser depTraverser = session.getDependencyTraverser(); 141 VersionFilter verFilter = session.getVersionFilter(); 142 143 Dependency root = request.getRoot(); 144 List<RemoteRepository> repositories = request.getRepositories(); 145 List<Dependency> dependencies = request.getDependencies(); 146 List<Dependency> managedDependencies = request.getManagedDependencies(); 147 148 Map<String, Object> stats = new LinkedHashMap<>(); 149 long time1 = System.nanoTime(); 150 151 DefaultDependencyNode node; 152 if (root != null) { 153 List<? extends Version> versions; 154 VersionRangeResult rangeResult; 155 try { 156 VersionRangeRequest rangeRequest = new VersionRangeRequest( 157 root.getArtifact(), request.getRepositories(), request.getRequestContext()); 158 rangeRequest.setTrace(trace); 159 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest); 160 versions = filterVersions(root, rangeResult, verFilter, new DefaultVersionFilterContext(session)); 161 } catch (VersionRangeResolutionException e) { 162 result.addException(e); 163 throw new DependencyCollectionException(result, e.getMessage()); 164 } 165 166 Version version = versions.get(versions.size() - 1); 167 root = root.setArtifact(root.getArtifact().setVersion(version.toString())); 168 169 ArtifactDescriptorResult descriptorResult; 170 try { 171 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 172 descriptorRequest.setArtifact(root.getArtifact()); 173 descriptorRequest.setRepositories(request.getRepositories()); 174 descriptorRequest.setRequestContext(request.getRequestContext()); 175 descriptorRequest.setTrace(trace); 176 if (isLackingDescriptor(root.getArtifact())) { 177 descriptorResult = new ArtifactDescriptorResult(descriptorRequest); 178 } else { 179 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest); 180 } 181 } catch (ArtifactDescriptorException e) { 182 result.addException(e); 183 throw new DependencyCollectionException(result, e.getMessage()); 184 } 185 186 root = root.setArtifact(descriptorResult.getArtifact()); 187 188 if (!session.isIgnoreArtifactDescriptorRepositories()) { 189 repositories = remoteRepositoryManager.aggregateRepositories( 190 session, repositories, descriptorResult.getRepositories(), true); 191 } 192 dependencies = mergeDeps(dependencies, descriptorResult.getDependencies()); 193 managedDependencies = mergeDeps(managedDependencies, descriptorResult.getManagedDependencies()); 194 195 node = new DefaultDependencyNode(root); 196 node.setRequestContext(request.getRequestContext()); 197 node.setRelocations(descriptorResult.getRelocations()); 198 node.setVersionConstraint(rangeResult.getVersionConstraint()); 199 node.setVersion(version); 200 node.setAliases(descriptorResult.getAliases()); 201 node.setRepositories(request.getRepositories()); 202 } else { 203 node = new DefaultDependencyNode(request.getRootArtifact()); 204 node.setRequestContext(request.getRequestContext()); 205 node.setRepositories(request.getRepositories()); 206 } 207 208 result.setRoot(node); 209 210 boolean traverse = root == null || depTraverser == null || depTraverser.traverseDependency(root); 211 String errorPath = null; 212 if (traverse && !dependencies.isEmpty()) { 213 DataPool pool = new DataPool(session); 214 215 DefaultDependencyCollectionContext context = new DefaultDependencyCollectionContext( 216 session, request.getRootArtifact(), root, managedDependencies); 217 218 DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext(session); 219 220 Results results = new Results(result, session); 221 222 doCollectDependencies( 223 session, 224 trace, 225 pool, 226 context, 227 versionContext, 228 request, 229 node, 230 repositories, 231 dependencies, 232 managedDependencies, 233 results); 234 235 errorPath = results.getErrorPath(); 236 } 237 238 long time2 = System.nanoTime(); 239 240 DependencyGraphTransformer transformer = session.getDependencyGraphTransformer(); 241 if (transformer != null) { 242 try { 243 DefaultDependencyGraphTransformationContext context = 244 new DefaultDependencyGraphTransformationContext(session); 245 context.put(TransformationContextKeys.STATS, stats); 246 result.setRoot(transformer.transformGraph(node, context)); 247 } catch (RepositoryException e) { 248 result.addException(e); 249 } 250 } 251 252 long time3 = System.nanoTime(); 253 if (logger.isDebugEnabled()) { 254 stats.put(getClass().getSimpleName() + ".collectTime", time2 - time1); 255 stats.put(getClass().getSimpleName() + ".transformTime", time3 - time2); 256 logger.debug("Dependency collection stats {}", stats); 257 } 258 259 if (errorPath != null) { 260 throw new DependencyCollectionException(result, "Failed to collect dependencies at " + errorPath); 261 } 262 if (!result.getExceptions().isEmpty()) { 263 throw new DependencyCollectionException(result); 264 } 265 266 return result; 267 } 268 269 /** 270 * Creates child {@link RequestTrace} instance from passed in {@link RequestTrace} and parameters by creating 271 * {@link CollectStepDataImpl} instance out of passed in data. Caller must ensure that passed in parameters are 272 * NOT affected by threading (or that there is no multi threading involved). In other words, the passed in values 273 * should be immutable. 274 * 275 * @param trace The current trace instance. 276 * @param context The context from {@link CollectRequest#getRequestContext()}, never {@code null}. 277 * @param path List representing the path of dependency nodes, never {@code null}. Caller must ensure, that this 278 * list does not change during the lifetime of the requested {@link RequestTrace} instance. If it may 279 * change, simplest is to pass here a copy of used list. 280 * @param node Currently collected node, that collector came by following the passed in path. 281 * @return A child request trance instance, never {@code null}. 282 */ 283 protected RequestTrace collectStepTrace( 284 RequestTrace trace, String context, List<DependencyNode> path, Dependency node) { 285 return RequestTrace.newChild(trace, new CollectStepDataImpl(context, path, node)); 286 } 287 288 @SuppressWarnings("checkstyle:parameternumber") 289 protected abstract void doCollectDependencies( 290 RepositorySystemSession session, 291 RequestTrace trace, 292 DataPool pool, 293 DefaultDependencyCollectionContext context, 294 DefaultVersionFilterContext versionContext, 295 CollectRequest request, 296 DependencyNode node, 297 List<RemoteRepository> repositories, 298 List<Dependency> dependencies, 299 List<Dependency> managedDependencies, 300 Results results); 301 302 protected RepositorySystemSession optimizeSession(RepositorySystemSession session) { 303 DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession(session); 304 optimized.setArtifactTypeRegistry(CachingArtifactTypeRegistry.newInstance(session)); 305 return optimized; 306 } 307 308 protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) { 309 List<Dependency> result; 310 if (dominant == null || dominant.isEmpty()) { 311 result = recessive; 312 } else if (recessive == null || recessive.isEmpty()) { 313 result = dominant; 314 } else { 315 int initialCapacity = dominant.size() + recessive.size(); 316 result = new ArrayList<>(initialCapacity); 317 Collection<String> ids = new HashSet<>(initialCapacity, 1.0f); 318 for (Dependency dependency : dominant) { 319 ids.add(getId(dependency.getArtifact())); 320 result.add(dependency); 321 } 322 for (Dependency dependency : recessive) { 323 if (!ids.contains(getId(dependency.getArtifact()))) { 324 result.add(dependency); 325 } 326 } 327 } 328 return result; 329 } 330 331 protected static String getId(Artifact a) { 332 return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); 333 } 334 335 @SuppressWarnings("checkstyle:parameternumber") 336 protected static DefaultDependencyNode createDependencyNode( 337 List<Artifact> relocations, 338 PremanagedDependency preManaged, 339 VersionRangeResult rangeResult, 340 Version version, 341 Dependency d, 342 Collection<Artifact> aliases, 343 List<RemoteRepository> repos, 344 String requestContext) { 345 DefaultDependencyNode child = new DefaultDependencyNode(d); 346 preManaged.applyTo(child); 347 child.setRelocations(relocations); 348 child.setVersionConstraint(rangeResult.getVersionConstraint()); 349 child.setVersion(version); 350 child.setAliases(aliases); 351 child.setRepositories(repos); 352 child.setRequestContext(requestContext); 353 return child; 354 } 355 356 protected static DefaultDependencyNode createDependencyNode( 357 List<Artifact> relocations, 358 PremanagedDependency preManaged, 359 VersionRangeResult rangeResult, 360 Version version, 361 Dependency d, 362 ArtifactDescriptorResult descriptorResult, 363 DependencyNode cycleNode) { 364 DefaultDependencyNode child = createDependencyNode( 365 relocations, 366 preManaged, 367 rangeResult, 368 version, 369 d, 370 descriptorResult.getAliases(), 371 cycleNode.getRepositories(), 372 cycleNode.getRequestContext()); 373 child.setChildren(cycleNode.getChildren()); 374 return child; 375 } 376 377 protected static ArtifactDescriptorRequest createArtifactDescriptorRequest( 378 String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) { 379 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 380 descriptorRequest.setArtifact(d.getArtifact()); 381 descriptorRequest.setRepositories(repositories); 382 descriptorRequest.setRequestContext(requestContext); 383 descriptorRequest.setTrace(requestTrace); 384 return descriptorRequest; 385 } 386 387 protected static VersionRangeRequest createVersionRangeRequest( 388 String requestContext, 389 RequestTrace requestTrace, 390 List<RemoteRepository> repositories, 391 Dependency dependency) { 392 VersionRangeRequest rangeRequest = new VersionRangeRequest(); 393 rangeRequest.setArtifact(dependency.getArtifact()); 394 rangeRequest.setRepositories(repositories); 395 rangeRequest.setRequestContext(requestContext); 396 rangeRequest.setTrace(requestTrace); 397 return rangeRequest; 398 } 399 400 protected VersionRangeResult cachedResolveRangeResult( 401 VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session) 402 throws VersionRangeResolutionException { 403 Object key = pool.toKey(rangeRequest); 404 VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest); 405 if (rangeResult == null) { 406 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest); 407 pool.putConstraint(key, rangeResult); 408 } 409 return rangeResult; 410 } 411 412 protected static boolean isLackingDescriptor(Artifact artifact) { 413 return artifact.getProperty(ArtifactProperties.LOCAL_PATH, null) != null; 414 } 415 416 protected static List<RemoteRepository> getRemoteRepositories( 417 ArtifactRepository repository, List<RemoteRepository> repositories) { 418 if (repository instanceof RemoteRepository) { 419 return Collections.singletonList((RemoteRepository) repository); 420 } 421 if (repository != null) { 422 return Collections.emptyList(); 423 } 424 return repositories; 425 } 426 427 protected static List<? extends Version> filterVersions( 428 Dependency dependency, 429 VersionRangeResult rangeResult, 430 VersionFilter verFilter, 431 DefaultVersionFilterContext verContext) 432 throws VersionRangeResolutionException { 433 if (rangeResult.getVersions().isEmpty()) { 434 throw new VersionRangeResolutionException( 435 rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range"); 436 } 437 438 List<? extends Version> versions; 439 if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) { 440 verContext.set(dependency, rangeResult); 441 try { 442 verFilter.filterVersions(verContext); 443 } catch (RepositoryException e) { 444 throw new VersionRangeResolutionException( 445 rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e); 446 } 447 versions = verContext.get(); 448 if (versions.isEmpty()) { 449 throw new VersionRangeResolutionException( 450 rangeResult, 451 "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions()); 452 } 453 } else { 454 versions = rangeResult.getVersions(); 455 } 456 return versions; 457 } 458 459 /** 460 * Helper class used during collection. 461 */ 462 protected static class Results { 463 464 private final CollectResult result; 465 466 final int maxExceptions; 467 468 final int maxCycles; 469 470 String errorPath; 471 472 public Results(CollectResult result, RepositorySystemSession session) { 473 this.result = result; 474 475 maxExceptions = 476 ConfigUtils.getInteger(session, CONFIG_PROP_MAX_EXCEPTIONS_DEFAULT, CONFIG_PROP_MAX_EXCEPTIONS); 477 478 maxCycles = ConfigUtils.getInteger(session, CONFIG_PROP_MAX_CYCLES_DEFAULT, CONFIG_PROP_MAX_CYCLES); 479 } 480 481 public String getErrorPath() { 482 return errorPath; 483 } 484 485 public void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) { 486 if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) { 487 result.addException(e); 488 if (errorPath == null) { 489 StringBuilder buffer = new StringBuilder(256); 490 for (DependencyNode node : nodes) { 491 if (buffer.length() > 0) { 492 buffer.append(" -> "); 493 } 494 Dependency dep = node.getDependency(); 495 if (dep != null) { 496 buffer.append(dep.getArtifact()); 497 } 498 } 499 if (buffer.length() > 0) { 500 buffer.append(" -> "); 501 } 502 buffer.append(dependency.getArtifact()); 503 errorPath = buffer.toString(); 504 } 505 } 506 } 507 508 public void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) { 509 if (maxCycles < 0 || result.getCycles().size() < maxCycles) { 510 result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency)); 511 } 512 } 513 } 514}