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