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