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