1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.impl.model;
20
21 import java.nio.file.FileSystem;
22 import java.nio.file.Path;
23 import java.nio.file.Paths;
24 import java.time.Instant;
25 import java.time.ZoneId;
26 import java.time.format.DateTimeFormatter;
27 import java.util.Calendar;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.TimeZone;
35 import java.util.concurrent.atomic.AtomicReference;
36
37 import com.google.common.jimfs.Configuration;
38 import com.google.common.jimfs.Jimfs;
39 import org.apache.maven.api.Session;
40 import org.apache.maven.api.di.Priority;
41 import org.apache.maven.api.di.Provides;
42 import org.apache.maven.api.model.Build;
43 import org.apache.maven.api.model.Dependency;
44 import org.apache.maven.api.model.Model;
45 import org.apache.maven.api.model.Organization;
46 import org.apache.maven.api.model.Repository;
47 import org.apache.maven.api.model.Resource;
48 import org.apache.maven.api.model.Scm;
49 import org.apache.maven.api.services.Lookup;
50 import org.apache.maven.api.services.ModelBuilderRequest;
51 import org.apache.maven.api.services.model.ModelInterpolator;
52 import org.apache.maven.api.services.model.RootLocator;
53 import org.apache.maven.impl.model.profile.SimpleProblemCollector;
54 import org.apache.maven.impl.standalone.ApiRunner;
55 import org.junit.jupiter.api.BeforeEach;
56 import org.junit.jupiter.api.Disabled;
57 import org.junit.jupiter.api.Test;
58
59 import static org.junit.jupiter.api.Assertions.assertEquals;
60 import static org.junit.jupiter.api.Assertions.assertNotNull;
61 import static org.junit.jupiter.api.Assertions.assertThrows;
62 import static org.junit.jupiter.api.Assertions.assertTrue;
63
64
65
66 class DefaultModelInterpolatorTest {
67
68 Map<String, String> context;
69 ModelInterpolator interpolator;
70 Session session;
71 AtomicReference<Path> rootDirectory;
72
73 @BeforeEach
74 public void setUp() {
75 context = new HashMap<>();
76 context.put("basedir", "myBasedir");
77 context.put("anotherdir", "anotherBasedir");
78 context.put("project.baseUri", "myBaseUri");
79
80 session = ApiRunner.createSession(injector -> {
81 injector.bindInstance(DefaultModelInterpolatorTest.class, this);
82 });
83 interpolator = session.getService(Lookup.class).lookup(DefaultModelInterpolator.class);
84 }
85
86 protected void assertProblemFree(SimpleProblemCollector collector) {
87 assertEquals(0, collector.getErrors().size(), "Expected no errors");
88 assertEquals(0, collector.getWarnings().size(), "Expected no warnings");
89 assertEquals(0, collector.getFatals().size(), "Expected no fatals");
90 }
91
92 @SuppressWarnings("SameParameterValue")
93 protected void assertCollectorState(
94 int numFatals, int numErrors, int numWarnings, SimpleProblemCollector collector) {
95 assertEquals(numErrors, collector.getErrors().size(), "Errors");
96 assertEquals(numWarnings, collector.getWarnings().size(), "Warnings");
97 assertEquals(numFatals, collector.getFatals().size(), "Fatals");
98 }
99
100 private ModelBuilderRequest.ModelBuilderRequestBuilder createModelBuildingRequest(Map<String, String> p) {
101 ModelBuilderRequest.ModelBuilderRequestBuilder config = ModelBuilderRequest.builder()
102 .session(session)
103 .requestType(ModelBuilderRequest.RequestType.BUILD_PROJECT);
104 if (p != null) {
105 config.systemProperties(p);
106 }
107 return config;
108 }
109
110 @Test
111 public void testDefaultBuildTimestampFormatShouldFormatTimeIn24HourFormat() {
112 Calendar cal = Calendar.getInstance();
113 cal.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
114 cal.set(Calendar.HOUR, 12);
115 cal.set(Calendar.AM_PM, Calendar.AM);
116
117
118 cal.set(Calendar.HOUR_OF_DAY, 0);
119 cal.set(Calendar.MINUTE, 16);
120 cal.set(Calendar.SECOND, 0);
121 cal.set(Calendar.YEAR, 1976);
122 cal.set(Calendar.MONTH, Calendar.NOVEMBER);
123 cal.set(Calendar.DATE, 11);
124
125 Instant firstTestDate = Instant.ofEpochMilli(cal.getTime().getTime());
126
127 cal.set(Calendar.HOUR, 11);
128 cal.set(Calendar.AM_PM, Calendar.PM);
129
130
131 cal.set(Calendar.HOUR_OF_DAY, 23);
132
133 Instant secondTestDate = Instant.ofEpochMilli(cal.getTime().getTime());
134
135 DateTimeFormatter format = DateTimeFormatter.ofPattern(MavenBuildTimestamp.DEFAULT_BUILD_TIMESTAMP_FORMAT)
136 .withZone(ZoneId.of("UTC"));
137
138 assertEquals("1976-11-11T00:16:00Z", format.format(firstTestDate));
139 assertEquals("1976-11-11T23:16:00Z", format.format(secondTestDate));
140 }
141
142 @Test
143 public void testDefaultBuildTimestampFormatWithLocalTimeZoneMidnightRollover() {
144 Calendar cal = Calendar.getInstance();
145 cal.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
146
147 cal.set(Calendar.HOUR_OF_DAY, 1);
148 cal.set(Calendar.MINUTE, 16);
149 cal.set(Calendar.SECOND, 0);
150 cal.set(Calendar.YEAR, 2014);
151 cal.set(Calendar.MONTH, Calendar.JUNE);
152 cal.set(Calendar.DATE, 16);
153
154 Instant firstTestDate = Instant.ofEpochMilli(cal.getTime().getTime());
155
156 cal.set(Calendar.MONTH, Calendar.NOVEMBER);
157
158 Instant secondTestDate = Instant.ofEpochMilli(cal.getTime().getTime());
159
160 DateTimeFormatter format = DateTimeFormatter.ofPattern(MavenBuildTimestamp.DEFAULT_BUILD_TIMESTAMP_FORMAT)
161 .withZone(ZoneId.of("UTC"));
162 assertEquals("2014-06-15T23:16:00Z", format.format(firstTestDate));
163 assertEquals("2014-11-16T00:16:00Z", format.format(secondTestDate));
164 }
165
166 @Test
167 public void testShouldNotThrowExceptionOnReferenceToNonExistentValue() throws Exception {
168 Scm scm = Scm.newBuilder().connection("${test}/somepath").build();
169 Model model = Model.newBuilder().scm(scm).build();
170
171 final SimpleProblemCollector collector = new SimpleProblemCollector();
172 Model out = interpolator.interpolateModel(
173 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
174
175 assertProblemFree(collector);
176 assertEquals("${test}/somepath", out.getScm().getConnection());
177 }
178
179 @Test
180 public void testShouldThrowExceptionOnRecursiveScmConnectionReference() throws Exception {
181 Scm scm = Scm.newBuilder()
182 .connection("${project.scm.connection}/somepath")
183 .build();
184 Model model = Model.newBuilder().scm(scm).build();
185
186 final SimpleProblemCollector collector = new SimpleProblemCollector();
187 interpolator.interpolateModel(
188 model, null, createModelBuildingRequest(context).build(), collector);
189 assertCollectorState(0, 1, 0, collector);
190 }
191
192 @Test
193 public void testShouldNotThrowExceptionOnReferenceToValueContainingNakedExpression() throws Exception {
194 Scm scm = Scm.newBuilder().connection("${test}/somepath").build();
195 Map<String, String> props = new HashMap<>();
196 props.put("test", "test");
197 Model model = Model.newBuilder().scm(scm).properties(props).build();
198
199 final SimpleProblemCollector collector = new SimpleProblemCollector();
200 Model out = interpolator.interpolateModel(
201 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
202
203 assertProblemFree(collector);
204
205 assertEquals("test/somepath", out.getScm().getConnection());
206 }
207
208 @Test
209 void shouldInterpolateOrganizationNameCorrectly() throws Exception {
210 String orgName = "MyCo";
211
212 Model model = Model.newBuilder()
213 .name("${project.organization.name} Tools")
214 .organization(Organization.newBuilder().name(orgName).build())
215 .build();
216
217 Model out = interpolator.interpolateModel(
218 model, Paths.get("."), createModelBuildingRequest(context).build(), new SimpleProblemCollector());
219
220 assertEquals(orgName + " Tools", out.getName());
221 }
222
223 @Test
224 public void shouldInterpolateDependencyVersionToSetSameAsProjectVersion() throws Exception {
225 Model model = Model.newBuilder()
226 .version("3.8.1")
227 .dependencies(Collections.singletonList(
228 Dependency.newBuilder().version("${project.version}").build()))
229 .build();
230
231 final SimpleProblemCollector collector = new SimpleProblemCollector();
232 Model out = interpolator.interpolateModel(
233 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
234 assertCollectorState(0, 0, 0, collector);
235
236 assertEquals("3.8.1", (out.getDependencies().get(0)).getVersion());
237 }
238
239 @Test
240 public void testShouldNotInterpolateDependencyVersionWithInvalidReference() throws Exception {
241 Model model = Model.newBuilder()
242 .version("3.8.1")
243 .dependencies(Collections.singletonList(
244 Dependency.newBuilder().version("${something}").build()))
245 .build();
246
247 final SimpleProblemCollector collector = new SimpleProblemCollector();
248 Model out = interpolator.interpolateModel(
249 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
250 assertProblemFree(collector);
251
252 assertEquals("${something}", (out.getDependencies().get(0)).getVersion());
253 }
254
255 @Test
256 public void testTwoReferences() throws Exception {
257 Model model = Model.newBuilder()
258 .version("3.8.1")
259 .artifactId("foo")
260 .dependencies(Collections.singletonList(Dependency.newBuilder()
261 .version("${project.artifactId}-${project.version}")
262 .build()))
263 .build();
264
265 final SimpleProblemCollector collector = new SimpleProblemCollector();
266 Model out = interpolator.interpolateModel(
267 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
268 assertCollectorState(0, 0, 0, collector);
269
270 assertEquals("foo-3.8.1", (out.getDependencies().get(0)).getVersion());
271 }
272
273 @Test
274 public void testProperty() throws Exception {
275 Model model = Model.newBuilder()
276 .version("3.8.1")
277 .artifactId("foo")
278 .repositories(Collections.singletonList(Repository.newBuilder()
279 .url("file://localhost/${anotherdir}/temp-repo")
280 .build()))
281 .build();
282
283 final SimpleProblemCollector collector = new SimpleProblemCollector();
284 Model out = interpolator.interpolateModel(
285 model,
286 Paths.get("projectBasedir"),
287 createModelBuildingRequest(context).build(),
288 collector);
289 assertProblemFree(collector);
290
291 assertEquals(
292 "file://localhost/anotherBasedir/temp-repo",
293 (out.getRepositories().get(0)).getUrl());
294 }
295
296 @Test
297 public void testBasedirUnx() throws Exception {
298 FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
299 Path projectBasedir = fs.getPath("projectBasedir");
300
301 Model model = Model.newBuilder()
302 .version("3.8.1")
303 .artifactId("foo")
304 .repositories(Collections.singletonList(
305 Repository.newBuilder().url("${basedir}/temp-repo").build()))
306 .build();
307
308 final SimpleProblemCollector collector = new SimpleProblemCollector();
309 Model out = interpolator.interpolateModel(
310 model, projectBasedir, createModelBuildingRequest(context).build(), collector);
311 assertProblemFree(collector);
312
313 assertEquals(
314 projectBasedir.toAbsolutePath() + "/temp-repo",
315 (out.getRepositories().get(0)).getUrl());
316 }
317
318 @Test
319 public void testBasedirWin() throws Exception {
320 FileSystem fs = Jimfs.newFileSystem(Configuration.windows());
321 Path projectBasedir = fs.getPath("projectBasedir");
322
323 Model model = Model.newBuilder()
324 .version("3.8.1")
325 .artifactId("foo")
326 .repositories(Collections.singletonList(
327 Repository.newBuilder().url("${basedir}/temp-repo").build()))
328 .build();
329
330 final SimpleProblemCollector collector = new SimpleProblemCollector();
331 Model out = interpolator.interpolateModel(
332 model, projectBasedir, createModelBuildingRequest(context).build(), collector);
333 assertProblemFree(collector);
334
335 assertEquals(
336 projectBasedir.toAbsolutePath() + "/temp-repo",
337 (out.getRepositories().get(0)).getUrl());
338 }
339
340 @Test
341 public void testBaseUri() throws Exception {
342 Path projectBasedir = Paths.get("projectBasedir");
343
344 Model model = Model.newBuilder()
345 .version("3.8.1")
346 .artifactId("foo")
347 .repositories(Collections.singletonList(Repository.newBuilder()
348 .url("${project.baseUri}/temp-repo")
349 .build()))
350 .build();
351
352 final SimpleProblemCollector collector = new SimpleProblemCollector();
353 Model out = interpolator.interpolateModel(
354 model, projectBasedir, createModelBuildingRequest(context).build(), collector);
355 assertProblemFree(collector);
356
357 assertEquals(
358 projectBasedir.resolve("temp-repo").toUri().toString(),
359 (out.getRepositories().get(0)).getUrl());
360 }
361
362 @Test
363 void testRootDirectory() throws Exception {
364 Path rootDirectory = Paths.get("myRootDirectory");
365
366 Model model = Model.newBuilder()
367 .version("3.8.1")
368 .artifactId("foo")
369 .repositories(Collections.singletonList(Repository.newBuilder()
370 .url("file:${project.rootDirectory}/temp-repo")
371 .build()))
372 .build();
373
374 final SimpleProblemCollector collector = new SimpleProblemCollector();
375 Model out = interpolator.interpolateModel(
376 model, rootDirectory, createModelBuildingRequest(context).build(), collector);
377 assertProblemFree(collector);
378
379 assertEquals("file:myRootDirectory/temp-repo", (out.getRepositories().get(0)).getUrl());
380 }
381
382 @Test
383 void testRootDirectoryWithUri() throws Exception {
384 Path rootDirectory = Paths.get("myRootDirectory");
385
386 Model model = Model.newBuilder()
387 .version("3.8.1")
388 .artifactId("foo")
389 .repositories(Collections.singletonList(Repository.newBuilder()
390 .url("${project.rootDirectory.uri}/temp-repo")
391 .build()))
392 .build();
393
394 final SimpleProblemCollector collector = new SimpleProblemCollector();
395 Model out = interpolator.interpolateModel(
396 model, rootDirectory, createModelBuildingRequest(context).build(), collector);
397 assertProblemFree(collector);
398
399 assertEquals(
400 rootDirectory.resolve("temp-repo").toUri().toString(),
401 (out.getRepositories().get(0)).getUrl());
402 }
403
404 @Test
405 void testRootDirectoryWithNull() throws Exception {
406 Path projectDirectory = Paths.get("myProjectDirectory");
407 this.rootDirectory = new AtomicReference<>(null);
408
409 Model model = Model.newBuilder()
410 .version("3.8.1")
411 .artifactId("foo")
412 .repositories(Collections.singletonList(Repository.newBuilder()
413 .url("file:///${project.rootDirectory}/temp-repo")
414 .build()))
415 .build();
416
417 final SimpleProblemCollector collector = new SimpleProblemCollector();
418 IllegalStateException e = assertThrows(
419 IllegalStateException.class,
420 () -> interpolator.interpolateModel(
421 model,
422 projectDirectory,
423 createModelBuildingRequest(context).build(),
424 collector));
425
426 assertEquals(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE, e.getMessage());
427 }
428
429 @Test
430 public void testEnvars() throws Exception {
431 context.put("env.HOME", "/path/to/home");
432
433 Map<String, String> modelProperties = new HashMap<>();
434 modelProperties.put("outputDirectory", "${env.HOME}");
435
436 Model model = Model.newBuilder().properties(modelProperties).build();
437
438 final SimpleProblemCollector collector = new SimpleProblemCollector();
439 Model out = interpolator.interpolateModel(
440 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
441 assertProblemFree(collector);
442
443 assertEquals("/path/to/home", out.getProperties().get("outputDirectory"));
444 }
445
446 @Test
447 public void envarExpressionThatEvaluatesToNullReturnsTheLiteralString() throws Exception {
448
449 Map<String, String> modelProperties = new HashMap<>();
450 modelProperties.put("outputDirectory", "${env.DOES_NOT_EXIST}");
451
452 Model model = Model.newBuilder().properties(modelProperties).build();
453
454 final SimpleProblemCollector collector = new SimpleProblemCollector();
455 Model out = interpolator.interpolateModel(
456 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
457 assertProblemFree(collector);
458
459 assertEquals("${env.DOES_NOT_EXIST}", out.getProperties().get("outputDirectory"));
460 }
461
462 @Test
463 public void expressionThatEvaluatesToNullReturnsTheLiteralString() throws Exception {
464 Map<String, String> modelProperties = new HashMap<>();
465 modelProperties.put("outputDirectory", "${DOES_NOT_EXIST}");
466
467 Model model = Model.newBuilder().properties(modelProperties).build();
468
469 final SimpleProblemCollector collector = new SimpleProblemCollector();
470 Model out = interpolator.interpolateModel(
471 model, Paths.get("."), createModelBuildingRequest(context).build(), collector);
472 assertProblemFree(collector);
473
474 assertEquals("${DOES_NOT_EXIST}", out.getProperties().get("outputDirectory"));
475 }
476
477 @Test
478 public void shouldInterpolateSourceDirectoryReferencedFromResourceDirectoryCorrectly() throws Exception {
479 Model model = Model.newBuilder()
480 .build(Build.newBuilder()
481 .sourceDirectory("correct")
482 .resources(List.of(Resource.newBuilder()
483 .directory("${project.build.sourceDirectory}")
484 .build()))
485 .build())
486 .build();
487
488 final SimpleProblemCollector collector = new SimpleProblemCollector();
489 Model out = interpolator.interpolateModel(
490 model, null, createModelBuildingRequest(context).build(), collector);
491 assertCollectorState(0, 0, 0, collector);
492
493 List<Resource> outResources = out.getBuild().getResources();
494 Iterator<Resource> resIt = outResources.iterator();
495
496 assertEquals(model.getBuild().getSourceDirectory(), resIt.next().getDirectory());
497 }
498
499 @Test
500 public void shouldInterpolateUnprefixedBasedirExpression() throws Exception {
501 Path basedir = Paths.get("/test/path");
502 Model model = Model.newBuilder()
503 .dependencies(Collections.singletonList(Dependency.newBuilder()
504 .systemPath("${basedir}/artifact.jar")
505 .build()))
506 .build();
507
508 final SimpleProblemCollector collector = new SimpleProblemCollector();
509 Model result = interpolator.interpolateModel(
510 model, basedir, createModelBuildingRequest(context).build(), collector);
511 assertProblemFree(collector);
512
513 List<Dependency> rDeps = result.getDependencies();
514 assertNotNull(rDeps);
515 assertEquals(1, rDeps.size());
516 assertEquals(
517 basedir.resolve("artifact.jar").toAbsolutePath(),
518 Paths.get(rDeps.get(0).getSystemPath()).toAbsolutePath());
519 }
520
521 @Test
522 public void testRecursiveExpressionCycleNPE() throws Exception {
523 Map<String, String> props = new HashMap<>();
524 props.put("aa", "${bb}");
525 props.put("bb", "${aa}");
526
527 Model model = Model.newBuilder().properties(props).build();
528
529 SimpleProblemCollector collector = new SimpleProblemCollector();
530
531 ModelBuilderRequest request = createModelBuildingRequest(Map.of()).build();
532 interpolator.interpolateModel(model, null, request, collector);
533
534 assertCollectorState(0, 2, 0, collector);
535 assertTrue(collector.getErrors().get(0).contains("recursive variable reference"));
536 }
537
538 @Disabled("per def cannot be recursive: ${basedir} is immediately going for project.basedir")
539 @Test
540 public void testRecursiveExpressionCycleBaseDir() throws Exception {
541 Map<String, String> props = new HashMap<>();
542 props.put("basedir", "${basedir}");
543 ModelBuilderRequest request = createModelBuildingRequest(Map.of()).build();
544
545 Model model = Model.newBuilder().properties(props).build();
546
547 SimpleProblemCollector collector = new SimpleProblemCollector();
548 ModelInterpolator interpolator = this.interpolator;
549 interpolator.interpolateModel(model, null, request, collector);
550
551 assertCollectorState(0, 1, 0, collector);
552 assertEquals(
553 "recursive variable reference: basedir", collector.getErrors().get(0));
554 }
555
556 @Test
557 void shouldIgnorePropertiesWithPomPrefix() throws Exception {
558 final String orgName = "MyCo";
559 final String uninterpolatedName = "${pom.organization.name} Tools";
560
561 Model model = Model.newBuilder()
562 .name(uninterpolatedName)
563 .organization(Organization.newBuilder().name(orgName).build())
564 .build();
565
566 SimpleProblemCollector collector = new SimpleProblemCollector();
567 Model out = interpolator.interpolateModel(
568 model,
569 null,
570 createModelBuildingRequest(context).build(),
571
572 collector);
573
574 assertCollectorState(0, 0, 0, collector);
575 assertEquals(uninterpolatedName, out.getName());
576 }
577
578 @Provides
579 @Priority(10)
580 @SuppressWarnings("unused")
581 RootLocator testRootLocator() {
582 return new RootLocator() {
583 @Override
584 public Path findRoot(Path basedir) {
585 return rootDirectory != null ? rootDirectory.get() : basedir;
586 }
587
588 @Override
589 public Path findMandatoryRoot(Path basedir) {
590 return Optional.ofNullable(findRoot(basedir))
591 .orElseThrow(() -> new IllegalStateException(getNoRootMessage()));
592 }
593 };
594 }
595 }