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.Path;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Optional;
28  import java.util.Set;
29  import java.util.function.BiFunction;
30  import java.util.function.Function;
31  
32  import org.apache.maven.api.di.Inject;
33  import org.apache.maven.api.di.Named;
34  import org.apache.maven.api.di.Singleton;
35  import org.apache.maven.api.model.Model;
36  import org.apache.maven.api.services.BuilderProblem;
37  import org.apache.maven.api.services.Interpolator;
38  import org.apache.maven.api.services.InterpolatorException;
39  import org.apache.maven.api.services.ModelBuilderRequest;
40  import org.apache.maven.api.services.ModelProblem;
41  import org.apache.maven.api.services.ModelProblemCollector;
42  import org.apache.maven.api.services.model.ModelInterpolator;
43  import org.apache.maven.api.services.model.PathTranslator;
44  import org.apache.maven.api.services.model.RootLocator;
45  import org.apache.maven.api.services.model.UrlNormalizer;
46  import org.apache.maven.internal.impl.model.reflection.ReflectionValueExtractor;
47  import org.apache.maven.model.v4.MavenTransformer;
48  
49  @Named
50  @Singleton
51  public class DefaultModelInterpolator implements ModelInterpolator {
52  
53      private static final String PREFIX_PROJECT = "project.";
54      private static final String PREFIX_POM = "pom.";
55      private static final List<String> PROJECT_PREFIXES_3_1 = Arrays.asList(PREFIX_POM, PREFIX_PROJECT);
56      private static final List<String> PROJECT_PREFIXES_4_0 = Collections.singletonList(PREFIX_PROJECT);
57  
58      // MNG-1927, MNG-2124, MNG-3355:
59      // If the build section is present and the project directory is non-null, we should make
60      // sure interpolation of the directories below uses translated paths.
61      // Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
62      // code below...
63      private static final Set<String> TRANSLATED_PATH_EXPRESSIONS = Set.of(
64              "build.directory",
65              "build.outputDirectory",
66              "build.testOutputDirectory",
67              "build.sourceDirectory",
68              "build.testSourceDirectory",
69              "build.scriptSourceDirectory",
70              "reporting.outputDirectory");
71  
72      private static final Set<String> URL_EXPRESSIONS = Set.of(
73              "project.url",
74              "project.scm.url",
75              "project.scm.connection",
76              "project.scm.developerConnection",
77              "project.distributionManagement.site.url");
78  
79      private final PathTranslator pathTranslator;
80      private final UrlNormalizer urlNormalizer;
81      private final RootLocator rootLocator;
82      private final Interpolator interpolator;
83  
84      @Inject
85      public DefaultModelInterpolator(
86              PathTranslator pathTranslator,
87              UrlNormalizer urlNormalizer,
88              RootLocator rootLocator,
89              Interpolator interpolator) {
90          this.pathTranslator = pathTranslator;
91          this.urlNormalizer = urlNormalizer;
92          this.rootLocator = rootLocator;
93          this.interpolator = interpolator;
94      }
95  
96      interface InnerInterpolator {
97          String interpolate(String value);
98      }
99  
100     @Override
101     public Model interpolateModel(
102             Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
103         InnerInterpolator innerInterpolator = createInterpolator(model, projectDir, request, problems);
104         return new MavenTransformer(innerInterpolator::interpolate).visit(model);
105     }
106 
107     private InnerInterpolator createInterpolator(
108             Model model, Path projectDir, ModelBuilderRequest request, ModelProblemCollector problems) {
109 
110         Map<String, Optional<String>> cache = new HashMap<>();
111         Function<String, Optional<String>> ucb =
112                 v -> Optional.ofNullable(callback(model, projectDir, request, problems, v));
113         Function<String, String> cb = v -> cache.computeIfAbsent(v, ucb).orElse(null);
114         BiFunction<String, String, String> postprocessor = (e, v) -> postProcess(projectDir, request, e, v);
115         return value -> {
116             try {
117                 return interpolator.interpolate(value, cb, postprocessor, false);
118             } catch (InterpolatorException e) {
119                 problems.add(BuilderProblem.Severity.ERROR, ModelProblem.Version.BASE, e.getMessage(), e);
120                 return null;
121             }
122         };
123     }
124 
125     protected List<String> getProjectPrefixes(ModelBuilderRequest request) {
126         return request.getRequestType() == ModelBuilderRequest.RequestType.BUILD_POM
127                 ? PROJECT_PREFIXES_4_0
128                 : PROJECT_PREFIXES_3_1;
129     }
130 
131     String callback(
132             Model model,
133             Path projectDir,
134             ModelBuilderRequest request,
135             ModelProblemCollector problems,
136             String expression) {
137         String value = doCallback(model, projectDir, request, problems, expression);
138         if (value != null) {
139             // value = postProcess(projectDir, request, expression, value);
140         }
141         return value;
142     }
143 
144     private String postProcess(Path projectDir, ModelBuilderRequest request, String expression, String value) {
145         // path translation
146         String exp = unprefix(expression, getProjectPrefixes(request));
147         if (TRANSLATED_PATH_EXPRESSIONS.contains(exp)) {
148             value = pathTranslator.alignToBaseDirectory(value, projectDir);
149         }
150         // normalize url
151         if (URL_EXPRESSIONS.contains(expression)) {
152             value = urlNormalizer.normalize(value);
153         }
154         return value;
155     }
156 
157     private String unprefix(String expression, List<String> prefixes) {
158         for (String prefix : prefixes) {
159             if (expression.startsWith(prefix)) {
160                 return expression.substring(prefix.length());
161             }
162         }
163         return expression;
164     }
165 
166     String doCallback(
167             Model model,
168             Path projectDir,
169             ModelBuilderRequest request,
170             ModelProblemCollector problems,
171             String expression) {
172         // timestamp
173         if ("build.timestamp".equals(expression) || "maven.build.timestamp".equals(expression)) {
174             return new MavenBuildTimestamp(request.getSession().getStartTime(), model.getProperties())
175                     .formattedTimestamp();
176         }
177         // prefixed model reflection
178         for (String prefix : getProjectPrefixes(request)) {
179             if (expression.startsWith(prefix)) {
180                 String subExpr = expression.substring(prefix.length());
181                 String v = projectProperty(model, projectDir, subExpr, true);
182                 if (v != null) {
183                     return v;
184                 }
185             }
186         }
187         // user properties
188         String value = request.getUserProperties().get(expression);
189         // model properties
190         if (value == null) {
191             value = model.getProperties().get(expression);
192         }
193         // system properties
194         if (value == null) {
195             value = request.getSystemProperties().get(expression);
196         }
197         // environment variables
198         if (value == null) {
199             value = request.getSystemProperties().get("env." + expression);
200         }
201         if (value != null) {
202             return value;
203         }
204         // model reflection
205         return projectProperty(model, projectDir, expression, false);
206     }
207 
208     String projectProperty(Model model, Path projectDir, String subExpr, boolean prefixed) {
209         if (projectDir != null) {
210             if (subExpr.equals("basedir")) {
211                 return projectDir.toAbsolutePath().toString();
212             } else if (subExpr.startsWith("basedir.")) {
213                 try {
214                     Object value = ReflectionValueExtractor.evaluate(subExpr, projectDir.toAbsolutePath(), false);
215                     if (value != null) {
216                         return value.toString();
217                     }
218                 } catch (Exception e) {
219                     // addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
220                 }
221             } else if (prefixed && subExpr.equals("baseUri")) {
222                 return projectDir.toAbsolutePath().toUri().toASCIIString();
223             } else if (prefixed && subExpr.startsWith("baseUri.")) {
224                 try {
225                     Object value = ReflectionValueExtractor.evaluate(
226                             subExpr, projectDir.toAbsolutePath().toUri(), false);
227                     if (value != null) {
228                         return value.toString();
229                     }
230                 } catch (Exception e) {
231                     // addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
232                 }
233             } else if (prefixed && subExpr.equals("rootDirectory")) {
234                 return rootLocator.findMandatoryRoot(projectDir).toString();
235             } else if (prefixed && subExpr.startsWith("rootDirectory.")) {
236                 try {
237                     Object value = ReflectionValueExtractor.evaluate(
238                             subExpr, projectDir.toAbsolutePath().toUri(), false);
239                     if (value != null) {
240                         return value.toString();
241                     }
242                 } catch (Exception e) {
243                     // addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
244                 }
245             }
246         }
247         try {
248             Object value = ReflectionValueExtractor.evaluate(subExpr, model, false);
249             if (value != null) {
250                 return value.toString();
251             }
252         } catch (Exception e) {
253             // addFeedback("Failed to extract \'" + expression + "\' from: " + root, e);
254         }
255         return null;
256     }
257 }