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 javax.inject.Inject;
22  import javax.xml.stream.XMLStreamException;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.StringReader;
27  import java.io.StringWriter;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Properties;
34  
35  import org.apache.maven.model.Model;
36  import org.apache.maven.model.v4.MavenStaxReader;
37  import org.apache.maven.model.v4.MavenStaxWriter;
38  import org.apache.maven.project.DefaultProjectBuilderConfiguration;
39  import org.apache.maven.project.ProjectBuilderConfiguration;
40  import org.apache.maven.project.path.PathTranslator;
41  import org.codehaus.plexus.interpolation.AbstractValueSource;
42  import org.codehaus.plexus.interpolation.InterpolationException;
43  import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
44  import org.codehaus.plexus.interpolation.Interpolator;
45  import org.codehaus.plexus.interpolation.MapBasedValueSource;
46  import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
47  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
48  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
49  import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
50  import org.codehaus.plexus.interpolation.RecursionInterceptor;
51  import org.codehaus.plexus.interpolation.ValueSource;
52  import org.codehaus.plexus.logging.AbstractLogEnabled;
53  import org.codehaus.plexus.logging.Logger;
54  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
55  import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
56  
57  /**
58   * Use a regular expression search to find and resolve expressions within the POM.
59   *
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      @Inject
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     @Deprecated
121     public Model interpolate(Model model, Map<String, ?> context, boolean strict) throws ModelInterpolationException {
122         Properties props = new Properties();
123         props.putAll(context);
124 
125         return interpolate(model, null, new DefaultProjectBuilderConfiguration().setExecutionProperties(props), true);
126     }
127 
128     public Model interpolate(Model model, File projectDir, ProjectBuilderConfiguration config, boolean debugEnabled)
129             throws ModelInterpolationException {
130         StringWriter sWriter = new StringWriter(1024);
131 
132         MavenStaxWriter writer = new MavenStaxWriter();
133         try {
134             writer.write(sWriter, model.getDelegate());
135         } catch (IOException | XMLStreamException e) {
136             throw new ModelInterpolationException("Cannot serialize project model for interpolation.", e);
137         }
138 
139         String serializedModel = sWriter.toString();
140         serializedModel = interpolate(serializedModel, model, projectDir, config, debugEnabled);
141 
142         StringReader sReader = new StringReader(serializedModel);
143 
144         MavenStaxReader modelReader = new MavenStaxReader();
145         try {
146             model = new Model(modelReader.read(sReader));
147         } catch (XMLStreamException e) {
148             throw new ModelInterpolationException(
149                     "Cannot read project model from interpolating filter of serialized version.", e);
150         }
151 
152         return model;
153     }
154 
155     /**
156      * Interpolates all expressions in the src parameter.
157      * <p>
158      * The algorithm used for each expression is:
159      * <ul>
160      *   <li>If it starts with either "pom." or "project.", the expression is evaluated against the model.</li>
161      *   <li>If the value is null, get the value from the context.</li>
162      *   <li>If the value is null, but the context contains the expression, don't replace the expression string
163      *       with the value, and continue to find other expressions.</li>
164      *   <li>If the value is null, get it from the model properties.</li>
165      * </ul>
166      */
167     public String interpolate(
168             String src, Model model, final File projectDir, ProjectBuilderConfiguration config, boolean debug)
169             throws ModelInterpolationException {
170         try {
171             List<ValueSource> valueSources = createValueSources(model, projectDir, config);
172             List<InterpolationPostProcessor> postProcessors = createPostProcessors(model, projectDir, config);
173 
174             return interpolateInternal(src, valueSources, postProcessors, debug);
175         } finally {
176             interpolator.clearAnswers();
177         }
178     }
179 
180     protected List<ValueSource> createValueSources(
181             final Model model, final File projectDir, final ProjectBuilderConfiguration config) {
182         String timestampFormat = DEFAULT_BUILD_TIMESTAMP_FORMAT;
183 
184         Properties modelProperties = model.getProperties();
185         if (modelProperties != null) {
186             timestampFormat = modelProperties.getProperty(BUILD_TIMESTAMP_FORMAT_PROPERTY, timestampFormat);
187         }
188 
189         ValueSource modelValueSource1 = new PrefixedObjectValueSource(PROJECT_PREFIXES, model, false);
190         ValueSource modelValueSource2 = new ObjectBasedValueSource(model);
191 
192         ValueSource basedirValueSource = new PrefixedValueSourceWrapper(
193                 new AbstractValueSource(false) {
194 
195                     public Object getValue(String expression) {
196                         if (projectDir != null && "basedir".equals(expression)) {
197                             return projectDir.getAbsolutePath();
198                         }
199                         return null;
200                     }
201                 },
202                 PROJECT_PREFIXES,
203                 true);
204         ValueSource baseUriValueSource = new PrefixedValueSourceWrapper(
205                 new AbstractValueSource(false) {
206 
207                     public Object getValue(String expression) {
208                         if (projectDir != null && "baseUri".equals(expression)) {
209                             return projectDir.getAbsoluteFile().toPath().toUri().toASCIIString();
210                         }
211                         return null;
212                     }
213                 },
214                 PROJECT_PREFIXES,
215                 false);
216 
217         List<ValueSource> valueSources = new ArrayList<>(9);
218 
219         // NOTE: Order counts here!
220         valueSources.add(basedirValueSource);
221         valueSources.add(baseUriValueSource);
222         valueSources.add(new BuildTimestampValueSource(config.getBuildStartTime(), timestampFormat));
223         valueSources.add(modelValueSource1);
224         valueSources.add(new MapBasedValueSource(config.getUserProperties()));
225         valueSources.add(new MapBasedValueSource(modelProperties));
226         valueSources.add(new MapBasedValueSource(config.getExecutionProperties()));
227         valueSources.add(new AbstractValueSource(false) {
228 
229             public Object getValue(String expression) {
230                 return config.getExecutionProperties().getProperty("env." + expression);
231             }
232         });
233         valueSources.add(modelValueSource2);
234 
235         return valueSources;
236     }
237 
238     protected List<InterpolationPostProcessor> createPostProcessors(
239             final Model model, final File projectDir, final ProjectBuilderConfiguration config) {
240         return Collections.singletonList((InterpolationPostProcessor) new PathTranslatingPostProcessor(
241                 PROJECT_PREFIXES, TRANSLATED_PATH_EXPRESSIONS, projectDir, pathTranslator));
242     }
243 
244     @SuppressWarnings("unchecked")
245     protected String interpolateInternal(
246             String src, List<ValueSource> valueSources, List<InterpolationPostProcessor> postProcessors, boolean debug)
247             throws ModelInterpolationException {
248         if (!src.contains("${")) {
249             return src;
250         }
251 
252         Logger logger = getLogger();
253 
254         String result = src;
255         synchronized (this) {
256             for (ValueSource vs : valueSources) {
257                 interpolator.addValueSource(vs);
258             }
259 
260             for (InterpolationPostProcessor postProcessor : postProcessors) {
261                 interpolator.addPostProcessor(postProcessor);
262             }
263 
264             try {
265                 try {
266                     result = interpolator.interpolate(result, recursionInterceptor);
267                 } catch (InterpolationException e) {
268                     throw new ModelInterpolationException(e.getMessage(), e);
269                 }
270 
271                 if (debug) {
272                     List<Object> feedback = interpolator.getFeedback();
273                     if (feedback != null && !feedback.isEmpty()) {
274                         logger.debug("Maven encountered the following problems during initial POM interpolation:");
275 
276                         Object last = null;
277                         for (Object next : feedback) {
278                             if (next instanceof Throwable) {
279                                 if (last == null) {
280                                     logger.debug("", ((Throwable) next));
281                                 } else {
282                                     logger.debug(String.valueOf(last), ((Throwable) next));
283                                 }
284                             } else {
285                                 if (last != null) {
286                                     logger.debug(String.valueOf(last));
287                                 }
288 
289                                 last = next;
290                             }
291                         }
292 
293                         if (last != null) {
294                             logger.debug(String.valueOf(last));
295                         }
296                     }
297                 }
298 
299                 interpolator.clearFeedback();
300             } finally {
301                 for (ValueSource vs : valueSources) {
302                     interpolator.removeValuesSource(vs);
303                 }
304 
305                 for (InterpolationPostProcessor postProcessor : postProcessors) {
306                     interpolator.removePostProcessor(postProcessor);
307                 }
308             }
309         }
310 
311         return result;
312     }
313 
314     protected RecursionInterceptor getRecursionInterceptor() {
315         return recursionInterceptor;
316     }
317 
318     protected void setRecursionInterceptor(RecursionInterceptor recursionInterceptor) {
319         this.recursionInterceptor = recursionInterceptor;
320     }
321 
322     protected abstract Interpolator createInterpolator();
323 
324     public void initialize() throws InitializationException {
325         interpolator = createInterpolator();
326         recursionInterceptor = new PrefixAwareRecursionInterceptor(PROJECT_PREFIXES);
327     }
328 
329     protected final Interpolator getInterpolator() {
330         return interpolator;
331     }
332 }