001    package org.apache.maven.model.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.building.ModelBuildingRequest;
024    import org.apache.maven.model.building.ModelProblemCollector;
025    import org.apache.maven.model.building.ModelProblem.Severity;
026    import org.apache.maven.model.path.PathTranslator;
027    import org.apache.maven.model.path.UrlNormalizer;
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    
041    import java.io.File;
042    import java.util.ArrayList;
043    import java.util.Arrays;
044    import java.util.Collection;
045    import java.util.HashSet;
046    import java.util.List;
047    import java.util.Properties;
048    
049    /**
050     * Use a regular expression search to find and resolve expressions within the POM.
051     *
052     * @author jdcasey Created on Feb 3, 2005
053     */
054    public abstract class AbstractStringBasedModelInterpolator
055        implements ModelInterpolator
056    {
057    
058        /**
059         * The default format used for build timestamps.
060         */
061        static final String DEFAULT_BUILD_TIMESTAMP_FORMAT = "yyyyMMdd-HHmm";
062    
063        /**
064         * The name of a property that if present in the model's {@code <properties>} section specifies a custom format for
065         * build timestamps. See {@link java.text.SimpleDateFormat} for details on the format.
066         */
067        private static final String BUILD_TIMESTAMP_FORMAT_PROPERTY = "maven.build.timestamp.format";
068    
069        private static final List<String> PROJECT_PREFIXES = Arrays.asList( new String[]{ "pom.", "project." } );
070    
071        private static final Collection<String> TRANSLATED_PATH_EXPRESSIONS;
072    
073        static
074        {
075            Collection<String> translatedPrefixes = new HashSet<String>();
076    
077            // MNG-1927, MNG-2124, MNG-3355:
078            // If the build section is present and the project directory is non-null, we should make
079            // sure interpolation of the directories below uses translated paths.
080            // Afterward, we'll double back and translate any paths that weren't covered during interpolation via the
081            // code below...
082            translatedPrefixes.add( "build.directory" );
083            translatedPrefixes.add( "build.outputDirectory" );
084            translatedPrefixes.add( "build.testOutputDirectory" );
085            translatedPrefixes.add( "build.sourceDirectory" );
086            translatedPrefixes.add( "build.testSourceDirectory" );
087            translatedPrefixes.add( "build.scriptSourceDirectory" );
088            translatedPrefixes.add( "reporting.outputDirectory" );
089    
090            TRANSLATED_PATH_EXPRESSIONS = translatedPrefixes;
091        }
092    
093        @Requirement
094        private PathTranslator pathTranslator;
095    
096        @Requirement
097        private UrlNormalizer urlNormalizer;
098    
099        private Interpolator interpolator;
100    
101        private RecursionInterceptor recursionInterceptor;
102    
103        public AbstractStringBasedModelInterpolator()
104        {
105            interpolator = createInterpolator();
106            recursionInterceptor = new PrefixAwareRecursionInterceptor( PROJECT_PREFIXES );
107        }
108    
109        public AbstractStringBasedModelInterpolator setPathTranslator( PathTranslator pathTranslator )
110        {
111            this.pathTranslator = pathTranslator;
112            return this;
113        }
114    
115        public AbstractStringBasedModelInterpolator setUrlNormalizer( UrlNormalizer urlNormalizer )
116        {
117            this.urlNormalizer = urlNormalizer;
118            return this;
119        }
120    
121        protected List<ValueSource> createValueSources( final Model model, final File projectDir,
122                                                        final ModelBuildingRequest config,
123                                                        final ModelProblemCollector problems )
124        {
125            Properties modelProperties = model.getProperties();
126    
127            ValueSource modelValueSource1 = new PrefixedObjectValueSource( PROJECT_PREFIXES, model, false );
128            if ( config.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
129            {
130                modelValueSource1 = new ProblemDetectingValueSource( modelValueSource1, "pom.", "project.", problems );
131            }
132    
133            ValueSource modelValueSource2 = new ObjectBasedValueSource( model );
134            if ( config.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0 )
135            {
136                modelValueSource2 = new ProblemDetectingValueSource( modelValueSource2, "", "project.", problems );
137            }
138    
139            // NOTE: Order counts here!
140            List<ValueSource> valueSources = new ArrayList<ValueSource>( 9 );
141    
142            if ( projectDir != null )
143            {
144                ValueSource basedirValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
145                {
146                    public Object getValue( String expression )
147                    {
148                        if ( "basedir".equals( expression ) )
149                        {
150                            return projectDir.getAbsolutePath();
151                        }
152                        return null;
153                    }
154                }, PROJECT_PREFIXES, true );
155                valueSources.add( basedirValueSource );
156    
157                ValueSource baseUriValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
158                {
159                    public Object getValue( String expression )
160                    {
161                        if ( "baseUri".equals( expression ) )
162                        {
163                            return projectDir.getAbsoluteFile().toURI().toString();
164                        }
165                        return null;
166                    }
167                }, PROJECT_PREFIXES, false );
168                valueSources.add( baseUriValueSource );
169    
170                String timestampFormat = DEFAULT_BUILD_TIMESTAMP_FORMAT;
171                if ( modelProperties != null )
172                {
173                    timestampFormat = modelProperties.getProperty( BUILD_TIMESTAMP_FORMAT_PROPERTY, timestampFormat );
174                }
175                valueSources.add( new BuildTimestampValueSource( config.getBuildStartTime(), timestampFormat ) );
176            }
177    
178            valueSources.add( modelValueSource1 );
179    
180            valueSources.add( new MapBasedValueSource( config.getUserProperties() ) );
181    
182            valueSources.add( new MapBasedValueSource( modelProperties ) );
183    
184            valueSources.add( new MapBasedValueSource( config.getSystemProperties() ) );
185    
186            valueSources.add( new AbstractValueSource( false )
187            {
188                public Object getValue( String expression )
189                {
190                    return config.getSystemProperties().getProperty( "env." + expression );
191                }
192            } );
193    
194            valueSources.add( modelValueSource2 );
195    
196            return valueSources;
197        }
198    
199        protected List<? extends InterpolationPostProcessor> createPostProcessors( final Model model,
200                                                                                   final File projectDir,
201                                                                                   final ModelBuildingRequest config )
202        {
203            List<InterpolationPostProcessor> processors = new ArrayList<InterpolationPostProcessor>( 2 );
204            if ( projectDir != null )
205            {
206                processors.add( new PathTranslatingPostProcessor( PROJECT_PREFIXES, TRANSLATED_PATH_EXPRESSIONS,
207                                                                  projectDir, pathTranslator ) );
208            }
209            processors.add( new UrlNormalizingPostProcessor( urlNormalizer ) );
210            return processors;
211        }
212    
213        protected String interpolateInternal( String src, List<? extends ValueSource> valueSources,
214                                              List<? extends InterpolationPostProcessor> postProcessors,
215                                              ModelProblemCollector problems )
216        {
217            if ( src.indexOf( "${" ) < 0 )
218            {
219                return src;
220            }
221    
222            String result = src;
223            synchronized ( this )
224            {
225    
226                for ( ValueSource vs : valueSources )
227                {
228                    interpolator.addValueSource( vs );
229                }
230    
231                for ( InterpolationPostProcessor postProcessor : postProcessors )
232                {
233                    interpolator.addPostProcessor( postProcessor );
234                }
235    
236                try
237                {
238                    try
239                    {
240                        result = interpolator.interpolate( result, recursionInterceptor );
241                    }
242                    catch ( InterpolationException e )
243                    {
244                        problems.add( Severity.ERROR, e.getMessage(), null, e );
245                    }
246    
247                    interpolator.clearFeedback();
248                }
249                finally
250                {
251                    for ( ValueSource vs : valueSources )
252                    {
253                        interpolator.removeValuesSource( vs );
254                    }
255    
256                    for ( InterpolationPostProcessor postProcessor : postProcessors )
257                    {
258                        interpolator.removePostProcessor( postProcessor );
259                    }
260                }
261            }
262    
263            return result;
264        }
265    
266        protected RecursionInterceptor getRecursionInterceptor()
267        {
268            return recursionInterceptor;
269        }
270    
271        protected void setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
272        {
273            this.recursionInterceptor = recursionInterceptor;
274        }
275    
276        protected abstract Interpolator createInterpolator();
277    
278        protected final Interpolator getInterpolator()
279        {
280            return interpolator;
281        }
282    
283    }