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