001package org.apache.maven.plugin; 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.Properties; 024 025import org.apache.maven.execution.MavenSession; 026import org.apache.maven.plugin.descriptor.MojoDescriptor; 027import org.apache.maven.plugin.descriptor.PluginDescriptor; 028import org.apache.maven.project.MavenProject; 029import org.apache.maven.project.path.PathTranslator; 030import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; 031import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator; 032import org.codehaus.plexus.logging.Logger; 033import org.codehaus.plexus.util.introspection.ReflectionValueExtractor; 034 035/** 036 * Evaluator for plugin parameters expressions. Content surrounded by <code>${</code> and <code>}</code> is evaluated. 037 * Recognized values are:<table border="1"> 038 * <tr><th>expression</th> <th></th> <th>evaluation result</th></tr> 039 * <tr><td><code>session</code></td> <td></td> <td>the actual {@link MavenSession}</td></tr> 040 * <tr><td><code>session.*</code></td> <td>(since Maven 3)</td><td></td></tr> 041 * <tr><td><code>localRepository</code></td> <td></td> <td>{@link MavenSession#getLocalRepository()}</td></tr> 042 * <tr><td><code>reactorProjects</code></td> <td></td> <td>{@link MavenSession#getProjects()}</td></tr> 043 * <tr><td><code>repositorySystemSession</code></td><td> (since Maven 3)</td><td>{@link MavenSession#getRepositorySession()}</td></tr> 044 * <tr><td><code>project</code></td> <td></td> <td>{@link MavenSession#getCurrentProject()}</td></tr> 045 * <tr><td><code>project.*</code></td> <td></td> <td></td></tr> 046 * <tr><td><code>pom.*</code></td> <td>(since Maven 3)</td><td>same as <code>project.*</code></td></tr> 047 * <tr><td><code>executedProject</code></td> <td></td> <td>{@link MavenProject#getExecutionProject()}</td></tr> 048 * <tr><td><code>settings</code></td> <td></td> <td>{@link MavenSession#getSettings()}</td></tr> 049 * <tr><td><code>settings.*</code></td> <td></td> <td></td></tr> 050 * <tr><td><code>basedir</code></td> <td></td> <td>{@link MavenSession#getExecutionRootDirectory()} or <code>System.getProperty( "user.dir" )</code> if null</td></tr> 051 * <tr><td><code>mojoExecution</code></td> <td></td> <td>the actual {@link MojoExecution}</td></tr> 052 * <tr><td><code>mojo</code></td> <td>(since Maven 3)</td><td>same as <code>mojoExecution</code></td></tr> 053 * <tr><td><code>mojo.*</code></td> <td>(since Maven 3)</td><td></td></tr> 054 * <tr><td><code>plugin</code></td> <td>(since Maven 3)</td><td>{@link MojoExecution#getMojoDescriptor()}.{@link MojoDescriptor#getPluginDescriptor() getPluginDescriptor()}</td></tr> 055 * <tr><td><code>plugin.*</code></td> <td></td> <td></td></tr> 056 * <tr><td><code>*</code></td> <td></td> <td>system properties</td></tr> 057 * <tr><td><code>*</code></td> <td></td> <td>project properties</td></tr> 058 * </table> 059 * <i>Notice:</i> <code>reports</code> was supported in Maven 2.x but was removed in Maven 3 060 * 061 * @author Jason van Zyl 062 * @see MavenSession 063 * @see MojoExecution 064 */ 065public class PluginParameterExpressionEvaluator 066 implements TypeAwareExpressionEvaluator 067{ 068 private MavenSession session; 069 070 private MojoExecution mojoExecution; 071 072 private MavenProject project; 073 074 private String basedir; 075 076 private Properties properties; 077 078 @Deprecated //TODO: used by the Enforcer plugin 079 public PluginParameterExpressionEvaluator( MavenSession session, MojoExecution mojoExecution, 080 PathTranslator pathTranslator, Logger logger, MavenProject project, 081 Properties properties ) 082 { 083 this( session, mojoExecution ); 084 } 085 086 public PluginParameterExpressionEvaluator( MavenSession session ) 087 { 088 this( session, null ); 089 } 090 091 public PluginParameterExpressionEvaluator( MavenSession session, MojoExecution mojoExecution ) 092 { 093 this.session = session; 094 this.mojoExecution = mojoExecution; 095 this.properties = session.getExecutionProperties(); 096 this.project = session.getCurrentProject(); 097 098 String basedir = null; 099 100 if ( project != null ) 101 { 102 File projectFile = project.getBasedir(); 103 104 // this should always be the case for non-super POM instances... 105 if ( projectFile != null ) 106 { 107 basedir = projectFile.getAbsolutePath(); 108 } 109 } 110 111 if ( basedir == null ) 112 { 113 basedir = session.getExecutionRootDirectory(); 114 } 115 116 if ( basedir == null ) 117 { 118 basedir = System.getProperty( "user.dir" ); 119 } 120 121 this.basedir = basedir; 122 } 123 124 public Object evaluate( String expr ) 125 throws ExpressionEvaluationException 126 { 127 return evaluate( expr, null ); 128 } 129 130 public Object evaluate( String expr, Class<?> type ) 131 throws ExpressionEvaluationException 132 { 133 Object value = null; 134 135 if ( expr == null ) 136 { 137 return null; 138 } 139 140 String expression = stripTokens( expr ); 141 if ( expression.equals( expr ) ) 142 { 143 int index = expr.indexOf( "${" ); 144 if ( index >= 0 ) 145 { 146 int lastIndex = expr.indexOf( "}", index ); 147 if ( lastIndex >= 0 ) 148 { 149 String retVal = expr.substring( 0, index ); 150 151 if ( ( index > 0 ) && ( expr.charAt( index - 1 ) == '$' ) ) 152 { 153 retVal += expr.substring( index + 1, lastIndex + 1 ); 154 } 155 else 156 { 157 Object subResult = evaluate( expr.substring( index, lastIndex + 1 ) ); 158 159 if ( subResult != null ) 160 { 161 retVal += subResult; 162 } 163 else 164 { 165 retVal += "$" + expr.substring( index + 1, lastIndex + 1 ); 166 } 167 } 168 169 retVal += evaluate( expr.substring( lastIndex + 1 ) ); 170 return retVal; 171 } 172 } 173 174 // Was not an expression 175 if ( expression.contains( "$$" ) ) 176 { 177 return expression.replaceAll( "\\$\\$", "\\$" ); 178 } 179 else 180 { 181 return expression; 182 } 183 } 184 185 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); 186 187 if ( "localRepository".equals( expression ) ) 188 { 189 value = session.getLocalRepository(); 190 } 191 else if ( "session".equals( expression ) ) 192 { 193 value = session; 194 } 195 else if ( expression.startsWith( "session" ) ) 196 { 197 try 198 { 199 int pathSeparator = expression.indexOf( "/" ); 200 201 if ( pathSeparator > 0 ) 202 { 203 String pathExpression = expression.substring( 1, pathSeparator ); 204 value = ReflectionValueExtractor.evaluate( pathExpression, session ); 205 value = value + expression.substring( pathSeparator ); 206 } 207 else 208 { 209 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), session ); 210 } 211 } 212 catch ( Exception e ) 213 { 214 // TODO: don't catch exception 215 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression, 216 e ); 217 } 218 } 219 else if ( "reactorProjects".equals( expression ) ) 220 { 221 value = session.getProjects(); 222 } 223 else if ( "mojoExecution".equals( expression ) ) 224 { 225 value = mojoExecution; 226 } 227 else if ( "project".equals( expression ) ) 228 { 229 value = project; 230 } 231 else if ( "executedProject".equals( expression ) ) 232 { 233 value = project.getExecutionProject(); 234 } 235 else if ( expression.startsWith( "project" ) || expression.startsWith( "pom" ) ) 236 { 237 try 238 { 239 int pathSeparator = expression.indexOf( "/" ); 240 241 if ( pathSeparator > 0 ) 242 { 243 String pathExpression = expression.substring( 0, pathSeparator ); 244 value = ReflectionValueExtractor.evaluate( pathExpression, project ); 245 value = value + expression.substring( pathSeparator ); 246 } 247 else 248 { 249 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), project ); 250 } 251 } 252 catch ( Exception e ) 253 { 254 // TODO: don't catch exception 255 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression, 256 e ); 257 } 258 } 259 else if ( expression.equals( "repositorySystemSession" ) ) 260 { 261 value = session.getRepositorySession(); 262 } 263 else if ( expression.equals( "mojo" ) ) 264 { 265 value = mojoExecution; 266 } 267 else if ( expression.startsWith( "mojo" ) ) 268 { 269 try 270 { 271 int pathSeparator = expression.indexOf( "/" ); 272 273 if ( pathSeparator > 0 ) 274 { 275 String pathExpression = expression.substring( 1, pathSeparator ); 276 value = ReflectionValueExtractor.evaluate( pathExpression, mojoExecution ); 277 value = value + expression.substring( pathSeparator ); 278 } 279 else 280 { 281 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), mojoExecution ); 282 } 283 } 284 catch ( Exception e ) 285 { 286 // TODO: don't catch exception 287 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression, 288 e ); 289 } 290 } 291 else if ( expression.equals( "plugin" ) ) 292 { 293 value = mojoDescriptor.getPluginDescriptor(); 294 } 295 else if ( expression.startsWith( "plugin" ) ) 296 { 297 try 298 { 299 int pathSeparator = expression.indexOf( "/" ); 300 301 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor(); 302 303 if ( pathSeparator > 0 ) 304 { 305 String pathExpression = expression.substring( 1, pathSeparator ); 306 value = ReflectionValueExtractor.evaluate( pathExpression, pluginDescriptor ); 307 value = value + expression.substring( pathSeparator ); 308 } 309 else 310 { 311 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), pluginDescriptor ); 312 } 313 } 314 catch ( Exception e ) 315 { 316 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression, 317 e ); 318 } 319 } 320 else if ( "settings".equals( expression ) ) 321 { 322 value = session.getSettings(); 323 } 324 else if ( expression.startsWith( "settings" ) ) 325 { 326 try 327 { 328 int pathSeparator = expression.indexOf( "/" ); 329 330 if ( pathSeparator > 0 ) 331 { 332 String pathExpression = expression.substring( 1, pathSeparator ); 333 value = ReflectionValueExtractor.evaluate( pathExpression, session.getSettings() ); 334 value = value + expression.substring( pathSeparator ); 335 } 336 else 337 { 338 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), session.getSettings() ); 339 } 340 } 341 catch ( Exception e ) 342 { 343 // TODO: don't catch exception 344 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression, 345 e ); 346 } 347 } 348 else if ( "basedir".equals( expression ) ) 349 { 350 value = basedir; 351 } 352 else if ( expression.startsWith( "basedir" ) ) 353 { 354 int pathSeparator = expression.indexOf( "/" ); 355 356 if ( pathSeparator > 0 ) 357 { 358 value = basedir + expression.substring( pathSeparator ); 359 } 360 } 361 362 /* 363 * MNG-4312: We neither have reserved all of the above magic expressions nor is their set fixed/well-known (it 364 * gets occasionally extended by newer Maven versions). This imposes the risk for existing plugins to 365 * unintentionally use such a magic expression for an ordinary system property. So here we check whether we 366 * ended up with a magic value that is not compatible with the type of the configured mojo parameter (a string 367 * could still be converted by the configurator so we leave those alone). If so, back off to evaluating the 368 * expression from properties only. 369 */ 370 if ( value != null && type != null && !( value instanceof String ) && !isTypeCompatible( type, value ) ) 371 { 372 value = null; 373 } 374 375 if ( value == null ) 376 { 377 // The CLI should win for defining properties 378 379 if ( properties != null ) 380 { 381 // We will attempt to get nab a system property as a way to specify a 382 // parameter to a plugins. My particular case here is allowing the surefire 383 // plugin to run a single test so I want to specify that class on the cli 384 // as a parameter. 385 386 value = properties.getProperty( expression ); 387 } 388 389 if ( ( value == null ) && ( ( project != null ) && ( project.getProperties() != null ) ) ) 390 { 391 value = project.getProperties().getProperty( expression ); 392 } 393 394 } 395 396 if ( value instanceof String ) 397 { 398 // TODO: without #, this could just be an evaluate call... 399 400 String val = (String) value; 401 402 int exprStartDelimiter = val.indexOf( "${" ); 403 404 if ( exprStartDelimiter >= 0 ) 405 { 406 if ( exprStartDelimiter > 0 ) 407 { 408 value = val.substring( 0, exprStartDelimiter ) + evaluate( val.substring( exprStartDelimiter ) ); 409 } 410 else 411 { 412 value = evaluate( val.substring( exprStartDelimiter ) ); 413 } 414 } 415 } 416 417 return value; 418 } 419 420 private static boolean isTypeCompatible( Class<?> type, Object value ) 421 { 422 if ( type.isInstance( value ) ) 423 { 424 return true; 425 } 426 // likely Boolean -> boolean, Short -> int etc. conversions, it's not the problem case we try to avoid 427 return ( ( type.isPrimitive() || type.getName().startsWith( "java.lang." ) ) 428 && value.getClass().getName().startsWith( "java.lang." ) ); 429 } 430 431 private String stripTokens( String expr ) 432 { 433 if ( expr.startsWith( "${" ) && ( expr.indexOf( "}" ) == expr.length() - 1 ) ) 434 { 435 expr = expr.substring( 2, expr.length() - 1 ); 436 } 437 return expr; 438 } 439 440 public File alignToBaseDirectory( File file ) 441 { 442 // TODO: Copied from the DefaultInterpolator. We likely want to resurrect the PathTranslator or at least a 443 // similar component for re-usage 444 if ( file != null ) 445 { 446 if ( file.isAbsolute() ) 447 { 448 // path was already absolute, just normalize file separator and we're done 449 } 450 else if ( file.getPath().startsWith( File.separator ) ) 451 { 452 // drive-relative Windows path, don't align with project directory but with drive root 453 file = file.getAbsoluteFile(); 454 } 455 else 456 { 457 // an ordinary relative path, align with project directory 458 file = new File( new File( basedir, file.getPath() ).toURI().normalize() ).getAbsoluteFile(); 459 } 460 } 461 return file; 462 } 463 464}