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.model.interpolation;
20  
21  import javax.inject.Inject;
22  
23  import java.io.File;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Properties;
30  
31  import org.apache.maven.model.Model;
32  import org.apache.maven.model.building.ModelBuildingRequest;
33  import org.apache.maven.model.building.ModelProblemCollector;
34  import org.apache.maven.model.path.PathTranslator;
35  import org.apache.maven.model.path.UrlNormalizer;
36  import org.apache.maven.model.root.RootLocator;
37  import org.codehaus.plexus.interpolation.AbstractValueSource;
38  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
39  import org.codehaus.plexus.interpolation.MapBasedValueSource;
40  import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
41  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
42  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
43  import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
44  import org.codehaus.plexus.interpolation.RecursionInterceptor;
45  import org.codehaus.plexus.interpolation.SingleResponseValueSource;
46  import org.codehaus.plexus.interpolation.ValueSource;
47  
48  /**
49   * Use a regular expression search to find and resolve expressions within the POM.
50   *
51   * @author jdcasey Created on Feb 3, 2005
52   */
53  public abstract class AbstractStringBasedModelInterpolator implements ModelInterpolator {
54  
55      /**
56       * User property for opting back into the previous behavior of interpolating
57       * repository-resolved models (built at {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL})
58       * against the full set of session properties (system, environment and CLI).
59       * When set to {@code "false"} (default), such models are interpolated only against
60       * their own {@code <properties>}, preventing property leaking from the requesting
61       * build into transitive POMs. When set to {@code "true"}, full interpolation is
62       * applied as in previous Maven versions.
63       * <p>
64       * In Maven 4.x this constant is promoted to
65       * {@code org.apache.maven.api.Constants.MAVEN_MODEL_DEPENDENCY_INTERPOLATION_FULL}
66       * with {@code @Config} so it appears in the auto-generated configuration documentation.
67       */
68      public static final String FULL_EXTERNAL_INTERPOLATION_PROPERTY = "maven.model.dependencyInterpolation.full";
69  
70      private static final List<String> PROJECT_PREFIXES = Arrays.asList("pom.", "project.");
71  
72      private static final Collection<String> TRANSLATED_PATH_EXPRESSIONS;
73  
74      static {
75          Collection<String> translatedPrefixes = new HashSet<>();
76  
77          // MNG-1927, MNG-2124, MNG-3355:
78          // If the build section is present and the project directory is non-null, we should make
79          // sure interpolation of the directories below uses translated paths.
80          // Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
81          // code below...
82          translatedPrefixes.add("build.directory");
83          translatedPrefixes.add("build.outputDirectory");
84          translatedPrefixes.add("build.testOutputDirectory");
85          translatedPrefixes.add("build.sourceDirectory");
86          translatedPrefixes.add("build.testSourceDirectory");
87          translatedPrefixes.add("build.scriptSourceDirectory");
88          translatedPrefixes.add("reporting.outputDirectory");
89  
90          TRANSLATED_PATH_EXPRESSIONS = translatedPrefixes;
91      }
92  
93      @Inject
94      private PathTranslator pathTranslator;
95  
96      @Inject
97      private UrlNormalizer urlNormalizer;
98  
99      @Inject
100     private ModelVersionProcessor versionProcessor;
101 
102     @Inject
103     private RootLocator rootLocator;
104 
105     public AbstractStringBasedModelInterpolator setPathTranslator(PathTranslator pathTranslator) {
106         this.pathTranslator = pathTranslator;
107         return this;
108     }
109 
110     public AbstractStringBasedModelInterpolator setUrlNormalizer(UrlNormalizer urlNormalizer) {
111         this.urlNormalizer = urlNormalizer;
112         return this;
113     }
114 
115     public AbstractStringBasedModelInterpolator setVersionPropertiesProcessor(ModelVersionProcessor processor) {
116         this.versionProcessor = processor;
117         return this;
118     }
119 
120     public AbstractStringBasedModelInterpolator setRootLocator(RootLocator rootLocator) {
121         this.rootLocator = rootLocator;
122         return this;
123     }
124 
125     protected List<ValueSource> createValueSources(
126             final Model model,
127             final File projectDir,
128             final ModelBuildingRequest config,
129             final ModelProblemCollector problems) {
130         Properties modelProperties = model.getProperties();
131 
132         ValueSource modelValueSource1 = new PrefixedObjectValueSource(PROJECT_PREFIXES, model, false);
133         if (config.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
134             modelValueSource1 = new ProblemDetectingValueSource(modelValueSource1, "pom.", "project.", problems);
135         }
136 
137         ValueSource modelValueSource2 = new ObjectBasedValueSource(model);
138         if (config.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) {
139             modelValueSource2 = new ProblemDetectingValueSource(modelValueSource2, "", "project.", problems);
140         }
141 
142         // NOTE: Order counts here!
143         List<ValueSource> valueSources = new ArrayList<>(11);
144 
145         if (projectDir != null) {
146             ValueSource basedirValueSource = new PrefixedValueSourceWrapper(
147                     new AbstractValueSource(false) {
148                         @Override
149                         public Object getValue(String expression) {
150                             if ("basedir".equals(expression)) {
151                                 return projectDir.getAbsolutePath();
152                             }
153                             return null;
154                         }
155                     },
156                     PROJECT_PREFIXES,
157                     true);
158             valueSources.add(basedirValueSource);
159 
160             ValueSource rootDirectoryValueSource = new PrefixedValueSourceWrapper(
161                     new AbstractValueSource(false) {
162                         @Override
163                         public Object getValue(String expression) {
164                             if ("rootDirectory".equals(expression)) {
165                                 return rootLocator
166                                         .findMandatoryRoot(projectDir.toPath())
167                                         .toAbsolutePath()
168                                         .toString();
169                             }
170                             return null;
171                         }
172                     },
173                     PROJECT_PREFIXES,
174                     true);
175             valueSources.add(rootDirectoryValueSource);
176 
177             ValueSource baseUriValueSource = new PrefixedValueSourceWrapper(
178                     new AbstractValueSource(false) {
179                         @Override
180                         public Object getValue(String expression) {
181                             if ("baseUri".equals(expression)) {
182                                 return projectDir
183                                         .getAbsoluteFile()
184                                         .toPath()
185                                         .toUri()
186                                         .toASCIIString();
187                             }
188                             return null;
189                         }
190                     },
191                     PROJECT_PREFIXES,
192                     false);
193             valueSources.add(baseUriValueSource);
194             valueSources.add(new BuildTimestampValueSource(config.getBuildStartTime(), modelProperties));
195         }
196 
197         valueSources.add(modelValueSource1);
198 
199         // Models built at VALIDATION_LEVEL_MINIMAL are the models Maven builds while resolving
200         // dependency, parent and BOM-import POMs from a repository, not the operator's own
201         // project. Such models interpolate only against their own properties and a small set
202         // of environment-independent expressions; everything else in the user/system property
203         // space stays uninterpolated. Operator project builds use a higher validation level and
204         // keep the full set of value sources, unchanged from previous behavior.
205         boolean restricted = restrictExternalModelInterpolation(config);
206 
207         ValueSource userPropertiesValueSource = new MapBasedValueSource(config.getUserProperties());
208         valueSources.add(restricted ? restrictToSafeExpressions(userPropertiesValueSource) : userPropertiesValueSource);
209 
210         // Overwrite existing values in model properties. Otherwise it's not possible
211         // to define them via command line e.g.: mvn -Drevision=6.5.7 ...
212         versionProcessor.overwriteModelProperties(modelProperties, config);
213         valueSources.add(new MapBasedValueSource(modelProperties));
214 
215         ValueSource systemPropertiesValueSource = new MapBasedValueSource(config.getSystemProperties());
216         valueSources.add(
217                 restricted ? restrictToSafeExpressions(systemPropertiesValueSource) : systemPropertiesValueSource);
218 
219         if (!restricted) {
220             valueSources.add(new AbstractValueSource(false) {
221                 @Override
222                 public Object getValue(String expression) {
223                     return config.getSystemProperties().getProperty("env." + expression);
224                 }
225             });
226         }
227 
228         valueSources.add(modelValueSource2);
229 
230         // last source: make sure Maven Repo Central is present (if is present anywhere else, it will prevail this one)
231         valueSources.add(new SingleResponseValueSource(MAVEN_REPO_CENTRAL_KEY, DEFAULT_MAVEN_REPO_CENTRAL_URL));
232 
233         return valueSources;
234     }
235 
236     private static boolean restrictExternalModelInterpolation(ModelBuildingRequest config) {
237         return config.getValidationLevel() < ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0
238                 && !Boolean.parseBoolean(config.getSystemProperties().getProperty(FULL_EXTERNAL_INTERPOLATION_PROPERTY))
239                 && !Boolean.parseBoolean(config.getUserProperties().getProperty(FULL_EXTERNAL_INTERPOLATION_PROPERTY));
240     }
241 
242     private static ValueSource restrictToSafeExpressions(ValueSource source) {
243         return new AbstractValueSource(false) {
244             @Override
245             public Object getValue(String expression) {
246                 return isSafeExternalExpression(expression) ? source.getValue(expression) : null;
247             }
248         };
249     }
250 
251     /**
252      * Expressions that models built at {@link ModelBuildingRequest#VALIDATION_LEVEL_MINIMAL}
253      * may still resolve from the session properties: JVM- and Maven-defined properties, plus
254      * the CI-friendly version properties (MNG-5895). All other expressions are left literal.
255      */
256     private static boolean isSafeExternalExpression(String expression) {
257         return expression.startsWith("java.")
258                 || expression.startsWith("os.")
259                 || expression.startsWith("maven.")
260                 || "file.separator".equals(expression)
261                 || "path.separator".equals(expression)
262                 || "line.separator".equals(expression)
263                 || "revision".equals(expression)
264                 || "changelist".equals(expression)
265                 || "sha1".equals(expression);
266     }
267 
268     protected List<? extends InterpolationPostProcessor> createPostProcessors(
269             final Model model, final File projectDir, final ModelBuildingRequest config) {
270         List<InterpolationPostProcessor> processors = new ArrayList<>(2);
271         if (projectDir != null) {
272             processors.add(new PathTranslatingPostProcessor(
273                     PROJECT_PREFIXES, TRANSLATED_PATH_EXPRESSIONS,
274                     projectDir, pathTranslator));
275         }
276         processors.add(new UrlNormalizingPostProcessor(urlNormalizer));
277         return processors;
278     }
279 
280     protected RecursionInterceptor createRecursionInterceptor() {
281         return new PrefixAwareRecursionInterceptor(PROJECT_PREFIXES);
282     }
283 }