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.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; // used in TestRootLocator below
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         // just to make sure all the bases are covered...
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         // just to make sure all the bases are covered...
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                 // .validationLevel(ModelBuilderRequest.VALIDATION_LEVEL_MAVEN_4_0),
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 }