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(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(resolutionScope)); 316 optimized.setDependencyGraphTransformer(scopeManager.getDependencyGraphTransformer(resolutionScope)); 317 } 318 return optimized; 319 } 320 321 protected List<Dependency> mergeDeps(List<Dependency> dominant, List<Dependency> recessive) { 322 List<Dependency> result; 323 if (dominant == null || dominant.isEmpty()) { 324 result = recessive; 325 } else if (recessive == null || recessive.isEmpty()) { 326 result = dominant; 327 } else { 328 int initialCapacity = dominant.size() + recessive.size(); 329 result = new ArrayList<>(initialCapacity); 330 Collection<String> ids = new HashSet<>(initialCapacity, 1.0f); 331 for (Dependency dependency : dominant) { 332 ids.add(getId(dependency.getArtifact())); 333 result.add(dependency); 334 } 335 for (Dependency dependency : recessive) { 336 if (!ids.contains(getId(dependency.getArtifact()))) { 337 result.add(dependency); 338 } 339 } 340 } 341 return result; 342 } 343 344 protected static String getId(Artifact a) { 345 return a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getClassifier() + ':' + a.getExtension(); 346 } 347 348 @SuppressWarnings("checkstyle:parameternumber") 349 protected static DefaultDependencyNode createDependencyNode( 350 List<Artifact> relocations, 351 PremanagedDependency preManaged, 352 VersionRangeResult rangeResult, 353 Version version, 354 Dependency d, 355 Collection<Artifact> aliases, 356 List<RemoteRepository> repos, 357 String requestContext) { 358 DefaultDependencyNode child = new DefaultDependencyNode(d); 359 preManaged.applyTo(child); 360 child.setRelocations(relocations); 361 child.setVersionConstraint(rangeResult.getVersionConstraint()); 362 child.setVersion(version); 363 child.setAliases(aliases); 364 child.setRepositories(repos); 365 child.setRequestContext(requestContext); 366 return child; 367 } 368 369 protected static DefaultDependencyNode createDependencyNode( 370 List<Artifact> relocations, 371 PremanagedDependency preManaged, 372 VersionRangeResult rangeResult, 373 Version version, 374 Dependency d, 375 ArtifactDescriptorResult descriptorResult, 376 DependencyNode cycleNode) { 377 DefaultDependencyNode child = createDependencyNode( 378 relocations, 379 preManaged, 380 rangeResult, 381 version, 382 d, 383 descriptorResult.getAliases(), 384 cycleNode.getRepositories(), 385 cycleNode.getRequestContext()); 386 child.setChildren(cycleNode.getChildren()); 387 return child; 388 } 389 390 protected static ArtifactDescriptorRequest createArtifactDescriptorRequest( 391 String requestContext, RequestTrace requestTrace, List<RemoteRepository> repositories, Dependency d) { 392 ArtifactDescriptorRequest descriptorRequest = new ArtifactDescriptorRequest(); 393 descriptorRequest.setArtifact(d.getArtifact()); 394 descriptorRequest.setRepositories(repositories); 395 descriptorRequest.setRequestContext(requestContext); 396 descriptorRequest.setTrace(requestTrace); 397 return descriptorRequest; 398 } 399 400 protected static VersionRangeRequest createVersionRangeRequest( 401 String requestContext, 402 RequestTrace requestTrace, 403 List<RemoteRepository> repositories, 404 Dependency dependency) { 405 VersionRangeRequest rangeRequest = new VersionRangeRequest(); 406 rangeRequest.setArtifact(dependency.getArtifact()); 407 rangeRequest.setRepositories(repositories); 408 rangeRequest.setRequestContext(requestContext); 409 rangeRequest.setTrace(requestTrace); 410 return rangeRequest; 411 } 412 413 protected VersionRangeResult cachedResolveRangeResult( 414 VersionRangeRequest rangeRequest, DataPool pool, RepositorySystemSession session) 415 throws VersionRangeResolutionException { 416 Object key = pool.toKey(rangeRequest); 417 VersionRangeResult rangeResult = pool.getConstraint(key, rangeRequest); 418 if (rangeResult == null) { 419 rangeResult = versionRangeResolver.resolveVersionRange(session, rangeRequest); 420 pool.putConstraint(key, rangeResult); 421 } 422 return rangeResult; 423 } 424 425 protected static boolean isLackingDescriptor(RepositorySystemSession session, Artifact artifact) { 426 SystemDependencyScope systemDependencyScope = session.getSystemDependencyScope(); 427 return systemDependencyScope != null && systemDependencyScope.getSystemPath(artifact) != null; 428 } 429 430 protected static List<RemoteRepository> getRemoteRepositories( 431 ArtifactRepository repository, List<RemoteRepository> repositories) { 432 if (repository instanceof RemoteRepository) { 433 return Collections.singletonList((RemoteRepository) repository); 434 } 435 if (repository != null) { 436 return Collections.emptyList(); 437 } 438 return repositories; 439 } 440 441 protected static List<? extends Version> filterVersions( 442 Dependency dependency, 443 VersionRangeResult rangeResult, 444 VersionFilter verFilter, 445 DefaultVersionFilterContext verContext) 446 throws VersionRangeResolutionException { 447 if (rangeResult.getVersions().isEmpty()) { 448 throw new VersionRangeResolutionException( 449 rangeResult, "No versions available for " + dependency.getArtifact() + " within specified range"); 450 } 451 452 List<? extends Version> versions; 453 if (verFilter != null && rangeResult.getVersionConstraint().getRange() != null) { 454 verContext.set(dependency, rangeResult); 455 try { 456 verFilter.filterVersions(verContext); 457 } catch (RepositoryException e) { 458 throw new VersionRangeResolutionException( 459 rangeResult, "Failed to filter versions for " + dependency.getArtifact(), e); 460 } 461 versions = verContext.get(); 462 if (versions.isEmpty()) { 463 throw new VersionRangeResolutionException( 464 rangeResult, 465 "No acceptable versions for " + dependency.getArtifact() + ": " + rangeResult.getVersions()); 466 } 467 } else { 468 versions = rangeResult.getVersions(); 469 } 470 return versions; 471 } 472 473 protected ArtifactDescriptorResult resolveCachedArtifactDescriptor( 474 DataPool pool, 475 ArtifactDescriptorRequest descriptorRequest, 476 RepositorySystemSession session, 477 Dependency d, 478 Results results, 479 List<DependencyNode> nodes) { 480 DataPool.DescriptorKey key = pool.toKey(descriptorRequest); 481 ArtifactDescriptorResult descriptorResult = pool.getDescriptor(key, descriptorRequest); 482 if (descriptorResult == null) { 483 try { 484 descriptorResult = descriptorReader.readArtifactDescriptor(session, descriptorRequest); 485 for (ArtifactDecorator decorator : Utils.getArtifactDecorators(session, artifactDecoratorFactories)) { 486 descriptorResult.setArtifact(decorator.decorateArtifact(descriptorResult)); 487 } 488 pool.putDescriptor(key, descriptorResult); 489 } catch (ArtifactDescriptorException e) { 490 results.addException(d, e, nodes); 491 pool.putDescriptor(key, e); 492 return null; 493 } 494 } else if (descriptorResult == DataPool.NO_DESCRIPTOR) { 495 return null; 496 } 497 return descriptorResult; 498 } 499 500 /** 501 * Helper class used during collection. 502 */ 503 protected static class Results { 504 505 private final CollectResult result; 506 507 final int maxExceptions; 508 509 final int maxCycles; 510 511 String errorPath; 512 513 public Results(CollectResult result, RepositorySystemSession session) { 514 this.result = result; 515 516 maxExceptions = ConfigUtils.getInteger(session, DEFAULT_MAX_EXCEPTIONS, CONFIG_PROP_MAX_EXCEPTIONS); 517 518 maxCycles = ConfigUtils.getInteger(session, DEFAULT_MAX_CYCLES, CONFIG_PROP_MAX_CYCLES); 519 } 520 521 public CollectResult getResult() { 522 return result; 523 } 524 525 public String getErrorPath() { 526 return errorPath; 527 } 528 529 public void addException(Dependency dependency, Exception e, List<DependencyNode> nodes) { 530 if (maxExceptions < 0 || result.getExceptions().size() < maxExceptions) { 531 result.addException(e); 532 if (errorPath == null) { 533 StringBuilder buffer = new StringBuilder(256); 534 for (DependencyNode node : nodes) { 535 if (buffer.length() > 0) { 536 buffer.append(" -> "); 537 } 538 Dependency dep = node.getDependency(); 539 if (dep != null) { 540 buffer.append(dep.getArtifact()); 541 } 542 } 543 if (buffer.length() > 0) { 544 buffer.append(" -> "); 545 } 546 buffer.append(dependency.getArtifact()); 547 errorPath = buffer.toString(); 548 } 549 } 550 } 551 552 public void addCycle(List<DependencyNode> nodes, int cycleEntry, Dependency dependency) { 553 if (maxCycles < 0 || result.getCycles().size() < maxCycles) { 554 result.addCycle(new DefaultDependencyCycle(nodes, cycleEntry, dependency)); 555 } 556 } 557 } 558}