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