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.nio.file.Files;
22  import java.nio.file.Path;
23  import java.nio.file.Paths;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.List;
27  import java.util.Objects;
28  import java.util.Optional;
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.Model;
34  import org.apache.maven.api.model.Parent;
35  import org.apache.maven.api.services.ModelTransformer;
36  import org.apache.maven.api.services.ModelTransformerContext;
37  
38  /**
39   * ModelSourceTransformer for the build pom
40   *
41   * @since 4.0.0
42   */
43  @Named
44  @Singleton
45  public class BuildModelTransformer implements ModelTransformer {
46  
47      public static final String NAMESPACE_PREFIX = "http://maven.apache.org/POM/";
48  
49      @Override
50      public Model transform(ModelTransformerContext context, Model model, Path path) {
51          Model.Builder builder = Model.newBuilder(model);
52          handleModelVersion(context, model, path, builder);
53          handleParent(context, model, path, builder);
54          handleReactorDependencies(context, model, path, builder);
55          handleCiFriendlyVersion(context, model, path, builder);
56          return builder.build();
57      }
58  
59      //
60      // Infer modelVersion from namespace URI
61      //
62      void handleModelVersion(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
63          String namespace = model.getNamespaceUri();
64          if (model.getModelVersion() == null && namespace != null && namespace.startsWith(NAMESPACE_PREFIX)) {
65              builder.modelVersion(namespace.substring(NAMESPACE_PREFIX.length()));
66          }
67      }
68  
69      //
70      // Infer parent information
71      //
72      void handleParent(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
73          Parent parent = model.getParent();
74          if (parent != null) {
75              String version = parent.getVersion();
76              String path = Optional.ofNullable(parent.getRelativePath()).orElse("..");
77              if (version == null && !path.isEmpty()) {
78                  Optional<RelativeProject> resolvedParent = resolveRelativePath(
79                          pomFile, context, Paths.get(path), parent.getGroupId(), parent.getArtifactId());
80                  if (resolvedParent.isPresent()) {
81                      version = resolvedParent.get().getVersion();
82                  }
83              }
84              // CI Friendly version for parent
85              String modVersion = replaceCiFriendlyVersion(context, version);
86              builder.parent(parent.withVersion(modVersion));
87          }
88      }
89  
90      //
91      // CI friendly versions
92      //
93      void handleCiFriendlyVersion(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
94          String version = model.getVersion();
95          String modVersion = replaceCiFriendlyVersion(context, version);
96          builder.version(modVersion);
97      }
98  
99      //
100     // Infer inner reactor dependencies version
101     //
102     void handleReactorDependencies(ModelTransformerContext context, Model model, Path pomFile, Model.Builder builder) {
103         List<Dependency> newDeps = new ArrayList<>();
104         boolean modified = false;
105         for (Dependency dep : model.getDependencies()) {
106             if (dep.getVersion() == null) {
107                 Model depModel = context.getRawModel(model.getPomFile(), dep.getGroupId(), dep.getArtifactId());
108                 if (depModel != null) {
109                     String v = depModel.getVersion();
110                     if (v == null && depModel.getParent() != null) {
111                         v = depModel.getParent().getVersion();
112                     }
113                     dep = dep.withVersion(v);
114                     modified = true;
115                 }
116             }
117             newDeps.add(dep);
118         }
119         if (modified) {
120             builder.dependencies(newDeps);
121         }
122     }
123 
124     protected String replaceCiFriendlyVersion(ModelTransformerContext context, String version) {
125         if (version != null) {
126             for (String key : Arrays.asList("changelist", "revision", "sha1")) {
127                 String val = context.getUserProperty(key);
128                 if (val != null) {
129                     version = version.replace("${" + key + "}", val);
130                 }
131             }
132         }
133         return version;
134     }
135 
136     protected Optional<RelativeProject> resolveRelativePath(
137             Path pomFile, ModelTransformerContext context, Path relativePath, String groupId, String artifactId) {
138         Path pomPath = pomFile.resolveSibling(relativePath).normalize();
139         if (Files.isDirectory(pomPath)) {
140             pomPath = context.locate(pomPath);
141         }
142 
143         if (pomPath == null || !Files.isRegularFile(pomPath)) {
144             return Optional.empty();
145         }
146 
147         Optional<RelativeProject> mappedProject = Optional.ofNullable(context.getRawModel(pomFile, pomPath.normalize()))
148                 .map(BuildModelTransformer::toRelativeProject);
149 
150         if (mappedProject.isPresent()) {
151             RelativeProject project = mappedProject.get();
152 
153             if (Objects.equals(groupId, project.getGroupId()) && Objects.equals(artifactId, project.getArtifactId())) {
154                 return mappedProject;
155             }
156         }
157         return Optional.empty();
158     }
159 
160     private static RelativeProject toRelativeProject(final Model m) {
161         String groupId = m.getGroupId();
162         if (groupId == null && m.getParent() != null) {
163             groupId = m.getParent().getGroupId();
164         }
165 
166         String version = m.getVersion();
167         if (version == null && m.getParent() != null) {
168             version = m.getParent().getVersion();
169         }
170 
171         return new RelativeProject(groupId, m.getArtifactId(), version);
172     }
173 
174     protected static class RelativeProject {
175         private final String groupId;
176 
177         private final String artifactId;
178 
179         private final String version;
180 
181         protected RelativeProject(String groupId, String artifactId, String version) {
182             this.groupId = groupId;
183             this.artifactId = artifactId;
184             this.version = version;
185         }
186 
187         public String getGroupId() {
188             return groupId;
189         }
190 
191         public String getArtifactId() {
192             return artifactId;
193         }
194 
195         public String getVersion() {
196             return version;
197         }
198     }
199 }