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    }