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