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.collection.CollectRequest; 035import org.eclipse.aether.collection.CollectResult; 036import org.eclipse.aether.collection.DependencyCollectionException; 037import org.eclipse.aether.collection.DependencyGraphTransformer; 038import org.eclipse.aether.collection.DependencyTraverser; 039import org.eclipse.aether.collection.VersionFilter; 040import org.eclipse.aether.graph.DefaultDependencyNode; 041import org.eclipse.aether.graph.Dependency; 042import org.eclipse.aether.graph.DependencyNode; 043import org.eclipse.aether.impl.ArtifactDescriptorReader; 044import org.eclipse.aether.impl.DependencyCollector; 045import org.eclipse.aether.impl.RemoteRepositoryManager; 046import org.eclipse.aether.impl.VersionRangeResolver; 047import org.eclipse.aether.impl.scope.InternalScopeManager; 048import org.eclipse.aether.internal.impl.Utils; 049import org.eclipse.aether.repository.ArtifactRepository; 050import org.eclipse.aether.repository.RemoteRepository; 051import org.eclipse.aether.resolution.ArtifactDescriptorException; 052import org.eclipse.aether.resolution.ArtifactDescriptorRequest; 053import org.eclipse.aether.resolution.ArtifactDescriptorResult; 054import org.eclipse.aether.resolution.VersionRangeRequest; 055import org.eclipse.aether.resolution.VersionRangeResolutionException; 056import org.eclipse.aether.resolution.VersionRangeResult; 057import org.eclipse.aether.scope.ResolutionScope; 058import org.eclipse.aether.scope.SystemDependencyScope; 059import org.eclipse.aether.spi.artifact.decorator.ArtifactDecorator; 060import org.eclipse.aether.spi.artifact.decorator.ArtifactDecoratorFactory; 061import org.eclipse.aether.util.ConfigUtils; 062import org.eclipse.aether.util.graph.transformer.TransformationContextKeys; 063import org.eclipse.aether.version.Version; 064import org.slf4j.Logger; 065import org.slf4j.LoggerFactory; 066 067import static java.util.Objects.requireNonNull; 068 069/** 070 * Helper class for delegate implementations, they MUST subclass this class. 071 * 072 * @since 1.8.0 073 */ 074public abstract class DependencyCollectorDelegate implements DependencyCollector { 075 /** 076 * Only exceptions up to the number given in this configuration property are emitted. Exceptions which exceed 077 * that number are swallowed. 078 * 079 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 080 * @configurationType {@link java.lang.Integer} 081 * @configurationDefaultValue {@link #DEFAULT_MAX_EXCEPTIONS} 082 */ 083 public static final String CONFIG_PROP_MAX_EXCEPTIONS = 084 DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxExceptions"; 085 086 public static final int DEFAULT_MAX_EXCEPTIONS = 50; 087 088 /** 089 * Only up to the given amount cyclic dependencies are emitted. 090 * 091 * @configurationSource {@link RepositorySystemSession#getConfigProperties()} 092 * @configurationType {@link java.lang.Integer} 093 * @configurationDefaultValue {@link #DEFAULT_MAX_CYCLES} 094 */ 095 public static final String CONFIG_PROP_MAX_CYCLES = DefaultDependencyCollector.CONFIG_PROPS_PREFIX + "maxCycles"; 096 097 public static final int DEFAULT_MAX_CYCLES = 10; 098 099 protected final Logger logger = LoggerFactory.getLogger(getClass()); 100 101 protected final RemoteRepositoryManager remoteRepositoryManager; 102 103 protected final ArtifactDescriptorReader descriptorReader; 104 105 protected final VersionRangeResolver versionRangeResolver; 106 107 protected final Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories; 108 109 protected DependencyCollectorDelegate( 110 RemoteRepositoryManager remoteRepositoryManager, 111 ArtifactDescriptorReader artifactDescriptorReader, 112 VersionRangeResolver versionRangeResolver, 113 Map<String, ArtifactDecoratorFactory> artifactDecoratorFactories) { 114 this.remoteRepositoryManager = 115 requireNonNull(remoteRepositoryManager, "remote repository manager cannot be null"); 116 this.descriptorReader = requireNonNull(artifactDescriptorReader, "artifact descriptor reader cannot be null"); 117 this.versionRangeResolver = requireNonNull(versionRangeResolver, "version range resolver cannot be null"); 118 this.artifactDecoratorFactories = 119 requireNonNull(artifactDecoratorFactories, "artifact decorator factories cannot be null"); 120 } 121 122 @SuppressWarnings("checkstyle:methodlength") 123 @Override 124 public final CollectResult collectDependencies(RepositorySystemSession session, CollectRequest request) 125 throws DependencyCollectionException { 126 requireNonNull(session, "session cannot be null"); 127 requireNonNull(request, "request cannot be null"); 128 129 InternalScopeManager scopeManager = (InternalScopeManager) session.getScopeManager(); 130 session = setUpSession(session, request, scopeManager); 131 132 RequestTrace trace = RequestTrace.newChild(request.getTrace(), request); 133 134 CollectResult result = new CollectResult(request); 135 136 DependencyTraverser depTraverser = session.getDependencyTraverser(); 137 VersionFilter verFilter = session.getVersionFilter(); 138 139 Dependency root = request.getRoot(); 140 List<RemoteRepository> repositories = request.getRepositories(); 141 List<Dependency> dependencies = request.getDependencies(); 142 List<Dependency> managedDependencies = request.getManagedDependencies(); 143 144 Map<String, Object> stats = new LinkedHashMap<>(); 145 long time1 = System.nanoTime(); 146 147 DefaultDependencyNode node; 148 if (root != null) { 149 List<? extends Version> versions; 150 VersionRangeResult rangeResult; 151 try { 152 VersionRangeRequest rangeRequest = new VersionRangeRequest( 153 root.getArtifact(), request.getRepositories(), request.getRequestContext()); 154 rangeRequest.setTrace(trace); 155 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest); 156 versions = filterVersions(root, rangeResult, verFilter, new DefaultVersionFilterContext(session)); 157 } catch (VersionRangeResolutionException e) { 158 result.addException(e); 159 throw new DependencyCollectionException(result, e.getMessage()); 160 } 161 162 Version version = versions.get(versions.size() - 1); 163 root = root.setArtifact(root.getArtifact().setVersion(version.toString())); 164 165 ArtifactDescriptorResult descriptorResult; 166 try { 167 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 168 descriptorRequest.setArtifact(root.getArtifact()); 169 descriptorRequest.setRepositories(request.getRepositories()); 170 descriptorRequest.setRequestContext(request.getRequestContext()); 171 descriptorRequest.setTrace(trace); 172 if (isLackingDescriptor(session, root.getArtifact())) { 173 descriptorResult = new ArtifactDescriptorResult(descriptorRequest); 174 } else { 175 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest); 176 for (ArtifactDecorator decorator : 177 Utils.getArtifactDecorators(session, artifactDecoratorFactories)) { 178 descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult)); 179 } 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 if (request.getResolutionScope() != null) { 267 return scopeManager.postProcess(session, request.getResolutionScope(), result); 268 } else { 269 return result; 270 } 271 } 272 273 /** 274 * Creates child {@link RequestTrace} instance from passed in {@link RequestTrace} and parameters by creating 275 * {@link CollectStepDataImpl} instance out of passed in data. Caller must ensure that passed in parameters are 276 * NOT affected by threading (or that there is no multi threading involved). In other words, the passed in values 277 * should be immutable. 278 * 279 * @param trace The current trace instance. 280 * @param context The context from {@link CollectRequest#getRequestContext()}, never {@code null}. 281 * @param path List representing the path of dependency nodes, never {@code null}. Caller must ensure, that this 282 * list does not change during the lifetime of the requested {@link RequestTrace} instance. If it may 283 * change, simplest is to pass here a copy of used list. 284 * @param node Currently collected node, that collector came by following the passed in path. 285 * @return A child request trance instance, never {@code null}. 286 */ 287 protected RequestTrace collectStepTrace( 288 RequestTrace trace, String context, List<DependencyNode> path, Dependency node) { 289 return RequestTrace.newChild(trace, new CollectStepDataImpl(context, path, node)); 290 } 291 292 @SuppressWarnings("checkstyle:parameternumber") 293 protected abstract void doCollectDependencies( 294 RepositorySystemSession session, 295 RequestTrace trace, 296 DataPool pool, 297 DefaultDependencyCollectionContext context, 298 DefaultVersionFilterContext versionContext, 299 CollectRequest request, 300 DependencyNode node, 301 List<RemoteRepository> repositories, 302 List<Dependency> dependencies, 303 List<Dependency> managedDependencies, 304 Results results) 305 throws DependencyCollectionException; 306 307 protected RepositorySystemSession setUpSession( 308 RepositorySystemSession session, CollectRequest collectRequest, InternalScopeManager scopeManager) { 309 DefaultRepositorySystemSession optimized = new DefaultRepositorySystemSession(session); 310 optimized.setArtifactTypeRegistry(CachingArtifactTypeRegistry.newInstance(session)); 311 312 ResolutionScope resolutionScope = collectRequest.getResolutionScope(); 313 if (resolutionScope != null) { 314 requireNonNull(scopeManager, "ScopeManager is not set on session"); 315 optimized.setDependencySelector(scopeManager.getDependencySelector(session, resolutionScope)); 316 } 317 return optimized; 318 } 319 320 protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) { 321 List<Dependency> result; 322 if (dominant == null || dominant.isEmpty()) { 323 result = recessive; 324 } else if (recessive == null || recessive.isEmpty()) { 325 result = dominant; 326 } else { 327 int initialCapacity = dominant.size() + recessive.size(); 328 result = new ArrayList<>(initialCapacity); 329 Collection<String> ids = new HashSet<>(initialCapacity, 1.0f); 330 for (Dependency dependency : dominant) { 331 ids.add(getId(dependency.getArtifact())); 332 result.add(dependency); 333 } 334 for (Dependency dependency : recessive) { 335 if (!ids.contains(getId(dependency.getArtifact()))) { 336 result.add(dependency); 337 } 338 } 339 } 340 return result; 341 } 342 343 protected static String getId(Artifact a) { 344 return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); 345 } 346 347 @SuppressWarnings("checkstyle:parameternumber") 348 protected static DefaultDependencyNode createDependencyNode( 349 List<Artifact> relocations, 350 PremanagedDependency preManaged, 351 VersionRangeResult rangeResult, 352 Version version, 353 Dependency d, 354 Collection<Artifact> aliases, 355 List<RemoteRepository> repos, 356 String requestContext) { 357 DefaultDependencyNode child = new DefaultDependencyNode(d); 358 preManaged.applyTo(child); 359 child.setRelocations(relocations); 360 child.setVersionConstraint(rangeResult.getVersionConstraint()); 361 child.setVersion(version); 362 child.setAliases(aliases); 363 child.setRepositories(repos); 364 child.setRequestContext(requestContext); 365 return child; 366 } 367 368 protected static DefaultDependencyNode createDependencyNode( 369 List<Artifact> relocations, 370 PremanagedDependency preManaged, 371 VersionRangeResult rangeResult, 372 Version version, 373 Dependency d, 374 ArtifactDescriptorResult descriptorResult, 375 DependencyNode cycleNode) { 376 DefaultDependencyNode child = createDependencyNode( 377 relocations, 378 preManaged, 379 rangeResult, 380 version, 381 d, 382 descriptorResult.getAliases(), 383 cycleNode.getRepositories(), 384 cycleNode.getRequestContext()); 385 child.setChildren(cycleNode.getChildren()); 386 return child; 387 } 388 389 protected static ArtifactDescriptorRequest createArtifactDescriptorRequest( 390 String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) { 391 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 392 descriptorRequest.setArtifact(d.getArtifact()); 393 descriptorRequest.setRepositories(repositories); 394 descriptorRequest.setRequestContext(requestContext); 395 descriptorRequest.setTrace(requestTrace); 396 return descriptorRequest; 397 } 398 399 protected static VersionRangeRequest createVersionRangeRequest( 400 String requestContext, 401 RequestTrace requestTrace, 402 List<RemoteRepository> repositories, 403 Dependency dependency) { 404 VersionRangeRequest rangeRequest = new VersionRangeRequest(); 405 rangeRequest.setArtifact(dependency.getArtifact()); 406 rangeRequest.setRepositories(repositories); 407 rangeRequest.setRequestContext(requestContext); 408 rangeRequest.setTrace(requestTrace); 409 return rangeRequest; 410 } 411 412 protected VersionRangeResult cachedResolveRangeResult( 413 VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session) 414 throws VersionRangeResolutionException { 415 Object key = pool.toKey(rangeRequest); 416 VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest); 417 if (rangeResult == null) { 418 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest); 419 pool.putConstraint(key, rangeResult); 420 } 421 return rangeResult; 422 } 423 424 protected static boolean isLackingDescriptor(RepositorySystemSession session, Artifact artifact) { 425 SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); 426 return systemDependencyScope != null && systemDependencyScope.getSystemPath(artifact) != null; 427 } 428 429 protected static List<RemoteRepository> getRemoteRepositories( 430 ArtifactRepository repository, List<RemoteRepository> repositories) { 431 if (repository instanceof RemoteRepository) { 432 return Collections.singletonList((RemoteRepository) repository); 433 } 434 if (repository != null) { 435 return Collections.emptyList(); 436 } 437 return repositories; 438 } 439 440 protected static List<? extends Version> filterVersions( 441 Dependency dependency, 442 VersionRangeResult rangeResult, 443 VersionFilter verFilter, 444 DefaultVersionFilterContext verContext) 445 throws VersionRangeResolutionException { 446 if (rangeResult.getVersions().isEmpty()) { 447 throw new VersionRangeResolutionException( 448 rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range"); 449 } 450 451 List<? extends Version> versions; 452 if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) { 453 verContext.set(dependency, rangeResult); 454 try { 455 verFilter.filterVersions(verContext); 456 } catch (RepositoryException e) { 457 throw new VersionRangeResolutionException( 458 rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e); 459 } 460 versions = verContext.get(); 461 if (versions.isEmpty()) { 462 throw new VersionRangeResolutionException( 463 rangeResult, 464 "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions()); 465 } 466 } else { 467 versions = rangeResult.getVersions(); 468 } 469 return versions; 470 } 471 472 protected ArtifactDescriptorResult resolveCachedArtifactDescriptor( 473 DataPool pool, 474 ArtifactDescriptorRequest descriptorRequest, 475 RepositorySystemSession session, 476 Dependency d, 477 Results results, 478 List<DependencyNode> nodes) { 479 DataPool.DescriptorKey key = pool.toKey(descriptorRequest); 480 ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest); 481 if (descriptorResult == null) { 482 try { 483 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest); 484 for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) { 485 descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult)); 486 } 487 pool.putDescriptor(key, descriptorResult); 488 } catch (ArtifactDescriptorException e) { 489 results.addException(d, e, nodes); 490 pool.putDescriptor(key, e); 491 return null; 492 } 493 } else if (descriptorResult == DataPool.NO_DESCRIPTOR) { 494 return null; 495 } 496 return descriptorResult; 497 } 498 499 /** 500 * Helper class used during collection. 501 */ 502 protected static class Results { 503 504 private final CollectResult result; 505 506 final int maxExceptions; 507 508 final int maxCycles; 509 510 String errorPath; 511 512 public Results(CollectResult result, RepositorySystemSession session) { 513 this.result = result; 514 515 maxExceptions = ConfigUtils.getInteger(session, DEFAULT_MAX_EXCEPTIONS, CONFIG_PROP_MAX_EXCEPTIONS); 516 517 maxCycles = ConfigUtils.getInteger(session, DEFAULT_MAX_CYCLES, CONFIG_PROP_MAX_CYCLES); 518 } 519 520 public CollectResult getResult() { 521 return result; 522 } 523 524 public String getErrorPath() { 525 return errorPath; 526 } 527 528 public void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) { 529 if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) { 530 result.addException(e); 531 if (errorPath == null) { 532 StringBuilder buffer = new StringBuilder(256); 533 for (DependencyNode node : nodes) { 534 if (buffer.length() > 0) { 535 buffer.append(" -> "); 536 } 537 Dependency dep = node.getDependency(); 538 if (dep != null) { 539 buffer.append(dep.getArtifact()); 540 } 541 } 542 if (buffer.length() > 0) { 543 buffer.append(" -> "); 544 } 545 buffer.append(dependency.getArtifact()); 546 errorPath = buffer.toString(); 547 } 548 } 549 } 550 551 public void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) { 552 if (maxCycles < 0 || result.getCycles().size() < maxCycles) { 553 result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency)); 554 } 555 } 556 } 557}