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.apache.maven.internal.impl.model;
20  
21  import java.util.Collection;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Set;
29  
30  import org.apache.maven.api.di.Named;
31  import org.apache.maven.api.di.Singleton;
32  import org.apache.maven.api.model.Dependency;
33  import org.apache.maven.api.model.DependencyManagement;
34  import org.apache.maven.api.model.Exclusion;
35  import org.apache.maven.api.model.InputLocation;
36  import org.apache.maven.api.model.InputSource;
37  import org.apache.maven.api.model.Model;
38  import org.apache.maven.api.services.BuilderProblem.Severity;
39  import org.apache.maven.api.services.ModelBuilderRequest;
40  import org.apache.maven.api.services.ModelProblem.Version;
41  import org.apache.maven.api.services.ModelProblemCollector;
42  import org.apache.maven.api.services.model.DependencyManagementImporter;
43  
44  /**
45   * Handles the import of dependency management from other models into the target model.
46   *
47   */
48  @Named
49  @Singleton
50  public class DefaultDependencyManagementImporter implements DependencyManagementImporter {
51  
52      @Override
53      public Model importManagement(
54              Model target,
55              List<? extends DependencyManagement> sources,
56              ModelBuilderRequest request,
57              ModelProblemCollector problems) {
58          if (sources != null && !sources.isEmpty()) {
59              Map<String, Dependency> dependencies = new LinkedHashMap<>();
60  
61              DependencyManagement depMgmt = target.getDependencyManagement();
62  
63              if (depMgmt != null) {
64                  for (Dependency dependency : depMgmt.getDependencies()) {
65                      dependencies.put(dependency.getManagementKey(), dependency);
66                  }
67              } else {
68                  depMgmt = DependencyManagement.newInstance();
69              }
70  
71              Set<String> directDependencies = new HashSet<>(dependencies.keySet());
72  
73              for (DependencyManagement source : sources) {
74                  for (Dependency dependency : source.getDependencies()) {
75                      String key = dependency.getManagementKey();
76                      Dependency present = dependencies.putIfAbsent(key, dependency);
77                      if (present != null && !equals(dependency, present) && !directDependencies.contains(key)) {
78                          // TODO: https://issues.apache.org/jira/browse/MNG-8004
79                          problems.add(
80                                  Severity.WARNING,
81                                  Version.V40,
82                                  "Ignored POM import for: " + toString(dependency) + " as already imported "
83                                          + toString(present) + ". Add the conflicting managed dependency directly "
84                                          + "to the dependencyManagement section of the POM.");
85                      }
86                      if (present == null && request.isLocationTracking()) {
87                          Dependency updatedDependency = updateWithImportedFrom(dependency, source);
88                          dependencies.put(key, updatedDependency);
89                      }
90                  }
91              }
92  
93              return target.withDependencyManagement(depMgmt.withDependencies(dependencies.values()));
94          }
95          return target;
96      }
97  
98      private String toString(Dependency dependency) {
99          StringBuilder stringBuilder = new StringBuilder();
100         stringBuilder
101                 .append(dependency.getGroupId())
102                 .append(":")
103                 .append(dependency.getArtifactId())
104                 .append(":")
105                 .append(dependency.getType());
106         if (dependency.getClassifier() != null && !dependency.getClassifier().isEmpty()) {
107             stringBuilder.append(":").append(dependency.getClassifier());
108         }
109         stringBuilder
110                 .append(":")
111                 .append(dependency.getVersion())
112                 .append("@")
113                 .append(dependency.getScope() == null ? "compile" : dependency.getScope());
114         if (dependency.isOptional()) {
115             stringBuilder.append("[optional]");
116         }
117         if (!dependency.getExclusions().isEmpty()) {
118             stringBuilder.append("[").append(dependency.getExclusions().size()).append(" exclusions]");
119         }
120         return stringBuilder.toString();
121     }
122 
123     private boolean equals(Dependency d1, Dependency d2) {
124         return Objects.equals(d1.getGroupId(), d2.getGroupId())
125                 && Objects.equals(d1.getArtifactId(), d2.getArtifactId())
126                 && Objects.equals(d1.getVersion(), d2.getVersion())
127                 && Objects.equals(d1.getType(), d2.getType())
128                 && Objects.equals(d1.getClassifier(), d2.getClassifier())
129                 && Objects.equals(d1.getScope(), d2.getScope())
130                 && Objects.equals(d1.getSystemPath(), d2.getSystemPath())
131                 && Objects.equals(d1.getOptional(), d2.getOptional())
132                 && equals(d1.getExclusions(), d2.getExclusions());
133     }
134 
135     private boolean equals(Collection<Exclusion> ce1, Collection<Exclusion> ce2) {
136         if (ce1.size() == ce2.size()) {
137             Iterator<Exclusion> i1 = ce1.iterator();
138             Iterator<Exclusion> i2 = ce2.iterator();
139             while (i1.hasNext() && i2.hasNext()) {
140                 if (!equals(i1.next(), i2.next())) {
141                     return false;
142                 }
143             }
144             return !i1.hasNext() && !i2.hasNext();
145         }
146         return false;
147     }
148 
149     private boolean equals(Exclusion e1, Exclusion e2) {
150         return Objects.equals(e1.getGroupId(), e2.getGroupId())
151                 && Objects.equals(e1.getArtifactId(), e2.getArtifactId());
152     }
153 
154     static Dependency updateWithImportedFrom(Dependency dependency, DependencyManagement bom) {
155         // We are only interested in the InputSource, so the location of the <dependency> element is sufficient
156         InputLocation dependencyLocation = dependency.getLocation("");
157         InputLocation bomLocation = bom.getLocation("");
158 
159         if (dependencyLocation == null || bomLocation == null) {
160             return dependency;
161         }
162 
163         InputSource dependencySource = dependencyLocation.getSource();
164         InputSource bomSource = bomLocation.getSource();
165 
166         // If the dependency and BOM have the same source, it means we found the root where the dependency is declared.
167         if (dependencySource == null
168                 || bomSource == null
169                 || Objects.equals(dependencySource.getModelId(), bomSource.getModelId())) {
170             return Dependency.newBuilder(dependency, true)
171                     .importedFrom(bomLocation)
172                     .build();
173         }
174 
175         while (dependencySource.getImportedFrom() != null) {
176             InputLocation importedFrom = dependencySource.getImportedFrom();
177 
178             // Stop if the BOM is already in the list, no update necessary
179             if (Objects.equals(importedFrom.getSource().getModelId(), bomSource.getModelId())) {
180                 return dependency;
181             }
182 
183             dependencySource = importedFrom.getSource();
184         }
185 
186         // We modify the input location that is used for the whole file.
187         // This is likely correct because the POM hierarchy applies to the whole POM, not just one dependency.
188         return Dependency.newBuilder(dependency, true)
189                 .importedFrom(new InputLocation(bomLocation))
190                 .build();
191     }
192 }