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.util.graph.manager; 020 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.HashMap; 025import java.util.LinkedHashSet; 026import java.util.Map; 027import java.util.Objects; 028 029import org.eclipse.aether.artifact.Artifact; 030import org.eclipse.aether.collection.DependencyCollectionContext; 031import org.eclipse.aether.collection.DependencyManagement; 032import org.eclipse.aether.collection.DependencyManager; 033import org.eclipse.aether.graph.Dependency; 034import org.eclipse.aether.graph.Exclusion; 035import org.eclipse.aether.scope.ScopeManager; 036import org.eclipse.aether.scope.SystemDependencyScope; 037 038import static java.util.Objects.requireNonNull; 039 040/** 041 * A dependency manager support class. 042 * <p> 043 * This implementation is Maven specific, as it works hand-in-hand along with Maven ModelBuilder. While model builder 044 * handles dependency management in the context of single POM (inheritance, imports, etc.), this implementation carries 045 * in-lineage modifications based on previously recorded dependency management rules sourced from ascendants while 046 * building the dependency graph. Root sourced management rules are special, in a way they are always applied, while 047 * en-route collected ones are carefully applied to proper descendants only, to not override work done by model 048 * builder already. 049 * <p> 050 * Details: Model builder handles version, scope from own dependency management (think "effective POM"). On the other 051 * hand it does not handle optional for example. System paths are aligned across whole graph, making sure there is 052 * same system path used by same dependency. Finally, exclusions (exclusions are additional information not effective 053 * or applied in same POM) are always applied. This implementation makes sure, that version and scope are not applied 054 * onto same node that actually provided the rules, to no override work that ModelBuilder did. It achieves this goal 055 * by tracking "depth" for each collected rule and ignoring rules coming from same depth as processed dependency node is. 056 * 057 * @since 2.0.0 058 */ 059public abstract class AbstractDependencyManager implements DependencyManager { 060 061 protected final int depth; 062 063 protected final int deriveUntil; 064 065 protected final int applyFrom; 066 067 protected final Map<Object, Holder<String>> managedVersions; 068 069 protected final Map<Object, Holder<String>> managedScopes; 070 071 protected final Map<Object, Holder<Boolean>> managedOptionals; 072 073 protected final Map<Object, Holder<String>> managedLocalPaths; 074 075 protected final Map<Object, Collection<Holder<Collection<Exclusion>>>> managedExclusions; 076 077 protected final SystemDependencyScope systemDependencyScope; 078 079 private final int hashCode; 080 081 protected AbstractDependencyManager(int deriveUntil, int applyFrom, ScopeManager scopeManager) { 082 this( 083 0, 084 deriveUntil, 085 applyFrom, 086 Collections.emptyMap(), 087 Collections.emptyMap(), 088 Collections.emptyMap(), 089 Collections.emptyMap(), 090 Collections.emptyMap(), 091 scopeManager != null 092 ? scopeManager.getSystemDependencyScope().orElse(null) 093 : SystemDependencyScope.LEGACY); 094 } 095 096 @SuppressWarnings("checkstyle:ParameterNumber") 097 protected AbstractDependencyManager( 098 int depth, 099 int deriveUntil, 100 int applyFrom, 101 Map<Object, Holder<String>> managedVersions, 102 Map<Object, Holder<String>> managedScopes, 103 Map<Object, Holder<Boolean>> managedOptionals, 104 Map<Object, Holder<String>> managedLocalPaths, 105 Map<Object, Collection<Holder<Collection<Exclusion>>>> managedExclusions, 106 SystemDependencyScope systemDependencyScope) { 107 this.depth = depth; 108 this.deriveUntil = deriveUntil; 109 this.applyFrom = applyFrom; 110 this.managedVersions = requireNonNull(managedVersions); 111 this.managedScopes = requireNonNull(managedScopes); 112 this.managedOptionals = requireNonNull(managedOptionals); 113 this.managedLocalPaths = requireNonNull(managedLocalPaths); 114 this.managedExclusions = requireNonNull(managedExclusions); 115 // nullable: if using scope manager, but there is no system scope defined 116 this.systemDependencyScope = systemDependencyScope; 117 118 this.hashCode = Objects.hash( 119 depth, 120 deriveUntil, 121 applyFrom, 122 managedVersions, 123 managedScopes, 124 managedOptionals, 125 managedLocalPaths, 126 managedExclusions); 127 } 128 129 protected abstract DependencyManager newInstance( 130 Map<Object, Holder<String>> managedVersions, 131 Map<Object, Holder<String>> managedScopes, 132 Map<Object, Holder<Boolean>> managedOptionals, 133 Map<Object, Holder<String>> managedLocalPaths, 134 Map<Object, Collection<Holder<Collection<Exclusion>>>> managedExclusions); 135 136 @Override 137 public DependencyManager deriveChildManager(DependencyCollectionContext context) { 138 requireNonNull(context, "context cannot be null"); 139 if (!isDerived()) { 140 return this; 141 } 142 143 Map<Object, Holder<String>> managedVersions = this.managedVersions; 144 Map<Object, Holder<String>> managedScopes = this.managedScopes; 145 Map<Object, Holder<Boolean>> managedOptionals = this.managedOptionals; 146 Map<Object, Holder<String>> managedLocalPaths = this.managedLocalPaths; 147 Map<Object, Collection<Holder<Collection<Exclusion>>>> managedExclusions = this.managedExclusions; 148 149 for (Dependency managedDependency : context.getManagedDependencies()) { 150 Artifact artifact = managedDependency.getArtifact(); 151 Object key = new Key(artifact); 152 153 String version = artifact.getVersion(); 154 if (!version.isEmpty() && !managedVersions.containsKey(key)) { 155 if (managedVersions == this.managedVersions) { 156 managedVersions = new HashMap<>(this.managedVersions); 157 } 158 managedVersions.put(key, new Holder<>(depth, version)); 159 } 160 161 String scope = managedDependency.getScope(); 162 if (!scope.isEmpty() && !managedScopes.containsKey(key)) { 163 if (managedScopes == this.managedScopes) { 164 managedScopes = new HashMap<>(this.managedScopes); 165 } 166 managedScopes.put(key, new Holder<>(depth, scope)); 167 } 168 169 Boolean optional = managedDependency.getOptional(); 170 if (optional != null && !managedOptionals.containsKey(key)) { 171 if (managedOptionals == this.managedOptionals) { 172 managedOptionals = new HashMap<>(this.managedOptionals); 173 } 174 managedOptionals.put(key, new Holder<>(depth, optional)); 175 } 176 177 String localPath = systemDependencyScope == null 178 ? null 179 : systemDependencyScope.getSystemPath(managedDependency.getArtifact()); 180 if (localPath != null && !managedLocalPaths.containsKey(key)) { 181 if (managedLocalPaths == this.managedLocalPaths) { 182 managedLocalPaths = new HashMap<>(this.managedLocalPaths); 183 } 184 managedLocalPaths.put(key, new Holder<>(depth, localPath)); 185 } 186 187 Collection<Exclusion> exclusions = managedDependency.getExclusions(); 188 if (!exclusions.isEmpty()) { 189 if (managedExclusions == this.managedExclusions) { 190 managedExclusions = new HashMap<>(this.managedExclusions); 191 } 192 Collection<Holder<Collection<Exclusion>>> managed = 193 managedExclusions.computeIfAbsent(key, k -> new ArrayList<>()); 194 managed.add(new Holder<>(depth, exclusions)); 195 } 196 } 197 198 return newInstance(managedVersions, managedScopes, managedOptionals, managedLocalPaths, managedExclusions); 199 } 200 201 @Override 202 public DependencyManagement manageDependency(Dependency dependency) { 203 requireNonNull(dependency, "dependency cannot be null"); 204 DependencyManagement management = null; 205 Object key = new Key(dependency.getArtifact()); 206 207 if (isApplied()) { 208 Holder<String> version = managedVersions.get(key); 209 // is managed locally by model builder 210 // apply only rules coming from "higher" levels 211 if (version != null && isApplicable(version)) { 212 management = new DependencyManagement(); 213 management.setVersion(version.getValue()); 214 } 215 216 Holder<String> scope = managedScopes.get(key); 217 // is managed locally by model builder 218 // apply only rules coming from "higher" levels 219 if (scope != null && isApplicable(scope)) { 220 if (management == null) { 221 management = new DependencyManagement(); 222 } 223 management.setScope(scope.getValue()); 224 225 if (systemDependencyScope != null 226 && !systemDependencyScope.is(scope.getValue()) 227 && systemDependencyScope.getSystemPath(dependency.getArtifact()) != null) { 228 Map<String, String> properties = 229 new HashMap<>(dependency.getArtifact().getProperties()); 230 systemDependencyScope.setSystemPath(properties, null); 231 management.setProperties(properties); 232 } 233 } 234 235 // system scope paths always applied to have them aligned 236 // (same artifact == same path) in whole graph 237 if (systemDependencyScope != null 238 && (scope != null && systemDependencyScope.is(scope.getValue()) 239 || (scope == null && systemDependencyScope.is(dependency.getScope())))) { 240 Holder<String> localPath = managedLocalPaths.get(key); 241 if (localPath != null) { 242 if (management == null) { 243 management = new DependencyManagement(); 244 } 245 Map<String, String> properties = 246 new HashMap<>(dependency.getArtifact().getProperties()); 247 systemDependencyScope.setSystemPath(properties, localPath.getValue()); 248 management.setProperties(properties); 249 } 250 } 251 252 // optional is not managed by model builder 253 // apply only rules coming from "higher" levels 254 Holder<Boolean> optional = managedOptionals.get(key); 255 if (optional != null && isApplicable(optional)) { 256 if (management == null) { 257 management = new DependencyManagement(); 258 } 259 management.setOptional(optional.getValue()); 260 } 261 } 262 263 // exclusions affect only downstream 264 // this will not "exclude" own dependency, 265 // is just added as additional information 266 // ModelBuilder does not merge exclusions (only applies if dependency does not have exclusion) 267 // so we merge it here even from same level 268 Collection<Holder<Collection<Exclusion>>> exclusions = managedExclusions.get(key); 269 if (exclusions != null) { 270 if (management == null) { 271 management = new DependencyManagement(); 272 } 273 Collection<Exclusion> result = new LinkedHashSet<>(dependency.getExclusions()); 274 for (Holder<Collection<Exclusion>> exclusion : exclusions) { 275 result.addAll(exclusion.getValue()); 276 } 277 management.setExclusions(result); 278 } 279 280 return management; 281 } 282 283 /** 284 * Returns {@code true} if current context should be factored in (collected/derived). 285 */ 286 protected boolean isDerived() { 287 return depth < deriveUntil; 288 } 289 290 /** 291 * Returns {@code true} if current dependency should be managed according to so far collected/derived rules. 292 */ 293 protected boolean isApplied() { 294 return depth >= applyFrom; 295 } 296 297 /** 298 * Returns {@code true} if rule in holder is applicable at current depth. 299 */ 300 protected boolean isApplicable(Holder<?> holder) { 301 // explanation: derive collects rules (at given depth) and then last 302 // call newInstance does depth++. This means that distance 1 is still "same node". 303 // Hence, rules from depth - 2 or above should be applied. 304 // root is special: is always applied. 305 return holder.getDepth() == 0 || depth > holder.getDepth() + 1; 306 } 307 308 @Override 309 public boolean equals(Object obj) { 310 if (this == obj) { 311 return true; 312 } else if (null == obj || !getClass().equals(obj.getClass())) { 313 return false; 314 } 315 316 AbstractDependencyManager that = (AbstractDependencyManager) obj; 317 return depth == that.depth 318 && deriveUntil == that.deriveUntil 319 && applyFrom == that.applyFrom 320 && managedVersions.equals(that.managedVersions) 321 && managedScopes.equals(that.managedScopes) 322 && managedOptionals.equals(that.managedOptionals) 323 && managedExclusions.equals(that.managedExclusions); 324 } 325 326 @Override 327 public int hashCode() { 328 return hashCode; 329 } 330 331 protected static class Key { 332 private final Artifact artifact; 333 private final int hashCode; 334 335 Key(Artifact artifact) { 336 this.artifact = artifact; 337 this.hashCode = Objects.hash(artifact.getGroupId(), artifact.getArtifactId()); 338 } 339 340 @Override 341 public boolean equals(Object obj) { 342 if (obj == this) { 343 return true; 344 } else if (!(obj instanceof Key)) { 345 return false; 346 } 347 Key that = (Key) obj; 348 return artifact.getArtifactId().equals(that.artifact.getArtifactId()) 349 && artifact.getGroupId().equals(that.artifact.getGroupId()) 350 && artifact.getExtension().equals(that.artifact.getExtension()) 351 && artifact.getClassifier().equals(that.artifact.getClassifier()); 352 } 353 354 @Override 355 public int hashCode() { 356 return hashCode; 357 } 358 359 @Override 360 public String toString() { 361 return String.valueOf(artifact); 362 } 363 } 364 365 protected static class Holder<T> { 366 private final int depth; 367 private final T value; 368 369 Holder(int depth, T value) { 370 this.depth = depth; 371 this.value = requireNonNull(value); 372 } 373 374 public int getDepth() { 375 return depth; 376 } 377 378 public T getValue() { 379 return value; 380 } 381 } 382}