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