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