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