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.project.interpolation;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.StringReader;
24  import java.io.StringWriter;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Properties;
31  
32  import org.apache.maven.model.Model;
33  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
34  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
35  import org.apache.maven.project.DefaultProjectBuilderConfiguration;
36  import org.apache.maven.project.ProjectBuilderConfiguration;
37  import org.apache.maven.project.path.PathTranslator;
38  import org.codehaus.plexus.component.annotations.Requirement;
39  import org.codehaus.plexus.interpolation.AbstractValueSource;
40  import org.codehaus.plexus.interpolation.InterpolationException;
41  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
42  import org.codehaus.plexus.interpolation.Interpolator;
43  import org.codehaus.plexus.interpolation.MapBasedValueSource;
44  import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
45  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
46  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
47  import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
48  import org.codehaus.plexus.interpolation.RecursionInterceptor;
49  import org.codehaus.plexus.interpolation.ValueSource;
50  import org.codehaus.plexus.logging.AbstractLogEnabled;
51  import org.codehaus.plexus.logging.Logger;
52  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
53  import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
54  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
55  
56  /**
57   * Use a regular expression search to find and resolve expressions within the POM.
58   *
59   * @author jdcasey Created on Feb 3, 2005
60   * TODO Consolidate this logic with the PluginParameterExpressionEvaluator, minus deprecations/bans.
61   */
62  @Deprecated
63  public abstract class AbstractStringBasedModelInterpolator extends AbstractLogEnabled
64          implements ModelInterpolator, Initializable {
65  
66      private static final List<String> PROJECT_PREFIXES = Arrays.asList("pom.", "project.");
67  
68      private static final List<String> TRANSLATED_PATH_EXPRESSIONS;
69  
70      static {
71          List<String> translatedPrefixes = new ArrayList<>();
72  
73          // MNG-1927, MNG-2124, MNG-3355:
74          // If the build section is present and the project directory is non-null, we should make
75          // sure interpolation of the directories below uses translated paths.
76          // Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
77          // code below...
78          translatedPrefixes.add("build.directory");
79          translatedPrefixes.add("build.outputDirectory");
80          translatedPrefixes.add("build.testOutputDirectory");
81          translatedPrefixes.add("build.sourceDirectory");
82          translatedPrefixes.add("build.testSourceDirectory");
83          translatedPrefixes.add("build.scriptSourceDirectory");
84          translatedPrefixes.add("reporting.outputDirectory");
85  
86          TRANSLATED_PATH_EXPRESSIONS = translatedPrefixes;
87      }
88  
89      @Requirement
90      private PathTranslator pathTranslator;
91  
92      private Interpolator interpolator;
93  
94      private RecursionInterceptor recursionInterceptor;
95  
96      // for testing.
97      protected AbstractStringBasedModelInterpolator(PathTranslator pathTranslator) {
98          this.pathTranslator = pathTranslator;
99      }
100 
101     protected AbstractStringBasedModelInterpolator() {}
102 
103     public Model interpolate(Model model, Map<String, ?> context) throws ModelInterpolationException {
104         return interpolate(model, context, true);
105     }
106 
107     /**
108      * Serialize the inbound Model instance to a StringWriter, perform the regex replacement to resolve
109      * POM expressions, then re-parse into the resolved Model instance.
110      * <p>
111      * <b>NOTE:</b> This will result in a different instance of Model being returned!!!
112      *
113      * @param model The inbound Model instance, to serialize and reference for expression resolution
114      * @param context The other context map to be used during resolution
115      *
116      * @return The resolved instance of the inbound Model. This is a different instance!
117      *
118      * @deprecated Use {@link ModelInterpolator#interpolate(Model, File, ProjectBuilderConfiguration, boolean)} instead.
119      */
120     public Model interpolate(Model model, Map<String, ?> context, boolean strict) throws ModelInterpolationException {
121         Properties props = new Properties();
122         props.putAll(context);
123 
124         return interpolate(model, null, new DefaultProjectBuilderConfiguration().setExecutionProperties(props), true);
125     }
126 
127     public Model interpolate(Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled)
128             throws ModelInterpolationException {
129         StringWriter sWriter = new StringWriter(1024);
130 
131         MavenXpp3Writer writer = new MavenXpp3Writer();
132         try {
133             writer.write(sWriter, model);
134         } catch (IOException e) {
135             throw new ModelInterpolationException("Cannot serialize project model for interpolation.", e);
136         }
137 
138         String serializedModel = sWriter.toString();
139         serializedModel = interpolate(serializedModel, model, projectDir, config, debugEnabled);
140 
141         StringReader sReader = new StringReader(serializedModel);
142 
143         MavenXpp3Reader modelReader = new MavenXpp3Reader();
144         try {
145             model = modelReader.read(sReader);
146         } catch (IOException | XmlPullParserException e) {
147             throw new ModelInterpolationException(
148                     "Cannot read project model from interpolating filter of serialized version.", e);
149         }
150 
151         return model;
152     }
153 
154     /**
155      * Interpolates all expressions in the src parameter.
156      * <p>
157      * The algorithm used for each expression is:
158      * <ul>
159      *   <li>If it starts with either "pom." or "project.", the expression is evaluated against the model.</li>
160      *   <li>If the value is null, get the value from the context.</li>
161      *   <li>If the value is null, but the context contains the expression, don't replace the expression string
162      *       with the value, and continue to find other expressions.</li>
163      *   <li>If the value is null, get it from the model properties.</li>
164      * </ul>
165      */
166     public String interpolate(
167             String src, Model model, final File projectDir, ProjectBuilderConfiguration config, boolean debug)
168             throws ModelInterpolationException {
169         try {
170             List<ValueSource> valueSources = createValueSources(model, projectDir, config);
171             List<InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, config);
172 
173             return interpolateInternal(src, valueSources, postProcessors, debug);
174         } finally {
175             interpolator.clearAnswers();
176         }
177     }
178 
179     protected List<ValueSource> createValueSources(
180             final Model model, final File projectDir, final ProjectBuilderConfiguration config) {
181         String timestampFormat = DEFAULT_BUILD_TIMESTAMP_FORMAT;
182 
183         Properties modelProperties = model.getProperties();
184         if (modelProperties != null) {
185             timestampFormat = modelProperties.getProperty(BUILD_TIMESTAMP_FORMAT_PROPERTY, timestampFormat);
186         }
187 
188         ValueSource modelValueSource1 = new PrefixedObjectValueSource(PROJECT_PREFIXES, model, false);
189         ValueSource modelValueSource2 = new ObjectBasedValueSource(model);
190 
191         ValueSource basedirValueSource = new PrefixedValueSourceWrapper(
192                 new AbstractValueSource(false) {
193 
194                     public Object getValue(String expression) {
195                         if (projectDir != null && "basedir".equals(expression)) {
196                             return projectDir.getAbsolutePath();
197                         }
198                         return null;
199                     }
200                 },
201                 PROJECT_PREFIXES,
202                 true);
203         ValueSource baseUriValueSource = new PrefixedValueSourceWrapper(
204                 new AbstractValueSource(false) {
205 
206                     public Object getValue(String expression) {
207                         if (projectDir != null && "baseUri".equals(expression)) {
208                             return projectDir.getAbsoluteFile().toPath().toUri().toASCIIString();
209                         }
210                         return null;
211                     }
212                 },
213                 PROJECT_PREFIXES,
214                 false);
215 
216         List<ValueSource> valueSources = new ArrayList<>(9);
217 
218         // NOTE: Order counts here!
219         valueSources.add(basedirValueSource);
220         valueSources.add(baseUriValueSource);
221         valueSources.add(new BuildTimestampValueSource(config.getBuildStartTime(), timestampFormat));
222         valueSources.add(modelValueSource1);
223         valueSources.add(new MapBasedValueSource(config.getUserProperties()));
224         valueSources.add(new MapBasedValueSource(modelProperties));
225         valueSources.add(new MapBasedValueSource(config.getExecutionProperties()));
226         valueSources.add(new AbstractValueSource(false) {
227 
228             public Object getValue(String expression) {
229                 return config.getExecutionProperties().getProperty("env." + expression);
230             }
231         });
232         valueSources.add(modelValueSource2);
233 
234         return valueSources;
235     }
236 
237     protected List<InterpolationPostProcessor> createPostProcessors(
238             final Model model, final File projectDir, final ProjectBuilderConfiguration config) {
239         return Collections.singletonList((InterpolationPostProcessor) new PathTranslatingPostProcessor(
240                 PROJECT_PREFIXES, TRANSLATED_PATH_EXPRESSIONS, projectDir, pathTranslator));
241     }
242 
243     @SuppressWarnings("unchecked")
244     protected String interpolateInternal(
245             String src, List<ValueSource> valueSources, List<InterpolationPostProcessor> postProcessors, boolean debug)
246             throws ModelInterpolationException {
247         if (!src.contains("${")) {
248             return src;
249         }
250 
251         Logger logger = getLogger();
252 
253         String result = src;
254         synchronized (this) {
255             for (ValueSource vs : valueSources) {
256                 interpolator.addValueSource(vs);
257             }
258 
259             for (InterpolationPostProcessor postProcessor : postProcessors) {
260                 interpolator.addPostProcessor(postProcessor);
261             }
262 
263             try {
264                 try {
265                     result = interpolator.interpolate(result, recursionInterceptor);
266                 } catch (InterpolationException e) {
267                     throw new ModelInterpolationException(e.getMessage(), e);
268                 }
269 
270                 if (debug) {
271                     List<Object> feedback = interpolator.getFeedback();
272                     if (feedback != null && !feedback.isEmpty()) {
273                         logger.debug("Maven encountered the following problems during initial POM interpolation:");
274 
275                         Object last = null;
276                         for (Object next : feedback) {
277                             if (next instanceof Throwable) {
278                                 if (last == null) {
279                                     logger.debug("", ((Throwable) next));
280                                 } else {
281                                     logger.debug(String.valueOf(last), ((Throwable) next));
282                                 }
283                             } else {
284                                 if (last != null) {
285                                     logger.debug(String.valueOf(last));
286                                 }
287 
288                                 last = next;
289                             }
290                         }
291 
292                         if (last != null) {
293                             logger.debug(String.valueOf(last));
294                         }
295                     }
296                 }
297 
298                 interpolator.clearFeedback();
299             } finally {
300                 for (ValueSource vs : valueSources) {
301                     interpolator.removeValuesSource(vs);
302                 }
303 
304                 for (InterpolationPostProcessor postProcessor : postProcessors) {
305                     interpolator.removePostProcessor(postProcessor);
306                 }
307             }
308         }
309 
310         return result;
311     }
312 
313     protected RecursionInterceptor getRecursionInterceptor() {
314         return recursionInterceptor;
315     }
316 
317     protected void setRecursionInterceptor(RecursionInterceptor recursionInterceptor) {
318         this.recursionInterceptor = recursionInterceptor;
319     }
320 
321     protected abstract Interpolator createInterpolator();
322 
323     public void initialize() throws InitializationException {
324         interpolator = createInterpolator();
325         recursionInterceptor = new PrefixAwareRecursionInterceptor(PROJECT_PREFIXES);
326     }
327 
328     protected final Interpolator getInterpolator() {
329         return interpolator;
330     }
331 }