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