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 }