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 }