View Javadoc

1   package org.apache.maven.plugin.invoker;
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.BufferedReader;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.PrintStream;
29  import java.io.Reader;
30  import java.io.Writer;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Collections;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Properties;
38  import java.util.StringTokenizer;
39  import java.util.TreeSet;
40  
41  import org.apache.maven.plugin.AbstractMojo;
42  import org.apache.maven.plugin.MojoExecutionException;
43  import org.apache.maven.plugin.MojoFailureException;
44  import org.apache.maven.project.MavenProject;
45  import org.apache.maven.settings.Settings;
46  import org.apache.maven.shared.invoker.CommandLineConfigurationException;
47  import org.apache.maven.shared.invoker.DefaultInvocationRequest;
48  import org.apache.maven.shared.invoker.InvocationRequest;
49  import org.apache.maven.shared.invoker.InvocationResult;
50  import org.apache.maven.shared.invoker.Invoker;
51  import org.apache.maven.shared.invoker.MavenCommandLineBuilder;
52  import org.apache.maven.shared.invoker.MavenInvocationException;
53  import org.apache.maven.shared.model.fileset.FileSet;
54  import org.apache.maven.shared.model.fileset.util.FileSetManager;
55  import org.codehaus.plexus.util.DirectoryScanner;
56  import org.codehaus.plexus.util.FileUtils;
57  import org.codehaus.plexus.util.IOUtil;
58  import org.codehaus.plexus.util.InterpolationFilterReader;
59  import org.codehaus.plexus.util.ReaderFactory;
60  import org.codehaus.plexus.util.StringUtils;
61  import org.codehaus.plexus.util.WriterFactory;
62  import org.codehaus.plexus.util.cli.CommandLineException;
63  import org.codehaus.plexus.interpolation.InterpolationException;
64  import org.codehaus.plexus.interpolation.Interpolator;
65  import org.codehaus.plexus.interpolation.MapBasedValueSource;
66  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
67  
68  import bsh.EvalError;
69  import bsh.Interpreter;
70  
71  /**
72   * Searches for integration test Maven projects, and executes each, collecting a log in the project directory, and
73   * outputting the results to the command line.
74   *
75   * @goal run
76   * @phase integration-test
77   * @requiresDependencyResolution test
78   * @since 1.0
79   *
80   * @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
81   * @author <a href="mailto:jdcasey@apache.org">John Casey</a>
82   * @version $Id: InvokerMojo.java 682848 2008-08-05 18:24:07Z bentmann $
83   */
84  public class InvokerMojo
85      extends AbstractMojo
86  {
87  
88      /**
89       * Flag used to suppress certain invocations. This is useful in tailoring the
90       * build using profiles.
91       *
92       * @parameter default-value="false"
93       * @since 1.1
94       */
95      private boolean skipInvocation;
96  
97      /**
98       * Flag used to suppress the summary output notifying of successes and failures. If set to <code>true</code>,
99       * the only indication of the build's success or failure will be the effect it has on the main
100      * build (if it fails, the main build should fail as well). If <code>streamLogs</code> is enabled, the sub-build
101      * summary will also provide an indication.
102      *
103      * @parameter default-value="false"
104      */
105     private boolean suppressSummaries;
106 
107     /**
108      * Flag used to determine whether the build logs should be output to the normal mojo log.
109      *
110      * @parameter expression="${invoker.streamLogs}" default-value="false"
111      */
112     private boolean streamLogs;
113 
114     /**
115      * The local repository for caching artifacts.
116      *
117      * @parameter expression="${invoker.localRepositoryPath}"
118      */
119     private File localRepositoryPath;
120 
121     /**
122      * Directory to search for integration tests.
123      *
124      * @parameter expression="${invoker.projectsDirectory}" default-value="${basedir}/src/projects/"
125      */
126     private File projectsDirectory;
127 
128     /**
129      * Directory to which projects should be cloned prior to execution. If not specified, each integration test will be
130      * run in the directory in which the corresponding IT POM was found. In this case, you most likely want to configure
131      * your SCM to ignore <code>target</code> and <code>build.log</code> in the test's base directory.
132      * 
133      * @parameter
134      * @since 1.1
135      */
136     private File cloneProjectsTo;
137 
138     /**
139      * Some files are normally excluded when copying the IT projects from the directory specified by the parameter
140      * projectsDirectory to the directory given by cloneProjectsTo (e.g. <code>.svn</code>, <code>CVS</code>,
141      * <code>*~</code>, etc). Setting this parameter to <code>true</code> will cause all files to be copied to the
142      * cloneProjectsTo directory.
143      * 
144      * @parameter default-value="false"
145      * @since 1.2
146      */
147     private boolean cloneAllFiles;
148 
149     /**
150      * A single POM to build, skipping any scanning parameters and behavior.
151      *
152      * @parameter expression="${invoker.pom}"
153      */
154     private File pom;
155 
156     /**
157      * Includes for searching the integration test directory. This parameter is meant to be set from the POM.
158      * If this parameter is not set, the plugin will search for all <code>pom.xml</code> files one directory below
159      * {@link #projectsDirectory} (<code>*&#47;pom.xml</code>).
160      *
161      * @parameter
162      */
163     private List pomIncludes = Collections.singletonList( "*/pom.xml" );
164 
165     /**
166      * Excludes for searching the integration test directory. This parameter is meant to be set from the POM. By
167      * default, no POM files are excluded.
168      * 
169      * @parameter
170      */
171     private List pomExcludes = Collections.EMPTY_LIST;
172 
173     /**
174      * The list of goals to execute on each project. Default value is: <code>package</code>.
175      *
176      * @parameter
177      */
178     private List goals = Collections.singletonList( "package" );
179 
180     /**
181      * The name of the project-specific file that contains the enumeration of goals to execute for that test.
182      * 
183      * @parameter expression="${invoker.goalsFile}" default-value="goals.txt"
184      * @deprecated As of version 1.2 the properties file specified by the parameter invokerPropertiesFile should be used
185      *             instead.
186      */
187     private String goalsFile;
188 
189     /**
190      * @component
191      */
192     private Invoker invoker;
193 
194     /**
195      * Relative path of a pre-build hook BeanShell script to run prior to executing the build.
196      *
197      * @parameter expression="${invoker.preBuildHookScript}" default-value="prebuild.bsh"
198      */
199     private String preBuildHookScript;
200 
201     /**
202      * Relative path of a cleanup/verification BeanShell script to run after executing the build.
203      *
204      * @parameter expression="${invoker.postBuildHookScript}" default-value="postbuild.bsh"
205      */
206     private String postBuildHookScript;
207 
208     /**
209      * Location of a properties file that defines CLI properties for the test.
210      *
211      * @parameter expression="${invoker.testPropertiesFile}" default-value="test.properties"
212      */
213     private String testPropertiesFile;
214 
215     /**
216      * Common set of test properties to pass in on each IT's command line, via -D parameters.
217      *
218      * @parameter
219      * @deprecated Use properties parameter instead.
220      */
221     private Properties testProperties;
222 
223     /**
224      * Common set of properties to pass in on each project's command line, via -D parameters.
225      *
226      * @parameter
227      * @since 1.1
228      */
229     private Map properties;
230 
231     /**
232      * Whether to show errors in the build output.
233      *
234      * @parameter expression="${invoker.showErrors}" default-value="false"
235      */
236     private boolean showErrors;
237 
238     /**
239      * Whether to show debug statements in the build output.
240      *
241      * @parameter expression="${invoker.debug}" default-value="false"
242      */
243     private boolean debug;
244 
245     /**
246      * Suppress logging to the <code>build.log</code> file.
247      *
248      * @parameter expression="${invoker.noLog}" default-value="false"
249      */
250     private boolean noLog;
251 
252     /**
253      * List of profile identifiers to explicitly trigger in the build.
254      * 
255      * @parameter
256      * @since 1.1
257      */
258     private List profiles;
259 
260     /**
261      * List of properties which will be used to interpolate goal files.
262      *
263      * @parameter
264      * @since 1.1
265      */
266     private Properties interpolationsProperties;
267 
268     /**
269      * The Maven Project Object
270      *
271      * @parameter expression="${project}"
272      * @required
273      * @readonly
274      * @since 1.1
275      */
276     private MavenProject project;
277 
278     /**
279      * Specify this parameter to run individual tests by file name, overriding the <code>pomIncludes</code>
280      * and <code>pomExcludes</code> parameters.  Each pattern you specify here will be used to create an 
281      * include pattern formatted like <code>${projectsDirectory}/${invoker.test}</code>, 
282      * so you can just type "-Dinvoker.test=MyTest" to run a single it in ${projectsDirectory}/${invoker.test}".  
283      * 
284      * @parameter expression="${invoker.test}"
285      * @since 1.1
286      */
287     private String invokerTest;
288 
289     /**
290      * The name of the project-specific file that contains the enumeration of profiles to use for that test. <b>If the
291      * file exists and empty no profiles will be used even if the profiles is set</b>
292      * 
293      * @parameter expression="${invoker.profilesFile}" default-value="profiles.txt"
294      * @since 1.1
295      * @deprecated As of version 1.2 the properties file specified by the parameter invokerPropertiesFile should be used
296      *             instead.
297      */
298     private String profilesFile;
299 
300     /**
301      * Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs.
302      * 
303      * @parameter expression="${invoker.settingsFile}"
304      * @since 1.2
305      */
306     private File settingsFile;
307 
308     /**
309      * The <code>MAVEN_OPTS</code> environment variable to use when invoking Maven. This value can be overridden for
310      * individual integration tests by using {@link #invokerPropertiesFile}.
311      * 
312      * @parameter expression="${invoker.mavenOpts}"
313      * @since 1.2
314      */
315     private String mavenOpts;
316 
317     /**
318      * The file encoding for the pre-/post-build scripts and the list files for goals and profiles.
319      * 
320      * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
321      * @since 1.2
322      */
323     private String encoding;
324     
325     /**
326      * The current user system settings for use in Maven.
327      *
328      * @parameter expression="${settings}"
329      * @required
330      * @readonly
331      * @since 1.2
332      */
333     private Settings settings;    
334 
335     /**
336      * A flag whether the test class path of the project under test should be included in the class path of the
337      * pre-/post-build scripts. If set to <code>false</code>, the class path of script interpreter consists only of
338      * the <a href="dependencies.html">runtime dependencies</a> of the Maven Invoker Plugin. If set the
339      * <code>true</code>, the project's test class path will be prepended to the interpreter class path. Among
340      * others, this feature allows the scripts to access utility classes from the test sources of your project.
341      * 
342      * @parameter expression="${invoker.addTestClassPath}" default-value="false"
343      * @since 1.2
344      */
345     private boolean addTestClassPath;
346 
347     /**
348      * The name of an optional test-specific file that contains properties used to configure the invocation of an
349      * integration test. This properties file may be used to specify settings for an individual test invocation. Any
350      * property present in the file will override the corresponding setting from the plugin configuration. The values of
351      * the properties are filtered and may use expressions like <code>${project.version}</code> to reference project
352      * properties or values from the parameter {@link #interpolationsProperties}. The snippet below describes the
353      * supported properties:
354      * 
355      * <pre>
356      * # A comma or space separated list of goals/phases to execute, may
357      * # specify an empty list to execute the default goal of the IT project
358      * invoker.goals=clean package site
359      * 
360      * # A comma or space separated list of profiles to activate
361      * invoker.profiles=its,jdk15
362      * 
363      * # The value for the environment variable MAVEN_OPTS
364      * invoker.mavenOpts=-Dfile.encoding=UTF-16 -Xms32m -Xmx256m
365      * 
366      * # Possible values are &quot;fail-fast&quot; (default), &quot;fail-at-end&quot; and &quot;fail-never&quot;
367      * invoker.failureBehavior=fail-never
368      * 
369      * # The expected result of the build, possible values are &quot;success&quot; (default) and &quot;failure&quot;
370      * invoker.buildResult=failure
371      * 
372      * # A boolean value controlling the -N flag, defaults to &quot;false&quot;
373      * invoker.nonRecursive=false
374      * </pre>
375      * 
376      * @parameter expression="${invoker.invokerPropertiesFile}" default-value="invoker.properties"
377      * @since 1.2
378      */
379     private String invokerPropertiesFile;
380 
381 
382     public void execute()
383         throws MojoExecutionException, MojoFailureException
384     {
385         if ( skipInvocation )
386         {
387             getLog().info( "Skipping invocation per configuration."
388                 + " If this is incorrect, ensure the skipInvocation parameter is not set to true." );
389             return;
390         }
391 
392         String[] includedPoms;
393         if ( pom != null )
394         {
395             try
396             {
397                 projectsDirectory = pom.getCanonicalFile().getParentFile();
398             }
399             catch ( IOException e )
400             {
401                 throw new MojoExecutionException( "Failed to discover projectsDirectory from pom File parameter."
402                     + " Reason: " + e.getMessage(), e );
403             }
404 
405             includedPoms = new String[]{ pom.getName() };
406         }
407         else
408         {
409             try
410             {
411                 includedPoms = getPoms();
412             }
413             catch ( final IOException e )
414             {
415                 throw new MojoExecutionException( "Error retrieving POM list from includes, excludes, "
416                                 + "and projects directory. Reason: " + e.getMessage(), e );
417             }
418         }
419 
420 
421         if ( ( includedPoms == null ) || ( includedPoms.length < 1 ) )
422         {
423             getLog().info( "No test-projects were selected for execution." );
424             return;
425         }
426 
427         if ( StringUtils.isEmpty( encoding ) )
428         {
429             getLog().warn(
430                            "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
431                                + ", i.e. build is platform dependent!" );
432         }
433 
434         File projectsDir = projectsDirectory;
435 
436         if ( cloneProjectsTo != null )
437         {
438             cloneProjectsTo.mkdirs();
439 
440             try
441             {
442                 cloneProjects( includedPoms );
443             }
444             catch ( IOException e )
445             {
446                 throw new MojoExecutionException( "Failed to clone projects from: " + projectsDirectory + " to: "
447                     + cloneProjectsTo + ". Reason: " + e.getMessage(), e );
448             }
449 
450             projectsDir = cloneProjectsTo;
451         }
452 
453         final List failures = new ArrayList();
454 
455         for ( int i = 0; i < includedPoms.length; i++ )
456         {
457             final String pom = includedPoms[i];
458 
459             runBuild( projectsDir, pom, failures );
460         }
461 
462 
463         if ( !suppressSummaries )
464         {
465             final StringBuffer summary = new StringBuffer();
466             summary.append( "\n\n" );
467             summary.append( "---------------------------------------\n" );
468             summary.append( "Execution Summary:\n" );
469             summary.append( "Builds Passing: " ).append( includedPoms.length - failures.size() ).append( "\n" );
470             summary.append( "Builds Failing: " ).append( failures.size() ).append( "\n" );
471             summary.append( "---------------------------------------\n" );
472 
473             if ( !failures.isEmpty() )
474             {
475                 summary.append( "\nThe following builds failed:\n" );
476 
477                 for ( final Iterator it = failures.iterator(); it.hasNext(); )
478                 {
479                     final String pom = ( String ) it.next();
480                     summary.append( "\n*  " ).append( pom );
481                 }
482 
483                 summary.append( "\n" );
484             }
485 
486             getLog().info( summary.toString() );
487         }
488 
489         if ( !failures.isEmpty() )
490         {
491             String message = failures.size() + " builds failed.";
492 
493             throw new MojoFailureException( this, message, message );
494         }
495     }
496 
497     /**
498      * Creates a new reader for the specified file, using the plugin's {@link #encoding} parameter.
499      * 
500      * @param file The file to create a reader for, must not be <code>null</code>.
501      * @return The reader for the file, never <code>null</code>.
502      * @throws IOException If the specified file was not found or the configured encoding is not supported.
503      */
504     private Reader newReader( File file )
505         throws IOException
506     {
507         if ( StringUtils.isNotEmpty( encoding ) )
508         {
509             return ReaderFactory.newReader( file, encoding );
510         }
511         else
512         {
513             return ReaderFactory.newPlatformReader( file );
514         }
515     }
516 
517     private void cloneProjects( String[] includedPoms )
518         throws IOException
519     {
520         List clonedSubpaths = new ArrayList();
521 
522         for ( int i = 0; i < includedPoms.length; i++ )
523         {
524             String subpath = includedPoms[i];
525             int lastSep = subpath.lastIndexOf( File.separator );
526 
527             if ( lastSep > -1 )
528             {
529                 subpath = subpath.substring( 0, lastSep );
530             }
531             else
532             {
533                 subpath = ".";
534             }
535 
536             // avoid copying subdirs that are already cloned.
537             if ( !alreadyCloned( subpath, clonedSubpaths ) )
538             {
539                 // avoid creating new files that point to dir/.
540                 if ( ".".equals( subpath ) )
541                 {
542                     String cloneSubdir = normalizePath( cloneProjectsTo, projectsDirectory.getCanonicalPath() );
543 
544                     // avoid infinite recursion if the cloneTo path is a subdirectory.
545                     if ( cloneSubdir != null )
546                     {
547                         File temp = File.createTempFile( "pre-invocation-clone.", "" );
548                         temp.delete();
549                         temp.mkdirs();
550 
551                         copyDirectoryStructure( projectsDirectory, temp );
552 
553                         FileUtils.deleteDirectory( new File( temp, cloneSubdir ) );
554 
555                         copyDirectoryStructure( temp, cloneProjectsTo );
556                     }
557                     else
558                     {
559                         copyDirectoryStructure( projectsDirectory, cloneProjectsTo );
560                     }
561                 }
562                 else
563                 {
564                     copyDirectoryStructure( new File( projectsDirectory, subpath ), new File( cloneProjectsTo,
565                                                                                               subpath ) );
566                 }
567 
568                 clonedSubpaths.add( subpath );
569             }
570         }
571     }
572     
573     /**
574      * Copied a directory structure with deafault exclusions (.svn, CVS, etc)
575      * 
576      * @param sourceDir
577      * @param destDir
578      * @throws IOException
579      */
580     private void copyDirectoryStructure( File sourceDir, File destDir ) throws IOException
581     {
582         DirectoryScanner scanner = new DirectoryScanner();
583         scanner.setBasedir( sourceDir );
584         if ( ! cloneAllFiles )
585         {
586             scanner.addDefaultExcludes();
587         }
588         scanner.scan();
589         
590         String [] includedFiles = scanner.getIncludedFiles();
591         for ( int i = 0; i < includedFiles.length; ++i )
592         {
593             File sourceFile = new File( sourceDir, includedFiles[ i ] );
594             File destFile = new File( destDir, includedFiles[ i ] );
595             FileUtils.copyFile( sourceFile, destFile );
596         }
597     }
598 
599     static boolean alreadyCloned( String subpath, List clonedSubpaths )
600     {
601         for ( Iterator iter = clonedSubpaths.iterator(); iter.hasNext(); )
602         {
603             String path = (String) iter.next();
604 
605             if ( ".".equals( path ) || subpath.equals( path ) || subpath.startsWith( path + File.separator ) )
606             {
607                 return true;
608             }
609         }
610 
611         return false;
612     }
613 
614     private void runBuild( final File projectsDir, final String pom, final List failures )
615         throws MojoExecutionException
616     {
617 
618         File pomFile = new File( projectsDir, pom );
619         final File basedir = pomFile.getParentFile();
620         File interpolatedPomFile = buildInterpolatedFile( pomFile, basedir, "interpolated-pom.xml" );
621         FileLogger logger = null;
622         try
623         {
624             getLog().info( "Building: " + pom );
625 
626             final File outputLog = new File( basedir, "build.log" );
627 
628             final Properties invokerProperties = getInvokerProperties( basedir );
629             if ( getLog().isDebugEnabled() && !invokerProperties.isEmpty() )
630             {
631                 getLog().debug( "Using invoker properties:" );
632                 for ( Iterator it = new TreeSet( invokerProperties.keySet() ).iterator(); it.hasNext(); )
633                 {
634                     String key = (String) it.next();
635                     String value = invokerProperties.getProperty( key );
636                     getLog().debug( "  " + key + " = " + value );
637                 }
638             }
639 
640             if ( !noLog )
641             {
642                 outputLog.getParentFile().mkdirs();
643 
644                 try
645                 {
646                     if ( streamLogs )
647                     {
648                         logger = new FileLogger( outputLog, getLog() );
649                     }
650                     else
651                     {
652                         logger = new FileLogger( outputLog );
653                     }
654 
655                     getLog().debug( "build log initialized in: " + outputLog );
656                 }
657                 catch ( final IOException e )
658                 {
659                     getLog().debug( "Error initializing build logfile in: " + outputLog, e );
660                     getLog().info( "...FAILED[could not initialize logfile in: " + outputLog + "]" );
661 
662                     failures.add( pom );
663 
664                     return;
665                 }
666             }
667 
668             if ( !prebuild( basedir, interpolatedPomFile, failures, logger ) )
669             {
670                 getLog().info( "...FAILED[pre-build script returned false]" );
671 
672                 failures.add( pom );
673 
674                 return;
675             }
676 
677             final InvocationRequest request = new DefaultInvocationRequest();
678 
679             final List invocationGoals = getGoals( basedir );
680 
681             if ( ( invocationGoals.size() == 1 ) && "_default".equals( invocationGoals.get( 0 ) ) )
682             {
683                 getLog().debug( "Executing default goal for project in: " + pom );
684             }
685             else
686             {
687                 getLog().debug( "Executing goals: " + invocationGoals + " for project in: " + pom );
688 
689                 request.setGoals( invocationGoals );
690             }
691 
692             try
693             {
694                 Properties collectedTestProperties = new Properties();
695 
696                 if ( testProperties != null )
697                 {
698                     collectedTestProperties.putAll( testProperties );
699                 }
700 
701                 if ( properties != null )
702                 {
703                     collectedTestProperties.putAll( properties );
704                 }
705 
706                 final Properties loadedProperties = loadTestProperties( basedir );
707 
708                 if ( loadedProperties != null )
709                 {
710                     collectedTestProperties.putAll( loadedProperties );
711                 }
712 
713                 request.setProperties( collectedTestProperties );
714             }
715             catch ( final IOException e )
716             {
717                 getLog().debug( "Error reading test-properties file in: " + testPropertiesFile, e );
718                 getLog().info( "...FAILED[error reading test properties in: " + testPropertiesFile + "]" );
719 
720                 failures.add( pom );
721 
722                 return;
723             }
724 
725             if ( localRepositoryPath != null )
726             {
727                 File localRepoDir = localRepositoryPath;
728 
729                 getLog().debug( "Using local repository: " + localRepoDir );
730 
731                 if ( ! localRepositoryPath.exists() )
732                 {
733                     localRepositoryPath.mkdirs();
734                 }
735                 
736                 request.setLocalRepositoryDirectory( localRepoDir );
737             }
738 
739             request.setInteractive( false );
740 
741             request.setShowErrors( showErrors );
742 
743             request.setDebug( debug );
744 
745             request.setBaseDirectory( basedir );
746 
747             if ( !noLog )
748             {
749                 request.setErrorHandler( logger );
750 
751                 request.setOutputHandler( logger );
752             }
753 
754             request.setPomFile( interpolatedPomFile );
755 
756             request.setProfiles( getProfiles( basedir ) );
757 
758             if ( settingsFile != null )
759             {
760                 buildInterpolatedFile( settingsFile, settingsFile.getParentFile(), settingsFile.getName()
761                     + ".interpolated" );
762                 request.setUserSettingsFile( new File( settingsFile.getParentFile(), settingsFile.getName()
763                     + ".interpolated" ) );
764             }
765 
766             request.setMavenOpts( mavenOpts );
767 
768             configureInvocation( request, invokerProperties );
769 
770             try
771             {
772                 getLog().debug( "Using MAVEN_OPTS: " + request.getMavenOpts() );
773                 getLog().debug( "Executing: " + new MavenCommandLineBuilder().build( request ) );
774             }
775             catch ( CommandLineConfigurationException e )
776             {
777                 getLog().debug( "Failed to display command line: " + e.getMessage() );
778             }
779 
780             InvocationResult result = null;
781 
782             try
783             {
784                 result = invoker.execute( request );
785             }
786             catch ( final MavenInvocationException e )
787             {
788                 getLog().debug( "Error invoking Maven: " + e.getMessage(), e );
789                 getLog().info( "...FAILED[error invoking Maven]" );
790 
791                 failures.add( pom );
792 
793                 return;
794             }
795 
796             final CommandLineException executionException = result.getExecutionException();
797             final boolean nonZeroExit =
798                 "failure".equalsIgnoreCase( invokerProperties.getProperty( "invoker.buildResult" ) );
799 
800             if ( executionException != null )
801             {
802                 if ( !suppressSummaries )
803                 {
804                     StringBuffer buffer = new StringBuffer( 256 );
805                     buffer.append( "...FAILED. " );
806                     if ( !noLog )
807                     {
808                         buffer.append( "See " ).append( outputLog.getAbsolutePath() ).append( " for details." );
809                     }
810                     else
811                     {
812                         buffer.append( "See console output for details." );
813                     }
814                     getLog().info( buffer.toString() );
815                 }
816 
817                 failures.add( pom );
818             }
819             else if ( ( result.getExitCode() != 0 ) != nonZeroExit )
820             {
821                 if ( !suppressSummaries )
822                 {
823                     StringBuffer buffer = new StringBuffer( 256 );
824                     buffer.append( "...FAILED[code=" ).append( result.getExitCode() ).append( "]. " );
825                     if ( !noLog )
826                     {
827                         buffer.append( "See " ).append( outputLog.getAbsolutePath() ).append( " for details." );
828                     }
829                     else
830                     {
831                         buffer.append( "See console output for details." );
832                     }
833                     getLog().info( buffer.toString() );
834                 }
835 
836                 failures.add( pom );
837             }
838             else if ( !verify( basedir, interpolatedPomFile, failures, logger ) )
839             {
840                 if ( !suppressSummaries )
841                 {
842                     getLog().info( "...FAILED[verify script returned false]." );
843                 }
844 
845                 failures.add( pom );
846             }
847             else if ( !suppressSummaries )
848             {
849                 getLog().info( "...SUCCESS." );
850             }
851         }
852         finally
853         {
854             if ( logger != null )
855             {
856                 logger.close();
857             }
858         }
859     }
860 
861     private Properties loadTestProperties( final File basedir )
862         throws IOException
863     {
864         final Properties testProps = new Properties();
865 
866         if ( testPropertiesFile != null )
867         {
868             final File testProperties = new File( basedir, testPropertiesFile );
869 
870             if ( testProperties.exists() )
871             {
872                 InputStream fin = null;
873                 try
874                 {
875                     fin = new FileInputStream( testProperties );
876 
877                     testProps.load( fin );
878                 }
879                 finally
880                 {
881                     IOUtil.close( fin );
882                 }
883             }
884         }
885 
886         return testProps;
887     }
888 
889     private boolean verify( final File basedir, final File pom, final List failures, final FileLogger logger )
890     {
891         boolean result = true;
892 
893         if ( postBuildHookScript != null )
894         {
895             try
896             {
897                 result = runScript( "verification script", basedir, postBuildHookScript, logger );
898             }
899             catch ( final IOException e )
900             {
901                 result = false;
902             }
903             catch ( final EvalError e )
904             {
905                 String errorMessage = "error evaluating script " + basedir.getPath() + File.separatorChar
906                     + postBuildHookScript + ", " + e.getMessage();
907                 getLog().error( errorMessage, e );
908                 result = false;
909             }
910         }
911 
912         return result;
913     }
914 
915     private boolean runScript( final String scriptDescription, final File basedir, final String relativeScriptPath,
916                                final FileLogger logger )
917         throws IOException, EvalError
918     {
919         final File script = new File( basedir, relativeScriptPath );
920 
921         boolean scriptResult = false;
922 
923         if ( script.exists() )
924         {
925             final Interpreter engine = new Interpreter();
926 
927             if ( addTestClassPath )
928             {
929                 getLog().debug( "Adding test class path to BeanShell interpreter:" );
930                 try
931                 {
932                     List testClassPath = project.getTestClasspathElements();
933                     for ( Iterator it = testClassPath.iterator(); it.hasNext(); )
934                     {
935                         String path = (String) it.next();
936                         getLog().debug( "  " + path );
937                         engine.getClassManager().addClassPath( new File( path ).toURI().toURL() );
938                     }
939                 }
940                 catch ( Exception e )
941                 {
942                     getLog().error( "Failed to add test class path to BeanShell interpreter", e );
943                 }
944             }
945 
946             PrintStream origOut = System.out;
947             PrintStream origErr = System.err;
948 
949             Reader reader = null;
950             try
951             {
952                 if ( !noLog )
953                 {
954                     logger.consumeLine( "Running " + scriptDescription + " in: " + script );
955 
956                     System.setErr( logger.getPrintStream() );
957                     System.setOut( logger.getPrintStream() );
958 
959                     engine.setErr( logger.getPrintStream() );
960                     engine.setOut( logger.getPrintStream() );
961                 }
962 
963                 engine.set( "basedir", basedir );
964 
965                 reader = newReader( script );
966 
967                 final Object result = engine.eval( reader );
968 
969                 scriptResult = Boolean.TRUE.equals( result ) || "true".equals( result );
970             }
971             finally
972             {
973                 IOUtil.close( reader );
974                 System.setErr( origErr );
975                 System.setOut( origOut );
976             }
977 
978             if ( !noLog )
979             {
980                 logger.consumeLine( "Finished " + scriptDescription + " in: " + script );
981             }
982         }
983         else
984         {
985             scriptResult = true;
986         }
987 
988         return scriptResult;
989     }
990 
991     private boolean prebuild( final File basedir, final File pom, final List failures, final FileLogger logger )
992     {
993         boolean result = true;
994 
995         if ( preBuildHookScript != null )
996         {
997             try
998             {
999                 result = runScript( "pre-build script", basedir, preBuildHookScript, logger );
1000             }
1001             catch ( final IOException e )
1002             {
1003                 result = false;
1004             }
1005             catch ( final EvalError e )
1006             {
1007                 String errorMessage = "error evaluating script " + basedir.getPath() + File.separatorChar
1008                     + postBuildHookScript + ", " + e.getMessage();
1009                 getLog().error( errorMessage, e );
1010                 result = false;
1011             }
1012         }
1013 
1014         return result;
1015     }
1016 
1017     protected List getGoals( final File basedir )
1018     {
1019         List invocationGoals = goals;
1020 
1021         if ( goalsFile != null )
1022         {
1023             final File projectGoalList = new File( basedir, goalsFile );
1024 
1025             if ( projectGoalList.exists() )
1026             {
1027                 final List goals = readFromFile( projectGoalList );
1028 
1029                 if ( ( goals != null ) && !goals.isEmpty() )
1030                 {
1031                     getLog().debug( "Using goals specified in file: " + projectGoalList );
1032                     invocationGoals = goals;
1033                 }
1034             }
1035         }
1036 
1037         return invocationGoals;
1038     }
1039 
1040     protected String[] getPoms()
1041         throws IOException
1042     {
1043         String[] poms;
1044 
1045         if ( ( pom != null ) && pom.exists() )
1046         {
1047             poms = new String[]{ pom.getAbsolutePath() };
1048         }
1049         else if ( invokerTest != null )
1050         {
1051             String[] testRegexes = StringUtils.split( invokerTest, "," );
1052             List /* String */includes = new ArrayList( testRegexes.length );
1053 
1054             for ( int i = 0, size = testRegexes.length; i < size; i++ )
1055             {
1056                 // user just use -Dinvoker.test=MWAR191,MNG111 to use a directory thats the end is not pom.xml
1057                 includes.add( testRegexes[i].endsWith( "pom.xml" ) ? testRegexes[i] : testRegexes[i]
1058                     + File.separatorChar + "pom.xml" );
1059             }
1060 
1061             final FileSet fs = new FileSet();
1062 
1063             fs.setIncludes( includes );
1064             //fs.setExcludes( pomExcludes );
1065             fs.setDirectory( projectsDirectory.getCanonicalPath() );
1066             fs.setFollowSymlinks( false );
1067             fs.setUseDefaultExcludes( false );
1068 
1069             final FileSetManager fsm = new FileSetManager( getLog() );
1070 
1071             poms = fsm.getIncludedFiles( fs );
1072         }
1073         else
1074         {
1075             final FileSet fs = new FileSet();
1076 
1077             fs.setIncludes( pomIncludes );
1078             fs.setExcludes( pomExcludes );
1079             fs.setDirectory( projectsDirectory.getCanonicalPath() );
1080             fs.setFollowSymlinks( false );
1081             fs.setUseDefaultExcludes( false );
1082 
1083             final FileSetManager fsm = new FileSetManager( getLog() );
1084 
1085             poms = fsm.getIncludedFiles( fs );
1086         }
1087 
1088         poms = normalizePomPaths( poms );
1089 
1090         return poms;
1091     }
1092 
1093     private String[] normalizePomPaths( String[] poms )
1094         throws IOException
1095     {
1096         String projectsDirPath = projectsDirectory.getCanonicalPath();
1097 
1098         String[] results = new String[poms.length];
1099         for ( int i = 0; i < poms.length; i++ )
1100         {
1101             String pomPath = poms[i];
1102 
1103             File pom = new File( pomPath );
1104 
1105             if ( !pom.isAbsolute() )
1106             {
1107                 pom = new File( projectsDirectory, pomPath );
1108             }
1109 
1110             String normalizedPath = normalizePath( pom, projectsDirPath );
1111 
1112             if ( normalizedPath == null )
1113             {
1114                 normalizedPath = pomPath;
1115             }
1116 
1117             results[i] = normalizedPath;
1118         }
1119 
1120         return results;
1121     }
1122 
1123     private String normalizePath( File path, String withinDirPath )
1124         throws IOException
1125     {
1126         String normalizedPath = path.getCanonicalPath();
1127 
1128         if ( normalizedPath.startsWith( withinDirPath ) )
1129         {
1130             normalizedPath = normalizedPath.substring( withinDirPath.length() );
1131             if ( normalizedPath.startsWith( File.separator ) )
1132             {
1133                 normalizedPath = normalizedPath.substring( File.separator.length() );
1134             }
1135 
1136             return normalizedPath;
1137         }
1138         else
1139         {
1140             return null;
1141         }
1142     }
1143 
1144     private List readFromFile( final File projectGoalList )
1145     {
1146         BufferedReader reader = null;
1147 
1148         List result = null;
1149 
1150         try
1151         {
1152             Map composite = new CompositeMap( this.project, this.interpolationsProperties );
1153             reader = new BufferedReader( new InterpolationFilterReader( newReader( projectGoalList ), composite ) );
1154 
1155             result = new ArrayList();
1156 
1157             String line = null;
1158             while ( ( line = reader.readLine() ) != null )
1159             {
1160                 result.addAll( collectListFromCSV( line ) );
1161             }
1162         }
1163         catch ( final IOException e )
1164         {
1165             getLog().warn(
1166                            "Failed to load goal list from file: " + projectGoalList
1167                                + ". Using 'goal' parameter configured on this plugin instead." );
1168             getLog().debug( "Error reading goals file: " + projectGoalList, e );
1169         }
1170         finally
1171         {
1172             IOUtil.close( reader );
1173         }
1174 
1175         return result;
1176     }
1177 
1178     private List collectListFromCSV( final String csv )
1179     {
1180         final List result = new ArrayList();
1181 
1182         if ( ( csv != null ) && ( csv.trim().length() > 0 ) )
1183         {
1184             final StringTokenizer st = new StringTokenizer( csv, "," );
1185 
1186             while ( st.hasMoreTokens() )
1187             {
1188                 result.add( st.nextToken().trim() );
1189             }
1190         }
1191 
1192         return result;
1193     }
1194 
1195     protected File buildInterpolatedFile( File originalFile, File targetDirectory, String targetFileName )
1196         throws MojoExecutionException
1197     {
1198         File interpolatedFile = new File( targetDirectory, targetFileName );
1199         if ( interpolatedFile.exists() )
1200         {
1201             interpolatedFile.delete();
1202         }
1203         interpolatedFile.deleteOnExit();
1204         if ( settings.getLocalRepository() != null )
1205         {
1206             if ( this.interpolationsProperties == null )
1207             {
1208                 this.interpolationsProperties = new Properties();
1209             }
1210             this.interpolationsProperties.put( "localRepository", settings.getLocalRepository() );
1211         }
1212         Map composite = new CompositeMap( this.project, this.interpolationsProperties );
1213 
1214         try
1215         {
1216             boolean created = interpolatedFile.createNewFile();
1217             if ( !created )
1218             {
1219                 throw new MojoExecutionException( "fail to create file " + interpolatedFile.getPath() );
1220             }
1221         }
1222         catch ( IOException e )
1223         {
1224             throw new MojoExecutionException( "fail to create file " + interpolatedFile.getPath() );
1225         }
1226         getLog().debug( "interpolate it pom to create interpolated in " + interpolatedFile.getPath() );
1227 
1228         BufferedReader reader = null;
1229         Writer writer = null;
1230         try
1231         {
1232             // interpolation with token @...@
1233             reader = new BufferedReader( new InterpolationFilterReader( ReaderFactory.newXmlReader( originalFile ),
1234                                                                         composite, "@", "@" ) );
1235             writer = WriterFactory.newXmlWriter( interpolatedFile );
1236             String line = null;
1237             while ( ( line = reader.readLine() ) != null )
1238             {
1239                 writer.write( line );
1240             }
1241             writer.flush();
1242         }
1243         catch ( IOException e )
1244         {
1245             String message = "error when interpolating it pom";
1246             throw new MojoExecutionException( message, e );
1247         }
1248         finally
1249         {
1250             // IOUtil in p-u is null check and silently NPE
1251             IOUtil.close( reader );
1252             IOUtil.close( writer );
1253         }
1254 
1255         if ( interpolatedFile == null )
1256         {
1257             // null check : normally impossibe but :-)
1258             throw new MojoExecutionException( "pom file is null after interpolation" );
1259         }
1260         return interpolatedFile;
1261     }
1262 
1263     protected List getProfiles( File projectDirectory )
1264         throws MojoExecutionException
1265     {
1266         if ( profilesFile == null )
1267         {
1268             return profiles == null ? Collections.EMPTY_LIST : profiles;
1269         }
1270         File projectProfilesFile = new File( projectDirectory, profilesFile );
1271         if ( !projectProfilesFile.exists() )
1272         {
1273             return profiles == null ? Collections.EMPTY_LIST : profiles;
1274         }
1275         BufferedReader reader = null;
1276         try
1277         {
1278             List profilesInFiles = new ArrayList();
1279             reader = new BufferedReader( newReader( projectProfilesFile ) );
1280             String line = null;
1281             while ( ( line = reader.readLine() ) != null )
1282             {
1283                 profilesInFiles.addAll( collectListFromCSV( line ) );
1284             }
1285             return profilesInFiles;
1286         }
1287         catch ( FileNotFoundException e )
1288         {
1289             // as we check first if the file it should not happened
1290             throw new MojoExecutionException( projectProfilesFile + " not found ", e );
1291         }
1292         catch ( IOException e )
1293         {
1294             throw new MojoExecutionException( "error reading profile in file " + projectProfilesFile + " not found ",
1295                                               e );
1296         }
1297         finally
1298         {
1299             IOUtil.close( reader );
1300         }
1301     }
1302 
1303     /**
1304      * Gets the (interpolated) invoker properties for an integration test.
1305      * 
1306      * @param projectDirectory The base directory of the IT project, must not be <code>null</code>.
1307      * @return The invoker properties, may be empty but never <code>null</code>.
1308      * @throws MojoExecutionException If an error occurred.
1309      */
1310     private Properties getInvokerProperties( final File projectDirectory )
1311         throws MojoExecutionException
1312     {
1313         Properties props = new Properties();
1314         if ( invokerPropertiesFile != null )
1315         {
1316             File propertiesFile = new File( projectDirectory, invokerPropertiesFile );
1317             if ( propertiesFile.isFile() )
1318             {
1319                 InputStream in = null;
1320                 try
1321                 {
1322                     in = new FileInputStream( propertiesFile );
1323                     props.load( in );
1324                 }
1325                 catch ( IOException e )
1326                 {
1327                     throw new MojoExecutionException( "Failed to read invoker properties: " + propertiesFile, e );
1328                 }
1329                 finally
1330                 {
1331                     IOUtil.close( in );
1332                 }
1333             }
1334 
1335             Map filter = new CompositeMap( project, this.interpolationsProperties );
1336             Interpolator interpolator = new RegexBasedInterpolator();
1337             interpolator.addValueSource( new MapBasedValueSource( filter ) );
1338             for ( Iterator it = props.keySet().iterator(); it.hasNext(); )
1339             {
1340                 String key = (String) it.next();
1341                 String value = props.getProperty( key );
1342                 try
1343                 {
1344                     value = interpolator.interpolate( value, "" );
1345                 }
1346                 catch ( InterpolationException e )
1347                 {
1348                     throw new MojoExecutionException( "Failed to interpolate invoker properties: " + propertiesFile,
1349                                                       e );
1350                 }
1351                 props.setProperty( key, value );
1352             }
1353         }
1354         return props;
1355     }
1356 
1357     /**
1358      * Configures the specified invocation request from the given invoker properties. Settings not present in the
1359      * invoker properties will be left unchanged in the invocation request.
1360      * 
1361      * @param request The invocation request to configure, must not be <code>null</code>.
1362      * @param properties The invoker properties used to configure the invocation, must not be <code>null</code>.
1363      * @return The configured invocation request.
1364      */
1365     private InvocationRequest configureInvocation( InvocationRequest request, Properties properties )
1366     {
1367         String goals = properties.getProperty( "invoker.goals" );
1368         if ( goals != null )
1369         {
1370             request.setGoals( new ArrayList( Arrays.asList( goals.split( "[,\\s]+" ) ) ) );
1371         }
1372 
1373         String profiles = properties.getProperty( "invoker.profiles" );
1374         if ( profiles != null )
1375         {
1376             request.setProfiles( new ArrayList( Arrays.asList( profiles.split( "[,\\s]+" ) ) ) );
1377         }
1378 
1379         String opts = properties.getProperty( "invoker.mavenOpts" );
1380         if ( opts != null )
1381         {
1382             request.setMavenOpts( opts );
1383         }
1384 
1385         String failureBehavior = properties.getProperty( "invoker.failureBehavior" );
1386         if ( failureBehavior != null )
1387         {
1388             request.setFailureBehavior( failureBehavior );
1389         }
1390 
1391         String nonRecursive = properties.getProperty( "invoker.nonRecursive" );
1392         if ( nonRecursive != null )
1393         {
1394             request.setRecursive( !Boolean.valueOf( nonRecursive ).booleanValue() );
1395         }
1396 
1397         return request;
1398     }
1399 
1400 }