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