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.transformation.impl;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  
24  import java.nio.file.Path;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.stream.Collectors;
28  
29  import org.apache.maven.api.SessionData;
30  import org.apache.maven.api.model.Dependency;
31  import org.apache.maven.api.model.DependencyManagement;
32  import org.apache.maven.api.model.DistributionManagement;
33  import org.apache.maven.api.model.Model;
34  import org.apache.maven.api.model.ModelBase;
35  import org.apache.maven.api.model.Profile;
36  import org.apache.maven.api.model.Repository;
37  import org.apache.maven.api.services.ModelBuilder;
38  import org.apache.maven.api.services.ModelBuilderException;
39  import org.apache.maven.api.services.ModelBuilderRequest;
40  import org.apache.maven.api.services.ModelBuilderResult;
41  import org.apache.maven.api.services.ModelSource;
42  import org.apache.maven.api.services.model.LifecycleBindingsInjector;
43  import org.apache.maven.internal.impl.InternalSession;
44  import org.apache.maven.model.v4.MavenModelVersion;
45  import org.apache.maven.project.MavenProject;
46  import org.eclipse.aether.RepositorySystemSession;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  @Named
51  class DefaultConsumerPomBuilder implements ConsumerPomBuilder {
52      private static final String BOM_PACKAGING = "bom";
53  
54      public static final String POM_PACKAGING = "pom";
55  
56      private static final Logger LOGGER = LoggerFactory.getLogger(DefaultConsumerPomBuilder.class);
57  
58      private final LifecycleBindingsInjector lifecycleBindingsInjector;
59  
60      @Inject
61      @SuppressWarnings("checkstyle:ParameterNumber")
62      DefaultConsumerPomBuilder(LifecycleBindingsInjector lifecycleBindingsInjector) {
63          this.lifecycleBindingsInjector = lifecycleBindingsInjector;
64      }
65  
66      @Override
67      public Model build(RepositorySystemSession session, MavenProject project, Path src) throws ModelBuilderException {
68          Model model = project.getModel().getDelegate();
69          String packaging = model.getPackaging();
70          String originalPackaging = project.getOriginalModel().getPackaging();
71          if (POM_PACKAGING.equals(packaging) && !BOM_PACKAGING.equals(originalPackaging)) {
72              return buildPom(session, project, src);
73          } else {
74              return buildNonPom(session, project, src);
75          }
76      }
77  
78      protected Model buildPom(RepositorySystemSession session, MavenProject project, Path src)
79              throws ModelBuilderException {
80          ModelBuilderResult result = buildModel(session, project, src);
81          Model model = result.getRawModel();
82          return transform(model, project);
83      }
84  
85      protected Model buildNonPom(RepositorySystemSession session, MavenProject project, Path src)
86              throws ModelBuilderException {
87          ModelBuilderResult result = buildModel(session, project, src);
88          Model model = result.getEffectiveModel();
89          return transform(model, project);
90      }
91  
92      private ModelBuilderResult buildModel(RepositorySystemSession session, MavenProject project, Path src)
93              throws ModelBuilderException {
94          InternalSession iSession = InternalSession.from(session);
95          ModelBuilderRequest.ModelBuilderRequestBuilder request = ModelBuilderRequest.builder();
96          request.requestType(ModelBuilderRequest.RequestType.BUILD_CONSUMER);
97          request.session(iSession);
98          request.source(ModelSource.fromPath(src));
99          request.locationTracking(false);
100         request.systemProperties(session.getSystemProperties());
101         request.userProperties(session.getUserProperties());
102         request.lifecycleBindingsInjector(lifecycleBindingsInjector::injectLifecycleBindings);
103         ModelBuilder.ModelBuilderSession mbSession =
104                 iSession.getData().get(SessionData.key(ModelBuilder.ModelBuilderSession.class));
105         return mbSession.build(request.build());
106     }
107 
108     static Model transform(Model model, MavenProject project) {
109         String packaging = model.getPackaging();
110         boolean preserveModelVersion = model.isPreserveModelVersion();
111         if (POM_PACKAGING.equals(packaging)) {
112             // raw to consumer transform
113             model = model.withRoot(false).withModules(null).withSubprojects(null);
114             if (model.getParent() != null) {
115                 model = model.withParent(model.getParent().withRelativePath(null));
116             }
117 
118             if (!preserveModelVersion) {
119                 model = model.withPreserveModelVersion(false);
120                 String modelVersion = new MavenModelVersion().getModelVersion(model);
121                 model = model.withModelVersion(modelVersion);
122             }
123         } else if (BOM_PACKAGING.equals(packaging)) {
124             DependencyManagement dependencyManagement =
125                     project.getOriginalModel().getDependencyManagement().getDelegate();
126             List<Dependency> dependencies = new ArrayList<>();
127             String version = model.getVersion();
128 
129             dependencyManagement
130                     .getDependencies()
131                     .forEach((dependency) -> dependencies.add(dependency.withVersion(version)));
132             Model.Builder builder = prune(
133                     Model.newBuilder(model, true)
134                             .preserveModelVersion(false)
135                             .root(false)
136                             .parent(null)
137                             .dependencyManagement(dependencyManagement.withDependencies(dependencies))
138                             .build(null),
139                     model);
140             builder.packaging(POM_PACKAGING);
141             builder.profiles(prune(model.getProfiles()));
142 
143             model = builder.build();
144             String modelVersion = new MavenModelVersion().getModelVersion(model);
145             if (!ModelBuilder.MODEL_VERSION_4_0_0.equals(modelVersion) && !preserveModelVersion) {
146                 warnNotDowngraded(project);
147             }
148             model = model.withModelVersion(modelVersion);
149         } else {
150             Model.Builder builder = prune(
151                     Model.newBuilder(model, true)
152                             .preserveModelVersion(false)
153                             .root(false)
154                             .parent(null)
155                             .build(null),
156                     model);
157             builder.profiles(prune(model.getProfiles()));
158 
159             model = builder.build();
160             String modelVersion = new MavenModelVersion().getModelVersion(model);
161             if (!ModelBuilder.MODEL_VERSION_4_0_0.equals(modelVersion) && !preserveModelVersion) {
162                 warnNotDowngraded(project);
163             }
164             model = model.withModelVersion(modelVersion);
165         }
166         return model;
167     }
168 
169     static void warnNotDowngraded(MavenProject project) {
170         LOGGER.warn("The consumer POM for " + project.getId() + " cannot be downgraded to 4.0.0. "
171                 + "If you intent your build to be consumed with Maven 3 projects, you need to remove "
172                 + "the features that request a newer model version.  If you're fine with having the "
173                 + "consumer POM not consumable with Maven 3, add the `preserve.model.version='true'` "
174                 + "attribute on the <project> element of your POM.");
175     }
176 
177     private static List<Profile> prune(List<Profile> profiles) {
178         return profiles.stream()
179                 .map(p -> {
180                     Profile.Builder builder = Profile.newBuilder(p, true);
181                     prune((ModelBase.Builder) builder, p);
182                     return builder.build(null).build();
183                 })
184                 .filter(p -> !isEmpty(p))
185                 .collect(Collectors.toList());
186     }
187 
188     private static boolean isEmpty(Profile profile) {
189         return profile.getActivation() == null
190                 && profile.getBuild() == null
191                 && profile.getDependencies().isEmpty()
192                 && (profile.getDependencyManagement() == null
193                         || profile.getDependencyManagement().getDependencies().isEmpty())
194                 && profile.getDistributionManagement() == null
195                 && profile.getModules().isEmpty()
196                 && profile.getSubprojects().isEmpty()
197                 && profile.getProperties().isEmpty()
198                 && profile.getRepositories().isEmpty()
199                 && profile.getPluginRepositories().isEmpty()
200                 && profile.getReporting() == null;
201     }
202 
203     private static <T extends ModelBase.Builder> T prune(T builder, ModelBase model) {
204         builder.properties(null).reporting(null);
205         if (model.getDistributionManagement() != null
206                 && model.getDistributionManagement().getRelocation() != null) {
207             // keep relocation only
208             builder.distributionManagement(DistributionManagement.newBuilder()
209                     .relocation(model.getDistributionManagement().getRelocation())
210                     .build());
211         }
212         // only keep repositories other than 'central'
213         builder.pluginRepositories(pruneRepositories(model.getPluginRepositories()));
214         builder.repositories(pruneRepositories(model.getRepositories()));
215         return builder;
216     }
217 
218     private static List<Repository> pruneRepositories(List<Repository> repositories) {
219         return repositories.stream()
220                 .filter(r -> !org.apache.maven.api.Repository.CENTRAL_ID.equals(r.getId()))
221                 .collect(Collectors.toList());
222     }
223 }