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:<table border="1">
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 @Deprecated //TODO: used by the Enforcer plugin
87 public PluginParameterExpressionEvaluator( MavenSession session, MojoExecution mojoExecution,
88 PathTranslator pathTranslator, Logger logger, MavenProject project,
89 Properties properties )
90 {
91 this( session, mojoExecution );
92 }
93
94 public PluginParameterExpressionEvaluator( MavenSession session )
95 {
96 this( session, null );
97 }
98
99 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 }