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    }