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}