001package 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
022import org.apache.maven.model.Model;
023import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
024import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
025import org.apache.maven.project.DefaultProjectBuilderConfiguration;
026import org.apache.maven.project.ProjectBuilderConfiguration;
027import org.apache.maven.project.path.PathTranslator;
028import org.codehaus.plexus.component.annotations.Requirement;
029import org.codehaus.plexus.interpolation.AbstractValueSource;
030import org.codehaus.plexus.interpolation.InterpolationException;
031import org.codehaus.plexus.interpolation.InterpolationPostProcessor;
032import org.codehaus.plexus.interpolation.Interpolator;
033import org.codehaus.plexus.interpolation.MapBasedValueSource;
034import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
035import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
036import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
037import org.codehaus.plexus.interpolation.PrefixedValueSourceWrapper;
038import org.codehaus.plexus.interpolation.RecursionInterceptor;
039import org.codehaus.plexus.interpolation.ValueSource;
040import org.codehaus.plexus.logging.AbstractLogEnabled;
041import org.codehaus.plexus.logging.Logger;
042import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
043import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
044import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
045
046import java.io.File;
047import java.io.IOException;
048import java.io.StringReader;
049import java.io.StringWriter;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.Collections;
053import java.util.List;
054import java.util.Map;
055import 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
064public 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}