Coverage Report - org.apache.maven.plugin.invoker.InvokerMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
InvokerMojo
20%
97/489
13%
33/246
6.429
 
 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.IOException;
 26  
 import java.io.InputStream;
 27  
 import java.io.PrintStream;
 28  
 import java.io.Reader;
 29  
 import java.io.Writer;
 30  
 import java.util.ArrayList;
 31  
 import java.util.Arrays;
 32  
 import java.util.Collection;
 33  
 import java.util.Collections;
 34  
 import java.util.HashMap;
 35  
 import java.util.Iterator;
 36  
 import java.util.LinkedHashMap;
 37  
 import java.util.LinkedHashSet;
 38  
 import java.util.List;
 39  
 import java.util.Locale;
 40  
 import java.util.Map;
 41  
 import java.util.Properties;
 42  
 import java.util.StringTokenizer;
 43  
 import java.util.TreeSet;
 44  
 
 45  
 import org.apache.maven.model.Model;
 46  
 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
 47  
 import org.apache.maven.plugin.AbstractMojo;
 48  
 import org.apache.maven.plugin.MojoExecutionException;
 49  
 import org.apache.maven.plugin.MojoFailureException;
 50  
 import org.apache.maven.project.MavenProject;
 51  
 import org.apache.maven.settings.Settings;
 52  
 import org.apache.maven.shared.invoker.CommandLineConfigurationException;
 53  
 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
 54  
 import org.apache.maven.shared.invoker.InvocationRequest;
 55  
 import org.apache.maven.shared.invoker.InvocationResult;
 56  
 import org.apache.maven.shared.invoker.Invoker;
 57  
 import org.apache.maven.shared.invoker.MavenCommandLineBuilder;
 58  
 import org.apache.maven.shared.invoker.MavenInvocationException;
 59  
 import org.apache.maven.shared.model.fileset.FileSet;
 60  
 import org.apache.maven.shared.model.fileset.util.FileSetManager;
 61  
 import org.codehaus.plexus.util.DirectoryScanner;
 62  
 import org.codehaus.plexus.util.FileUtils;
 63  
 import org.codehaus.plexus.util.IOUtil;
 64  
 import org.codehaus.plexus.util.InterpolationFilterReader;
 65  
 import org.codehaus.plexus.util.ReaderFactory;
 66  
 import org.codehaus.plexus.util.StringUtils;
 67  
 import org.codehaus.plexus.util.WriterFactory;
 68  
 import org.codehaus.plexus.interpolation.InterpolationException;
 69  
 import org.codehaus.plexus.interpolation.Interpolator;
 70  
 import org.codehaus.plexus.interpolation.MapBasedValueSource;
 71  
 import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
 72  
 
 73  
 /**
 74  
  * Searches for integration test Maven projects, and executes each, collecting a log in the project directory, and
 75  
  * outputting the results to the command line.
 76  
  *
 77  
  * @goal run
 78  
  * @phase integration-test
 79  
  * @requiresDependencyResolution test
 80  
  * @since 1.0
 81  
  *
 82  
  * @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
 83  
  * @author <a href="mailto:jdcasey@apache.org">John Casey</a>
 84  
  * @version $Id: InvokerMojo.java 698174 2008-09-23 13:20:41Z bentmann $
 85  
  */
 86  9
 public class InvokerMojo
 87  
     extends AbstractMojo
 88  
 {
 89  
 
 90  
     /**
 91  
      * Flag used to suppress certain invocations. This is useful in tailoring the build using profiles.
 92  
      * 
 93  
      * @parameter default-value="false"
 94  
      * @since 1.1
 95  
      */
 96  
     private boolean skipInvocation;
 97  
 
 98  
     /**
 99  
      * Flag used to suppress the summary output notifying of successes and failures. If set to <code>true</code>, the
 100  
      * only indication of the build's success or failure will be the effect it has on the main build (if it fails, the
 101  
      * main build should fail as well). If {@link #streamLogs} is enabled, the sub-build summary will also provide an
 102  
      * indication.
 103  
      * 
 104  
      * @parameter default-value="false"
 105  
      */
 106  
     private boolean suppressSummaries;
 107  
 
 108  
     /**
 109  
      * Flag used to determine whether the build logs should be output to the normal mojo log.
 110  
      *
 111  
      * @parameter expression="${invoker.streamLogs}" default-value="false"
 112  
      */
 113  
     private boolean streamLogs;
 114  
 
 115  
     /**
 116  
      * The local repository for caching artifacts. It is strongly recommended to specify a path to an isolated
 117  
      * repository like <code>${project.build.directory}/it-repo</code>. Otherwise, your ordinary local repository will
 118  
      * be used, potentially soiling it with broken artifacts.
 119  
      * 
 120  
      * @parameter expression="${invoker.localRepositoryPath}" default-value="${settings.localRepository}"
 121  
      */
 122  
     private File localRepositoryPath;
 123  
 
 124  
     /**
 125  
      * Directory to search for integration tests.
 126  
      *
 127  
      * @parameter expression="${invoker.projectsDirectory}" default-value="${basedir}/src/it/"
 128  
      */
 129  
     private File projectsDirectory;
 130  
 
 131  
     /**
 132  
      * Directory to which projects should be cloned prior to execution. If not specified, each integration test will be
 133  
      * run in the directory in which the corresponding IT POM was found. In this case, you most likely want to configure
 134  
      * your SCM to ignore <code>target</code> and <code>build.log</code> in the test's base directory.
 135  
      * 
 136  
      * @parameter
 137  
      * @since 1.1
 138  
      */
 139  
     private File cloneProjectsTo;
 140  
 
 141  
     /**
 142  
      * Some files are normally excluded when copying the IT projects from the directory specified by the parameter
 143  
      * projectsDirectory to the directory given by cloneProjectsTo (e.g. <code>.svn</code>, <code>CVS</code>,
 144  
      * <code>*~</code>, etc). Setting this parameter to <code>true</code> will cause all files to be copied to the
 145  
      * cloneProjectsTo directory.
 146  
      * 
 147  
      * @parameter default-value="false"
 148  
      * @since 1.2
 149  
      */
 150  
     private boolean cloneAllFiles;
 151  
 
 152  
     /**
 153  
      * A single POM to build, skipping any scanning parameters and behavior.
 154  
      *
 155  
      * @parameter expression="${invoker.pom}"
 156  
      */
 157  
     private File pom;
 158  
 
 159  
     /**
 160  
      * Include patterns for searching the integration test directory for projects. This parameter is meant to be set
 161  
      * from the POM. If this parameter is not set, the plugin will search for all <code>pom.xml</code> files one
 162  
      * directory below {@link #projectsDirectory} (i.e. <code>*&#47;pom.xml</code>).<br>
 163  
      * <br>
 164  
      * Starting with version 1.3, mere directories can also be matched by these patterns. For example, the include
 165  
      * pattern <code>*</code> will run Maven builds on all immediate sub directories of {@link #projectsDirectory},
 166  
      * regardless if they contain a <code>pom.xml</code>. This allows to perform builds that need/should not depend on
 167  
      * the existence of a POM.
 168  
      * 
 169  
      * @parameter
 170  
      */
 171  9
     private List pomIncludes = Collections.singletonList( "*/pom.xml" );
 172  
 
 173  
     /**
 174  
      * Exclude patterns for searching the integration test directory. This parameter is meant to be set from the POM. By
 175  
      * default, no POM files are excluded. For the convenience of using an include pattern like <code>*</code>, the
 176  
      * custom settings file specified by the parameter {@link #settingsFile} will always be excluded automatically.
 177  
      * 
 178  
      * @parameter
 179  
      */
 180  9
     private List pomExcludes = Collections.EMPTY_LIST;
 181  
 
 182  
     /**
 183  
      * Include patterns for searching the projects directory for projects that need to be run before the other projects.
 184  
      * This parameter allows to declare projects that perform setup tasks like installing utility artifacts into the
 185  
      * local repository. Projects matched by these patterns are implicitly excluded from the scan for ordinary projects.
 186  
      * Also, the exclusions defined by the parameter {@link #pomExcludes} apply to the setup projects, too. Default
 187  
      * value is: <code>setup*&#47;pom.xml</code>.
 188  
      * 
 189  
      * @parameter
 190  
      * @since 1.3
 191  
      */
 192  9
     private List setupIncludes = Collections.singletonList( "setup*/pom.xml" );
 193  
 
 194  
     /**
 195  
      * The list of goals to execute on each project. Default value is: <code>package</code>.
 196  
      *
 197  
      * @parameter
 198  
      */
 199  9
     private List goals = Collections.singletonList( "package" );
 200  
 
 201  
     /**
 202  
      * The name of the project-specific file that contains the enumeration of goals to execute for that test.
 203  
      * 
 204  
      * @parameter expression="${invoker.goalsFile}" default-value="goals.txt"
 205  
      * @deprecated As of version 1.2, the key <code>invoker.goals</code> from the properties file specified by the
 206  
      *             parameter {@link #invokerPropertiesFile} should be used instead.
 207  
      */
 208  
     private String goalsFile;
 209  
 
 210  
     /**
 211  
      * @component
 212  
      */
 213  
     private Invoker invoker;
 214  
 
 215  
     /**
 216  
      * Relative path of a pre-build hook script to run prior to executing the build. This script may be written with
 217  
      * either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>prebuild</code>), the plugin
 218  
      * searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>. If this
 219  
      * script exists for a particular project but returns any value different from <code>true</code> or throws an
 220  
      * exception, the corresponding build is flagged as a failure. In this case, neither Maven nor the post-build hook
 221  
      * script will be invoked.
 222  
      * 
 223  
      * @parameter expression="${invoker.preBuildHookScript}" default-value="prebuild"
 224  
      */
 225  
     private String preBuildHookScript;
 226  
 
 227  
     /**
 228  
      * Relative path of a cleanup/verification hook script to run after executing the build. This script may be written
 229  
      * with either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>verify</code>), the
 230  
      * plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>.
 231  
      * If this script exists for a particular project but returns any value different from <code>true</code> or throws
 232  
      * an exception, the corresponding build is flagged as a failure.
 233  
      * 
 234  
      * @parameter expression="${invoker.postBuildHookScript}" default-value="postbuild"
 235  
      */
 236  
     private String postBuildHookScript;
 237  
 
 238  
     /**
 239  
      * Location of a properties file that defines CLI properties for the test.
 240  
      *
 241  
      * @parameter expression="${invoker.testPropertiesFile}" default-value="test.properties"
 242  
      */
 243  
     private String testPropertiesFile;
 244  
 
 245  
     /**
 246  
      * Common set of test properties to pass in on each IT's command line, via -D parameters.
 247  
      * 
 248  
      * @parameter
 249  
      * @deprecated As of version 1.1, use the {@link #properties} parameter instead.
 250  
      */
 251  
     private Properties testProperties;
 252  
 
 253  
     /**
 254  
      * Common set of properties to pass in on each project's command line, via -D parameters.
 255  
      *
 256  
      * @parameter
 257  
      * @since 1.1
 258  
      */
 259  
     private Map properties;
 260  
 
 261  
     /**
 262  
      * Whether to show errors in the build output.
 263  
      *
 264  
      * @parameter expression="${invoker.showErrors}" default-value="false"
 265  
      */
 266  
     private boolean showErrors;
 267  
 
 268  
     /**
 269  
      * Whether to show debug statements in the build output.
 270  
      *
 271  
      * @parameter expression="${invoker.debug}" default-value="false"
 272  
      */
 273  
     private boolean debug;
 274  
 
 275  
     /**
 276  
      * Suppress logging to the <code>build.log</code> file.
 277  
      *
 278  
      * @parameter expression="${invoker.noLog}" default-value="false"
 279  
      */
 280  
     private boolean noLog;
 281  
 
 282  
     /**
 283  
      * List of profile identifiers to explicitly trigger in the build.
 284  
      * 
 285  
      * @parameter
 286  
      * @since 1.1
 287  
      */
 288  
     private List profiles;
 289  
 
 290  
     /**
 291  
      * List of properties which will be used to interpolate goal files.
 292  
      * 
 293  
      * @parameter
 294  
      * @since 1.1
 295  
      * @deprecated As of version 1.3, the parameter {@link #filterProperties} should be used instead.
 296  
      */
 297  
     private Properties interpolationsProperties;
 298  
 
 299  
     /**
 300  
      * A list of additional properties which will be used to filter tokens in POMs and goal files.
 301  
      * 
 302  
      * @parameter
 303  
      * @since 1.3
 304  
      */
 305  
     private Map filterProperties;
 306  
 
 307  
     /**
 308  
      * The Maven Project Object
 309  
      *
 310  
      * @parameter expression="${project}"
 311  
      * @required
 312  
      * @readonly
 313  
      * @since 1.1
 314  
      */
 315  
     private MavenProject project;
 316  
 
 317  
     /**
 318  
      * A comma separated list of project names to run. Specify this parameter to run individual tests by file name,
 319  
      * overriding the {@link #setupIncludes}, {@link #pomIncludes} and {@link #pomExcludes} parameters. Each pattern you
 320  
      * specify here will be used to create an include pattern formatted like
 321  
      * <code>${projectsDirectory}/<i>pattern</i></code>, so you can just type
 322  
      * <code>-Dinvoker.test=FirstTest,SecondTest</code> to run builds in <code>${projectsDirectory}/FirstTest</code> and
 323  
      * <code>${projectsDirectory}/SecondTest</code>.
 324  
      * 
 325  
      * @parameter expression="${invoker.test}"
 326  
      * @since 1.1
 327  
      */
 328  
     private String invokerTest;
 329  
 
 330  
     /**
 331  
      * The name of the project-specific file that contains the enumeration of profiles to use for that test. <b>If the
 332  
      * file exists and is empty no profiles will be used even if the parameter {@link #profiles} is set.</b>
 333  
      * 
 334  
      * @parameter expression="${invoker.profilesFile}" default-value="profiles.txt"
 335  
      * @since 1.1
 336  
      * @deprecated As of version 1.2, the key <code>invoker.profiles</code> from the properties file specified by the
 337  
      *             parameter {@link #invokerPropertiesFile} should be used instead.
 338  
      */
 339  
     private String profilesFile;
 340  
 
 341  
     /**
 342  
      * Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs. Note that the
 343  
      * <code>&lt;localRepository&gt;</code> element of this settings file is always ignored, i.e. the path given by the
 344  
      * parameter {@link #localRepositoryPath} is dominant.
 345  
      * 
 346  
      * @parameter expression="${invoker.settingsFile}"
 347  
      * @since 1.2
 348  
      */
 349  
     private File settingsFile;
 350  
 
 351  
     /**
 352  
      * The <code>MAVEN_OPTS</code> environment variable to use when invoking Maven. This value can be overridden for
 353  
      * individual integration tests by using {@link #invokerPropertiesFile}.
 354  
      * 
 355  
      * @parameter expression="${invoker.mavenOpts}"
 356  
      * @since 1.2
 357  
      */
 358  
     private String mavenOpts;
 359  
 
 360  
     /**
 361  
      * The home directory of the Maven installation to use for the forked builds. Defaults to the current Maven
 362  
      * installation.
 363  
      * 
 364  
      * @parameter expression="${invoker.mavenHome}"
 365  
      * @since 1.3
 366  
      */
 367  
     private File mavenHome;
 368  
 
 369  
     /**
 370  
      * The <code>JAVA_HOME</code> environment variable to use for forked Maven invocations. Defaults to the current Java
 371  
      * home directory.
 372  
      * 
 373  
      * @parameter expression="${invoker.javaHome}"
 374  
      * @since 1.3
 375  
      */
 376  
     private File javaHome;
 377  
 
 378  
     /**
 379  
      * The file encoding for the pre-/post-build scripts and the list files for goals and profiles.
 380  
      * 
 381  
      * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
 382  
      * @since 1.2
 383  
      */
 384  
     private String encoding;
 385  
     
 386  
     /**
 387  
      * The current user system settings for use in Maven.
 388  
      *
 389  
      * @parameter expression="${settings}"
 390  
      * @required
 391  
      * @readonly
 392  
      * @since 1.2
 393  
      */
 394  
     private Settings settings;    
 395  
 
 396  
     /**
 397  
      * A flag whether the test class path of the project under test should be included in the class path of the
 398  
      * pre-/post-build scripts. If set to <code>false</code>, the class path of script interpreter consists only of
 399  
      * the <a href="dependencies.html">runtime dependencies</a> of the Maven Invoker Plugin. If set the
 400  
      * <code>true</code>, the project's test class path will be prepended to the interpreter class path. Among
 401  
      * others, this feature allows the scripts to access utility classes from the test sources of your project.
 402  
      * 
 403  
      * @parameter expression="${invoker.addTestClassPath}" default-value="false"
 404  
      * @since 1.2
 405  
      */
 406  
     private boolean addTestClassPath;
 407  
 
 408  
     /**
 409  
      * The test class path of the project under test.
 410  
      * 
 411  
      * @parameter default-value="${project.testClasspathElements}"
 412  
      * @readonly
 413  
      */
 414  
     private List testClassPath;
 415  
 
 416  
     /**
 417  
      * The name of an optional test-specific file that contains properties used to configure the invocation of an
 418  
      * integration test. This properties file may be used to specify settings for an individual test invocation. Any
 419  
      * property present in the file will override the corresponding setting from the plugin configuration. The values of
 420  
      * the properties are filtered and may use expressions like <code>${project.version}</code> to reference project
 421  
      * properties or values from the parameter {@link #filterProperties}. The snippet below describes the
 422  
      * supported properties:
 423  
      * 
 424  
      * <pre>
 425  
      * # A comma or space separated list of goals/phases to execute, may
 426  
      * # specify an empty list to execute the default goal of the IT project
 427  
      * invoker.goals=clean install
 428  
      * 
 429  
      * # Optionally, a list of goals to run during further invocations of Maven
 430  
      * invoker.goals.2=${project.groupId}:${project.artifactId}:${project.version}:run
 431  
      * 
 432  
      * # A comma or space separated list of profiles to activate
 433  
      * invoker.profiles=its,jdk15
 434  
      * 
 435  
      * # The value for the environment variable MAVEN_OPTS
 436  
      * invoker.mavenOpts=-Dfile.encoding=UTF-16 -Xms32m -Xmx256m
 437  
      * 
 438  
      * # Possible values are &quot;fail-fast&quot; (default), &quot;fail-at-end&quot; and &quot;fail-never&quot;
 439  
      * invoker.failureBehavior=fail-never
 440  
      * 
 441  
      * # The expected result of the build, possible values are &quot;success&quot; (default) and &quot;failure&quot;
 442  
      * invoker.buildResult=failure
 443  
      * 
 444  
      * # A boolean value controlling the -N flag, defaults to &quot;false&quot;
 445  
      * invoker.nonRecursive=false
 446  
      * </pre>
 447  
      * 
 448  
      * @parameter expression="${invoker.invokerPropertiesFile}" default-value="invoker.properties"
 449  
      * @since 1.2
 450  
      */
 451  
     private String invokerPropertiesFile;
 452  
 
 453  
     /**
 454  
      * A flag controlling whether failures of the sub builds should fail the main build, too. If set to
 455  
      * <code>true</code>, the main build will proceed even if one or more sub builds failed.
 456  
      * 
 457  
      * @parameter expression="${maven.test.failure.ignore}" default-value="false"
 458  
      * @since 1.3
 459  
      */
 460  
     private boolean ignoreFailures;
 461  
 
 462  
     /**
 463  
      * The supported script interpreters, indexed by the file extension of their associated script files.
 464  
      */
 465  
     private Map scriptInterpreters;
 466  
 
 467  
     /**
 468  
      * A string used to prefix the file name of the filtered POMs in case the POMs couldn't be filtered in-place (i.e.
 469  
      * the projects were not cloned to a temporary directory), can be <code>null</code>. This will be set to
 470  
      * <code>null</code> if the POMs have already been filtered during cloning.
 471  
      */
 472  9
     private String filteredPomPrefix = "interpolated-";
 473  
 
 474  
     /**
 475  
      * Invokes Maven on the configured test projects.
 476  
      * 
 477  
      * @throws MojoExecutionException If the goal encountered severe errors.
 478  
      * @throws MojoFailureException If any of the Maven builds failed.
 479  
      */
 480  
     public void execute()
 481  
         throws MojoExecutionException, MojoFailureException
 482  
     {
 483  0
         if ( skipInvocation )
 484  
         {
 485  0
             getLog().info( "Skipping invocation per configuration."
 486  
                 + " If this is incorrect, ensure the skipInvocation parameter is not set to true." );
 487  0
             return;
 488  
         }
 489  
 
 490  
         String[] includedPoms;
 491  0
         if ( pom != null )
 492  
         {
 493  
             try
 494  
             {
 495  0
                 projectsDirectory = pom.getCanonicalFile().getParentFile();
 496  
             }
 497  0
             catch ( IOException e )
 498  
             {
 499  0
                 throw new MojoExecutionException( "Failed to discover projectsDirectory from pom File parameter."
 500  
                     + " Reason: " + e.getMessage(), e );
 501  0
             }
 502  
 
 503  0
             includedPoms = new String[]{ pom.getName() };
 504  
         }
 505  
         else
 506  
         {
 507  
             try
 508  
             {
 509  0
                 includedPoms = getPoms();
 510  
             }
 511  0
             catch ( final IOException e )
 512  
             {
 513  0
                 throw new MojoExecutionException( "Error retrieving POM list from includes, excludes, "
 514  
                                 + "and projects directory. Reason: " + e.getMessage(), e );
 515  0
             }
 516  
         }
 517  
 
 518  
 
 519  0
         if ( ( includedPoms == null ) || ( includedPoms.length < 1 ) )
 520  
         {
 521  0
             getLog().info( "No test projects were selected for execution." );
 522  0
             return;
 523  
         }
 524  
 
 525  0
         if ( StringUtils.isEmpty( encoding ) )
 526  
         {
 527  0
             getLog().warn(
 528  
                            "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
 529  
                                + ", i.e. build is platform dependent!" );
 530  
         }
 531  
 
 532  0
         scriptInterpreters = new LinkedHashMap();
 533  0
         scriptInterpreters.put( "bsh", new BeanShellScriptInterpreter() );
 534  0
         scriptInterpreters.put( "groovy", new GroovyScriptInterpreter() );
 535  
 
 536  0
         Collection collectedProjects = new LinkedHashSet();
 537  0
         for ( int i = 0; i < includedPoms.length; i++ )
 538  
         {
 539  0
             collectProjects( projectsDirectory, includedPoms[i], collectedProjects, true );
 540  
         }
 541  
 
 542  0
         File projectsDir = projectsDirectory;
 543  
 
 544  0
         if ( cloneProjectsTo != null )
 545  
         {
 546  0
             cloneProjects( collectedProjects );
 547  0
             projectsDir = cloneProjectsTo;
 548  
         }
 549  
         else
 550  
         {
 551  0
             getLog().warn( "Filtering of parent/child POMs is not supported without cloning the projects" );
 552  
         }
 553  
 
 554  0
         List failures = runBuilds( projectsDir, includedPoms );
 555  
 
 556  0
         if ( !suppressSummaries )
 557  
         {
 558  0
             getLog().info( "---------------------------------------" );
 559  0
             getLog().info( "Execution Summary:" );
 560  0
             getLog().info( "  Builds Passing: " + ( includedPoms.length - failures.size() ) );
 561  0
             getLog().info( "  Builds Failing: " + failures.size() );
 562  0
             getLog().info( "---------------------------------------" );
 563  
 
 564  0
             if ( !failures.isEmpty() )
 565  
             {
 566  0
                 String heading = "The following builds failed:";
 567  0
                 if ( ignoreFailures )
 568  
                 {
 569  0
                     getLog().warn( heading );
 570  
                 }
 571  
                 else
 572  
                 {
 573  0
                     getLog().error( heading );
 574  
                 }
 575  
 
 576  0
                 for ( final Iterator it = failures.iterator(); it.hasNext(); )
 577  
                 {
 578  0
                     String item = "*  " + (String) it.next();
 579  0
                     if ( ignoreFailures )
 580  
                     {
 581  0
                         getLog().warn( item );
 582  
                     }
 583  
                     else
 584  
                     {
 585  0
                         getLog().error( item );
 586  
                     }
 587  
                 }
 588  
 
 589  0
                 getLog().info( "---------------------------------------" );
 590  
             }
 591  
         }
 592  
 
 593  0
         if ( !failures.isEmpty() )
 594  
         {
 595  0
             String message = failures.size() + " build" + ( failures.size() == 1 ? "" : "s" ) + " failed.";
 596  
 
 597  0
             if ( ignoreFailures )
 598  
             {
 599  0
                 getLog().warn( "Ignoring that " + message );
 600  
             }
 601  
             else
 602  
             {
 603  0
                 throw new MojoFailureException( this, message, message );
 604  
             }
 605  
         }
 606  0
     }
 607  
 
 608  
     /**
 609  
      * Creates a new reader for the specified file, using the plugin's {@link #encoding} parameter.
 610  
      * 
 611  
      * @param file The file to create a reader for, must not be <code>null</code>.
 612  
      * @return The reader for the file, never <code>null</code>.
 613  
      * @throws IOException If the specified file was not found or the configured encoding is not supported.
 614  
      */
 615  
     private Reader newReader( File file )
 616  
         throws IOException
 617  
     {
 618  4
         if ( StringUtils.isNotEmpty( encoding ) )
 619  
         {
 620  0
             return ReaderFactory.newReader( file, encoding );
 621  
         }
 622  
         else
 623  
         {
 624  4
             return ReaderFactory.newPlatformReader( file );
 625  
         }
 626  
     }
 627  
 
 628  
     /**
 629  
      * Collects all projects locally reachable from the specified project. The method will as such try to read the POM
 630  
      * and recursively follow its parent/module elements.
 631  
      * 
 632  
      * @param projectsDir The base directory of all projects, must not be <code>null</code>.
 633  
      * @param projectPath The relative path of the current project, can denote either the POM or its base directory,
 634  
      *            must not be <code>null</code>.
 635  
      * @param projectPaths The set of already collected projects to add new projects to, must not be <code>null</code>.
 636  
      *            This set will hold the relative paths to either a POM file or a project base directory.
 637  
      * @param included A flag indicating whether the specified project has been explicitly included via the parameter
 638  
      *            {@link #pomIncludes}. Such projects will always be added to the result set even if there is no
 639  
      *            corresponding POM.
 640  
      * @throws MojoExecutionException If the project tree could not be traversed.
 641  
      */
 642  
     private void collectProjects( File projectsDir, String projectPath, Collection projectPaths, boolean included )
 643  
         throws MojoExecutionException
 644  
     {
 645  0
         projectPath = projectPath.replace( '\\', '/' );
 646  0
         File pomFile = new File( projectsDir, projectPath );
 647  0
         if ( pomFile.isDirectory() )
 648  
         {
 649  0
             pomFile = new File( pomFile, "pom.xml" );
 650  0
             if ( !pomFile.exists() )
 651  
             {
 652  0
                 if ( included )
 653  
                 {
 654  0
                     projectPaths.add( projectPath );
 655  
                 }
 656  0
                 return;
 657  
             }
 658  0
             if ( !projectPath.endsWith( "/" ) )
 659  
             {
 660  0
                 projectPath += '/';
 661  
             }
 662  0
             projectPath += "pom.xml";
 663  
         }
 664  0
         else if ( !pomFile.isFile() )
 665  
         {
 666  0
             return;
 667  
         }
 668  0
         if ( !projectPaths.add( projectPath ) )
 669  
         {
 670  0
             return;
 671  
         }
 672  0
         getLog().debug( "Collecting parent/child projects of " + projectPath );
 673  
 
 674  
         Model model;
 675  
 
 676  0
         Reader reader = null;
 677  
         try
 678  
         {
 679  0
             reader = ReaderFactory.newXmlReader( pomFile );
 680  0
             model = new MavenXpp3Reader().read( reader );
 681  
         }
 682  0
         catch ( Exception e )
 683  
         {
 684  0
             throw new MojoExecutionException( "Failed to parse POM: " + pomFile, e );
 685  
         }
 686  
         finally
 687  
         {
 688  0
             IOUtil.close( reader );
 689  0
         }
 690  
 
 691  
         try
 692  
         {
 693  0
             String projectsRoot = projectsDir.getCanonicalPath();
 694  0
             String projectDir = pomFile.getParent();
 695  
 
 696  0
             String parentPath = "../pom.xml";
 697  0
             if ( model.getParent() != null && StringUtils.isNotEmpty( model.getParent().getRelativePath() ) )
 698  
             {
 699  0
                 parentPath = model.getParent().getRelativePath();
 700  
             }
 701  0
             String parent = relativizePath( new File( projectDir, parentPath ), projectsRoot );
 702  0
             if ( parent != null )
 703  
             {
 704  0
                 collectProjects( projectsDir, parent, projectPaths, false );
 705  
             }
 706  
 
 707  0
             if ( model.getModules() != null )
 708  
             {
 709  0
                 for ( Iterator it = model.getModules().iterator(); it.hasNext(); )
 710  
                 {
 711  0
                     String modulePath = (String) it.next();
 712  0
                     String module = relativizePath( new File( projectDir, modulePath ), projectsRoot );
 713  0
                     if ( module != null )
 714  
                     {
 715  0
                         collectProjects( projectsDir, module, projectPaths, false );
 716  
                     }
 717  
                 }
 718  
             }
 719  
         }
 720  0
         catch ( IOException e )
 721  
         {
 722  0
             throw new MojoExecutionException( "Failed to analyze POM: " + pomFile, e );
 723  0
         }
 724  0
     }
 725  
 
 726  
     /**
 727  
      * Copies the specified projects to the directory given by {@link #cloneProjectsTo}. A project may either be denoted
 728  
      * by a path to a POM file or merely by a path to a base directory. During cloning, the POM files will be filtered.
 729  
      * 
 730  
      * @param projectPaths The paths to the projects to clone, relative to the projects directory, must not be
 731  
      *            <code>null</code> nor contain <code>null</code> elements.
 732  
      * @throws MojoExecutionException If the the projects could not be copied/filtered.
 733  
      */
 734  
     private void cloneProjects( Collection projectPaths )
 735  
         throws MojoExecutionException
 736  
     {
 737  0
         cloneProjectsTo.mkdirs();
 738  
 
 739  
         // determine project directories to clone
 740  0
         Collection dirs = new LinkedHashSet();
 741  0
         for ( Iterator it = projectPaths.iterator(); it.hasNext(); )
 742  
         {
 743  0
             String projectPath = (String) it.next();
 744  0
             if ( !new File( projectsDirectory, projectPath ).isDirectory() )
 745  
             {
 746  0
                 projectPath = getParentPath( projectPath );
 747  
             }
 748  0
             dirs.add( projectPath );
 749  
         }
 750  
 
 751  0
         boolean filter = false;
 752  
 
 753  
         // clone project directories
 754  
         try
 755  
         {
 756  0
             filter = !cloneProjectsTo.getCanonicalFile().equals( projectsDirectory.getCanonicalFile() );
 757  
 
 758  0
             List clonedSubpaths = new ArrayList();
 759  
 
 760  0
             for ( Iterator it = dirs.iterator(); it.hasNext(); )
 761  
             {
 762  0
                 String subpath = (String) it.next();
 763  
 
 764  
                 // skip this project if its parent directory is also scheduled for cloning
 765  0
                 if ( !".".equals( subpath ) && dirs.contains( getParentPath( subpath ) ) )
 766  
                 {
 767  0
                     continue;
 768  
                 }
 769  
 
 770  
                 // avoid copying subdirs that are already cloned.
 771  0
                 if ( !alreadyCloned( subpath, clonedSubpaths ) )
 772  
                 {
 773  
                     // avoid creating new files that point to dir/.
 774  0
                     if ( ".".equals( subpath ) )
 775  
                     {
 776  0
                         String cloneSubdir = relativizePath( cloneProjectsTo, projectsDirectory.getCanonicalPath() );
 777  
 
 778  
                         // avoid infinite recursion if the cloneTo path is a subdirectory.
 779  0
                         if ( cloneSubdir != null )
 780  
                         {
 781  0
                             File temp = File.createTempFile( "pre-invocation-clone.", "" );
 782  0
                             temp.delete();
 783  0
                             temp.mkdirs();
 784  
 
 785  0
                             copyDirectoryStructure( projectsDirectory, temp );
 786  
 
 787  0
                             FileUtils.deleteDirectory( new File( temp, cloneSubdir ) );
 788  
 
 789  0
                             copyDirectoryStructure( temp, cloneProjectsTo );
 790  
                         }
 791  
                         else
 792  
                         {
 793  0
                             copyDirectoryStructure( projectsDirectory, cloneProjectsTo );
 794  
                         }
 795  
                     }
 796  
                     else
 797  
                     {
 798  0
                         File srcDir = new File( projectsDirectory, subpath );
 799  0
                         File dstDir = new File( cloneProjectsTo, subpath );
 800  0
                         copyDirectoryStructure( srcDir, dstDir );
 801  
                     }
 802  
 
 803  0
                     clonedSubpaths.add( subpath );
 804  
                 }
 805  
             }
 806  
         }
 807  0
         catch ( IOException e )
 808  
         {
 809  0
             throw new MojoExecutionException( "Failed to clone projects from: " + projectsDirectory + " to: "
 810  
                 + cloneProjectsTo + ". Reason: " + e.getMessage(), e );
 811  0
         }
 812  
 
 813  
         // filter cloned POMs
 814  0
         if ( filter )
 815  
         {
 816  0
             for ( Iterator it = projectPaths.iterator(); it.hasNext(); )
 817  
             {
 818  0
                 String projectPath = (String) it.next();
 819  0
                 File pomFile = new File( cloneProjectsTo, projectPath );
 820  0
                 if ( pomFile.isFile() )
 821  
                 {
 822  0
                     buildInterpolatedFile( pomFile, pomFile );
 823  
                 }
 824  
             }
 825  0
             filteredPomPrefix = null;
 826  
         }
 827  0
     }
 828  
 
 829  
     /**
 830  
      * Gets the parent path of the specified relative path.
 831  
      * 
 832  
      * @param path The relative path whose parent should be retrieved, must not be <code>null</code>.
 833  
      * @return The parent path or "." if the specified path has no parent, never <code>null</code>.
 834  
      */
 835  
     private String getParentPath( String path )
 836  
     {
 837  0
         int lastSep = Math.max( path.lastIndexOf( '/' ), path.lastIndexOf( '\\' ) );
 838  0
         return ( lastSep < 0 ) ? "." : path.substring( 0, lastSep );
 839  
     }
 840  
 
 841  
     /**
 842  
      * Copied a directory structure with deafault exclusions (.svn, CVS, etc)
 843  
      * 
 844  
      * @param sourceDir The source directory to copy, must not be <code>null</code>.
 845  
      * @param destDir The target directory to copy to, must not be <code>null</code>.
 846  
      * @throws IOException If the directory structure could not be copied.
 847  
      */
 848  
     private void copyDirectoryStructure( File sourceDir, File destDir )
 849  
         throws IOException
 850  
     {
 851  0
         DirectoryScanner scanner = new DirectoryScanner();
 852  0
         scanner.setBasedir( sourceDir );
 853  0
         if ( !cloneAllFiles )
 854  
         {
 855  0
             scanner.addDefaultExcludes();
 856  
         }
 857  0
         scanner.scan();
 858  
 
 859  
         /*
 860  
          * NOTE: Make sure the destination directory is always there (even if empty) to support POM-less ITs.
 861  
          */
 862  0
         destDir.mkdirs();
 863  0
         String[] includedDirs = scanner.getIncludedDirectories();
 864  0
         for ( int i = 0; i < includedDirs.length; ++i )
 865  
         {
 866  0
             File clonedDir = new File( destDir, includedDirs[i] );
 867  0
             clonedDir.mkdirs();
 868  
         }
 869  
 
 870  0
         String[] includedFiles = scanner.getIncludedFiles();
 871  0
         for ( int i = 0; i < includedFiles.length; ++i )
 872  
         {
 873  0
             File sourceFile = new File( sourceDir, includedFiles[i] );
 874  0
             File destFile = new File( destDir, includedFiles[i] );
 875  0
             FileUtils.copyFile( sourceFile, destFile );
 876  
         }
 877  0
     }
 878  
 
 879  
     /**
 880  
      * Determines whether the specified sub path has already been cloned, i.e. whether one of its ancestor directories
 881  
      * was already cloned.
 882  
      * 
 883  
      * @param subpath The sub path to check, must not be <code>null</code>.
 884  
      * @param clonedSubpaths The list of already cloned paths, must not be <code>null</code> nor contain
 885  
      *            <code>null</code> elements.
 886  
      * @return <code>true</code> if the specified path has already been cloned, <code>false</code> otherwise.
 887  
      */
 888  
     static boolean alreadyCloned( String subpath, List clonedSubpaths )
 889  
     {
 890  4
         for ( Iterator iter = clonedSubpaths.iterator(); iter.hasNext(); )
 891  
         {
 892  3
             String path = (String) iter.next();
 893  
 
 894  3
             if ( ".".equals( path ) || subpath.equals( path ) || subpath.startsWith( path + File.separator ) )
 895  
             {
 896  2
                 return true;
 897  
             }
 898  
         }
 899  
 
 900  2
         return false;
 901  
     }
 902  
 
 903  
     /**
 904  
      * Runs the specified projects.
 905  
      * 
 906  
      * @param projectsDir The base directory of all projects, must not be <code>null</code>.
 907  
      * @param projects The relative paths to the projects, either to their POM file or merely to their base directory,
 908  
      *            must not be <code>null</code> nor contain <code>null</code> elements.
 909  
      * @return The list of projects that failed, can be empty but never <code>null</code>.
 910  
      * @throws MojoExecutionException If any project could not be launched.
 911  
      */
 912  
     private List runBuilds( File projectsDir, String[] projects )
 913  
         throws MojoExecutionException
 914  
     {
 915  0
         List failures = new ArrayList();
 916  
 
 917  0
         if ( !localRepositoryPath.exists() )
 918  
         {
 919  0
             localRepositoryPath.mkdirs();
 920  
         }
 921  
 
 922  0
         File interpolatedSettingsFile = null;
 923  0
         if ( settingsFile != null )
 924  
         {
 925  0
             if ( cloneProjectsTo != null )
 926  
             {
 927  0
                 interpolatedSettingsFile = new File( cloneProjectsTo, "interpolated-" + settingsFile.getName() );
 928  
             }
 929  
             else
 930  
             {
 931  0
                 interpolatedSettingsFile =
 932  
                     new File( settingsFile.getParentFile(), "interpolated-" + settingsFile.getName() );
 933  
             }
 934  0
             buildInterpolatedFile( settingsFile, interpolatedSettingsFile );
 935  
         }
 936  
 
 937  
         try
 938  
         {
 939  0
             for ( int i = 0; i < projects.length; i++ )
 940  
             {
 941  0
                 String project = projects[i];
 942  
                 try
 943  
                 {
 944  0
                     runBuild( projectsDir, project, interpolatedSettingsFile );
 945  
                 }
 946  0
                 catch ( BuildFailureException e )
 947  
                 {
 948  0
                     failures.add( project );
 949  0
                 }
 950  
             }
 951  0
         }
 952  
         finally
 953  
         {
 954  0
             if ( interpolatedSettingsFile != null && cloneProjectsTo == null )
 955  
             {
 956  0
                 interpolatedSettingsFile.delete();
 957  
             }
 958  0
         }
 959  
 
 960  0
         return failures;
 961  
     }
 962  
 
 963  
     /**
 964  
      * Runs the specified project.
 965  
      * 
 966  
      * @param projectsDir The base directory of all projects, must not be <code>null</code>.
 967  
      * @param project The relative path to the project, either to a POM file or merely to a directory, must not be
 968  
      *            <code>null</code>.
 969  
      * @param settingsFile The (already interpolated) user settings file for the build, may be <code>null</code> to use
 970  
      *            the current user settings.
 971  
      * @throws MojoExecutionException If the project could not be launched.
 972  
      * @throws BuildFailureException If either a hook script or the build itself failed.
 973  
      */
 974  
     private void runBuild( File projectsDir, String project, File settingsFile )
 975  
         throws MojoExecutionException, BuildFailureException
 976  
     {
 977  0
         File pomFile = new File( projectsDir, project );
 978  
         File basedir;
 979  0
         if ( pomFile.isDirectory() )
 980  
         {
 981  0
             basedir = pomFile;
 982  0
             pomFile = new File( basedir, "pom.xml" );
 983  0
             if ( !pomFile.exists() )
 984  
             {
 985  0
                 pomFile = null;
 986  
             }
 987  
             else
 988  
             {
 989  0
                 project += File.separator + "pom.xml";
 990  
             }
 991  
         }
 992  
         else
 993  
         {
 994  0
             basedir = pomFile.getParentFile();
 995  
         }
 996  
 
 997  0
         getLog().info( "Building: " + project );
 998  
 
 999  0
         File interpolatedPomFile = null;
 1000  0
         if ( pomFile != null )
 1001  
         {
 1002  0
             if ( filteredPomPrefix != null )
 1003  
             {
 1004  0
                 interpolatedPomFile = new File( basedir, filteredPomPrefix + pomFile.getName() );
 1005  0
                 buildInterpolatedFile( pomFile, interpolatedPomFile );
 1006  
             }
 1007  
             else
 1008  
             {
 1009  0
                 interpolatedPomFile = pomFile;
 1010  
             }
 1011  
         }
 1012  
 
 1013  
         try
 1014  
         {
 1015  0
             runBuild( basedir, interpolatedPomFile, settingsFile );
 1016  
 
 1017  0
             if ( !suppressSummaries )
 1018  
             {
 1019  0
                 getLog().info( "...SUCCESS." );
 1020  
             }
 1021  0
         }
 1022  0
         catch ( BuildFailureException e )
 1023  
         {
 1024  0
             if ( !suppressSummaries )
 1025  
             {
 1026  0
                 getLog().info( "...FAILED. " + e.getMessage() );
 1027  
             }
 1028  0
             throw e;
 1029  
         }
 1030  
         finally
 1031  
         {
 1032  0
             if ( interpolatedPomFile != null && StringUtils.isNotEmpty( filteredPomPrefix ) )
 1033  
             {
 1034  0
                 interpolatedPomFile.delete();
 1035  
             }
 1036  0
         }
 1037  0
     }
 1038  
 
 1039  
     /**
 1040  
      * Runs the specified project.
 1041  
      * 
 1042  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1043  
      * @param pomFile The (already interpolated) POM file, may be <code>null</code> for a POM-less Maven invocation.
 1044  
      * @param settingsFile The (already interpolated) user settings file for the build, may be <code>null</code> to use
 1045  
      *            the current user settings.
 1046  
      * @throws MojoExecutionException If the project could not be launched.
 1047  
      * @throws BuildFailureException If either a hook script or the build itself failed.
 1048  
      */
 1049  
     private void runBuild( File basedir, File pomFile, File settingsFile )
 1050  
         throws MojoExecutionException, BuildFailureException
 1051  
     {
 1052  0
         InvokerProperties invokerProperties = getInvokerProperties( basedir );
 1053  0
         if ( getLog().isDebugEnabled() && !invokerProperties.getProperties().isEmpty() )
 1054  
         {
 1055  0
             Properties props = invokerProperties.getProperties();
 1056  0
             getLog().debug( "Using invoker properties:" );
 1057  0
             for ( Iterator it = new TreeSet( props.keySet() ).iterator(); it.hasNext(); )
 1058  
             {
 1059  0
                 String key = (String) it.next();
 1060  0
                 String value = props.getProperty( key );
 1061  0
                 getLog().debug( "  " + key + " = " + value );
 1062  
             }
 1063  
         }
 1064  
 
 1065  0
         List goals = getGoals( basedir );
 1066  
 
 1067  0
         List profiles = getProfiles( basedir );
 1068  
 
 1069  0
         Properties systemProperties = getTestProperties( basedir );
 1070  
 
 1071  0
         FileLogger logger = setupLogger( basedir );
 1072  
         try
 1073  
         {
 1074  0
             runScript( "pre-build script", basedir, preBuildHookScript, logger );
 1075  
 
 1076  0
             final InvocationRequest request = new DefaultInvocationRequest();
 1077  
 
 1078  0
             request.setLocalRepositoryDirectory( localRepositoryPath );
 1079  
 
 1080  0
             request.setUserSettingsFile( settingsFile );
 1081  
 
 1082  0
             request.setProperties( systemProperties );
 1083  
 
 1084  0
             request.setInteractive( false );
 1085  
 
 1086  0
             request.setShowErrors( showErrors );
 1087  
 
 1088  0
             request.setDebug( debug );
 1089  
 
 1090  0
             if ( logger != null )
 1091  
             {
 1092  0
                 request.setErrorHandler( logger );
 1093  
 
 1094  0
                 request.setOutputHandler( logger );
 1095  
             }
 1096  
 
 1097  0
             request.setBaseDirectory( basedir );
 1098  
 
 1099  0
             if ( pomFile != null )
 1100  
             {
 1101  0
                 request.setPomFile( pomFile );
 1102  
             }
 1103  
 
 1104  0
             if ( mavenHome != null )
 1105  
             {
 1106  0
                 invoker.setMavenHome( mavenHome );
 1107  0
                 request.addShellEnvironment( "M2_HOME", mavenHome.getAbsolutePath() );
 1108  
             }
 1109  
 
 1110  0
             if ( javaHome != null )
 1111  
             {
 1112  0
                 request.setJavaHome( javaHome );
 1113  
             }
 1114  
 
 1115  0
             for ( int invocationIndex = 1;; invocationIndex++ )
 1116  
             {
 1117  0
                 if ( invocationIndex > 1 && !invokerProperties.isInvocationDefined( invocationIndex ) )
 1118  
                 {
 1119  0
                     break;
 1120  
                 }
 1121  
 
 1122  0
                 request.setGoals( goals );
 1123  
 
 1124  0
                 request.setProfiles( profiles );
 1125  
 
 1126  0
                 request.setMavenOpts( mavenOpts );
 1127  
 
 1128  0
                 invokerProperties.configureInvocation( request, invocationIndex );
 1129  
 
 1130  
                 try
 1131  
                 {
 1132  0
                     getLog().debug( "Using MAVEN_OPTS: " + request.getMavenOpts() );
 1133  0
                     getLog().debug( "Executing: " + new MavenCommandLineBuilder().build( request ) );
 1134  
                 }
 1135  0
                 catch ( CommandLineConfigurationException e )
 1136  
                 {
 1137  0
                     getLog().debug( "Failed to display command line: " + e.getMessage() );
 1138  0
                 }
 1139  
 
 1140  
                 InvocationResult result;
 1141  
 
 1142  
                 try
 1143  
                 {
 1144  0
                     result = invoker.execute( request );
 1145  
                 }
 1146  0
                 catch ( final MavenInvocationException e )
 1147  
                 {
 1148  0
                     getLog().debug( "Error invoking Maven: " + e.getMessage(), e );
 1149  0
                     throw new BuildFailureException( "Maven invocation failed. " + e.getMessage() );
 1150  0
                 }
 1151  
 
 1152  0
                 verify( result, invocationIndex, invokerProperties, logger );
 1153  
             }
 1154  
 
 1155  0
             runScript( "post-build script", basedir, postBuildHookScript, logger );
 1156  
         }
 1157  
         finally
 1158  
         {
 1159  0
             if ( logger != null )
 1160  
             {
 1161  0
                 logger.close();
 1162  
             }
 1163  
         }
 1164  0
     }
 1165  
 
 1166  
     /**
 1167  
      * Initializes the build logger for the specified project.
 1168  
      * 
 1169  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1170  
      * @return The build logger or <code>null</code> if logging has been disabled.
 1171  
      * @throws MojoExecutionException If the log file could not be created.
 1172  
      */
 1173  
     private FileLogger setupLogger( File basedir )
 1174  
         throws MojoExecutionException
 1175  
     {
 1176  0
         FileLogger logger = null;
 1177  
 
 1178  0
         if ( !noLog )
 1179  
         {
 1180  0
             File outputLog = new File( basedir, "build.log" );
 1181  
             try
 1182  
             {
 1183  0
                 if ( streamLogs )
 1184  
                 {
 1185  0
                     logger = new FileLogger( outputLog, getLog() );
 1186  
                 }
 1187  
                 else
 1188  
                 {
 1189  0
                     logger = new FileLogger( outputLog );
 1190  
                 }
 1191  
 
 1192  0
                 getLog().debug( "build log initialized in: " + outputLog );
 1193  
             }
 1194  0
             catch ( IOException e )
 1195  
             {
 1196  0
                 throw new MojoExecutionException( "Error initializing build logfile in: " + outputLog, e );
 1197  0
             }
 1198  
         }
 1199  
 
 1200  0
         return logger;
 1201  
     }
 1202  
 
 1203  
     /**
 1204  
      * Gets the system properties to use for the specified project.
 1205  
      * 
 1206  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1207  
      * @return The system properties to use, may be empty but never <code>null</code>.
 1208  
      * @throws MojoExecutionException If the properties file exists but could not be read.
 1209  
      */
 1210  
     private Properties getTestProperties( final File basedir )
 1211  
         throws MojoExecutionException
 1212  
     {
 1213  0
         Properties collectedTestProperties = new Properties();
 1214  
 
 1215  0
         if ( testProperties != null )
 1216  
         {
 1217  0
             collectedTestProperties.putAll( testProperties );
 1218  
         }
 1219  
 
 1220  0
         if ( properties != null )
 1221  
         {
 1222  0
             collectedTestProperties.putAll( properties );
 1223  
         }
 1224  
 
 1225  0
         if ( testPropertiesFile != null )
 1226  
         {
 1227  0
             final File testProperties = new File( basedir, testPropertiesFile );
 1228  
 
 1229  0
             if ( testProperties.exists() )
 1230  
             {
 1231  0
                 InputStream fin = null;
 1232  
                 try
 1233  
                 {
 1234  0
                     fin = new FileInputStream( testProperties );
 1235  
 
 1236  0
                     Properties loadedProperties = new Properties();
 1237  0
                     loadedProperties.load( fin );
 1238  0
                     collectedTestProperties.putAll( loadedProperties );
 1239  
                 }
 1240  0
                 catch ( IOException e )
 1241  
                 {
 1242  0
                     throw new MojoExecutionException( "Error reading system properties for test: "
 1243  
                         + testPropertiesFile );
 1244  
                 }
 1245  
                 finally
 1246  
                 {
 1247  0
                     IOUtil.close( fin );
 1248  0
                 }
 1249  
             }
 1250  
         }
 1251  
 
 1252  0
         return collectedTestProperties;
 1253  
     }
 1254  
 
 1255  
     /**
 1256  
      * Verifies the invocation result.
 1257  
      * 
 1258  
      * @param result The invocation result to check, must not be <code>null</code>.
 1259  
      * @param invocationIndex The index of the invocation for which to check the exit code, must not be negative.
 1260  
      * @param invokerProperties The invoker properties used to check the exit code, must not be <code>null</code>.
 1261  
      * @param logger The build logger, may be <code>null</code> if logging is disabled.
 1262  
      * @throws BuildFailureException If the invocation result indicates a build failure.
 1263  
      */
 1264  
     private void verify( InvocationResult result, int invocationIndex, InvokerProperties invokerProperties,
 1265  
                          FileLogger logger )
 1266  
         throws BuildFailureException
 1267  
     {
 1268  0
         if ( result.getExecutionException() != null )
 1269  
         {
 1270  0
             throw new BuildFailureException( "The Maven invocation failed. "
 1271  
                 + result.getExecutionException().getMessage() );
 1272  
         }
 1273  0
         else if ( !invokerProperties.isExpectedResult( result.getExitCode(), invocationIndex ) )
 1274  
         {
 1275  0
             StringBuffer buffer = new StringBuffer( 256 );
 1276  0
             buffer.append( "The build exited with code " ).append( result.getExitCode() ).append( ". " );
 1277  0
             if ( logger != null )
 1278  
             {
 1279  0
                 buffer.append( "See " );
 1280  0
                 buffer.append( logger.getOutputFile().getAbsolutePath() );
 1281  0
                 buffer.append( " for details." );
 1282  
             }
 1283  
             else
 1284  
             {
 1285  0
                 buffer.append( "See console output for details." );
 1286  
             }
 1287  0
             throw new BuildFailureException( buffer.toString() );
 1288  
         }
 1289  0
     }
 1290  
 
 1291  
     /**
 1292  
      * Runs the specified hook script of the specified project (if any).
 1293  
      * 
 1294  
      * @param scriptDescription The description of the script to use for logging, must not be <code>null</code>.
 1295  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1296  
      * @param relativeScriptPath The path to the script relative to the project base directory, may be <code>null</code>
 1297  
      *            to skip the script execution.
 1298  
      * @param logger The logger to redirect the script output to, may be <code>null</code> to use stdout/stderr.
 1299  
      * @throws MojoExecutionException If an I/O error occurred while reading the script file.
 1300  
      * @throws BuildFailureException If the script did not return <code>true</code> of threw an exception.
 1301  
      */
 1302  
     private void runScript( final String scriptDescription, final File basedir, final String relativeScriptPath,
 1303  
                             final FileLogger logger )
 1304  
         throws MojoExecutionException, BuildFailureException
 1305  
     {
 1306  0
         if ( relativeScriptPath == null )
 1307  
         {
 1308  0
             return;
 1309  
         }
 1310  
 
 1311  0
         final File scriptFile = resolveScript( new File( basedir, relativeScriptPath ) );
 1312  
 
 1313  0
         if ( scriptFile.exists() )
 1314  
         {
 1315  0
             List classPath = addTestClassPath ? testClassPath : Collections.EMPTY_LIST;
 1316  
 
 1317  0
             Map globalVariables = new HashMap();
 1318  0
             globalVariables.put( "basedir", basedir );
 1319  0
             globalVariables.put( "localRepositoryPath", localRepositoryPath );
 1320  
 
 1321  0
             PrintStream out = ( logger != null ) ? logger.getPrintStream() : null;
 1322  
 
 1323  0
             ScriptInterpreter interpreter = getInterpreter( scriptFile );
 1324  0
             if ( getLog().isDebugEnabled() )
 1325  
             {
 1326  0
                 String name = interpreter.getClass().getName();
 1327  0
                 name = name.substring( name.lastIndexOf( '.' ) + 1 );
 1328  0
                 getLog().debug( "Running script with " + name + ": " + scriptFile );
 1329  
             }
 1330  
 
 1331  
             String script;
 1332  
             try
 1333  
             {
 1334  0
                 script = FileUtils.fileRead( scriptFile, encoding );
 1335  
             }
 1336  0
             catch ( IOException e )
 1337  
             {
 1338  0
                 String errorMessage =
 1339  
                     "error reading " + scriptDescription + " " + scriptFile.getPath() + ", " + e.getMessage();
 1340  0
                 throw new MojoExecutionException( errorMessage, e );
 1341  0
             }
 1342  
 
 1343  
             Object result;
 1344  
             try
 1345  
             {
 1346  0
                 if ( logger != null )
 1347  
                 {
 1348  0
                     logger.consumeLine( "Running " + scriptDescription + " in: " + scriptFile );
 1349  
                 }
 1350  0
                 result = interpreter.evaluateScript( script, classPath, globalVariables, out );
 1351  0
                 if ( logger != null )
 1352  
                 {
 1353  0
                     logger.consumeLine( "Finished " + scriptDescription + " in: " + scriptFile );
 1354  
                 }
 1355  
             }
 1356  0
             catch ( ScriptEvaluationException e )
 1357  
             {
 1358  0
                 Throwable t = ( e.getCause() != null ) ? e.getCause() : e;
 1359  0
                 String errorMessage =
 1360  
                     "error evaluating " + scriptDescription + " " + scriptFile.getPath() + ", " + t.getMessage();
 1361  0
                 getLog().debug( errorMessage, t );
 1362  0
                 if ( logger != null )
 1363  
                 {
 1364  0
                     t.printStackTrace( logger.getPrintStream() );
 1365  
                 }
 1366  0
                 throw new BuildFailureException( "The " + scriptDescription + " did not succeed. " + t.getMessage() );
 1367  0
             }
 1368  
 
 1369  0
             if ( !( Boolean.TRUE.equals( result ) || "true".equals( result ) ) )
 1370  
             {
 1371  0
                 throw new BuildFailureException( "The " + scriptDescription + " returned " + result + "." );
 1372  
             }
 1373  
         }
 1374  0
     }
 1375  
 
 1376  
     /**
 1377  
      * Gets the effective path to the specified script. For convenience, we allow to specify a script path as "verify"
 1378  
      * and have the plugin auto-append the file extension to search for "verify.bsh" and "verify.groovy".
 1379  
      * 
 1380  
      * @param scriptFile The script file to resolve, may be <code>null</code>.
 1381  
      * @return The effective path to the script file or <code>null</code> if the input was <code>null</code>.
 1382  
      */
 1383  
     private File resolveScript( File scriptFile )
 1384  
     {
 1385  0
         if ( scriptFile != null && !scriptFile.exists() )
 1386  
         {
 1387  0
             for ( Iterator it = this.scriptInterpreters.keySet().iterator(); it.hasNext(); )
 1388  
             {
 1389  0
                 String ext = (String) it.next();
 1390  0
                 File candidateFile = new File( scriptFile.getPath() + '.' + ext );
 1391  0
                 if ( candidateFile.exists() )
 1392  
                 {
 1393  0
                     scriptFile = candidateFile;
 1394  0
                     break;
 1395  
                 }
 1396  
             }
 1397  
         }
 1398  0
         return scriptFile;
 1399  
     }
 1400  
 
 1401  
     /**
 1402  
      * Determines the script interpreter for the specified script file by looking at its file extension. In this
 1403  
      * context, file extensions are considered case-insensitive. For backward compatibility with plugin versions 1.2-,
 1404  
      * the BeanShell interpreter will be used for any unrecognized extension.
 1405  
      * 
 1406  
      * @param scriptFile The script file for which to determine an interpreter, must not be <code>null</code>.
 1407  
      * @return The script interpreter for the file, never <code>null</code>.
 1408  
      */
 1409  
     private ScriptInterpreter getInterpreter( File scriptFile )
 1410  
     {
 1411  0
         String ext = FileUtils.extension( scriptFile.getName() ).toLowerCase( Locale.ENGLISH );
 1412  0
         ScriptInterpreter interpreter = (ScriptInterpreter) scriptInterpreters.get( ext );
 1413  0
         if ( interpreter == null )
 1414  
         {
 1415  0
             interpreter = (ScriptInterpreter) scriptInterpreters.get( "bsh" );
 1416  
         }
 1417  0
         return interpreter;
 1418  
     }
 1419  
 
 1420  
     /**
 1421  
      * Gets the goal list for the specified project.
 1422  
      * 
 1423  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1424  
      * @return The list of goals to run when building the project, may be empty but never <code>null</code>.
 1425  
      * @throws MojoExecutionException If the profile file could not be read.
 1426  
      */
 1427  
     List getGoals( final File basedir )
 1428  
         throws MojoExecutionException
 1429  
     {
 1430  
         try
 1431  
         {
 1432  5
             return getTokens( basedir, goalsFile, goals );
 1433  
         }
 1434  0
         catch ( IOException e )
 1435  
         {
 1436  0
             throw new MojoExecutionException( "error reading goals", e );
 1437  
         }
 1438  
     }
 1439  
 
 1440  
     /**
 1441  
      * Gets the profile list for the specified project.
 1442  
      * 
 1443  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1444  
      * @return The list of profiles to activate when building the project, may be empty but never <code>null</code>.
 1445  
      * @throws MojoExecutionException If the profile file could not be read.
 1446  
      */
 1447  
     List getProfiles( File basedir )
 1448  
         throws MojoExecutionException
 1449  
     {
 1450  
         try
 1451  
         {
 1452  3
             return getTokens( basedir, profilesFile, profiles );
 1453  
         }
 1454  0
         catch ( IOException e )
 1455  
         {
 1456  0
             throw new MojoExecutionException( "error reading profiles", e );
 1457  
         }
 1458  
     }
 1459  
 
 1460  
     /**
 1461  
      * Gets the paths to the projects that should be build. Each path may either denote a POM file or merely a project
 1462  
      * base directory. The returned paths will be relative to the projects directory. Finally note that the order of the
 1463  
      * returned project paths is significant.
 1464  
      * 
 1465  
      * @return The paths to the projects that should be build, may be empty but never <code>null</code>.
 1466  
      * @throws IOException If the projects directory could not be scanned.
 1467  
      */
 1468  
     String[] getPoms()
 1469  
         throws IOException
 1470  
     {
 1471  
         String[] poms;
 1472  
 
 1473  3
         if ( ( pom != null ) && pom.exists() )
 1474  
         {
 1475  0
             poms = new String[] { pom.getAbsolutePath() };
 1476  
         }
 1477  3
         else if ( invokerTest != null )
 1478  
         {
 1479  3
             String[] testRegexes = StringUtils.split( invokerTest, "," );
 1480  3
             List /* String */includes = new ArrayList( testRegexes.length );
 1481  
 
 1482  7
             for ( int i = 0, size = testRegexes.length; i < size; i++ )
 1483  
             {
 1484  
                 // user just use -Dinvoker.test=MWAR191,MNG111 to use a directory thats the end is not pom.xml
 1485  4
                 includes.add( testRegexes[i] );
 1486  
             }
 1487  
 
 1488  3
             poms = scanProjectsDirectory( includes, null );
 1489  
         }
 1490  
         else
 1491  
         {
 1492  0
             List excludes = ( pomExcludes != null ) ? new ArrayList( pomExcludes ) : new ArrayList();
 1493  0
             if ( this.settingsFile != null )
 1494  
             {
 1495  0
                 String exclude = relativizePath( this.settingsFile, projectsDirectory.getCanonicalPath() );
 1496  0
                 if ( exclude != null )
 1497  
                 {
 1498  0
                     excludes.add( exclude.replace( '\\', '/' ) );
 1499  0
                     getLog().debug( "Automatically excluded " + exclude + " from project scanning" );
 1500  
                 }
 1501  
             }
 1502  
 
 1503  0
             String[] setupPoms = scanProjectsDirectory( setupIncludes, excludes );
 1504  
 
 1505  0
             excludes.addAll( setupIncludes );
 1506  0
             String[] normalPoms = scanProjectsDirectory( pomIncludes, excludes );
 1507  
 
 1508  0
             poms = new String[setupPoms.length + normalPoms.length];
 1509  0
             System.arraycopy( setupPoms, 0, poms, 0, setupPoms.length );
 1510  0
             System.arraycopy( normalPoms, 0, poms, setupPoms.length, normalPoms.length );
 1511  
         }
 1512  
 
 1513  3
         poms = relativizeProjectPaths( poms );
 1514  
 
 1515  3
         return poms;
 1516  
     }
 1517  
 
 1518  
     /**
 1519  
      * Scans the projects directory for projects to build. Both (POM) files and mere directories will be matched by the
 1520  
      * scanner patterns.
 1521  
      * 
 1522  
      * @param includes The include patterns for the scanner, may be <code>null</code>.
 1523  
      * @param excludes The exclude patterns for the scanner, may be <code>null</code> to exclude nothing.
 1524  
      * @return The relative paths to either POM files or project base directories, never <code>null</code>.
 1525  
      * @throws IOException If the project directory could not be scanned.
 1526  
      */
 1527  
     private String[] scanProjectsDirectory( List includes, List excludes )
 1528  
         throws IOException
 1529  
     {
 1530  3
         final FileSet fs = new FileSet();
 1531  
 
 1532  3
         fs.setIncludes( includes );
 1533  3
         fs.setExcludes( excludes );
 1534  3
         fs.setDirectory( projectsDirectory.getCanonicalPath() );
 1535  3
         fs.setFollowSymlinks( false );
 1536  3
         fs.setUseDefaultExcludes( true );
 1537  
 
 1538  3
         final FileSetManager fsm = new FileSetManager( getLog() );
 1539  
 
 1540  3
         List included = new ArrayList();
 1541  3
         included.addAll( Arrays.asList( fsm.getIncludedFiles( fs ) ) );
 1542  3
         included.addAll( Arrays.asList( fsm.getIncludedDirectories( fs ) ) );
 1543  
 
 1544  3
         return (String[]) included.toArray( new String[included.size()] );
 1545  
     }
 1546  
 
 1547  
     /**
 1548  
      * Relativizes the specified project paths against the directory specified by {@link #projectsDirectory} (if
 1549  
      * possible). If a project path does not denote a sub path of the projects directory, it is returned as is.
 1550  
      * 
 1551  
      * @param projectPaths The project paths to relativize, must not be <code>null</code> nor contain <code>null</code>
 1552  
      *            elements.
 1553  
      * @return The relativized project paths, never <code>null</code>.
 1554  
      * @throws IOException If any path could not be relativized.
 1555  
      */
 1556  
     private String[] relativizeProjectPaths( String[] projectPaths )
 1557  
         throws IOException
 1558  
     {
 1559  3
         String projectsDirPath = projectsDirectory.getCanonicalPath();
 1560  
 
 1561  3
         String[] results = new String[projectPaths.length];
 1562  
 
 1563  10
         for ( int i = 0; i < projectPaths.length; i++ )
 1564  
         {
 1565  7
             String projectPath = projectPaths[i];
 1566  
 
 1567  7
             File file = new File( projectPath );
 1568  
 
 1569  7
             if ( !file.isAbsolute() )
 1570  
             {
 1571  7
                 file = new File( projectsDirectory, projectPath );
 1572  
             }
 1573  
 
 1574  7
             String relativizedPath = relativizePath( file, projectsDirPath );
 1575  
 
 1576  7
             if ( relativizedPath == null )
 1577  
             {
 1578  0
                 relativizedPath = projectPath;
 1579  
             }
 1580  
 
 1581  7
             results[i] = relativizedPath;
 1582  
         }
 1583  
 
 1584  3
         return results;
 1585  
     }
 1586  
 
 1587  
     /**
 1588  
      * Relativizes the specified path against the given base directory. Besides relativization, the returned path will
 1589  
      * also be normalized, e.g. directory references like ".." will be removed.
 1590  
      * 
 1591  
      * @param path The path to relativize, must not be <code>null</code>.
 1592  
      * @param basedir The (canonical path of the) base directory to relativize against, must not be <code>null</code>.
 1593  
      * @return The relative path in normal form or <code>null</code> if the input path does not denote a sub path of the
 1594  
      *         base directory.
 1595  
      * @throws IOException If the path could not be relativized.
 1596  
      */
 1597  
     private String relativizePath( File path, String basedir )
 1598  
         throws IOException
 1599  
     {
 1600  7
         String relativizedPath = path.getCanonicalPath();
 1601  
 
 1602  7
         if ( relativizedPath.startsWith( basedir ) )
 1603  
         {
 1604  7
             relativizedPath = relativizedPath.substring( basedir.length() );
 1605  7
             if ( relativizedPath.startsWith( File.separator ) )
 1606  
             {
 1607  7
                 relativizedPath = relativizedPath.substring( File.separator.length() );
 1608  
             }
 1609  
 
 1610  7
             return relativizedPath;
 1611  
         }
 1612  
         else
 1613  
         {
 1614  0
             return null;
 1615  
         }
 1616  
     }
 1617  
 
 1618  
     /**
 1619  
      * Returns the map-based value source used to interpolate POMs and other stuff.
 1620  
      * 
 1621  
      * @return The map-based value source for interpolation, never <code>null</code>.
 1622  
      */
 1623  
     private Map getInterpolationValueSource()
 1624  
     {
 1625  6
         Map props = new HashMap();
 1626  6
         if ( interpolationsProperties != null )
 1627  
         {
 1628  3
             props.putAll( interpolationsProperties );
 1629  
         }
 1630  6
         if ( filterProperties != null )
 1631  
         {
 1632  0
             props.putAll( filterProperties );
 1633  
         }
 1634  6
         if ( settings.getLocalRepository() != null )
 1635  
         {
 1636  0
             props.put( "localRepository", settings.getLocalRepository() );
 1637  
             /*
 1638  
              * NOTE: Maven fails to properly handle percent-encoded "file:" URLs (WAGON-111) so don't use File.toURI()
 1639  
              * here and just do it the simple way.
 1640  
              */
 1641  0
             String url = settings.getLocalRepository();
 1642  0
             if ( !url.startsWith( "/" ) )
 1643  
             {
 1644  0
                 url = '/' + url;
 1645  
             }
 1646  0
             url = "file://" + url.replace( '\\', '/' );
 1647  0
             props.put( "localRepositoryUrl", url );
 1648  
         }
 1649  6
         return new CompositeMap( this.project, props );
 1650  
     }
 1651  
 
 1652  
     /**
 1653  
      * Gets goal/profile names for the specified project, either directly from the plugin configuration or from an
 1654  
      * external token file.
 1655  
      * 
 1656  
      * @param basedir The base directory of the test project, must not be <code>null</code>.
 1657  
      * @param filename The (simple) name of an optional file in the project base directory from which to read
 1658  
      *            goals/profiles, may be <code>null</code>.
 1659  
      * @param defaultTokens The list of tokens to return in case the specified token file does not exist, may be
 1660  
      *            <code>null</code>.
 1661  
      * @return The list of goal/profile names, may be empty but never <code>null</code>.
 1662  
      * @throws IOException If the token file exists but could not be parsed.
 1663  
      */
 1664  
     private List getTokens( File basedir, String filename, List defaultTokens )
 1665  
         throws IOException
 1666  
     {
 1667  8
         List tokens = ( defaultTokens != null ) ? defaultTokens : new ArrayList();
 1668  
 
 1669  8
         if ( StringUtils.isNotEmpty( filename ) )
 1670  
         {
 1671  8
             File tokenFile = new File( basedir, filename );
 1672  
 
 1673  8
             if ( tokenFile.exists() )
 1674  
             {
 1675  4
                 tokens = readTokens( tokenFile );
 1676  
             }
 1677  
         }
 1678  
 
 1679  8
         return tokens;
 1680  
     }
 1681  
 
 1682  
     /**
 1683  
      * Reads the tokens from the specified file. Tokens are separated either by line terminators or commas. During
 1684  
      * parsing, the file contents will be interpolated.
 1685  
      * 
 1686  
      * @param tokenFile The file to read the tokens from, must not be <code>null</code>.
 1687  
      * @return The list of tokens, may be empty but never <code>null</code>.
 1688  
      * @throws IOException If the token file could not be read.
 1689  
      */
 1690  
     private List readTokens( final File tokenFile )
 1691  
         throws IOException
 1692  
     {
 1693  4
         List result = new ArrayList();
 1694  
 
 1695  4
         BufferedReader reader = null;
 1696  
         try
 1697  
         {
 1698  4
             Map composite = getInterpolationValueSource();
 1699  4
             reader = new BufferedReader( new InterpolationFilterReader( newReader( tokenFile ), composite ) );
 1700  
 
 1701  4
             String line = null;
 1702  9
             while ( ( line = reader.readLine() ) != null )
 1703  
             {
 1704  5
                 result.addAll( collectListFromCSV( line ) );
 1705  
             }
 1706  
         }
 1707  
         finally
 1708  
         {
 1709  4
             IOUtil.close( reader );
 1710  4
         }
 1711  
 
 1712  4
         return result;
 1713  
     }
 1714  
 
 1715  
     /**
 1716  
      * Gets a list of comma separated tokens from the specified line.
 1717  
      * 
 1718  
      * @param csv The line with comma separated tokens, may be <code>null</code>.
 1719  
      * @return The list of tokens from the line, may be empty but never <code>null</code>.
 1720  
      */
 1721  
     private List collectListFromCSV( final String csv )
 1722  
     {
 1723  5
         final List result = new ArrayList();
 1724  
 
 1725  5
         if ( ( csv != null ) && ( csv.trim().length() > 0 ) )
 1726  
         {
 1727  5
             final StringTokenizer st = new StringTokenizer( csv, "," );
 1728  
 
 1729  12
             while ( st.hasMoreTokens() )
 1730  
             {
 1731  7
                 result.add( st.nextToken().trim() );
 1732  
             }
 1733  
         }
 1734  
 
 1735  5
         return result;
 1736  
     }
 1737  
 
 1738  
     /**
 1739  
      * Interpolates the specified POM/settings file to a temporary file. The destination file may be same as the input
 1740  
      * file, i.e. interpolation can be performed in-place.
 1741  
      * 
 1742  
      * @param originalFile The XML file to interpolate, must not be <code>null</code>.
 1743  
      * @param interpolatedFile The target file to write the interpolated contents of the original file to, must not be
 1744  
      *            <code>null</code>.
 1745  
      * @throws MojoExecutionException If the target file could not be created.
 1746  
      */
 1747  
     void buildInterpolatedFile( File originalFile, File interpolatedFile )
 1748  
         throws MojoExecutionException
 1749  
     {
 1750  2
         getLog().debug( "Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath() );
 1751  
 
 1752  
         try
 1753  
         {
 1754  
             String xml;
 1755  
 
 1756  2
             Reader reader = null;
 1757  
             try
 1758  
             {
 1759  
                 // interpolation with token @...@
 1760  2
                 Map composite = getInterpolationValueSource();
 1761  2
                 reader = ReaderFactory.newXmlReader( originalFile );
 1762  2
                 reader = new InterpolationFilterReader( reader, composite, "@", "@" );
 1763  2
                 xml = IOUtil.toString( reader );
 1764  
             }
 1765  
             finally
 1766  
             {
 1767  2
                 IOUtil.close( reader );
 1768  2
             }
 1769  
 
 1770  2
             Writer writer = null;
 1771  
             try
 1772  
             {
 1773  2
                 interpolatedFile.getParentFile().mkdirs();
 1774  2
                 writer = WriterFactory.newXmlWriter( interpolatedFile );
 1775  2
                 writer.write( xml );
 1776  2
                 writer.flush();
 1777  
             }
 1778  
             finally
 1779  
             {
 1780  2
                 IOUtil.close( writer );
 1781  2
             }
 1782  
         }
 1783  0
         catch ( IOException e )
 1784  
         {
 1785  0
             throw new MojoExecutionException( "Failed to interpolate file " + originalFile.getPath(), e );
 1786  2
         }
 1787  2
     }
 1788  
 
 1789  
     /**
 1790  
      * Gets the (interpolated) invoker properties for an integration test.
 1791  
      * 
 1792  
      * @param projectDirectory The base directory of the IT project, must not be <code>null</code>.
 1793  
      * @return The invoker properties, may be empty but never <code>null</code>.
 1794  
      * @throws MojoExecutionException If an I/O error occurred during reading the properties.
 1795  
      */
 1796  
     private InvokerProperties getInvokerProperties( final File projectDirectory )
 1797  
         throws MojoExecutionException
 1798  
     {
 1799  0
         Properties props = new Properties();
 1800  0
         if ( invokerPropertiesFile != null )
 1801  
         {
 1802  0
             File propertiesFile = new File( projectDirectory, invokerPropertiesFile );
 1803  0
             if ( propertiesFile.isFile() )
 1804  
             {
 1805  0
                 InputStream in = null;
 1806  
                 try
 1807  
                 {
 1808  0
                     in = new FileInputStream( propertiesFile );
 1809  0
                     props.load( in );
 1810  
                 }
 1811  0
                 catch ( IOException e )
 1812  
                 {
 1813  0
                     throw new MojoExecutionException( "Failed to read invoker properties: " + propertiesFile, e );
 1814  
                 }
 1815  
                 finally
 1816  
                 {
 1817  0
                     IOUtil.close( in );
 1818  0
                 }
 1819  
             }
 1820  
 
 1821  0
             Interpolator interpolator = new RegexBasedInterpolator();
 1822  0
             interpolator.addValueSource( new MapBasedValueSource( getInterpolationValueSource() ) );
 1823  0
             for ( Iterator it = props.keySet().iterator(); it.hasNext(); )
 1824  
             {
 1825  0
                 String key = (String) it.next();
 1826  0
                 String value = props.getProperty( key );
 1827  
                 try
 1828  
                 {
 1829  0
                     value = interpolator.interpolate( value, "" );
 1830  
                 }
 1831  0
                 catch ( InterpolationException e )
 1832  
                 {
 1833  0
                     throw new MojoExecutionException( "Failed to interpolate invoker properties: " + propertiesFile,
 1834  
                                                       e );
 1835  0
                 }
 1836  0
                 props.setProperty( key, value );
 1837  
             }
 1838  
         }
 1839  0
         return new InvokerProperties( props );
 1840  
     }
 1841  
 
 1842  
 }