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