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<String>();
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<ValueSource>( 9 );
131
132        if ( projectDir != null )
133        {
134            ValueSource basedirValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
135            {
136                public Object getValue( String expression )
137                {
138                    if ( "basedir".equals( expression ) )
139                    {
140                        return projectDir.getAbsolutePath();
141                    }
142                    return null;
143                }
144            }, PROJECT_PREFIXES, true );
145            valueSources.add( basedirValueSource );
146
147            ValueSource baseUriValueSource = new PrefixedValueSourceWrapper( new AbstractValueSource( false )
148            {
149                public Object getValue( String expression )
150                {
151                    if ( "baseUri".equals( expression ) )
152                    {
153                        return projectDir.getAbsoluteFile().toURI().toString();
154                    }
155                    return null;
156                }
157            }, PROJECT_PREFIXES, false );
158            valueSources.add( baseUriValueSource );
159            valueSources.add( new BuildTimestampValueSource( config.getBuildStartTime(), modelProperties ) );
160        }
161
162        valueSources.add( modelValueSource1 );
163
164        valueSources.add( new MapBasedValueSource( config.getUserProperties() ) );
165
166        valueSources.add( new MapBasedValueSource( modelProperties ) );
167
168        valueSources.add( new MapBasedValueSource( config.getSystemProperties() ) );
169
170        valueSources.add( new AbstractValueSource( false )
171        {
172            public Object getValue( String expression )
173            {
174                return config.getSystemProperties().getProperty( "env." + expression );
175            }
176        } );
177
178        valueSources.add( modelValueSource2 );
179
180        return valueSources;
181    }
182
183    protected List<? extends InterpolationPostProcessor> createPostProcessors( final Model model,
184                                                                               final File projectDir,
185                                                                               final ModelBuildingRequest config )
186    {
187        List<InterpolationPostProcessor> processors = new ArrayList<InterpolationPostProcessor>( 2 );
188        if ( projectDir != null )
189        {
190            processors.add( new PathTranslatingPostProcessor( PROJECT_PREFIXES, TRANSLATED_PATH_EXPRESSIONS,
191                                                              projectDir, pathTranslator ) );
192        }
193        processors.add( new UrlNormalizingPostProcessor( urlNormalizer ) );
194        return processors;
195    }
196
197    protected String interpolateInternal( String src, List<? extends ValueSource> valueSources,
198                                          List<? extends InterpolationPostProcessor> postProcessors,
199                                          ModelProblemCollector problems )
200    {
201        if ( !src.contains( "${" ) )
202        {
203            return src;
204        }
205
206        String result = src;
207        synchronized ( this )
208        {
209
210            for ( ValueSource vs : valueSources )
211            {
212                interpolator.addValueSource( vs );
213            }
214
215            for ( InterpolationPostProcessor postProcessor : postProcessors )
216            {
217                interpolator.addPostProcessor( postProcessor );
218            }
219
220            try
221            {
222                try
223                {
224                    result = interpolator.interpolate( result, recursionInterceptor );
225                }
226                catch ( InterpolationException e )
227                {
228                    problems.add( new ModelProblemCollectorRequest( Severity.ERROR, Version.BASE )
229                        .setMessage( e.getMessage() ).setException( e ) );
230                }
231
232                interpolator.clearFeedback();
233            }
234            finally
235            {
236                for ( ValueSource vs : valueSources )
237                {
238                    interpolator.removeValuesSource( vs );
239                }
240
241                for ( InterpolationPostProcessor postProcessor : postProcessors )
242                {
243                    interpolator.removePostProcessor( postProcessor );
244                }
245            }
246        }
247
248        return result;
249    }
250
251    protected RecursionInterceptor getRecursionInterceptor()
252    {
253        return recursionInterceptor;
254    }
255
256    protected void setRecursionInterceptor( RecursionInterceptor recursionInterceptor )
257    {
258        this.recursionInterceptor = recursionInterceptor;
259    }
260
261    protected abstract Interpolator createInterpolator();
262
263    protected final Interpolator getInterpolator()
264    {
265        return interpolator;
266    }
267
268}