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<>();
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 | XmlPullParserException e )
172        {
173            throw new ModelInterpolationException(
174                "Cannot read project model from interpolating filter of serialized version.", e );
175        }
176
177        return model;
178    }
179
180    /**
181     * Interpolates all expressions in the src parameter.
182     * <p>
183     * The algorithm used for each expression is:
184     * <ul>
185     *   <li>If it starts with either "pom." or "project.", the expression is evaluated against the model.</li>
186     *   <li>If the value is null, get the value from the context.</li>
187     *   <li>If the value is null, but the context contains the expression, don't replace the expression string
188     *       with the value, and continue to find other expressions.</li>
189     *   <li>If the value is null, get it from the model properties.</li>
190     *   <li>
191     * @param overrideContext
192     * @param outputDebugMessages
193     */
194    public String interpolate( String src,
195                               Model model,
196                               final File projectDir,
197                               ProjectBuilderConfiguration config,
198                               boolean debug )
199        throws ModelInterpolationException
200    {
201        try
202        {
203            List<ValueSource> valueSources = createValueSources( model, projectDir, config );
204            List<InterpolationPostProcessor> postProcessors = createPostProcessors( model, projectDir, config );
205
206            return interpolateInternal( src, valueSources, postProcessors, debug );
207        }
208        finally
209        {
210            interpolator.clearAnswers();
211        }
212    }
213
214    protected List<ValueSource> createValueSources( final Model model, final File projectDir,
215                                                    final ProjectBuilderConfiguration config )
216    {
217        String timestampFormat = DEFAULT_BUILD_TIMESTAMP_FORMAT;
218
219        Properties modelProperties = model.getProperties();
220        if ( modelProperties != null )
221        {
222            timestampFormat = modelProperties.getProperty( BUILD_TIMESTAMP_FORMAT_PROPERTY, timestampFormat );
223        }
224
225        ValueSource modelValueSource1 = new PrefixedObjectValueSource( PROJECT_PREFIXES, model, false );
226        ValueSource modelValueSource2 = new ObjectBasedValueSource( model );
227
228        ValueSource basedirValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
229        {
230            public Object getValue( String expression )
231            {
232                if ( projectDir != null && "basedir".equals( expression ) )
233                {
234                    return projectDir.getAbsolutePath();
235                }
236                return null;
237            }
238        }, PROJECT_PREFIXES, true );
239        ValueSource baseUriValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
240        {
241            public Object getValue( String expression )
242            {
243                if ( projectDir != null && "baseUri".equals( expression ) )
244                {
245                    return projectDir.getAbsoluteFile().toURI().toString();
246                }
247                return null;
248            }
249        }, PROJECT_PREFIXES, false );
250
251        List<ValueSource> valueSources = new ArrayList<>( 9 );
252
253        // NOTE: Order counts here!
254        valueSources.add( basedirValueSource );
255        valueSources.add( baseUriValueSource );
256        valueSources.add( new BuildTimestampValueSource( config.getBuildStartTime(), timestampFormat ) );
257        valueSources.add( modelValueSource1 );
258        valueSources.add( new MapBasedValueSource( config.getUserProperties() ) );
259        valueSources.add( new MapBasedValueSource( modelProperties ) );
260        valueSources.add( new MapBasedValueSource( config.getExecutionProperties() ) );
261        valueSources.add( new AbstractValueSource( false )
262        {
263            public Object getValue( String expression )
264            {
265                return config.getExecutionProperties().getProperty( "env." + expression );
266            }
267        } );
268        valueSources.add( modelValueSource2 );
269
270        return valueSources;
271    }
272
273    protected List<InterpolationPostProcessor> createPostProcessors( final Model model, final File projectDir,
274                                                                     final ProjectBuilderConfiguration config )
275    {
276        return Collections.singletonList( (InterpolationPostProcessor) new PathTranslatingPostProcessor(
277                                                                                                         PROJECT_PREFIXES,
278                                                                                                         TRANSLATED_PATH_EXPRESSIONS,
279                                                                                                         projectDir,
280                                                                                                         pathTranslator ) );
281    }
282
283    @SuppressWarnings( "unchecked" )
284    protected String interpolateInternal( String src, List<ValueSource> valueSources,
285                                          List<InterpolationPostProcessor> postProcessors, boolean debug )
286        throws ModelInterpolationException
287    {
288        if ( !src.contains( "${" ) )
289        {
290            return src;
291        }
292
293        Logger logger = getLogger();
294
295        String result = src;
296        synchronized ( this )
297        {
298
299            for ( ValueSource vs : valueSources )
300            {
301                interpolator.addValueSource( vs );
302            }
303
304            for ( InterpolationPostProcessor postProcessor : postProcessors )
305            {
306                interpolator.addPostProcessor( postProcessor );
307            }
308
309            try
310            {
311                try
312                {
313                    result = interpolator.interpolate( result, recursionInterceptor );
314                }
315                catch ( InterpolationException e )
316                {
317                    throw new ModelInterpolationException( e.getMessage(), e );
318                }
319
320                if ( debug )
321                {
322                    List<Object> feedback = interpolator.getFeedback();
323                    if ( feedback != null && !feedback.isEmpty() )
324                    {
325                        logger.debug( "Maven encountered the following problems during initial POM interpolation:" );
326
327                        Object last = null;
328                        for ( Object next : feedback )
329                        {
330                            if ( next instanceof Throwable )
331                            {
332                                if ( last == null )
333                                {
334                                    logger.debug( "", ( (Throwable) next ) );
335                                }
336                                else
337                                {
338                                    logger.debug( String.valueOf( last ), ( (Throwable) next ) );
339                                }
340                            }
341                            else
342                            {
343                                if ( last != null )
344                                {
345                                    logger.debug( String.valueOf( last ) );
346                                }
347
348                                last = next;
349                            }
350                        }
351
352                        if ( last != null )
353                        {
354                            logger.debug( String.valueOf( last ) );
355                        }
356                    }
357                }
358
359                interpolator.clearFeedback();
360            }
361            finally
362            {
363                for ( ValueSource vs : valueSources )
364                {
365                    interpolator.removeValuesSource( vs );
366                }
367
368                for ( InterpolationPostProcessor postProcessor : postProcessors )
369                {
370                    interpolator.removePostProcessor( postProcessor );
371                }
372            }
373        }
374
375        return result;
376    }
377
378    protected RecursionInterceptor getRecursionInterceptor()
379    {
380        return recursionInterceptor;
381    }
382
383    protected void setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
384    {
385        this.recursionInterceptor = recursionInterceptor;
386    }
387
388    protected abstract Interpolator createInterpolator();
389
390    public void initialize()
391        throws InitializationException
392    {
393        interpolator = createInterpolator();
394        recursionInterceptor = new PrefixAwareRecursionInterceptor( PROJECT_PREFIXES );
395    }
396
397    protected final Interpolator getInterpolator()
398    {
399        return interpolator;
400    }
401
402}