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