1 package org.apache.maven.plugin;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.File;
23 import java.util.Properties;
24
25 import org.apache.maven.execution.MavenSession;
26 import org.apache.maven.plugin.descriptor.MojoDescriptor;
27 import org.apache.maven.plugin.descriptor.PluginDescriptor;
28 import org.apache.maven.project.MavenProject;
29 import org.apache.maven.project.path.PathTranslator;
30 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
31 import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
32 import org.codehaus.plexus.logging.Logger;
33 import org.codehaus.plexus.util.introspection.ReflectionValueExtractor;
34
35 /**
36 * Evaluator for plugin parameters expressions. Content surrounded by <code>${</code> and <code>}</code> is evaluated.
37 * Recognized values are:
38 * <table border="1">
39 * <caption>Expression matrix</caption>
40 * <tr><th>expression</th> <th></th> <th>evaluation result</th></tr>
41 * <tr><td><code>session</code></td> <td></td> <td>the actual {@link MavenSession}</td></tr>
42 * <tr><td><code>session.*</code></td> <td>(since Maven 3)</td><td></td></tr>
43 * <tr><td><code>localRepository</code></td> <td></td>
44 * <td>{@link MavenSession#getLocalRepository()}</td></tr>
45 * <tr><td><code>reactorProjects</code></td> <td></td> <td>{@link MavenSession#getProjects()}</td></tr>
46 * <tr><td><code>repositorySystemSession</code></td><td> (since Maven 3)</td>
47 * <td>{@link MavenSession#getRepositorySession()}</td></tr>
48 * <tr><td><code>project</code></td> <td></td>
49 * <td>{@link MavenSession#getCurrentProject()}</td></tr>
50 * <tr><td><code>project.*</code></td> <td></td> <td></td></tr>
51 * <tr><td><code>pom.*</code></td> <td>(since Maven 3)</td><td>same as <code>project.*</code></td></tr>
52 * <tr><td><code>executedProject</code></td> <td></td>
53 * <td>{@link MavenProject#getExecutionProject()}</td></tr>
54 * <tr><td><code>settings</code></td> <td></td> <td>{@link MavenSession#getSettings()}</td></tr>
55 * <tr><td><code>settings.*</code></td> <td></td> <td></td></tr>
56 * <tr><td><code>basedir</code></td> <td></td>
57 * <td>{@link MavenSession#getExecutionRootDirectory()} or
58 * <code>System.getProperty( "user.dir" )</code> if null</td></tr>
59 * <tr><td><code>mojoExecution</code></td> <td></td> <td>the actual {@link MojoExecution}</td></tr>
60 * <tr><td><code>mojo</code></td> <td>(since Maven 3)</td><td>same as <code>mojoExecution</code></td></tr>
61 * <tr><td><code>mojo.*</code></td> <td>(since Maven 3)</td><td></td></tr>
62 * <tr><td><code>plugin</code></td> <td>(since Maven 3)</td>
63 * <td>{@link MojoExecution#getMojoDescriptor()}.{@link MojoDescriptor#getPluginDescriptor()
64 * getPluginDescriptor()}</td></tr>
65 * <tr><td><code>plugin.*</code></td> <td></td> <td></td></tr>
66 * <tr><td><code>*</code></td> <td></td> <td>system properties</td></tr>
67 * <tr><td><code>*</code></td> <td></td> <td>project properties</td></tr>
68 * </table>
69 * <i>Notice:</i> <code>reports</code> was supported in Maven 2.x but was removed in Maven 3
70 *
71 * @author Jason van Zyl
72 * @see MavenSession
73 * @see MojoExecution
74 */
75 public class PluginParameterExpressionEvaluator
76 implements TypeAwareExpressionEvaluator
77 {
78 private MavenSession session;
79
80 private MojoExecution mojoExecution;
81
82 private MavenProject project;
83
84 private String basedir;
85
86 private Properties properties;
87
88 @Deprecated //TODO used by the Enforcer plugin
89 public PluginParameterExpressionEvaluator( MavenSession session, MojoExecution mojoExecution,
90 PathTranslator pathTranslator, Logger logger, MavenProject project,
91 Properties properties )
92 {
93 this( session, mojoExecution );
94 }
95
96 public PluginParameterExpressionEvaluator( MavenSession session )
97 {
98 this( session, null );
99 }
100
101 public PluginParameterExpressionEvaluator( MavenSession session, MojoExecution mojoExecution )
102 {
103 this.session = session;
104 this.mojoExecution = mojoExecution;
105 this.properties = new Properties();
106 this.project = session.getCurrentProject();
107
108 //
109 // Maven4: We may want to evaluate how this is used but we add these separate as the
110 // getExecutionProperties is deprecated in MavenSession.
111 //
112 this.properties.putAll( session.getUserProperties() );
113 this.properties.putAll( session.getSystemProperties() );
114
115 String basedir = null;
116
117 if ( project != null )
118 {
119 File projectFile = project.getBasedir();
120
121 // this should always be the case for non-super POM instances...
122 if ( projectFile != null )
123 {
124 basedir = projectFile.getAbsolutePath();
125 }
126 }
127
128 if ( basedir == null )
129 {
130 basedir = session.getExecutionRootDirectory();
131 }
132
133 if ( basedir == null )
134 {
135 basedir = System.getProperty( "user.dir" );
136 }
137
138 this.basedir = basedir;
139 }
140
141 @Override
142 public Object evaluate( String expr )
143 throws ExpressionEvaluationException
144 {
145 return evaluate( expr, null );
146 }
147
148 @Override
149 @SuppressWarnings( "checkstyle:methodlength" )
150 public Object evaluate( String expr, Class<?> type )
151 throws ExpressionEvaluationException
152 {
153 Object value = null;
154
155 if ( expr == null )
156 {
157 return null;
158 }
159
160 String expression = stripTokens( expr );
161 if ( expression.equals( expr ) )
162 {
163 int index = expr.indexOf( "${" );
164 if ( index >= 0 )
165 {
166 int lastIndex = expr.indexOf( '}', index );
167 if ( lastIndex >= 0 )
168 {
169 String retVal = expr.substring( 0, index );
170
171 if ( ( index > 0 ) && ( expr.charAt( index - 1 ) == '$' ) )
172 {
173 retVal += expr.substring( index + 1, lastIndex + 1 );
174 }
175 else
176 {
177 Object subResult = evaluate( expr.substring( index, lastIndex + 1 ) );
178
179 if ( subResult != null )
180 {
181 retVal += subResult;
182 }
183 else
184 {
185 retVal += "$" + expr.substring( index + 1, lastIndex + 1 );
186 }
187 }
188
189 retVal += evaluate( expr.substring( lastIndex + 1 ) );
190 return retVal;
191 }
192 }
193
194 // Was not an expression
195 if ( expression.contains( "$$" ) )
196 {
197 return expression.replaceAll( "\\$\\$", "\\$" );
198 }
199 else
200 {
201 return expression;
202 }
203 }
204
205 MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
206
207 if ( "localRepository".equals( expression ) )
208 {
209 value = session.getLocalRepository();
210 }
211 else if ( "session".equals( expression ) )
212 {
213 value = session;
214 }
215 else if ( expression.startsWith( "session" ) )
216 {
217 try
218 {
219 int pathSeparator = expression.indexOf( '/' );
220
221 if ( pathSeparator > 0 )
222 {
223 String pathExpression = expression.substring( 1, pathSeparator );
224 value = ReflectionValueExtractor.evaluate( pathExpression, session );
225 value = value + expression.substring( pathSeparator );
226 }
227 else
228 {
229 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), session );
230 }
231 }
232 catch ( Exception e )
233 {
234 // TODO don't catch exception
235 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
236 e );
237 }
238 }
239 else if ( "reactorProjects".equals( expression ) )
240 {
241 value = session.getProjects();
242 }
243 else if ( "mojoExecution".equals( expression ) )
244 {
245 value = mojoExecution;
246 }
247 else if ( "project".equals( expression ) )
248 {
249 value = project;
250 }
251 else if ( "executedProject".equals( expression ) )
252 {
253 value = project.getExecutionProject();
254 }
255 else if ( expression.startsWith( "project" ) || expression.startsWith( "pom" ) )
256 {
257 try
258 {
259 int pathSeparator = expression.indexOf( '/' );
260
261 if ( pathSeparator > 0 )
262 {
263 String pathExpression = expression.substring( 0, pathSeparator );
264 value = ReflectionValueExtractor.evaluate( pathExpression, project );
265 value = value + expression.substring( pathSeparator );
266 }
267 else
268 {
269 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), project );
270 }
271 }
272 catch ( Exception e )
273 {
274 // TODO don't catch exception
275 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
276 e );
277 }
278 }
279 else if ( expression.equals( "repositorySystemSession" ) )
280 {
281 value = session.getRepositorySession();
282 }
283 else if ( expression.equals( "mojo" ) )
284 {
285 value = mojoExecution;
286 }
287 else if ( expression.startsWith( "mojo" ) )
288 {
289 try
290 {
291 int pathSeparator = expression.indexOf( '/' );
292
293 if ( pathSeparator > 0 )
294 {
295 String pathExpression = expression.substring( 1, pathSeparator );
296 value = ReflectionValueExtractor.evaluate( pathExpression, mojoExecution );
297 value = value + expression.substring( pathSeparator );
298 }
299 else
300 {
301 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), mojoExecution );
302 }
303 }
304 catch ( Exception e )
305 {
306 // TODO don't catch exception
307 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
308 e );
309 }
310 }
311 else if ( expression.equals( "plugin" ) )
312 {
313 value = mojoDescriptor.getPluginDescriptor();
314 }
315 else if ( expression.startsWith( "plugin" ) )
316 {
317 try
318 {
319 int pathSeparator = expression.indexOf( '/' );
320
321 PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
322
323 if ( pathSeparator > 0 )
324 {
325 String pathExpression = expression.substring( 1, pathSeparator );
326 value = ReflectionValueExtractor.evaluate( pathExpression, pluginDescriptor );
327 value = value + expression.substring( pathSeparator );
328 }
329 else
330 {
331 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), pluginDescriptor );
332 }
333 }
334 catch ( Exception e )
335 {
336 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
337 e );
338 }
339 }
340 else if ( "settings".equals( expression ) )
341 {
342 value = session.getSettings();
343 }
344 else if ( expression.startsWith( "settings" ) )
345 {
346 try
347 {
348 int pathSeparator = expression.indexOf( '/' );
349
350 if ( pathSeparator > 0 )
351 {
352 String pathExpression = expression.substring( 1, pathSeparator );
353 value = ReflectionValueExtractor.evaluate( pathExpression, session.getSettings() );
354 value = value + expression.substring( pathSeparator );
355 }
356 else
357 {
358 value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), session.getSettings() );
359 }
360 }
361 catch ( Exception e )
362 {
363 // TODO don't catch exception
364 throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
365 e );
366 }
367 }
368 else if ( "basedir".equals( expression ) )
369 {
370 value = basedir;
371 }
372 else if ( expression.startsWith( "basedir" ) )
373 {
374 int pathSeparator = expression.indexOf( '/' );
375
376 if ( pathSeparator > 0 )
377 {
378 value = basedir + expression.substring( pathSeparator );
379 }
380 }
381
382 /*
383 * MNG-4312: We neither have reserved all of the above magic expressions nor is their set fixed/well-known (it
384 * gets occasionally extended by newer Maven versions). This imposes the risk for existing plugins to
385 * unintentionally use such a magic expression for an ordinary system property. So here we check whether we
386 * ended up with a magic value that is not compatible with the type of the configured mojo parameter (a string
387 * could still be converted by the configurator so we leave those alone). If so, back off to evaluating the
388 * expression from properties only.
389 */
390 if ( value != null && type != null && !( value instanceof String ) && !isTypeCompatible( type, value ) )
391 {
392 value = null;
393 }
394
395 if ( value == null )
396 {
397 // The CLI should win for defining properties
398
399 if ( properties != null )
400 {
401 // We will attempt to get nab a system property as a way to specify a
402 // parameter to a plugins. My particular case here is allowing the surefire
403 // plugin to run a single test so I want to specify that class on the cli
404 // as a parameter.
405
406 value = properties.getProperty( expression );
407 }
408
409 if ( ( value == null ) && ( ( project != null ) && ( project.getProperties() != null ) ) )
410 {
411 value = project.getProperties().getProperty( expression );
412 }
413
414 }
415
416 if ( value instanceof String )
417 {
418 // TODO without #, this could just be an evaluate call...
419
420 String val = (String) value;
421
422 int exprStartDelimiter = val.indexOf( "${" );
423
424 if ( exprStartDelimiter >= 0 )
425 {
426 if ( exprStartDelimiter > 0 )
427 {
428 value = val.substring( 0, exprStartDelimiter ) + evaluate( val.substring( exprStartDelimiter ) );
429 }
430 else
431 {
432 value = evaluate( val.substring( exprStartDelimiter ) );
433 }
434 }
435 }
436
437 return value;
438 }
439
440 private static boolean isTypeCompatible( Class<?> type, Object value )
441 {
442 if ( type.isInstance( value ) )
443 {
444 return true;
445 }
446 // likely Boolean -> boolean, Short -> int etc. conversions, it's not the problem case we try to avoid
447 return ( ( type.isPrimitive() || type.getName().startsWith( "java.lang." ) )
448 && value.getClass().getName().startsWith( "java.lang." ) );
449 }
450
451 private String stripTokens( String expr )
452 {
453 if ( expr.startsWith( "${" ) && ( expr.indexOf( '}' ) == expr.length() - 1 ) )
454 {
455 expr = expr.substring( 2, expr.length() - 1 );
456 }
457 return expr;
458 }
459
460 @Override
461 public File alignToBaseDirectory( File file )
462 {
463 // TODO Copied from the DefaultInterpolator. We likely want to resurrect the PathTranslator or at least a
464 // similar component for re-usage
465 if ( file != null )
466 {
467 if ( file.isAbsolute() )
468 {
469 // path was already absolute, just normalize file separator and we're done
470 }
471 else if ( file.getPath().startsWith( File.separator ) )
472 {
473 // drive-relative Windows path, don't align with project directory but with drive root
474 file = file.getAbsoluteFile();
475 }
476 else
477 {
478 // an ordinary relative path, align with project directory
479 file = new File( new File( basedir, file.getPath() ).toURI().normalize() ).getAbsoluteFile();
480 }
481 }
482 return file;
483 }
484
485 }