001    package 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    
022    import java.io.File;
023    import java.util.Properties;
024    
025    import org.apache.maven.execution.MavenSession;
026    import org.apache.maven.plugin.descriptor.MojoDescriptor;
027    import org.apache.maven.plugin.descriptor.PluginDescriptor;
028    import org.apache.maven.project.MavenProject;
029    import org.apache.maven.project.path.PathTranslator;
030    import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
031    import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
032    import org.codehaus.plexus.logging.Logger;
033    import 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>               <td>{@link MavenSession#getLocalRepository()}</td></tr>
042     * <tr><td><code>reactorProjects</code></td>   <td></td>               <td>{@link MavenSession#getProjects()}</td></tr>
043     * <tr><td><code>repositorySystemSession</code></td><td> (since Maven 3)</td><td>{@link MavenSession#getRepositorySession()}</td></tr>
044     * <tr><td><code>project</code></td>           <td></td>               <td>{@link MavenSession#getCurrentProject()}</td></tr>
045     * <tr><td><code>project.*</code></td>         <td></td>               <td></td></tr>
046     * <tr><td><code>pom.*</code></td>             <td>(since Maven 3)</td><td>same as <code>project.*</code></td></tr>
047     * <tr><td><code>executedProject</code></td>   <td></td>               <td>{@link MavenProject#getExecutionProject()}</td></tr>
048     * <tr><td><code>settings</code></td>          <td></td>               <td>{@link MavenSession#getSettings()}</td></tr>
049     * <tr><td><code>settings.*</code></td>        <td></td>               <td></td></tr>
050     * <tr><td><code>basedir</code></td>           <td></td>               <td>{@link MavenSession#getExecutionRootDirectory()} or <code>System.getProperty( "user.dir" )</code> if null</td></tr>
051     * <tr><td><code>mojoExecution</code></td>     <td></td>               <td>the actual {@link MojoExecution}</td></tr>
052     * <tr><td><code>mojo</code></td>              <td>(since Maven 3)</td><td>same as <code>mojoExecution</code></td></tr>
053     * <tr><td><code>mojo.*</code></td>            <td>(since Maven 3)</td><td></td></tr>
054     * <tr><td><code>plugin</code></td>            <td>(since Maven 3)</td><td>{@link MojoExecution#getMojoDescriptor()}.{@link MojoDescriptor#getPluginDescriptor() getPluginDescriptor()}</td></tr>
055     * <tr><td><code>plugin.*</code></td>          <td></td>               <td></td></tr>
056     * <tr><td><code>*</code></td>                 <td></td>               <td>system properties</td></tr>
057     * <tr><td><code>*</code></td>                 <td></td>               <td>project properties</td></tr>
058     * </table>
059     * <i>Notice:</i> <code>reports</code> was supported in Maven 2.x but was removed in Maven 3
060     * 
061     * @author Jason van Zyl
062     * @see MavenSession
063     * @see MojoExecution
064     */
065    public class PluginParameterExpressionEvaluator
066        implements TypeAwareExpressionEvaluator
067    {
068        private MavenSession session;
069    
070        private MojoExecution mojoExecution;
071    
072        private MavenProject project;
073    
074        private String basedir;
075    
076        private Properties properties;
077    
078        @Deprecated //TODO: used by the Enforcer plugin
079        public PluginParameterExpressionEvaluator( MavenSession session, MojoExecution mojoExecution,
080                                                   PathTranslator pathTranslator, Logger logger, MavenProject project,
081                                                   Properties properties )
082        {
083            this( session, mojoExecution );
084        }
085    
086        public PluginParameterExpressionEvaluator( MavenSession session )
087        {
088            this( session, null );
089        }
090    
091        public PluginParameterExpressionEvaluator( MavenSession session, MojoExecution mojoExecution )
092        {
093            this.session = session;
094            this.mojoExecution = mojoExecution;
095            this.properties = session.getExecutionProperties();
096            this.project = session.getCurrentProject();
097    
098            String basedir = null;
099    
100            if ( project != null )
101            {
102                File projectFile = project.getBasedir();
103    
104                // this should always be the case for non-super POM instances...
105                if ( projectFile != null )
106                {
107                    basedir = projectFile.getAbsolutePath();
108                }
109            }
110    
111            if ( ( basedir == null ) && ( session != null ) )
112            {
113                basedir = session.getExecutionRootDirectory();
114            }
115    
116            if ( basedir == null )
117            {
118                basedir = System.getProperty( "user.dir" );
119            }
120    
121            this.basedir = basedir;
122        }
123    
124        public Object evaluate( String expr )
125            throws ExpressionEvaluationException
126        {
127            return evaluate( expr, null );
128        }
129    
130        public Object evaluate( String expr, Class<?> type )
131            throws ExpressionEvaluationException
132        {
133            Object value = null;
134    
135            if ( expr == null )
136            {
137                return null;
138            }
139    
140            String expression = stripTokens( expr );
141            if ( expression.equals( expr ) )
142            {
143                int index = expr.indexOf( "${" );
144                if ( index >= 0 )
145                {
146                    int lastIndex = expr.indexOf( "}", index );
147                    if ( lastIndex >= 0 )
148                    {
149                        String retVal = expr.substring( 0, index );
150    
151                        if ( ( index > 0 ) && ( expr.charAt( index - 1 ) == '$' ) )
152                        {
153                            retVal += expr.substring( index + 1, lastIndex + 1 );
154                        }
155                        else
156                        {
157                            Object subResult = evaluate( expr.substring( index, lastIndex + 1 ) );
158    
159                            if ( subResult != null )
160                            {
161                                retVal += subResult;
162                            }
163                            else
164                            {
165                                retVal += "$" + expr.substring( index + 1, lastIndex + 1 );
166                            }
167                        }
168    
169                        retVal += evaluate( expr.substring( lastIndex + 1 ) );
170                        return retVal;
171                    }
172                }
173    
174                // Was not an expression
175                if ( expression.contains( "$$" ) )
176                {
177                    return expression.replaceAll( "\\$\\$", "\\$" );
178                }
179                else
180                {
181                    return expression;
182                }
183            }
184    
185            MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
186    
187            if ( "localRepository".equals( expression ) )
188            {
189                value = session.getLocalRepository();
190            }
191            else if ( "session".equals( expression ) )
192            {
193                value = session;
194            }
195            else if ( expression.startsWith( "session" ) )
196            {
197                try
198                {
199                    int pathSeparator = expression.indexOf( "/" );
200    
201                    if ( pathSeparator > 0 )
202                    {
203                        String pathExpression = expression.substring( 1, pathSeparator );
204                        value = ReflectionValueExtractor.evaluate( pathExpression, session );
205                        value = value + expression.substring( pathSeparator );
206                    }
207                    else
208                    {
209                        value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), session );
210                    }
211                }
212                catch ( Exception e )
213                {
214                    // TODO: don't catch exception
215                    throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
216                                                             e );
217                }
218            }
219            else if ( "reactorProjects".equals( expression ) )
220            {
221                value = session.getProjects();
222            }
223            else if ( "mojoExecution".equals( expression ) )
224            {
225                value = mojoExecution;
226            }
227            else if ( "project".equals( expression ) )
228            {
229                value = project;
230            }
231            else if ( "executedProject".equals( expression ) )
232            {
233                value = project.getExecutionProject();
234            }
235            else if ( expression.startsWith( "project" ) || expression.startsWith( "pom" ) )
236            {
237                try
238                {
239                    int pathSeparator = expression.indexOf( "/" );
240    
241                    if ( pathSeparator > 0 )
242                    {
243                        String pathExpression = expression.substring( 0, pathSeparator );
244                        value = ReflectionValueExtractor.evaluate( pathExpression, project );
245                        value = value + expression.substring( pathSeparator );
246                    }
247                    else
248                    {
249                        value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), project );
250                    }
251                }
252                catch ( Exception e )
253                {
254                    // TODO: don't catch exception
255                    throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
256                                                             e );
257                }
258            }
259            else if ( expression.equals( "repositorySystemSession" ) )
260            {
261                value = session.getRepositorySession();
262            }
263            else if ( expression.equals( "mojo" ) )
264            {
265                value = mojoExecution;
266            }
267            else if ( expression.startsWith( "mojo" ) )
268            {
269                try
270                {
271                    int pathSeparator = expression.indexOf( "/" );
272    
273                    if ( pathSeparator > 0 )
274                    {
275                        String pathExpression = expression.substring( 1, pathSeparator );
276                        value = ReflectionValueExtractor.evaluate( pathExpression, mojoExecution );
277                        value = value + expression.substring( pathSeparator );
278                    }
279                    else
280                    {
281                        value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), mojoExecution );
282                    }
283                }
284                catch ( Exception e )
285                {
286                    // TODO: don't catch exception
287                    throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
288                                                             e );
289                }
290            }
291            else if ( expression.equals( "plugin" ) )
292            {
293                value = mojoDescriptor.getPluginDescriptor();
294            }
295            else if ( expression.startsWith( "plugin" ) )
296            {
297                try
298                {
299                    int pathSeparator = expression.indexOf( "/" );
300    
301                    PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
302    
303                    if ( pathSeparator > 0 )
304                    {
305                        String pathExpression = expression.substring( 1, pathSeparator );
306                        value = ReflectionValueExtractor.evaluate( pathExpression, pluginDescriptor );
307                        value = value + expression.substring( pathSeparator );
308                    }
309                    else
310                    {
311                        value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), pluginDescriptor );
312                    }
313                }
314                catch ( Exception e )
315                {
316                    throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
317                                                             e );
318                }
319            }
320            else if ( "settings".equals( expression ) )
321            {
322                value = session.getSettings();
323            }
324            else if ( expression.startsWith( "settings" ) )
325            {
326                try
327                {
328                    int pathSeparator = expression.indexOf( "/" );
329    
330                    if ( pathSeparator > 0 )
331                    {
332                        String pathExpression = expression.substring( 1, pathSeparator );
333                        value = ReflectionValueExtractor.evaluate( pathExpression, session.getSettings() );
334                        value = value + expression.substring( pathSeparator );
335                    }
336                    else
337                    {
338                        value = ReflectionValueExtractor.evaluate( expression.substring( 1 ), session.getSettings() );
339                    }
340                }
341                catch ( Exception e )
342                {
343                    // TODO: don't catch exception
344                    throw new ExpressionEvaluationException( "Error evaluating plugin parameter expression: " + expression,
345                                                             e );
346                }
347            }
348            else if ( "basedir".equals( expression ) )
349            {
350                value = basedir;
351            }
352            else if ( expression.startsWith( "basedir" ) )
353            {
354                int pathSeparator = expression.indexOf( "/" );
355    
356                if ( pathSeparator > 0 )
357                {
358                    value = basedir + expression.substring( pathSeparator );
359                }
360            }
361    
362            /*
363             * MNG-4312: We neither have reserved all of the above magic expressions nor is their set fixed/well-known (it
364             * gets occasionally extended by newer Maven versions). This imposes the risk for existing plugins to
365             * unintentionally use such a magic expression for an ordinary system property. So here we check whether we
366             * ended up with a magic value that is not compatible with the type of the configured mojo parameter (a string
367             * could still be converted by the configurator so we leave those alone). If so, back off to evaluating the
368             * expression from properties only.
369             */
370            if ( value != null && type != null && !( value instanceof String ) && !isTypeCompatible( type, value ) )
371            {
372                value = null;
373            }
374    
375            if ( value == null )
376            {
377                // The CLI should win for defining properties
378    
379                if ( ( value == null ) && ( properties != null ) )
380                {
381                    // We will attempt to get nab a system property as a way to specify a
382                    // parameter to a plugins. My particular case here is allowing the surefire
383                    // plugin to run a single test so I want to specify that class on the cli
384                    // as a parameter.
385    
386                    value = properties.getProperty( expression );
387                }
388    
389                if ( ( value == null ) && ( ( project != null ) && ( project.getProperties() != null ) ) )
390                {
391                    value = project.getProperties().getProperty( expression );
392                }
393    
394            }
395    
396            if ( value instanceof String )
397            {
398                // TODO: without #, this could just be an evaluate call...
399    
400                String val = (String) value;
401    
402                int exprStartDelimiter = val.indexOf( "${" );
403    
404                if ( exprStartDelimiter >= 0 )
405                {
406                    if ( exprStartDelimiter > 0 )
407                    {
408                        value = val.substring( 0, exprStartDelimiter ) + evaluate( val.substring( exprStartDelimiter ) );
409                    }
410                    else
411                    {
412                        value = evaluate( val.substring( exprStartDelimiter ) );
413                    }
414                }
415            }
416    
417            return value;
418        }
419    
420        private static boolean isTypeCompatible( Class<?> type, Object value )
421        {
422            if ( type.isInstance( value ) )
423            {
424                return true;
425            }
426            // likely Boolean -> boolean, Short -> int etc. conversions, it's not the problem case we try to avoid
427            return ( ( type.isPrimitive() || type.getName().startsWith( "java.lang." ) )
428                            && value.getClass().getName().startsWith( "java.lang." ) );
429        }
430    
431        private String stripTokens( String expr )
432        {
433            if ( expr.startsWith( "${" ) && ( expr.indexOf( "}" ) == expr.length() - 1 ) )
434            {
435                expr = expr.substring( 2, expr.length() - 1 );
436            }
437            return expr;
438        }
439    
440        public File alignToBaseDirectory( File file )
441        {
442            // TODO: Copied from the DefaultInterpolator. We likely want to resurrect the PathTranslator or at least a
443            // similar component for re-usage
444            if ( file != null )
445            {
446                if ( file.isAbsolute() )
447                {
448                    // path was already absolute, just normalize file separator and we're done
449                }
450                else if ( file.getPath().startsWith( File.separator ) )
451                {
452                    // drive-relative Windows path, don't align with project directory but with drive root
453                    file = file.getAbsoluteFile();
454                }
455                else
456                {
457                    // an ordinary relative path, align with project directory
458                    file = new File( new File( basedir, file.getPath() ).toURI().normalize() ).getAbsoluteFile();
459                }
460            }
461            return file;
462        }
463    
464    }