View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.util.graph.manager;
20  
21  import java.util.ArrayList;
22  import java.util.Collection;
23  import java.util.HashMap;
24  import java.util.LinkedHashSet;
25  import java.util.Objects;
26  
27  import org.eclipse.aether.artifact.Artifact;
28  import org.eclipse.aether.collection.DependencyCollectionContext;
29  import org.eclipse.aether.collection.DependencyManagement;
30  import org.eclipse.aether.collection.DependencyManager;
31  import org.eclipse.aether.graph.Dependency;
32  import org.eclipse.aether.graph.Exclusion;
33  import org.eclipse.aether.scope.ScopeManager;
34  import org.eclipse.aether.scope.SystemDependencyScope;
35  
36  import static java.util.Objects.requireNonNull;
37  
38  /**
39   * A dependency manager support class.
40   * <p>
41   * This implementation is Maven specific, as it works hand-in-hand along with Maven ModelBuilder. While model builder
42   * handles dependency management in the context of single POM (inheritance, imports, etc.), this implementation carries
43   * in-lineage modifications based on previously recorded dependency management rules sourced from ascendants while
44   * building the dependency graph. Root sourced management rules are special, in a way they are always applied, while
45   * en-route collected ones are carefully applied to proper descendants only, to not override work done by model
46   * builder already.
47   * <p>
48   * Details: Model builder handles version, scope from own dependency management (think "effective POM"). On the other
49   * hand it does not handle optional for example. System paths are aligned across whole graph, making sure there is
50   * same system path used by same dependency. Finally, exclusions (exclusions are additional information not effective
51   * or applied in same POM) are always applied. This implementation makes sure, that version and scope are not applied
52   * onto same node that actually provided the rules, to no override work that ModelBuilder did. It achieves this goal
53   * by tracking "depth" for each collected rule and ignoring rules coming from same depth as processed dependency node is.
54   * <p>
55   * Note for future: the field {@code managedLocalPaths} is <em>intentionally left out of hash/equals</em>, with
56   * reason explained above.
57   *
58   * @since 2.0.0
59   */
60  public abstract class AbstractDependencyManager implements DependencyManager {
61  
62      protected final int depth;
63  
64      protected final int deriveUntil;
65  
66      protected final int applyFrom;
67  
68      protected final MMap<Key, Holder<String>> managedVersions;
69  
70      protected final MMap<Key, Holder<String>> managedScopes;
71  
72      protected final MMap<Key, Holder<Boolean>> managedOptionals;
73  
74      protected final MMap<Key, Holder<String>> managedLocalPaths;
75  
76      protected final MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions;
77  
78      protected final SystemDependencyScope systemDependencyScope;
79  
80      private final int hashCode;
81  
82      protected AbstractDependencyManager(int deriveUntil, int applyFrom, ScopeManager scopeManager) {
83          this(
84                  0,
85                  deriveUntil,
86                  applyFrom,
87                  MMap.empty(),
88                  MMap.empty(),
89                  MMap.empty(),
90                  MMap.empty(),
91                  MMap.empty(),
92                  scopeManager != null
93                          ? scopeManager.getSystemDependencyScope().orElse(null)
94                          : SystemDependencyScope.LEGACY);
95      }
96  
97      @SuppressWarnings("checkstyle:ParameterNumber")
98      protected AbstractDependencyManager(
99              int depth,
100             int deriveUntil,
101             int applyFrom,
102             MMap<Key, Holder<String>> managedVersions,
103             MMap<Key, Holder<String>> managedScopes,
104             MMap<Key, Holder<Boolean>> managedOptionals,
105             MMap<Key, Holder<String>> managedLocalPaths,
106             MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions,
107             SystemDependencyScope systemDependencyScope) {
108         this.depth = depth;
109         this.deriveUntil = deriveUntil;
110         this.applyFrom = applyFrom;
111         this.managedVersions = requireNonNull(managedVersions);
112         this.managedScopes = requireNonNull(managedScopes);
113         this.managedOptionals = requireNonNull(managedOptionals);
114         this.managedLocalPaths = requireNonNull(managedLocalPaths);
115         this.managedExclusions = requireNonNull(managedExclusions);
116         // nullable: if using scope manager, but there is no system scope defined
117         this.systemDependencyScope = systemDependencyScope;
118 
119         // exclude managedLocalPaths
120         this.hashCode = Objects.hash(depth, managedVersions, managedScopes, managedOptionals, managedExclusions);
121     }
122 
123     protected abstract DependencyManager newInstance(
124             MMap<Key, Holder<String>> managedVersions,
125             MMap<Key, Holder<String>> managedScopes,
126             MMap<Key, Holder<Boolean>> managedOptionals,
127             MMap<Key, Holder<String>> managedLocalPaths,
128             MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions);
129 
130     @Override
131     public DependencyManager deriveChildManager(DependencyCollectionContext context) {
132         requireNonNull(context, "context cannot be null");
133         if (!isDerived()) {
134             return this;
135         }
136 
137         MMap<Key, Holder<String>> managedVersions = this.managedVersions;
138         MMap<Key, Holder<String>> managedScopes = this.managedScopes;
139         MMap<Key, Holder<Boolean>> managedOptionals = this.managedOptionals;
140         MMap<Key, Holder<String>> managedLocalPaths = this.managedLocalPaths;
141         MMap<Key, Collection<Holder<Collection<Exclusion>>>> managedExclusions = this.managedExclusions;
142 
143         for (Dependency managedDependency : context.getManagedDependencies()) {
144             Artifact artifact = managedDependency.getArtifact();
145             Key key = new Key(artifact);
146 
147             String version = artifact.getVersion();
148             if (!version.isEmpty() && !managedVersions.containsKey(key)) {
149                 if (managedVersions == this.managedVersions) {
150                     managedVersions = MMap.copy(this.managedVersions);
151                 }
152                 managedVersions.put(key, new Holder<>(depth, version));
153             }
154 
155             String scope = managedDependency.getScope();
156             if (!scope.isEmpty() && !managedScopes.containsKey(key)) {
157                 if (managedScopes == this.managedScopes) {
158                     managedScopes = MMap.copy(this.managedScopes);
159                 }
160                 managedScopes.put(key, new Holder<>(depth, scope));
161             }
162 
163             Boolean optional = managedDependency.getOptional();
164             if (optional != null && !managedOptionals.containsKey(key)) {
165                 if (managedOptionals == this.managedOptionals) {
166                     managedOptionals = MMap.copy(this.managedOptionals);
167                 }
168                 managedOptionals.put(key, new Holder<>(depth, optional));
169             }
170 
171             String localPath = systemDependencyScope == null
172                     ? null
173                     : systemDependencyScope.getSystemPath(managedDependency.getArtifact());
174             if (localPath != null && !managedLocalPaths.containsKey(key)) {
175                 if (managedLocalPaths == this.managedLocalPaths) {
176                     managedLocalPaths = MMap.copy(this.managedLocalPaths);
177                 }
178                 managedLocalPaths.put(key, new Holder<>(depth, localPath));
179             }
180 
181             Collection<Exclusion> exclusions = managedDependency.getExclusions();
182             if (!exclusions.isEmpty()) {
183                 if (managedExclusions == this.managedExclusions) {
184                     managedExclusions = MMap.copyWithKey(key, this.managedExclusions);
185                 }
186                 Collection<Holder<Collection<Exclusion>>> managed = managedExclusions.get(key);
187                 if (managed == null) {
188                     managed = new ArrayList<>();
189                     managedExclusions.put(key, managed);
190                 }
191                 managed.add(new Holder<>(depth, exclusions));
192             }
193         }
194 
195         return newInstance(
196                 managedVersions.done(),
197                 managedScopes.done(),
198                 managedOptionals.done(),
199                 managedLocalPaths.done(),
200                 managedExclusions.done());
201     }
202 
203     @Override
204     public DependencyManagement manageDependency(Dependency dependency) {
205         requireNonNull(dependency, "dependency cannot be null");
206         DependencyManagement management = null;
207         Key key = new Key(dependency.getArtifact());
208 
209         if (isApplied()) {
210             Holder<String> version = managedVersions.get(key);
211             // is managed locally by model builder
212             // apply only rules coming from "higher" levels
213             if (version != null && isApplicable(version)) {
214                 management = new DependencyManagement();
215                 management.setVersion(version.getValue());
216             }
217 
218             Holder<String> scope = managedScopes.get(key);
219             // is managed locally by model builder
220             // apply only rules coming from "higher" levels
221             if (scope != null && isApplicable(scope)) {
222                 if (management == null) {
223                     management = new DependencyManagement();
224                 }
225                 management.setScope(scope.getValue());
226 
227                 if (systemDependencyScope != null
228                         && !systemDependencyScope.is(scope.getValue())
229                         && systemDependencyScope.getSystemPath(dependency.getArtifact()) != null) {
230                     HashMap<String, String> properties =
231                             new HashMap<>(dependency.getArtifact().getProperties());
232                     systemDependencyScope.setSystemPath(properties, null);
233                     management.setProperties(properties);
234                 }
235             }
236 
237             // system scope paths always applied to have them aligned
238             // (same artifact == same path) in whole graph
239             if (systemDependencyScope != null
240                     && (scope != null && systemDependencyScope.is(scope.getValue())
241                             || (scope == null && systemDependencyScope.is(dependency.getScope())))) {
242                 Holder<String> localPath = managedLocalPaths.get(key);
243                 if (localPath != null) {
244                     if (management == null) {
245                         management = new DependencyManagement();
246                     }
247                     HashMap<String, String> properties =
248                             new HashMap<>(dependency.getArtifact().getProperties());
249                     systemDependencyScope.setSystemPath(properties, localPath.getValue());
250                     management.setProperties(properties);
251                 }
252             }
253 
254             // optional is not managed by model builder
255             // apply only rules coming from "higher" levels
256             Holder<Boolean> optional = managedOptionals.get(key);
257             if (optional != null && isApplicable(optional)) {
258                 if (management == null) {
259                     management = new DependencyManagement();
260                 }
261                 management.setOptional(optional.getValue());
262             }
263         }
264 
265         // exclusions affect only downstream
266         // this will not "exclude" own dependency,
267         // is just added as additional information
268         // ModelBuilder does not merge exclusions (only applies if dependency does not have exclusion)
269         // so we merge it here even from same level
270         Collection<Holder<Collection<Exclusion>>> exclusions = managedExclusions.get(key);
271         if (exclusions != null) {
272             if (management == null) {
273                 management = new DependencyManagement();
274             }
275             Collection<Exclusion> result = new LinkedHashSet<>(dependency.getExclusions());
276             for (Holder<Collection<Exclusion>> exclusion : exclusions) {
277                 result.addAll(exclusion.getValue());
278             }
279             management.setExclusions(result);
280         }
281 
282         return management;
283     }
284 
285     /**
286      * Returns {@code true} if current context should be factored in (collected/derived).
287      */
288     protected boolean isDerived() {
289         return depth < deriveUntil;
290     }
291 
292     /**
293      * Returns {@code true} if current dependency should be managed according to so far collected/derived rules.
294      */
295     protected boolean isApplied() {
296         return depth >= applyFrom;
297     }
298 
299     /**
300      * Returns {@code true} if rule in holder is applicable at current depth.
301      */
302     protected boolean isApplicable(Holder<?> holder) {
303         // explanation: derive collects rules (at given depth) and then last
304         // call newInstance does depth++. This means that distance 1 is still "same node".
305         // Hence, rules from depth - 2 or above should be applied.
306         // root is special: is always applied.
307         return holder.getDepth() == 0 || depth > holder.getDepth() + 1;
308     }
309 
310     @Override
311     public boolean equals(Object obj) {
312         if (this == obj) {
313             return true;
314         } else if (null == obj || !getClass().equals(obj.getClass())) {
315             return false;
316         }
317 
318         AbstractDependencyManager that = (AbstractDependencyManager) obj;
319         // exclude managedLocalPaths
320         return depth == that.depth
321                 && managedVersions.equals(that.managedVersions)
322                 && managedScopes.equals(that.managedScopes)
323                 && managedOptionals.equals(that.managedOptionals)
324                 && managedExclusions.equals(that.managedExclusions);
325     }
326 
327     @Override
328     public int hashCode() {
329         return hashCode;
330     }
331 
332     protected static class Key {
333         private final Artifact artifact;
334         private final int hashCode;
335 
336         Key(Artifact artifact) {
337             this.artifact = artifact;
338             this.hashCode = Objects.hash(
339                     artifact.getArtifactId(), artifact.getGroupId(), artifact.getExtension(), artifact.getClassifier());
340         }
341 
342         @Override
343         public boolean equals(Object obj) {
344             if (obj == this) {
345                 return true;
346             } else if (!(obj instanceof Key)) {
347                 return false;
348             }
349             Key that = (Key) obj;
350             return artifact.getArtifactId().equals(that.artifact.getArtifactId())
351                     && artifact.getGroupId().equals(that.artifact.getGroupId())
352                     && artifact.getExtension().equals(that.artifact.getExtension())
353                     && artifact.getClassifier().equals(that.artifact.getClassifier());
354         }
355 
356         @Override
357         public int hashCode() {
358             return hashCode;
359         }
360 
361         @Override
362         public String toString() {
363             return String.valueOf(artifact);
364         }
365     }
366 
367     protected static class Holder<T> {
368         private final int depth;
369         private final T value;
370         private final int hashCode;
371 
372         Holder(int depth, T value) {
373             this.depth = depth;
374             this.value = requireNonNull(value);
375             this.hashCode = Objects.hash(depth, value);
376         }
377 
378         public int getDepth() {
379             return depth;
380         }
381 
382         public T getValue() {
383             return value;
384         }
385 
386         @Override
387         public boolean equals(Object o) {
388             if (!(o instanceof Holder)) {
389                 return false;
390             }
391             Holder<?> holder = (Holder<?>) o;
392             return depth == holder.depth && Objects.equals(value, holder.value);
393         }
394 
395         @Override
396         public int hashCode() {
397             return hashCode;
398         }
399     }
400 }