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.Collections;
24  import java.util.HashMap;
25  import java.util.LinkedHashSet;
26  import java.util.Map;
27  import java.util.Objects;
28  
29  import org.eclipse.aether.artifact.Artifact;
30  import org.eclipse.aether.collection.DependencyCollectionContext;
31  import org.eclipse.aether.collection.DependencyManagement;
32  import org.eclipse.aether.collection.DependencyManager;
33  import org.eclipse.aether.graph.Dependency;
34  import org.eclipse.aether.graph.Exclusion;
35  import org.eclipse.aether.scope.ScopeManager;
36  import org.eclipse.aether.scope.SystemDependencyScope;
37  
38  import static java.util.Objects.requireNonNull;
39  
40  /**
41   * A dependency manager support class.
42   * <p>
43   * This implementation is Maven specific, as it works hand-in-hand along with Maven ModelBuilder. While model builder
44   * handles dependency management in the context of single POM (inheritance, imports, etc.), this implementation carries
45   * in-lineage modifications based on previously recorded dependency management rules sourced from ascendants while
46   * building the dependency graph. Root sourced management rules are special, in a way they are always applied, while
47   * en-route collected ones are carefully applied to proper descendants only, to not override work done by model
48   * builder already.
49   * <p>
50   * Details: Model builder handles version, scope from own dependency management (think "effective POM"). On the other
51   * hand it does not handle optional for example. System paths are aligned across whole graph, making sure there is
52   * same system path used by same dependency. Finally, exclusions (exclusions are additional information not effective
53   * or applied in same POM) are always applied. This implementation makes sure, that version and scope are not applied
54   * onto same node that actually provided the rules, to no override work that ModelBuilder did. It achieves this goal
55   * by tracking "depth" for each collected rule and ignoring rules coming from same depth as processed dependency node is.
56   *
57   * @since 2.0.0
58   */
59  public abstract class AbstractDependencyManager implements DependencyManager {
60  
61      protected final int depth;
62  
63      protected final int deriveUntil;
64  
65      protected final int applyFrom;
66  
67      protected final Map<Object, Holder<String>> managedVersions;
68  
69      protected final Map<Object, Holder<String>> managedScopes;
70  
71      protected final Map<Object, Holder<Boolean>> managedOptionals;
72  
73      protected final Map<Object, Holder<String>> managedLocalPaths;
74  
75      protected final Map<Object, Collection<Holder<Collection<Exclusion>>>> managedExclusions;
76  
77      protected final SystemDependencyScope systemDependencyScope;
78  
79      private final int hashCode;
80  
81      protected AbstractDependencyManager(int deriveUntil, int applyFrom, ScopeManager scopeManager) {
82          this(
83                  0,
84                  deriveUntil,
85                  applyFrom,
86                  Collections.emptyMap(),
87                  Collections.emptyMap(),
88                  Collections.emptyMap(),
89                  Collections.emptyMap(),
90                  Collections.emptyMap(),
91                  scopeManager != null
92                          ? scopeManager.getSystemDependencyScope().orElse(null)
93                          : SystemDependencyScope.LEGACY);
94      }
95  
96      @SuppressWarnings("checkstyle:ParameterNumber")
97      protected AbstractDependencyManager(
98              int depth,
99              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 }