1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
59
60
61
62
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
140 }
141 return value;
142 }
143
144 private String postProcess(Path projectDir, ModelBuilderRequest request, String expression, String value) {
145
146 String exp = unprefix(expression, getProjectPrefixes(request));
147 if (TRANSLATED_PATH_EXPRESSIONS.contains(exp)) {
148 value = pathTranslator.alignToBaseDirectory(value, projectDir);
149 }
150
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
173 if ("build.timestamp".equals(expression) || "maven.build.timestamp".equals(expression)) {
174 return new MavenBuildTimestamp(request.getSession().getStartTime(), model.getProperties())
175 .formattedTimestamp();
176 }
177
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
188 String value = request.getUserProperties().get(expression);
189
190 if (value == null) {
191 value = model.getProperties().get(expression);
192 }
193
194 if (value == null) {
195 value = request.getSystemProperties().get(expression);
196 }
197
198 if (value == null) {
199 value = request.getSystemProperties().get("env." + expression);
200 }
201 if (value != null) {
202 return value;
203 }
204
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
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
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
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
254 }
255 return null;
256 }
257 }