View Javadoc
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 = 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 }