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 }