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