001 package org.apache.maven.project.interpolation;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import org.apache.maven.model.Model;
023 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
024 import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
025 import org.apache.maven.project.DefaultProjectBuilderConfiguration;
026 import org.apache.maven.project.ProjectBuilderConfiguration;
027 import org.apache.maven.project.path.PathTranslator;
028 import org.codehaus.plexus.interpolation.AbstractValueSource;
029 import org.codehaus.plexus.interpolation.InterpolationException;
030 import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
031 import org.codehaus.plexus.interpolation.Interpolator;
032 import org.codehaus.plexus.interpolation.MapBasedValueSource;
033 import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
034 import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
035 import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
036 import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
037 import org.codehaus.plexus.interpolation.RecursionInterceptor;
038 import org.codehaus.plexus.interpolation.ValueSource;
039 import org.codehaus.plexus.logging.AbstractLogEnabled;
040 import org.codehaus.plexus.logging.Logger;
041 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
042 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
043 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
044
045 import java.io.File;
046 import java.io.IOException;
047 import java.io.StringReader;
048 import java.io.StringWriter;
049 import java.util.ArrayList;
050 import java.util.Arrays;
051 import java.util.Collections;
052 import java.util.List;
053 import java.util.Map;
054 import java.util.Properties;
055
056 /**
057 * Use a regular expression search to find and resolve expressions within the POM.
058 *
059 * @author jdcasey Created on Feb 3, 2005
060 * @todo Consolidate this logic with the PluginParameterExpressionEvaluator, minus deprecations/bans.
061 */
062 @Deprecated
063 public abstract class AbstractStringBasedModelInterpolator
064 extends AbstractLogEnabled
065 implements ModelInterpolator, Initializable
066 {
067 private static final List<String> PROJECT_PREFIXES = Arrays.asList( new String[]{ "pom.", "project." } );
068
069 private static final List<String> TRANSLATED_PATH_EXPRESSIONS;
070
071 static
072 {
073 List<String> translatedPrefixes = new ArrayList<String>();
074
075 // MNG-1927, MNG-2124, MNG-3355:
076 // If the build section is present and the project directory is non-null, we should make
077 // sure interpolation of the directories below uses translated paths.
078 // Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
079 // code below...
080 translatedPrefixes.add( "build.directory" );
081 translatedPrefixes.add( "build.outputDirectory" );
082 translatedPrefixes.add( "build.testOutputDirectory" );
083 translatedPrefixes.add( "build.sourceDirectory" );
084 translatedPrefixes.add( "build.testSourceDirectory" );
085 translatedPrefixes.add( "build.scriptSourceDirectory" );
086 translatedPrefixes.add( "reporting.outputDirectory" );
087
088 TRANSLATED_PATH_EXPRESSIONS = translatedPrefixes;
089 }
090
091 private PathTranslator pathTranslator;
092
093 private Interpolator interpolator;
094
095 private RecursionInterceptor recursionInterceptor;
096
097 // for testing.
098 protected AbstractStringBasedModelInterpolator( PathTranslator pathTranslator )
099 {
100 this.pathTranslator = pathTranslator;
101 }
102
103 /**
104 * @todo: Remove the throws clause.
105 * @throws IOException This exception is not thrown any more, and needs to be removed.
106 */
107 protected AbstractStringBasedModelInterpolator()
108 {
109 }
110
111 public Model interpolate( Model model, Map<String, ?> context )
112 throws ModelInterpolationException
113 {
114 return interpolate( model, context, true );
115 }
116
117 /**
118 * Serialize the inbound Model instance to a StringWriter, perform the regex replacement to resolve
119 * POM expressions, then re-parse into the resolved Model instance.
120 * <br/>
121 * <b>NOTE:</b> This will result in a different instance of Model being returned!!!
122 *
123 * @param model The inbound Model instance, to serialize and reference for expression resolution
124 * @param context The other context map to be used during resolution
125 * @return The resolved instance of the inbound Model. This is a different instance!
126 *
127 * @deprecated Use {@link ModelInterpolator#interpolate(Model, File, ProjectBuilderConfiguration, boolean)} instead.
128 */
129 public Model interpolate( Model model, Map<String, ?> context, boolean strict )
130 throws ModelInterpolationException
131 {
132 Properties props = new Properties();
133 props.putAll( context );
134
135 return interpolate( model,
136 null,
137 new DefaultProjectBuilderConfiguration().setExecutionProperties( props ),
138 true );
139 }
140
141 public Model interpolate( Model model,
142 File projectDir,
143 ProjectBuilderConfiguration config,
144 boolean debugEnabled )
145 throws ModelInterpolationException
146 {
147 StringWriter sWriter = new StringWriter( 1024 );
148
149 MavenXpp3Writer writer = new MavenXpp3Writer();
150 try
151 {
152 writer.write( sWriter, model );
153 }
154 catch ( IOException e )
155 {
156 throw new ModelInterpolationException( "Cannot serialize project model for interpolation.", e );
157 }
158
159 String serializedModel = sWriter.toString();
160 serializedModel = interpolate( serializedModel, model, projectDir, config, debugEnabled );
161
162 StringReader sReader = new StringReader( serializedModel );
163
164 MavenXpp3Reader modelReader = new MavenXpp3Reader();
165 try
166 {
167 model = modelReader.read( sReader );
168 }
169 catch ( IOException e )
170 {
171 throw new ModelInterpolationException(
172 "Cannot read project model from interpolating filter of serialized version.", e );
173 }
174 catch ( XmlPullParserException e )
175 {
176 throw new ModelInterpolationException(
177 "Cannot read project model from interpolating filter of serialized version.", e );
178 }
179
180 return model;
181 }
182
183 /**
184 * Interpolates all expressions in the src parameter.
185 * <p>
186 * The algorithm used for each expression is:
187 * <ul>
188 * <li>If it starts with either "pom." or "project.", the expression is evaluated against the model.</li>
189 * <li>If the value is null, get the value from the context.</li>
190 * <li>If the value is null, but the context contains the expression, don't replace the expression string
191 * with the value, and continue to find other expressions.</li>
192 * <li>If the value is null, get it from the model properties.</li>
193 * <li>
194 * @param overrideContext
195 * @param outputDebugMessages
196 */
197 public String interpolate( String src,
198 Model model,
199 final File projectDir,
200 ProjectBuilderConfiguration config,
201 boolean debug )
202 throws ModelInterpolationException
203 {
204 try
205 {
206 List<ValueSource> valueSources = createValueSources( model, projectDir, config );
207 List<InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir, config );
208
209 return interpolateInternal( src, valueSources, postProcessors, debug );
210 }
211 finally
212 {
213 interpolator.clearAnswers();
214 }
215 }
216
217 protected List<ValueSource> createValueSources( final Model model, final File projectDir,
218 final ProjectBuilderConfiguration config )
219 {
220 String timestampFormat = DEFAULT_BUILD_TIMESTAMP_FORMAT;
221
222 Properties modelProperties = model.getProperties();
223 if ( modelProperties != null )
224 {
225 timestampFormat = modelProperties.getProperty( BUILD_TIMESTAMP_FORMAT_PROPERTY, timestampFormat );
226 }
227
228 ValueSource modelValueSource1 = new PrefixedObjectValueSource( PROJECT_PREFIXES, model, false );
229 ValueSource modelValueSource2 = new ObjectBasedValueSource( model );
230
231 ValueSource basedirValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
232 {
233 public Object getValue( String expression )
234 {
235 if ( projectDir != null && "basedir".equals( expression ) )
236 {
237 return projectDir.getAbsolutePath();
238 }
239 return null;
240 }
241 }, PROJECT_PREFIXES, true );
242 ValueSource baseUriValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
243 {
244 public Object getValue( String expression )
245 {
246 if ( projectDir != null && "baseUri".equals( expression ) )
247 {
248 return projectDir.getAbsoluteFile().toURI().toString();
249 }
250 return null;
251 }
252 }, PROJECT_PREFIXES, false );
253
254 List<ValueSource> valueSources = new ArrayList<ValueSource>( 9 );
255
256 // NOTE: Order counts here!
257 valueSources.add( basedirValueSource );
258 valueSources.add( baseUriValueSource );
259 valueSources.add( new BuildTimestampValueSource( config.getBuildStartTime(), timestampFormat ) );
260 valueSources.add( modelValueSource1 );
261 valueSources.add( new MapBasedValueSource( config.getUserProperties() ) );
262 valueSources.add( new MapBasedValueSource( modelProperties ) );
263 valueSources.add( new MapBasedValueSource( config.getExecutionProperties() ) );
264 valueSources.add( new AbstractValueSource( false )
265 {
266 public Object getValue( String expression )
267 {
268 return config.getExecutionProperties().getProperty( "env." + expression );
269 }
270 } );
271 valueSources.add( modelValueSource2 );
272
273 return valueSources;
274 }
275
276 protected List<InterpolationPostProcessor> createPostProcessors( final Model model, final File projectDir,
277 final ProjectBuilderConfiguration config )
278 {
279 return Collections.singletonList( (InterpolationPostProcessor) new PathTranslatingPostProcessor(
280 PROJECT_PREFIXES,
281 TRANSLATED_PATH_EXPRESSIONS,
282 projectDir,
283 pathTranslator ) );
284 }
285
286 @SuppressWarnings("unchecked")
287 protected String interpolateInternal( String src, List<ValueSource> valueSources,
288 List<InterpolationPostProcessor> postProcessors, boolean debug )
289 throws ModelInterpolationException
290 {
291 if ( src.indexOf( "${" ) < 0 )
292 {
293 return src;
294 }
295
296 Logger logger = getLogger();
297
298 String result = src;
299 synchronized( this )
300 {
301
302 for ( ValueSource vs : valueSources )
303 {
304 interpolator.addValueSource( vs );
305 }
306
307 for ( InterpolationPostProcessor postProcessor : postProcessors )
308 {
309 interpolator.addPostProcessor( postProcessor );
310 }
311
312 try
313 {
314 try
315 {
316 result = interpolator.interpolate( result, recursionInterceptor );
317 }
318 catch( InterpolationException e )
319 {
320 throw new ModelInterpolationException( e.getMessage(), e );
321 }
322
323 if ( debug )
324 {
325 List<Object> feedback = interpolator.getFeedback();
326 if ( feedback != null && !feedback.isEmpty() )
327 {
328 logger.debug( "Maven encountered the following problems during initial POM interpolation:" );
329
330 Object last = null;
331 for ( Object next : feedback )
332 {
333 if ( next instanceof Throwable )
334 {
335 if ( last == null )
336 {
337 logger.debug( "", ( (Throwable) next ) );
338 }
339 else
340 {
341 logger.debug( String.valueOf( last ), ( (Throwable) next ) );
342 }
343 }
344 else
345 {
346 if ( last != null )
347 {
348 logger.debug( String.valueOf( last ) );
349 }
350
351 last = next;
352 }
353 }
354
355 if ( last != null )
356 {
357 logger.debug( String.valueOf( last ) );
358 }
359 }
360 }
361
362 interpolator.clearFeedback();
363 }
364 finally
365 {
366 for ( ValueSource vs : valueSources )
367 {
368 interpolator.removeValuesSource( vs );
369 }
370
371 for ( InterpolationPostProcessor postProcessor : postProcessors )
372 {
373 interpolator.removePostProcessor( postProcessor );
374 }
375 }
376 }
377
378 return result;
379 }
380
381 protected RecursionInterceptor getRecursionInterceptor()
382 {
383 return recursionInterceptor;
384 }
385
386 protected void setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
387 {
388 this.recursionInterceptor = recursionInterceptor;
389 }
390
391 protected abstract Interpolator createInterpolator();
392
393 public void initialize()
394 throws InitializationException
395 {
396 interpolator = createInterpolator();
397 recursionInterceptor = new PrefixAwareRecursionInterceptor( PROJECT_PREFIXES );
398 }
399
400 protected final Interpolator getInterpolator()
401 {
402 return interpolator;
403 }
404
405 }