Coverage Report - org.apache.maven.plugin.invoker.AbstractInvokerMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractInvokerMojo
18%
112/610
13%
43/308
6,838
AbstractInvokerMojo$1
0%
0/6
N/A
6,838
AbstractInvokerMojo$2
0%
0/3
N/A
6,838
 
 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.FileOutputStream;
 26  
 import java.io.FileWriter;
 27  
 import java.io.IOException;
 28  
 import java.io.InputStream;
 29  
 import java.io.OutputStreamWriter;
 30  
 import java.io.Reader;
 31  
 import java.io.Writer;
 32  
 import java.text.DecimalFormat;
 33  
 import java.text.DecimalFormatSymbols;
 34  
 import java.util.ArrayList;
 35  
 import java.util.Arrays;
 36  
 import java.util.Collection;
 37  
 import java.util.Collections;
 38  
 import java.util.HashMap;
 39  
 import java.util.LinkedHashMap;
 40  
 import java.util.LinkedHashSet;
 41  
 import java.util.List;
 42  
 import java.util.Locale;
 43  
 import java.util.Map;
 44  
 import java.util.Properties;
 45  
 import java.util.Set;
 46  
 import java.util.StringTokenizer;
 47  
 import java.util.TreeSet;
 48  
 import java.util.concurrent.ExecutorService;
 49  
 import java.util.concurrent.Executors;
 50  
 import java.util.concurrent.TimeUnit;
 51  
 
 52  
 import org.apache.commons.io.input.XmlStreamReader;
 53  
 import org.apache.maven.artifact.Artifact;
 54  
 import org.apache.maven.model.Model;
 55  
 import org.apache.maven.model.Profile;
 56  
 import org.apache.maven.plugin.AbstractMojo;
 57  
 import org.apache.maven.plugin.MojoExecution;
 58  
 import org.apache.maven.plugin.MojoExecutionException;
 59  
 import org.apache.maven.plugin.MojoFailureException;
 60  
 import org.apache.maven.plugin.invoker.model.BuildJob;
 61  
 import org.apache.maven.plugin.invoker.model.io.xpp3.BuildJobXpp3Writer;
 62  
 import org.apache.maven.plugin.registry.TrackableBase;
 63  
 import org.apache.maven.plugins.annotations.Component;
 64  
 import org.apache.maven.plugins.annotations.Parameter;
 65  
 import org.apache.maven.project.MavenProject;
 66  
 import org.apache.maven.settings.RuntimeInfo;
 67  
 import org.apache.maven.settings.Settings;
 68  
 import org.apache.maven.settings.SettingsUtils;
 69  
 import org.apache.maven.settings.io.xpp3.SettingsXpp3Reader;
 70  
 import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
 71  
 import org.apache.maven.shared.invoker.CommandLineConfigurationException;
 72  
 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
 73  
 import org.apache.maven.shared.invoker.InvocationRequest;
 74  
 import org.apache.maven.shared.invoker.InvocationResult;
 75  
 import org.apache.maven.shared.invoker.Invoker;
 76  
 import org.apache.maven.shared.invoker.MavenCommandLineBuilder;
 77  
 import org.apache.maven.shared.invoker.MavenInvocationException;
 78  
 import org.apache.maven.shared.scriptinterpreter.RunErrorException;
 79  
 import org.apache.maven.shared.scriptinterpreter.RunFailureException;
 80  
 import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
 81  
 import org.codehaus.plexus.interpolation.InterpolationException;
 82  
 import org.codehaus.plexus.interpolation.Interpolator;
 83  
 import org.codehaus.plexus.interpolation.MapBasedValueSource;
 84  
 import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
 85  
 import org.codehaus.plexus.util.DirectoryScanner;
 86  
 import org.codehaus.plexus.util.FileUtils;
 87  
 import org.codehaus.plexus.util.IOUtil;
 88  
 import org.codehaus.plexus.util.InterpolationFilterReader;
 89  
 import org.codehaus.plexus.util.ReaderFactory;
 90  
 import org.codehaus.plexus.util.ReflectionUtils;
 91  
 import org.codehaus.plexus.util.StringUtils;
 92  
 import org.codehaus.plexus.util.WriterFactory;
 93  
 import org.codehaus.plexus.util.cli.CommandLineException;
 94  
 import org.codehaus.plexus.util.cli.CommandLineUtils;
 95  
 import org.codehaus.plexus.util.cli.Commandline;
 96  
 import org.codehaus.plexus.util.cli.StreamConsumer;
 97  
 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 98  
 
 99  
 /**
 100  
  * Provides common code for mojos invoking sub builds.
 101  
  *
 102  
  * @author Stephen Connolly
 103  
  * @since 15-Aug-2009 09:09:29
 104  
  */
 105  18
 public abstract class AbstractInvokerMojo
 106  
     extends AbstractMojo
 107  
 {
 108  
 
 109  
     private static final int SELECTOR_MAVENVERSION = 1;
 110  
 
 111  
     private static final int SELECTOR_JREVERSION = 2;
 112  
 
 113  
     private static final int SELECTOR_OSFAMILY = 4;
 114  
 
 115  
     /**
 116  
      * Flag used to suppress certain invocations. This is useful in tailoring the build using profiles.
 117  
      *
 118  
      * @since 1.1
 119  
      */
 120  
     @Parameter( property = "invoker.skip", defaultValue = "false" )
 121  
     private boolean skipInvocation;
 122  
 
 123  
     /**
 124  
      * Flag used to suppress the summary output notifying of successes and failures. If set to <code>true</code>, the
 125  
      * only indication of the build's success or failure will be the effect it has on the main build (if it fails, the
 126  
      * main build should fail as well). If {@link #streamLogs} is enabled, the sub-build summary will also provide an
 127  
      * indication.
 128  
      */
 129  
     @Parameter( defaultValue = "false" )
 130  
     protected boolean suppressSummaries;
 131  
 
 132  
     /**
 133  
      * Flag used to determine whether the build logs should be output to the normal mojo log.
 134  
      */
 135  
     @Parameter( property = "invoker.streamLogs", defaultValue = "false" )
 136  
     private boolean streamLogs;
 137  
 
 138  
     /**
 139  
      * The local repository for caching artifacts. It is strongly recommended to specify a path to an isolated
 140  
      * repository like <code>${project.build.directory}/it-repo</code>. Otherwise, your ordinary local repository will
 141  
      * be used, potentially soiling it with broken artifacts.
 142  
      */
 143  
     @Parameter( property = "invoker.localRepositoryPath", defaultValue = "${settings.localRepository}" )
 144  
     private File localRepositoryPath;
 145  
 
 146  
     /**
 147  
      * Directory to search for integration tests.
 148  
      */
 149  
     @Parameter( property = "invoker.projectsDirectory", defaultValue = "${basedir}/src/it/" )
 150  
     private File projectsDirectory;
 151  
 
 152  
     /**
 153  
      * Base directory where all build reports are written to.
 154  
      * Every execution of an integration test will produce an XML file which contains the information
 155  
      * about success or failure of that particular build job. The format of the resulting XML
 156  
      * file is documented in the given <a href="./build-job.html">build-job</a> reference.
 157  
      *
 158  
      * @since 1.4
 159  
      */
 160  
     @Parameter( property = "invoker.reportsDirectory", defaultValue = "${project.build.directory}/invoker-reports" )
 161  
     private File reportsDirectory;
 162  
 
 163  
     /**
 164  
      * A flag to disable the generation of build reports.
 165  
      *
 166  
      * @since 1.4
 167  
      */
 168  
     @Parameter( property = "invoker.disableReports", defaultValue = "false" )
 169  
     private boolean disableReports;
 170  
 
 171  
     /**
 172  
      * Directory to which projects should be cloned prior to execution. If not specified, each integration test will be
 173  
      * run in the directory in which the corresponding IT POM was found. In this case, you most likely want to configure
 174  
      * your SCM to ignore <code>target</code> and <code>build.log</code> in the test's base directory.
 175  
      *
 176  
      * @since 1.1
 177  
      */
 178  
     @Parameter
 179  
     private File cloneProjectsTo;
 180  
 
 181  
     /**
 182  
      * Some files are normally excluded when copying the IT projects from the directory specified by the parameter
 183  
      * projectsDirectory to the directory given by cloneProjectsTo (e.g. <code>.svn</code>, <code>CVS</code>,
 184  
      * <code>*~</code>, etc). Setting this parameter to <code>true</code> will cause all files to be copied to the
 185  
      * cloneProjectsTo directory.
 186  
      *
 187  
      * @since 1.2
 188  
      */
 189  
     @Parameter( defaultValue = "false" )
 190  
     private boolean cloneAllFiles;
 191  
 
 192  
     /**
 193  
      * Ensure the {@link #cloneProjectsTo} directory is not polluted with files from earlier invoker runs.
 194  
      *
 195  
      * @since 1.6
 196  
      */
 197  
     @Parameter( defaultValue = "false" )
 198  
     private boolean cloneClean;
 199  
 
 200  
     /**
 201  
      * A single POM to build, skipping any scanning parameters and behavior.
 202  
      */
 203  
     @Parameter( property = "invoker.pom" )
 204  
     private File pom;
 205  
 
 206  
     /**
 207  
      * Include patterns for searching the integration test directory for projects. This parameter is meant to be set
 208  
      * from the POM. If this parameter is not set, the plugin will search for all <code>pom.xml</code> files one
 209  
      * directory below {@link #projectsDirectory} (i.e. <code>*&#47;pom.xml</code>).<br>
 210  
      * <br>
 211  
      * Starting with version 1.3, mere directories can also be matched by these patterns. For example, the include
 212  
      * pattern <code>*</code> will run Maven builds on all immediate sub directories of {@link #projectsDirectory},
 213  
      * regardless if they contain a <code>pom.xml</code>. This allows to perform builds that need/should not depend on
 214  
      * the existence of a POM.
 215  
      */
 216  18
     @Parameter
 217  
     private List<String> pomIncludes = Collections.singletonList( "*/pom.xml" );
 218  
 
 219  
     /**
 220  
      * Exclude patterns for searching the integration test directory. This parameter is meant to be set from the POM. By
 221  
      * default, no POM files are excluded. For the convenience of using an include pattern like <code>*</code>, the
 222  
      * custom settings file specified by the parameter {@link #settingsFile} will always be excluded automatically.
 223  
      */
 224  18
     @Parameter
 225  
     private List<String> pomExcludes = Collections.emptyList();
 226  
 
 227  
     /**
 228  
      * Include patterns for searching the projects directory for projects that need to be run before the other projects.
 229  
      * This parameter allows to declare projects that perform setup tasks like installing utility artifacts into the
 230  
      * local repository. Projects matched by these patterns are implicitly excluded from the scan for ordinary projects.
 231  
      * Also, the exclusions defined by the parameter {@link #pomExcludes} apply to the setup projects, too. Default
 232  
      * value is: <code>setup*&#47;pom.xml</code>.
 233  
      *
 234  
      * @since 1.3
 235  
      */
 236  18
     @Parameter
 237  
     private List<String> setupIncludes = Collections.singletonList( "setup*/pom.xml" );
 238  
 
 239  
     /**
 240  
      * The list of goals to execute on each project. Default value is: <code>package</code>.
 241  
      */
 242  18
     @Parameter
 243  
     private List<String> goals = Collections.singletonList( "package" );
 244  
 
 245  
     /**
 246  
      * The name of the project-specific file that contains the enumeration of goals to execute for that test.
 247  
      *
 248  
      * @deprecated As of version 1.2, the key <code>invoker.goals</code> from the properties file specified by the
 249  
      *             parameter {@link #invokerPropertiesFile} should be used instead.
 250  
      */
 251  
     @Parameter( property = "invoker.goalsFile", defaultValue = "goals.txt" )
 252  
     private String goalsFile;
 253  
 
 254  
     /**
 255  
      */
 256  
     @Component
 257  
     private Invoker invoker;
 258  
 
 259  
     /**
 260  
      * Relative path of a selector script to run prior in order to decide if the build should be executed. This script
 261  
      * may be written with either BeanShell or Groovy. If the file extension is omitted (e.g. <code>selector</code>),
 262  
      * the plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>.
 263  
      * If this script exists for a particular project but returns any non-null value different from <code>true</code>,
 264  
      * the corresponding build is flagged as skipped. In this case, none of the pre-build hook script,
 265  
      * Maven nor the post-build hook script will be invoked. If this script throws an exception, the corresponding
 266  
      * build is flagged as in error, and none of the pre-build hook script, Maven not the post-build hook script will
 267  
      * be invoked.
 268  
      *
 269  
      * @since 1.5
 270  
      */
 271  
     @Parameter( property = "invoker.selectorScript", defaultValue = "selector" )
 272  
     private String selectorScript;
 273  
 
 274  
     /**
 275  
      * Relative path of a pre-build hook script to run prior to executing the build. This script may be written with
 276  
      * either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>prebuild</code>), the plugin
 277  
      * searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>. If this
 278  
      * script exists for a particular project but returns any non-null value different from <code>true</code> or throws
 279  
      * an exception, the corresponding build is flagged as a failure. In this case, neither Maven nor the post-build
 280  
      * hook script will be invoked.
 281  
      */
 282  
     @Parameter( property = "invoker.preBuildHookScript", defaultValue = "prebuild" )
 283  
     private String preBuildHookScript;
 284  
 
 285  
     /**
 286  
      * Relative path of a cleanup/verification hook script to run after executing the build. This script may be written
 287  
      * with either BeanShell or Groovy (since 1.3). If the file extension is omitted (e.g. <code>verify</code>), the
 288  
      * plugin searches for the file by trying out the well-known extensions <code>.bsh</code> and <code>.groovy</code>.
 289  
      * If this script exists for a particular project but returns any non-null value different from <code>true</code> or
 290  
      * throws an exception, the corresponding build is flagged as a failure.
 291  
      */
 292  
     @Parameter( property = "invoker.postBuildHookScript", defaultValue = "postbuild" )
 293  
     private String postBuildHookScript;
 294  
 
 295  
     /**
 296  
      * Location of a properties file that defines CLI properties for the test.
 297  
      */
 298  
     @Parameter( property = "invoker.testPropertiesFile", defaultValue = "test.properties" )
 299  
     private String testPropertiesFile;
 300  
 
 301  
     /**
 302  
      * Common set of test properties to pass in on each IT's command line, via -D parameters.
 303  
      *
 304  
      * @deprecated As of version 1.1, use the {@link #properties} parameter instead.
 305  
      */
 306  
     @Parameter
 307  
     private Properties testProperties;
 308  
 
 309  
     /**
 310  
      * Common set of properties to pass in on each project's command line, via -D parameters.
 311  
      *
 312  
      * @since 1.1
 313  
      */
 314  
     @Parameter
 315  
     private Map<String, String> properties;
 316  
 
 317  
     /**
 318  
      * Whether to show errors in the build output.
 319  
      */
 320  
     @Parameter( property = "invoker.showErrors", defaultValue = "false" )
 321  
     private boolean showErrors;
 322  
 
 323  
     /**
 324  
      * Whether to show debug statements in the build output.
 325  
      */
 326  
     @Parameter( property = "invoker.debug", defaultValue = "false" )
 327  
     private boolean debug;
 328  
 
 329  
     /**
 330  
      * Suppress logging to the <code>build.log</code> file.
 331  
      */
 332  
     @Parameter( property = "invoker.noLog", defaultValue = "false" )
 333  
     private boolean noLog;
 334  
 
 335  
     /**
 336  
      * List of profile identifiers to explicitly trigger in the build.
 337  
      *
 338  
      * @since 1.1
 339  
      */
 340  
     @Parameter
 341  
     private List<String> profiles;
 342  
 
 343  
     /**
 344  
      * List of properties which will be used to interpolate goal files.
 345  
      *
 346  
      * @since 1.1
 347  
      * @deprecated As of version 1.3, the parameter {@link #filterProperties} should be used instead.
 348  
      */
 349  
     @Parameter
 350  
     private Properties interpolationsProperties;
 351  
 
 352  
     /**
 353  
      * A list of additional properties which will be used to filter tokens in POMs and goal files.
 354  
      *
 355  
      * @since 1.3
 356  
      */
 357  
     @Parameter
 358  
     private Map<String, String> filterProperties;
 359  
 
 360  
     /**
 361  
      * The Maven Project Object
 362  
      *
 363  
      * @since 1.1
 364  
      */
 365  
     @Component
 366  
     private MavenProject project;
 367  
     
 368  
     @Component
 369  
     private MojoExecution mojoExecution;
 370  
 
 371  
     /**
 372  
      * A comma separated list of projectname patterns to run. Specify this parameter to run individual tests by file name,
 373  
      * overriding the {@link #setupIncludes}, {@link #pomIncludes} and {@link #pomExcludes} parameters. Each pattern you
 374  
      * specify here will be used to create an include/exclude pattern formatted like
 375  
      * <code>${projectsDirectory}/<i>pattern</i></code>. To exclude a test, prefix the pattern with a '<code>!</code>'. 
 376  
      * So you can just type
 377  
      * <nobr><code>-Dinvoker.test=SimpleTest,Comp*Test,!Compare*</code></nobr> to run builds in 
 378  
      * <code>${projectsDirectory}/SimpleTest</code> and
 379  
      * <code>${projectsDirectory}/ComplexTest</code>, but not <code>${projectsDirectory}/CompareTest</code> 
 380  
      *
 381  
      * @since 1.1 (exclusion since 1.8)
 382  
      */
 383  
     @Parameter( property = "invoker.test" )
 384  
     private String invokerTest;
 385  
 
 386  
     /**
 387  
      * The name of the project-specific file that contains the enumeration of profiles to use for that test. <b>If the
 388  
      * file exists and is empty no profiles will be used even if the parameter {@link #profiles} is set.</b>
 389  
      *
 390  
      * @since 1.1
 391  
      * @deprecated As of version 1.2, the key <code>invoker.profiles</code> from the properties file specified by the
 392  
      *             parameter {@link #invokerPropertiesFile} should be used instead.
 393  
      */
 394  
     @Parameter( property = "invoker.profilesFile", defaultValue = "profiles.txt" )
 395  
     private String profilesFile;
 396  
 
 397  
     /**
 398  
      * Path to an alternate <code>settings.xml</code> to use for Maven invocation with all ITs. Note that the
 399  
      * <code>&lt;localRepository&gt;</code> element of this settings file is always ignored, i.e. the path given by the
 400  
      * parameter {@link #localRepositoryPath} is dominant.
 401  
      *
 402  
      * @since 1.2
 403  
      */
 404  
     @Parameter( property = "invoker.settingsFile" )
 405  
     private File settingsFile;
 406  
 
 407  
     /**
 408  
      * The <code>MAVEN_OPTS</code> environment variable to use when invoking Maven. This value can be overridden for
 409  
      * individual integration tests by using {@link #invokerPropertiesFile}.
 410  
      *
 411  
      * @since 1.2
 412  
      */
 413  
     @Parameter( property = "invoker.mavenOpts" )
 414  
     private String mavenOpts;
 415  
 
 416  
     /**
 417  
      * The home directory of the Maven installation to use for the forked builds. Defaults to the current Maven
 418  
      * installation.
 419  
      *
 420  
      * @since 1.3
 421  
      */
 422  
     @Parameter( property = "invoker.mavenHome" )
 423  
     private File mavenHome;
 424  
 
 425  
     /**
 426  
      * mavenExecutable can either be a file relative to <code>${maven.home}/bin/</code> or an absolute file.
 427  
      * 
 428  
      * @since 1.8
 429  
      * @see Invoker#setMavenExecutable(File)
 430  
      */
 431  
     @Parameter( property = "invoker.mavenExecutable" )
 432  
     private String mavenExecutable;
 433  
     
 434  
     /**
 435  
      * The <code>JAVA_HOME</code> environment variable to use for forked Maven invocations. Defaults to the current Java
 436  
      * home directory.
 437  
      *
 438  
      * @since 1.3
 439  
      */
 440  
     @Parameter( property = "invoker.javaHome" )
 441  
     private File javaHome;
 442  
 
 443  
     /**
 444  
      * The file encoding for the pre-/post-build scripts and the list files for goals and profiles.
 445  
      *
 446  
      * @since 1.2
 447  
      */
 448  
     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
 449  
     private String encoding;
 450  
 
 451  
     /**
 452  
      * The current user system settings for use in Maven.
 453  
      *
 454  
      * @since 1.2
 455  
      */
 456  
     @Component
 457  
     private Settings settings;
 458  
 
 459  
     /**
 460  
      * A flag whether the test class path of the project under test should be included in the class path of the
 461  
      * pre-/post-build scripts. If set to <code>false</code>, the class path of script interpreter consists only of
 462  
      * the <a href="dependencies.html">runtime dependencies</a> of the Maven Invoker Plugin. If set the
 463  
      * <code>true</code>, the project's test class path will be prepended to the interpreter class path. Among
 464  
      * others, this feature allows the scripts to access utility classes from the test sources of your project.
 465  
      *
 466  
      * @since 1.2
 467  
      */
 468  
     @Parameter( property = "invoker.addTestClassPath", defaultValue = "false" )
 469  
     private boolean addTestClassPath;
 470  
 
 471  
     /**
 472  
      * The test class path of the project under test.
 473  
      */
 474  
     @Parameter( defaultValue = "${project.testClasspathElements}", readonly = true )
 475  
     private List<String> testClassPath;
 476  
 
 477  
     /**
 478  
      * The name of an optional project-specific file that contains properties used to specify settings for an individual
 479  
      * Maven invocation. Any property present in the file will override the corresponding setting from the plugin
 480  
      * configuration. The values of the properties are filtered and may use expressions like
 481  
      * <code>${project.version}</code> to reference project properties or values from the parameter
 482  
      * {@link #filterProperties}. The snippet below describes the supported properties:
 483  
      * <p/>
 484  
      * <pre>
 485  
      * # A comma or space separated list of goals/phases to execute, may
 486  
      * # specify an empty list to execute the default goal of the IT project
 487  
      * invoker.goals = clean install
 488  
      *
 489  
      * # Optionally, a list of goals to run during further invocations of Maven
 490  
      * invoker.goals.2 = ${project.groupId}:${project.artifactId}:${project.version}:run
 491  
      *
 492  
      * # A comma or space separated list of profiles to activate
 493  
      * invoker.profiles = its,jdk15
 494  
      *
 495  
      * # The path to an alternative POM or base directory to invoke Maven on, defaults to the
 496  
      * # project that was originally specified in the plugin configuration
 497  
      * # Since plugin version 1.4
 498  
      * invoker.project = sub-module
 499  
      *
 500  
      * # The value for the environment variable MAVEN_OPTS
 501  
      * invoker.mavenOpts = -Dfile.encoding=UTF-16 -Xms32m -Xmx256m
 502  
      *
 503  
      * # Possible values are &quot;fail-fast&quot; (default), &quot;fail-at-end&quot; and &quot;fail-never&quot;
 504  
      * invoker.failureBehavior = fail-never
 505  
      *
 506  
      * # The expected result of the build, possible values are &quot;success&quot; (default) and &quot;failure&quot;
 507  
      * invoker.buildResult = failure
 508  
      *
 509  
      * # A boolean value controlling the aggregator mode of Maven, defaults to &quot;false&quot;
 510  
      * invoker.nonRecursive = true
 511  
      *
 512  
      * # A boolean value controlling the network behavior of Maven, defaults to &quot;false&quot;
 513  
      * # Since plugin version 1.4
 514  
      * invoker.offline = true
 515  
      *
 516  
      * # The path to the properties file from which to load system properties, defaults to the
 517  
      * # filename given by the plugin parameter testPropertiesFile
 518  
      * # Since plugin version 1.4
 519  
      * invoker.systemPropertiesFile = test.properties
 520  
      *
 521  
      * # An optional human friendly name for this build job to be included in the build reports.
 522  
      * # Since plugin version 1.4
 523  
      * invoker.name = Test Build 01
 524  
      *
 525  
      * # An optional description for this build job to be included in the build reports.
 526  
      * # Since plugin version 1.4
 527  
      * invoker.description = Checks the support for build reports.
 528  
      *
 529  
      * # A comma separated list of JRE versions on which this build job should be run.
 530  
      * # Since plugin version 1.4
 531  
      * invoker.java.version = 1.4+, !1.4.1, 1.7-
 532  
      *
 533  
      * # A comma separated list of OS families on which this build job should be run.
 534  
      * # Since plugin version 1.4
 535  
      * invoker.os.family = !windows, unix, mac
 536  
      *
 537  
      * # A comma separated list of Maven versions on which this build should be run.
 538  
      * # Since plugin version 1.5
 539  
      * invoker.maven.version = 2.0.10+, !2.1.0, !2.2.0
 540  
      * 
 541  
      * # A boolean value controlling the debug logging level of Maven, , defaults to &quot;false&quot;
 542  
      * # Since plugin version 1.8
 543  
      * invoker.debug = true
 544  
      * </pre>
 545  
      *
 546  
      * @since 1.2
 547  
      */
 548  
     @Parameter( property = "invoker.invokerPropertiesFile", defaultValue = "invoker.properties" )
 549  
     private String invokerPropertiesFile;
 550  
 
 551  
     /**
 552  
      * flag to enable show mvn version used for running its (cli option : -V,--show-version )
 553  
      *
 554  
      * @since 1.4
 555  
      */
 556  
     @Parameter( property = "invoker.showVersion", defaultValue = "false" )
 557  
     private boolean showVersion;
 558  
 
 559  
     /**
 560  
      * number of threads for running tests in parallel.
 561  
      * This will be the number of maven forked process in parallel.
 562  
      *
 563  
      * @since 1.6
 564  
      */
 565  
     @Parameter( property = "invoker.parallelThreads", defaultValue = "1" )
 566  
     private int parallelThreads;
 567  
 
 568  
     /**
 569  
      * @since 1.6
 570  
      */
 571  
     @Parameter( property = "plugin.artifacts", required = true, readonly = true )
 572  
     private List<Artifact> pluginArtifacts;
 573  
 
 574  
 
 575  
     /**
 576  
      * If enable and if you have a settings file configured for the execution, it will be merged with your user settings.
 577  
      *
 578  
      * @since 1.6
 579  
      */
 580  
     @Parameter( property = "invoker.mergeUserSettings", defaultValue = "false" )
 581  
     private boolean mergeUserSettings;
 582  
 
 583  
     /**
 584  
      * Additional environment variables to set on the command line.
 585  
      * @since 1.8
 586  
      */
 587  
     @Parameter
 588  
     private Map<String, String> environmentVariables;  
 589  
     /**
 590  
      * The scripter runner that is responsible to execute hook scripts.
 591  
      */
 592  
     private ScriptRunner scriptRunner;
 593  
 
 594  
     /**
 595  
      * A string used to prefix the file name of the filtered POMs in case the POMs couldn't be filtered in-place (i.e.
 596  
      * the projects were not cloned to a temporary directory), can be <code>null</code>. This will be set to
 597  
      * <code>null</code> if the POMs have already been filtered during cloning.
 598  
      */
 599  18
     private String filteredPomPrefix = "interpolated-";
 600  
 
 601  
     /**
 602  
      * The format for elapsed build time.
 603  
      */
 604  18
     private final DecimalFormat secFormat = new DecimalFormat( "(0.0 s)", new DecimalFormatSymbols( Locale.ENGLISH ) );
 605  
     
 606  
     /**
 607  
      * The version of Maven which is used to run the builds
 608  
      */
 609  
     private String actualMavenVersion;
 610  
 
 611  
     /**
 612  
      * The version of the JRE which is used to run the builds
 613  
      */
 614  
     private String actualJreVersion;
 615  
     
 616  
     
 617  
     private void setActualJreVersion( String actualJreVersion )
 618  
     {
 619  0
         this.actualJreVersion = actualJreVersion;
 620  0
     }
 621  
     
 622  
     /**
 623  
      * Invokes Maven on the configured test projects.
 624  
      *
 625  
      * @throws org.apache.maven.plugin.MojoExecutionException
 626  
      *          If the goal encountered severe errors.
 627  
      * @throws org.apache.maven.plugin.MojoFailureException
 628  
      *          If any of the Maven builds failed.
 629  
      */
 630  
     public void execute()
 631  
         throws MojoExecutionException, MojoFailureException
 632  
     {
 633  0
         if ( skipInvocation )
 634  
         {
 635  0
             getLog().info( "Skipping invocation per configuration."
 636  
                                + " If this is incorrect, ensure the skipInvocation parameter is not set to true." );
 637  0
             return;
 638  
         }
 639  
 
 640  
         // done it here to prevent issues with concurrent access in case of parallel run
 641  0
         if ( !disableReports && !reportsDirectory.exists() )
 642  
         {
 643  0
             reportsDirectory.mkdirs();
 644  
         }
 645  
 
 646  
         BuildJob[] buildJobs;
 647  0
         if ( pom != null )
 648  
         {
 649  
             try
 650  
             {
 651  0
                 projectsDirectory = pom.getCanonicalFile().getParentFile();
 652  
             }
 653  0
             catch ( IOException e )
 654  
             {
 655  0
                 throw new MojoExecutionException(
 656  
                     "Failed to discover projectsDirectory from pom File parameter." + " Reason: " + e.getMessage(), e );
 657  0
             }
 658  
 
 659  0
             buildJobs = new BuildJob[]{ new BuildJob( pom.getName(), BuildJob.Type.NORMAL ) };
 660  
         }
 661  
         else
 662  
         {
 663  
             try
 664  
             {
 665  0
                 buildJobs = getBuildJobs();
 666  
             }
 667  0
             catch ( final IOException e )
 668  
             {
 669  0
                 throw new MojoExecutionException(
 670  
                     "Error retrieving POM list from includes, excludes, " + "and projects directory. Reason: "
 671  
                         + e.getMessage(), e );
 672  0
             }
 673  
         }
 674  
 
 675  0
         if ( ( buildJobs == null ) || ( buildJobs.length < 1 ) )
 676  
         {
 677  0
             getLog().info( "No projects were selected for execution." );
 678  0
             return;
 679  
         }
 680  
 
 681  0
         if ( StringUtils.isEmpty( encoding ) )
 682  
         {
 683  0
             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
 684  
                                + ", i.e. build is platform dependent!" );
 685  
         }
 686  
 
 687  
         final List<String> scriptClassPath;
 688  0
         if ( addTestClassPath )
 689  
         {
 690  0
             scriptClassPath = new ArrayList<String>( testClassPath );
 691  0
             for ( Artifact pluginArtifact : pluginArtifacts )
 692  
             {
 693  0
                 scriptClassPath.remove( pluginArtifact.getFile().getAbsolutePath() );
 694  
             }
 695  
         }
 696  
         else
 697  
         {
 698  0
             scriptClassPath = null;
 699  
         }
 700  0
         scriptRunner = new ScriptRunner( getLog() );
 701  0
         scriptRunner.setScriptEncoding( encoding );
 702  0
         scriptRunner.setGlobalVariable( "localRepositoryPath", localRepositoryPath );
 703  0
         scriptRunner.setClassPath( scriptClassPath );
 704  
 
 705  0
         Collection<String> collectedProjects = new LinkedHashSet<String>();
 706  0
         for ( BuildJob buildJob : buildJobs )
 707  
         {
 708  0
             collectProjects( projectsDirectory, buildJob.getProject(), collectedProjects, true );
 709  
         }
 710  
 
 711  0
         File projectsDir = projectsDirectory;
 712  
 
 713  0
         if ( cloneProjectsTo != null )
 714  
         {
 715  0
             cloneProjects( collectedProjects );
 716  0
             projectsDir = cloneProjectsTo;
 717  
         }
 718  
         else
 719  
         {
 720  0
             getLog().warn( "Filtering of parent/child POMs is not supported without cloning the projects" );
 721  
         }
 722  
 
 723  0
         runBuilds( projectsDir, buildJobs );
 724  
 
 725  0
         processResults( new InvokerSession( buildJobs ) );
 726  0
     }
 727  
 
 728  
     /**
 729  
      * Processes the results of invoking the build jobs.
 730  
      *
 731  
      * @param invokerSession The session with the build jobs, must not be <code>null</code>.
 732  
      * @throws MojoFailureException If the mojo had failed as a result of invoking the build jobs.
 733  
      * @since 1.4
 734  
      */
 735  
     abstract void processResults( InvokerSession invokerSession )
 736  
         throws MojoFailureException;
 737  
 
 738  
     /**
 739  
      * Creates a new reader for the specified file, using the plugin's {@link #encoding} parameter.
 740  
      *
 741  
      * @param file The file to create a reader for, must not be <code>null</code>.
 742  
      * @return The reader for the file, never <code>null</code>.
 743  
      * @throws java.io.IOException If the specified file was not found or the configured encoding is not supported.
 744  
      */
 745  
     private Reader newReader( File file )
 746  
         throws IOException
 747  
     {
 748  8
         if ( StringUtils.isNotEmpty( encoding ) )
 749  
         {
 750  0
             return ReaderFactory.newReader( file, encoding );
 751  
         }
 752  
         else
 753  
         {
 754  8
             return ReaderFactory.newPlatformReader( file );
 755  
         }
 756  
     }
 757  
 
 758  
     /**
 759  
      * Collects all projects locally reachable from the specified project. The method will as such try to read the POM
 760  
      * and recursively follow its parent/module elements.
 761  
      *
 762  
      * @param projectsDir  The base directory of all projects, must not be <code>null</code>.
 763  
      * @param projectPath  The relative path of the current project, can denote either the POM or its base directory,
 764  
      *                     must not be <code>null</code>.
 765  
      * @param projectPaths The set of already collected projects to add new projects to, must not be <code>null</code>.
 766  
      *                     This set will hold the relative paths to either a POM file or a project base directory.
 767  
      * @param included     A flag indicating whether the specified project has been explicitly included via the parameter
 768  
      *                     {@link #pomIncludes}. Such projects will always be added to the result set even if there is no
 769  
      *                     corresponding POM.
 770  
      * @throws org.apache.maven.plugin.MojoExecutionException
 771  
      *          If the project tree could not be traversed.
 772  
      */
 773  
     private void collectProjects( File projectsDir, String projectPath, Collection<String> projectPaths,
 774  
                                   boolean included )
 775  
         throws MojoExecutionException
 776  
     {
 777  0
         projectPath = projectPath.replace( '\\', '/' );
 778  0
         File pomFile = new File( projectsDir, projectPath );
 779  0
         if ( pomFile.isDirectory() )
 780  
         {
 781  0
             pomFile = new File( pomFile, "pom.xml" );
 782  0
             if ( !pomFile.exists() )
 783  
             {
 784  0
                 if ( included )
 785  
                 {
 786  0
                     projectPaths.add( projectPath );
 787  
                 }
 788  0
                 return;
 789  
             }
 790  0
             if ( !projectPath.endsWith( "/" ) )
 791  
             {
 792  0
                 projectPath += '/';
 793  
             }
 794  0
             projectPath += "pom.xml";
 795  
         }
 796  0
         else if ( !pomFile.isFile() )
 797  
         {
 798  0
             return;
 799  
         }
 800  0
         if ( !projectPaths.add( projectPath ) )
 801  
         {
 802  0
             return;
 803  
         }
 804  0
         getLog().debug( "Collecting parent/child projects of " + projectPath );
 805  
 
 806  0
         Model model = PomUtils.loadPom( pomFile );
 807  
 
 808  
         try
 809  
         {
 810  0
             String projectsRoot = projectsDir.getCanonicalPath();
 811  0
             String projectDir = pomFile.getParent();
 812  
 
 813  0
             String parentPath = "../pom.xml";
 814  0
             if ( model.getParent() != null && StringUtils.isNotEmpty( model.getParent().getRelativePath() ) )
 815  
             {
 816  0
                 parentPath = model.getParent().getRelativePath();
 817  
             }
 818  0
             String parent = relativizePath( new File( projectDir, parentPath ), projectsRoot );
 819  0
             if ( parent != null )
 820  
             {
 821  0
                 collectProjects( projectsDir, parent, projectPaths, false );
 822  
             }
 823  
 
 824  0
             Collection<String> modulePaths = new LinkedHashSet<String>();
 825  
 
 826  0
             modulePaths.addAll( (List<String>) model.getModules() );
 827  
 
 828  0
             for ( Profile profile : (List<Profile>) model.getProfiles() )
 829  
             {
 830  0
                 modulePaths.addAll( (List<String>) profile.getModules() );
 831  
             }
 832  
 
 833  0
             for ( String modulePath : modulePaths )
 834  
             {
 835  0
                 String module = relativizePath( new File( projectDir, modulePath ), projectsRoot );
 836  0
                 if ( module != null )
 837  
                 {
 838  0
                     collectProjects( projectsDir, module, projectPaths, false );
 839  
                 }
 840  0
             }
 841  
         }
 842  0
         catch ( IOException e )
 843  
         {
 844  0
             throw new MojoExecutionException( "Failed to analyze POM: " + pomFile, e );
 845  0
         }
 846  0
     }
 847  
 
 848  
     /**
 849  
      * Copies the specified projects to the directory given by {@link #cloneProjectsTo}. A project may either be denoted
 850  
      * by a path to a POM file or merely by a path to a base directory. During cloning, the POM files will be filtered.
 851  
      *
 852  
      * @param projectPaths The paths to the projects to clone, relative to the projects directory, must not be
 853  
      *                     <code>null</code> nor contain <code>null</code> elements.
 854  
      * @throws org.apache.maven.plugin.MojoExecutionException
 855  
      *          If the the projects could not be copied/filtered.
 856  
      */
 857  
     private void cloneProjects( Collection<String> projectPaths )
 858  
         throws MojoExecutionException
 859  
     {
 860  0
         if ( !cloneProjectsTo.mkdirs() && cloneClean )
 861  
         {
 862  
             try
 863  
             {
 864  0
                 FileUtils.cleanDirectory( cloneProjectsTo );
 865  
             }
 866  0
             catch ( IOException e )
 867  
             {
 868  0
                 throw new MojoExecutionException(
 869  
                     "Could not clean the cloneProjectsTo directory. Reason: " + e.getMessage(), e );
 870  0
             }
 871  
         }
 872  
 
 873  
         // determine project directories to clone
 874  0
         Collection<String> dirs = new LinkedHashSet<String>();
 875  0
         for ( String projectPath : projectPaths )
 876  
         {
 877  0
             if ( !new File( projectsDirectory, projectPath ).isDirectory() )
 878  
             {
 879  0
                 projectPath = getParentPath( projectPath );
 880  
             }
 881  0
             dirs.add( projectPath );
 882  
         }
 883  
 
 884  0
         boolean filter = false;
 885  
 
 886  
         // clone project directories
 887  
         try
 888  
         {
 889  0
             filter = !cloneProjectsTo.getCanonicalFile().equals( projectsDirectory.getCanonicalFile() );
 890  
 
 891  0
             List<String> clonedSubpaths = new ArrayList<String>();
 892  
 
 893  0
             for ( String subpath : dirs )
 894  
             {
 895  
                 // skip this project if its parent directory is also scheduled for cloning
 896  0
                 if ( !".".equals( subpath ) && dirs.contains( getParentPath( subpath ) ) )
 897  
                 {
 898  0
                     continue;
 899  
                 }
 900  
 
 901  
                 // avoid copying subdirs that are already cloned.
 902  0
                 if ( !alreadyCloned( subpath, clonedSubpaths ) )
 903  
                 {
 904  
                     // avoid creating new files that point to dir/.
 905  0
                     if ( ".".equals( subpath ) )
 906  
                     {
 907  0
                         String cloneSubdir = relativizePath( cloneProjectsTo, projectsDirectory.getCanonicalPath() );
 908  
 
 909  
                         // avoid infinite recursion if the cloneTo path is a subdirectory.
 910  0
                         if ( cloneSubdir != null )
 911  
                         {
 912  0
                             File temp = File.createTempFile( "pre-invocation-clone.", "" );
 913  0
                             temp.delete();
 914  0
                             temp.mkdirs();
 915  
 
 916  0
                             copyDirectoryStructure( projectsDirectory, temp );
 917  
 
 918  0
                             FileUtils.deleteDirectory( new File( temp, cloneSubdir ) );
 919  
 
 920  0
                             copyDirectoryStructure( temp, cloneProjectsTo );
 921  0
                         }
 922  
                         else
 923  
                         {
 924  0
                             copyDirectoryStructure( projectsDirectory, cloneProjectsTo );
 925  
                         }
 926  0
                     }
 927  
                     else
 928  
                     {
 929  0
                         File srcDir = new File( projectsDirectory, subpath );
 930  0
                         File dstDir = new File( cloneProjectsTo, subpath );
 931  0
                         copyDirectoryStructure( srcDir, dstDir );
 932  
                     }
 933  
 
 934  0
                     clonedSubpaths.add( subpath );
 935  
                 }
 936  
             }
 937  
         }
 938  0
         catch ( IOException e )
 939  
         {
 940  0
             throw new MojoExecutionException(
 941  
                 "Failed to clone projects from: " + projectsDirectory + " to: " + cloneProjectsTo + ". Reason: "
 942  
                     + e.getMessage(), e );
 943  0
         }
 944  
 
 945  
         // filter cloned POMs
 946  0
         if ( filter )
 947  
         {
 948  0
             for ( String projectPath : projectPaths )
 949  
             {
 950  0
                 File pomFile = new File( cloneProjectsTo, projectPath );
 951  0
                 if ( pomFile.isFile() )
 952  
                 {
 953  0
                     buildInterpolatedFile( pomFile, pomFile );
 954  
                 }
 955  0
             }
 956  0
             filteredPomPrefix = null;
 957  
         }
 958  0
     }
 959  
 
 960  
     /**
 961  
      * Gets the parent path of the specified relative path.
 962  
      *
 963  
      * @param path The relative path whose parent should be retrieved, must not be <code>null</code>.
 964  
      * @return The parent path or "." if the specified path has no parent, never <code>null</code>.
 965  
      */
 966  
     private String getParentPath( String path )
 967  
     {
 968  0
         int lastSep = Math.max( path.lastIndexOf( '/' ), path.lastIndexOf( '\\' ) );
 969  0
         return ( lastSep < 0 ) ? "." : path.substring( 0, lastSep );
 970  
     }
 971  
 
 972  
     /**
 973  
      * Copied a directory structure with deafault exclusions (.svn, CVS, etc)
 974  
      *
 975  
      * @param sourceDir The source directory to copy, must not be <code>null</code>.
 976  
      * @param destDir   The target directory to copy to, must not be <code>null</code>.
 977  
      * @throws java.io.IOException If the directory structure could not be copied.
 978  
      */
 979  
     private void copyDirectoryStructure( File sourceDir, File destDir )
 980  
         throws IOException
 981  
     {
 982  0
         DirectoryScanner scanner = new DirectoryScanner();
 983  0
         scanner.setBasedir( sourceDir );
 984  0
         if ( !cloneAllFiles )
 985  
         {
 986  0
             scanner.addDefaultExcludes();
 987  
         }
 988  0
         scanner.scan();
 989  
 
 990  
         /*
 991  
          * NOTE: Make sure the destination directory is always there (even if empty) to support POM-less ITs.
 992  
          */
 993  0
         destDir.mkdirs();
 994  0
         for ( String includedDir : scanner.getIncludedDirectories() )
 995  
         {
 996  0
             File clonedDir = new File( destDir, includedDir );
 997  0
             clonedDir.mkdirs();
 998  
         }
 999  
 
 1000  0
         for ( String includedFile : scanner.getIncludedFiles() )
 1001  
         {
 1002  0
             File sourceFile = new File( sourceDir, includedFile );
 1003  0
             File destFile = new File( destDir, includedFile );
 1004  0
             FileUtils.copyFile( sourceFile, destFile );
 1005  
         }
 1006  0
     }
 1007  
 
 1008  
     /**
 1009  
      * Determines whether the specified sub path has already been cloned, i.e. whether one of its ancestor directories
 1010  
      * was already cloned.
 1011  
      *
 1012  
      * @param subpath        The sub path to check, must not be <code>null</code>.
 1013  
      * @param clonedSubpaths The list of already cloned paths, must not be <code>null</code> nor contain
 1014  
      *                       <code>null</code> elements.
 1015  
      * @return <code>true</code> if the specified path has already been cloned, <code>false</code> otherwise.
 1016  
      */
 1017  
     static boolean alreadyCloned( String subpath, List<String> clonedSubpaths )
 1018  
     {
 1019  8
         for ( String path : clonedSubpaths )
 1020  
         {
 1021  6
             if ( ".".equals( path ) || subpath.equals( path ) || subpath.startsWith( path + File.separator ) )
 1022  
             {
 1023  4
                 return true;
 1024  
             }
 1025  
         }
 1026  
 
 1027  4
         return false;
 1028  
     }
 1029  
 
 1030  
     /**
 1031  
      * Runs the specified build jobs.
 1032  
      *
 1033  
      * @param projectsDir The base directory of all projects, must not be <code>null</code>.
 1034  
      * @param buildJobs   The build jobs to run must not be <code>null</code> nor contain <code>null</code> elements.
 1035  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1036  
      *          If any build could not be launched.
 1037  
      */
 1038  
     private void runBuilds( final File projectsDir, BuildJob[] buildJobs )
 1039  
         throws MojoExecutionException
 1040  
     {
 1041  0
         if ( !localRepositoryPath.exists() )
 1042  
         {
 1043  0
             localRepositoryPath.mkdirs();
 1044  
         }
 1045  
 
 1046  
         //-----------------------------------------------
 1047  
         // interpolate settings file
 1048  
         //-----------------------------------------------
 1049  
 
 1050  0
         File interpolatedSettingsFile = null;
 1051  0
         if ( settingsFile != null )
 1052  
         {
 1053  0
             if ( cloneProjectsTo != null )
 1054  
             {
 1055  0
                 interpolatedSettingsFile = new File( cloneProjectsTo, "interpolated-" + settingsFile.getName() );
 1056  
             }
 1057  
             else
 1058  
             {
 1059  0
                 interpolatedSettingsFile =
 1060  
                     new File( settingsFile.getParentFile(), "interpolated-" + settingsFile.getName() );
 1061  
             }
 1062  0
             buildInterpolatedFile( settingsFile, interpolatedSettingsFile );
 1063  
         }
 1064  
 
 1065  
         //-----------------------------------------------
 1066  
         // merge settings file
 1067  
         //-----------------------------------------------
 1068  
 
 1069  0
         SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer();
 1070  
 
 1071  
         File mergedSettingsFile;
 1072  0
         Settings mergedSettings = this.settings;
 1073  0
         if ( mergeUserSettings )
 1074  
         {
 1075  0
             if ( interpolatedSettingsFile != null )
 1076  
             {
 1077  
                 // Have to merge the specified settings file (dominant) and the one of the invoking Maven process
 1078  0
                 Reader reader = null;
 1079  
                 try
 1080  
                 {
 1081  0
                     reader = new XmlStreamReader( interpolatedSettingsFile );
 1082  0
                     SettingsXpp3Reader settingsReader = new SettingsXpp3Reader();
 1083  0
                     Settings dominantSettings = settingsReader.read( reader );
 1084  
 
 1085  
                     // MINVOKER-137: NPE on dominantSettings.getRuntimeInfo()
 1086  
                     // DefaultMavenSettingsBuilder does the same trick
 1087  0
                     if ( dominantSettings.getRuntimeInfo() == null )
 1088  
                     {
 1089  0
                         RuntimeInfo rtInfo = new RuntimeInfo( dominantSettings );
 1090  0
                         rtInfo.setFile( interpolatedSettingsFile );
 1091  0
                         dominantSettings.setRuntimeInfo( rtInfo );
 1092  
                     }
 1093  
 
 1094  0
                     Settings recessiveSettings = cloneSettings();
 1095  
 
 1096  0
                     SettingsUtils.merge( dominantSettings, recessiveSettings, TrackableBase.USER_LEVEL );
 1097  
 
 1098  0
                     mergedSettings = dominantSettings;
 1099  0
                     getLog().debug( "Merged specified settings file with settings of invoking process" );
 1100  
                 }
 1101  0
                 catch ( XmlPullParserException e )
 1102  
                 {
 1103  0
                     throw new MojoExecutionException( "Could not read specified settings file", e );
 1104  
                 }
 1105  0
                 catch ( IOException e )
 1106  
                 {
 1107  0
                     throw new MojoExecutionException( "Could not read specified settings file", e );
 1108  
                 }
 1109  
                 finally
 1110  
                 {
 1111  0
                     IOUtil.close( reader );
 1112  0
                 }
 1113  
             }
 1114  
         }
 1115  0
         if ( this.settingsFile != null && !mergeUserSettings )
 1116  
         {
 1117  0
             mergedSettingsFile = interpolatedSettingsFile;
 1118  
         }
 1119  
         else
 1120  
         {
 1121  
             try
 1122  
             {
 1123  0
                 mergedSettingsFile = File.createTempFile( "invoker-settings", ".xml" );
 1124  
 
 1125  0
                 FileWriter fileWriter = null;
 1126  
                 try
 1127  
                 {
 1128  0
                     fileWriter = new FileWriter( mergedSettingsFile );
 1129  0
                     settingsWriter.write( fileWriter, mergedSettings );
 1130  
                 }
 1131  
                 finally
 1132  
                 {
 1133  0
                     IOUtil.close( fileWriter );
 1134  0
                 }
 1135  
 
 1136  0
                 if ( getLog().isDebugEnabled() )
 1137  
                 {
 1138  0
                     getLog().debug(
 1139  
                         "Created temporary file for invoker settings.xml: " + mergedSettingsFile.getAbsolutePath() );
 1140  
                 }
 1141  
 
 1142  
             }
 1143  0
             catch ( IOException e )
 1144  
             {
 1145  0
                 throw new MojoExecutionException( "Could not create temporary file for invoker settings.xml", e );
 1146  0
             }
 1147  
         }
 1148  0
         final File finalSettingsFile = mergedSettingsFile;
 1149  
         
 1150  0
         if ( mavenHome != null )
 1151  
         {
 1152  0
             actualMavenVersion = SelectorUtils.getMavenVersion( mavenHome );
 1153  
         }
 1154  
         else
 1155  
         {
 1156  0
             actualMavenVersion = SelectorUtils.getMavenVersion();
 1157  
         }
 1158  
         
 1159  0
         if ( javaHome != null )
 1160  
         {
 1161  0
             resolveExternalJreVersion();
 1162  
         }
 1163  
         else
 1164  
         {
 1165  0
             actualJreVersion = SelectorUtils.getJreVersion();
 1166  
         }
 1167  
         
 1168  
         try
 1169  
         {
 1170  0
             if ( isParallelRun() )
 1171  
             {
 1172  0
                 getLog().info( "use parallelThreads " + parallelThreads );
 1173  
 
 1174  0
                 ExecutorService executorService = Executors.newFixedThreadPool( parallelThreads );
 1175  0
                 for ( final BuildJob job : buildJobs )
 1176  
                 {
 1177  0
                     executorService.execute( new Runnable()
 1178  0
                     {
 1179  
 
 1180  
                         public void run()
 1181  
                         {
 1182  
                             try
 1183  
                             {
 1184  0
                                 runBuild( projectsDir, job, finalSettingsFile );
 1185  
                             }
 1186  0
                             catch ( MojoExecutionException e )
 1187  
                             {
 1188  0
                                 throw new RuntimeException( e.getMessage(), e );
 1189  0
                             }
 1190  0
                         }
 1191  
                     } );
 1192  
                 }
 1193  
 
 1194  
                 try
 1195  
                 {
 1196  0
                     executorService.shutdown();
 1197  
                     // TODO add a configurable time out
 1198  0
                     executorService.awaitTermination( Long.MAX_VALUE, TimeUnit.MILLISECONDS );
 1199  
                 }
 1200  0
                 catch ( InterruptedException e )
 1201  
                 {
 1202  0
                     throw new MojoExecutionException( e.getMessage(), e );
 1203  0
                 }
 1204  
 
 1205  0
             }
 1206  
             else
 1207  
             {
 1208  0
                 for ( BuildJob job : buildJobs )
 1209  
                 {
 1210  0
                     runBuild( projectsDir, job, finalSettingsFile );
 1211  
                 }
 1212  
             }
 1213  
         }
 1214  
         finally
 1215  
         {
 1216  0
             if ( interpolatedSettingsFile != null && cloneProjectsTo == null )
 1217  
             {
 1218  0
                 interpolatedSettingsFile.delete();
 1219  
             }
 1220  0
             if ( mergedSettingsFile != null && mergedSettingsFile.exists() )
 1221  
             {
 1222  0
                 mergedSettingsFile.delete();
 1223  
             }
 1224  
 
 1225  
         }
 1226  0
     }
 1227  
 
 1228  
     private Settings cloneSettings()
 1229  
     {
 1230  0
         Settings recessiveSettings = SettingsUtils.copySettings( this.settings );
 1231  
 
 1232  
         // MINVOKER-133: reset sourceLevelSet
 1233  0
         resetSourceLevelSet( recessiveSettings );
 1234  0
         for ( org.apache.maven.settings.Mirror mirror : recessiveSettings.getMirrors() )
 1235  
         {
 1236  0
             resetSourceLevelSet( mirror );
 1237  
         }
 1238  0
         for ( org.apache.maven.settings.Server server : recessiveSettings.getServers() )
 1239  
         {
 1240  0
             resetSourceLevelSet( server );
 1241  
         }
 1242  0
         for ( org.apache.maven.settings.Proxy proxy : recessiveSettings.getProxies() )
 1243  
         {
 1244  0
             resetSourceLevelSet( proxy );
 1245  
         }
 1246  0
         for ( org.apache.maven.settings.Profile profile : recessiveSettings.getProfiles() )
 1247  
         {
 1248  0
             resetSourceLevelSet( profile );
 1249  
         }
 1250  
 
 1251  0
         return recessiveSettings;
 1252  
     }
 1253  
 
 1254  
     private void resetSourceLevelSet( org.apache.maven.settings.TrackableBase trackable )
 1255  
     {
 1256  
         try
 1257  
         {
 1258  0
             ReflectionUtils.setVariableValueInObject( trackable, "sourceLevelSet", Boolean.FALSE );
 1259  0
             getLog().debug( "sourceLevelSet: " + ReflectionUtils.getValueIncludingSuperclasses( "sourceLevelSet", trackable ) );
 1260  
         }
 1261  0
         catch ( IllegalAccessException e )
 1262  
         {
 1263  
             //noop
 1264  0
         }
 1265  0
     }
 1266  
 
 1267  
     private void resolveExternalJreVersion()
 1268  
     {
 1269  0
         Artifact pluginArtifact = mojoExecution.getMojoDescriptor().getPluginDescriptor().getPluginArtifact();
 1270  0
         pluginArtifact.getFile();
 1271  
         
 1272  0
         Commandline commandLine = new Commandline();
 1273  0
         commandLine.setExecutable( new File( javaHome, "bin/java" ).getAbsolutePath() );
 1274  0
         commandLine.createArg().setValue( "-cp" );
 1275  0
         commandLine.createArg().setFile( pluginArtifact.getFile() );
 1276  0
         commandLine.createArg().setValue( SystemPropertyPrinter.class.getName() );
 1277  0
         commandLine.createArg().setValue( "java.version" );
 1278  
         
 1279  0
         StreamConsumer consumer = new StreamConsumer()
 1280  0
         {
 1281  
             public void consumeLine( String line )
 1282  
             {
 1283  0
                 setActualJreVersion( line );
 1284  0
             }
 1285  
         };
 1286  
         try
 1287  
         {
 1288  0
             CommandLineUtils.executeCommandLine( commandLine, consumer, null );
 1289  
         }
 1290  0
         catch ( CommandLineException e )
 1291  
         {
 1292  0
             getLog().warn( e.getMessage() );
 1293  0
         }
 1294  0
     }
 1295  
 
 1296  
     /**
 1297  
      * Runs the specified project.
 1298  
      *
 1299  
      * @param projectsDir  The base directory of all projects, must not be <code>null</code>.
 1300  
      * @param buildJob     The build job to run, must not be <code>null</code>.
 1301  
      * @param settingsFile The (already interpolated) user settings file for the build, may be <code>null</code> to use
 1302  
      *                     the current user settings.
 1303  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1304  
      *          If the project could not be launched.
 1305  
      */
 1306  
     private void runBuild( File projectsDir, BuildJob buildJob, File settingsFile )
 1307  
         throws MojoExecutionException
 1308  
     {
 1309  0
         File pomFile = new File( projectsDir, buildJob.getProject() );
 1310  
         File basedir;
 1311  0
         if ( pomFile.isDirectory() )
 1312  
         {
 1313  0
             basedir = pomFile;
 1314  0
             pomFile = new File( basedir, "pom.xml" );
 1315  0
             if ( !pomFile.exists() )
 1316  
             {
 1317  0
                 pomFile = null;
 1318  
             }
 1319  
             else
 1320  
             {
 1321  0
                 buildJob.setProject( buildJob.getProject() + File.separator + "pom.xml" );
 1322  
             }
 1323  
         }
 1324  
         else
 1325  
         {
 1326  0
             basedir = pomFile.getParentFile();
 1327  
         }
 1328  
 
 1329  0
         getLog().info( "Building: " + buildJob.getProject() );
 1330  
 
 1331  0
         File interpolatedPomFile = null;
 1332  0
         if ( pomFile != null )
 1333  
         {
 1334  0
             if ( filteredPomPrefix != null )
 1335  
             {
 1336  0
                 interpolatedPomFile = new File( basedir, filteredPomPrefix + pomFile.getName() );
 1337  0
                 buildInterpolatedFile( pomFile, interpolatedPomFile );
 1338  
             }
 1339  
             else
 1340  
             {
 1341  0
                 interpolatedPomFile = pomFile;
 1342  
             }
 1343  
         }
 1344  
 
 1345  0
         InvokerProperties invokerProperties = getInvokerProperties( basedir );
 1346  
 
 1347  
         // let's set what details we can
 1348  0
         buildJob.setName( invokerProperties.getJobName() );
 1349  0
         buildJob.setDescription( invokerProperties.getJobDescription() );
 1350  
 
 1351  
         try
 1352  
         {
 1353  0
             int selection = getSelection( invokerProperties ); 
 1354  0
             if ( selection == 0 )
 1355  
             {
 1356  0
                 long milliseconds = System.currentTimeMillis();
 1357  
                 boolean executed;
 1358  
                 try
 1359  
                 {
 1360  0
                     executed = runBuild( basedir, interpolatedPomFile, settingsFile, invokerProperties );
 1361  
                 }
 1362  
                 finally
 1363  
                 {
 1364  0
                     milliseconds = System.currentTimeMillis() - milliseconds;
 1365  0
                     buildJob.setTime( milliseconds / 1000.0 );
 1366  0
                 }
 1367  
 
 1368  0
                 if ( executed )
 1369  
                 {
 1370  
 
 1371  0
                     buildJob.setResult( BuildJob.Result.SUCCESS );
 1372  
 
 1373  0
                     if ( !suppressSummaries )
 1374  
                     {
 1375  0
                         getLog().info( "..SUCCESS " + formatTime( buildJob.getTime() ) );
 1376  
                     }
 1377  
                 }
 1378  
                 else
 1379  
                 {
 1380  0
                     buildJob.setResult( BuildJob.Result.SKIPPED );
 1381  
 
 1382  0
                     if ( !suppressSummaries )
 1383  
                     {
 1384  0
                         getLog().info( "..SKIPPED " + formatTime( buildJob.getTime() ) );
 1385  
                     }
 1386  
                 }
 1387  0
             }
 1388  
             else
 1389  
             {
 1390  0
                 buildJob.setResult( BuildJob.Result.SKIPPED );
 1391  
 
 1392  0
                 StringBuilder message = new StringBuilder();
 1393  0
                 if ( ( selection & SELECTOR_MAVENVERSION ) != 0 )
 1394  
                 {
 1395  0
                     message.append( "Maven version" );
 1396  
                 }
 1397  0
                 if ( ( selection & SELECTOR_JREVERSION ) != 0 )
 1398  
                 {
 1399  0
                     if ( message.length() > 0 )
 1400  
                     {
 1401  0
                         message.append( ", " );
 1402  
                     }
 1403  0
                     message.append( "JRE version" );
 1404  
                 }
 1405  0
                 if ( ( selection & SELECTOR_OSFAMILY ) != 0 )
 1406  
                 {
 1407  0
                     if ( message.length() > 0 )
 1408  
                     {
 1409  0
                         message.append( ", " );
 1410  
                     }
 1411  0
                     message.append( "OS" );
 1412  
                 }
 1413  
 
 1414  0
                 if ( !suppressSummaries )
 1415  
                 {
 1416  0
                     getLog().info( "..SKIPPED due to " + message.toString() );
 1417  
                 }
 1418  
                 
 1419  
                 // Abuse failureMessage, the field in the report which should contain the reason for skipping
 1420  
                 // Consider skipCode + I18N
 1421  0
                 buildJob.setFailureMessage( "Skipped due to " + message.toString() );
 1422  
             }
 1423  
         }
 1424  0
         catch ( RunErrorException e )
 1425  
         {
 1426  0
             buildJob.setResult( BuildJob.Result.ERROR );
 1427  0
             buildJob.setFailureMessage( e.getMessage() );
 1428  
 
 1429  0
             if ( !suppressSummaries )
 1430  
             {
 1431  0
                 getLog().info( "..ERROR " + formatTime( buildJob.getTime() ) );
 1432  0
                 getLog().info( "  " + e.getMessage() );
 1433  
             }
 1434  
         }
 1435  0
         catch ( RunFailureException e )
 1436  
         {
 1437  0
             buildJob.setResult( e.getType() );
 1438  0
             buildJob.setFailureMessage( e.getMessage() );
 1439  
 
 1440  0
             if ( !suppressSummaries )
 1441  
             {
 1442  0
                 getLog().info( "..FAILED " + formatTime( buildJob.getTime() ) );
 1443  0
                 getLog().info( "  " + e.getMessage() );
 1444  
             }
 1445  
         }
 1446  
         finally
 1447  
         {
 1448  0
             if ( interpolatedPomFile != null && StringUtils.isNotEmpty( filteredPomPrefix ) )
 1449  
             {
 1450  0
                 interpolatedPomFile.delete();
 1451  
             }
 1452  0
             writeBuildReport( buildJob );
 1453  0
         }
 1454  0
     }
 1455  
 
 1456  
     /**
 1457  
      * Determines whether selector conditions of the specified invoker properties match the current environment.
 1458  
      *
 1459  
      * @param invokerProperties The invoker properties to check, must not be <code>null</code>.
 1460  
      * @return <code>0</code> if the job corresponding to the properties should be run, 
 1461  
      *   otherwise a bitwise value representing the reason why it should be skipped.
 1462  
      */
 1463  
     private int getSelection( InvokerProperties invokerProperties )
 1464  
     {
 1465  0
         int selection = 0;
 1466  0
         if ( !SelectorUtils.isMavenVersion( invokerProperties.getMavenVersion(), actualMavenVersion ) )
 1467  
         {
 1468  0
             selection |= SELECTOR_MAVENVERSION;
 1469  
         }
 1470  
 
 1471  0
         if ( !SelectorUtils.isJreVersion( invokerProperties.getJreVersion(), actualJreVersion ) )
 1472  
         {
 1473  0
             selection |= SELECTOR_JREVERSION;
 1474  
         }
 1475  
 
 1476  0
         if ( !SelectorUtils.isOsFamily( invokerProperties.getOsFamily() ) )
 1477  
         {
 1478  0
             selection |= SELECTOR_OSFAMILY;
 1479  
         }
 1480  
 
 1481  0
         return selection;
 1482  
     }
 1483  
 
 1484  
     /**
 1485  
      * Writes the XML report for the specified build job unless report generation has been disabled.
 1486  
      *
 1487  
      * @param buildJob The build job whose report should be written, must not be <code>null</code>.
 1488  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1489  
      *          If the report could not be written.
 1490  
      */
 1491  
     private void writeBuildReport( BuildJob buildJob )
 1492  
         throws MojoExecutionException
 1493  
     {
 1494  0
         if ( disableReports )
 1495  
         {
 1496  0
             return;
 1497  
         }
 1498  
 
 1499  0
         String safeFileName = buildJob.getProject().replace( '/', '_' ).replace( '\\', '_' ).replace( ' ', '_' );
 1500  0
         if ( safeFileName.endsWith( "_pom.xml" ) )
 1501  
         {
 1502  0
             safeFileName = safeFileName.substring( 0, safeFileName.length() - "_pom.xml".length() );
 1503  
         }
 1504  
 
 1505  0
         File reportFile = new File( reportsDirectory, "BUILD-" + safeFileName + ".xml" );
 1506  
         try
 1507  
         {
 1508  0
             FileOutputStream fos = new FileOutputStream( reportFile );
 1509  
             try
 1510  
             {
 1511  0
                 Writer osw = new OutputStreamWriter( fos, buildJob.getModelEncoding() );
 1512  0
                 BuildJobXpp3Writer writer = new BuildJobXpp3Writer();
 1513  0
                 writer.write( osw, buildJob );
 1514  0
                 osw.close();
 1515  
             }
 1516  
             finally
 1517  
             {
 1518  0
                 fos.close();
 1519  0
             }
 1520  
         }
 1521  0
         catch ( IOException e )
 1522  
         {
 1523  0
             throw new MojoExecutionException( "Failed to write build report " + reportFile, e );
 1524  0
         }
 1525  0
     }
 1526  
 
 1527  
     /**
 1528  
      * Formats the specified build duration time.
 1529  
      *
 1530  
      * @param seconds The duration of the build.
 1531  
      * @return The formatted time, never <code>null</code>.
 1532  
      */
 1533  
     private String formatTime( double seconds )
 1534  
     {
 1535  0
         return secFormat.format( seconds );
 1536  
     }
 1537  
 
 1538  
     /**
 1539  
      * Runs the specified project.
 1540  
      *
 1541  
      * @param basedir           The base directory of the project, must not be <code>null</code>.
 1542  
      * @param pomFile           The (already interpolated) POM file, may be <code>null</code> for a POM-less Maven invocation.
 1543  
      * @param settingsFile      The (already interpolated) user settings file for the build, may be <code>null</code>. Will be
 1544  
      *                          merged with the settings file of the invoking Maven process.
 1545  
      * @param invokerProperties The properties to use.
 1546  
      * @return <code>true</code> if the project was launched or <code>false</code> if the selector script indicated that
 1547  
      *         the project should be skipped.
 1548  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1549  
      *          If the project could not be launched.
 1550  
      * @throws org.apache.maven.shared.scriptinterpreter.RunFailureException
 1551  
      *          If either a hook script or the build itself failed.
 1552  
      */
 1553  
     private boolean runBuild( File basedir, File pomFile, File settingsFile, InvokerProperties invokerProperties )
 1554  
         throws MojoExecutionException, RunFailureException
 1555  
     {
 1556  0
         if ( getLog().isDebugEnabled() && !invokerProperties.getProperties().isEmpty() )
 1557  
         {
 1558  0
             Properties props = invokerProperties.getProperties();
 1559  0
             getLog().debug( "Using invoker properties:" );
 1560  0
             for ( String key : new TreeSet<String>( (Set) props.keySet() ) )
 1561  
             {
 1562  0
                 String value = props.getProperty( key );
 1563  0
                 getLog().debug( "  " + key + " = " + value );
 1564  0
             }
 1565  
         }
 1566  
 
 1567  0
         List<String> goals = getGoals( basedir );
 1568  
 
 1569  0
         List<String> profiles = getProfiles( basedir );
 1570  
 
 1571  0
         Map<String, Object> context = new LinkedHashMap<String, Object>();
 1572  
 
 1573  0
         FileLogger logger = setupLogger( basedir );
 1574  
         try
 1575  
         {
 1576  
             try
 1577  
             {
 1578  0
                 scriptRunner.run( "selector script", basedir, selectorScript, context, logger, BuildJob.Result.SKIPPED,
 1579  
                                   false );
 1580  
             }
 1581  0
             catch ( RunErrorException e )
 1582  
             {
 1583  0
                 throw e;
 1584  
             }
 1585  0
             catch ( RunFailureException e )
 1586  
             {
 1587  0
                 return false;
 1588  0
             }
 1589  
 
 1590  0
             scriptRunner.run( "pre-build script", basedir, preBuildHookScript, context, logger,
 1591  
                               BuildJob.Result.FAILURE_PRE_HOOK, false );
 1592  
 
 1593  0
             final InvocationRequest request = new DefaultInvocationRequest();
 1594  
 
 1595  0
             request.setLocalRepositoryDirectory( localRepositoryPath );
 1596  
 
 1597  0
             request.setInteractive( false );
 1598  
 
 1599  0
             request.setShowErrors( showErrors );
 1600  
 
 1601  0
             request.setDebug( debug );
 1602  
 
 1603  0
             request.setShowVersion( showVersion );
 1604  
             
 1605  0
             if ( logger != null )
 1606  
             {
 1607  0
                 request.setErrorHandler( logger );
 1608  
 
 1609  0
                 request.setOutputHandler( logger );
 1610  
             }
 1611  
 
 1612  0
             if ( mavenHome != null )
 1613  
             {
 1614  0
                 invoker.setMavenHome( mavenHome );
 1615  0
                 request.addShellEnvironment( "M2_HOME", mavenHome.getAbsolutePath() );
 1616  
             }
 1617  
             
 1618  0
             if ( mavenExecutable != null )
 1619  
             {
 1620  0
                 invoker.setMavenExecutable( new File( mavenExecutable ) );
 1621  
             }
 1622  
 
 1623  0
             if ( javaHome != null )
 1624  
             {
 1625  0
                 request.setJavaHome( javaHome );
 1626  
             }
 1627  
             
 1628  0
             if ( environmentVariables != null )
 1629  
             {
 1630  0
                 for ( Map.Entry<String, String> variable : environmentVariables.entrySet() )
 1631  
                 {
 1632  0
                     request.addShellEnvironment( variable.getKey(), variable.getValue() );
 1633  
                 }
 1634  
             }
 1635  
 
 1636  0
             for ( int invocationIndex = 1; ; invocationIndex++ )
 1637  
             {
 1638  0
                 if ( invocationIndex > 1 && !invokerProperties.isInvocationDefined( invocationIndex ) )
 1639  
                 {
 1640  0
                     break;
 1641  
                 }
 1642  
 
 1643  0
                 request.setBaseDirectory( basedir );
 1644  
 
 1645  0
                 request.setPomFile( pomFile );
 1646  
 
 1647  0
                 request.setGoals( goals );
 1648  
 
 1649  0
                 request.setProfiles( profiles );
 1650  
 
 1651  0
                 request.setMavenOpts( mavenOpts );
 1652  
 
 1653  0
                 request.setOffline( false );
 1654  
 
 1655  0
                 request.setUserSettingsFile( settingsFile );
 1656  
 
 1657  0
                 Properties systemProperties =
 1658  
                     getSystemProperties( basedir, invokerProperties.getSystemPropertiesFile( invocationIndex ) );
 1659  0
                 request.setProperties( systemProperties );
 1660  
 
 1661  0
                 invokerProperties.configureInvocation( request, invocationIndex );
 1662  
 
 1663  0
                 if ( getLog().isDebugEnabled() )
 1664  
                 {
 1665  
                     try
 1666  
                     {
 1667  0
                         getLog().debug( "Using MAVEN_OPTS: " + request.getMavenOpts() );
 1668  0
                         getLog().debug( "Executing: " + new MavenCommandLineBuilder().build( request ) );
 1669  
                     }
 1670  0
                     catch ( CommandLineConfigurationException e )
 1671  
                     {
 1672  0
                         getLog().debug( "Failed to display command line: " + e.getMessage() );
 1673  0
                     }
 1674  
                 }
 1675  
 
 1676  
                 InvocationResult result;
 1677  
 
 1678  
                 try
 1679  
                 {
 1680  0
                     result = invoker.execute( request );
 1681  
                 }
 1682  0
                 catch ( final MavenInvocationException e )
 1683  
                 {
 1684  0
                     getLog().debug( "Error invoking Maven: " + e.getMessage(), e );
 1685  0
                     throw new RunFailureException( "Maven invocation failed. " + e.getMessage(),
 1686  
                                                    BuildJob.Result.FAILURE_BUILD );
 1687  0
                 }
 1688  
 
 1689  0
                 verify( result, invocationIndex, invokerProperties, logger );
 1690  
             }
 1691  
 
 1692  0
             scriptRunner.run( "post-build script", basedir, postBuildHookScript, context, logger,
 1693  
                               BuildJob.Result.FAILURE_POST_HOOK, true );
 1694  
         }
 1695  0
         catch ( IOException e )
 1696  
         {
 1697  0
             throw new MojoExecutionException( e.getMessage(), e );
 1698  
         }
 1699  
         finally
 1700  
         {
 1701  0
             if ( logger != null )
 1702  
             {
 1703  0
                 logger.close();
 1704  
             }
 1705  
         }
 1706  0
         return true;
 1707  
     }
 1708  
 
 1709  
     /**
 1710  
      * Initializes the build logger for the specified project.
 1711  
      *
 1712  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1713  
      * @return The build logger or <code>null</code> if logging has been disabled.
 1714  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1715  
      *          If the log file could not be created.
 1716  
      */
 1717  
     private FileLogger setupLogger( File basedir )
 1718  
         throws MojoExecutionException
 1719  
     {
 1720  0
         FileLogger logger = null;
 1721  
 
 1722  0
         if ( !noLog )
 1723  
         {
 1724  0
             File outputLog = new File( basedir, "build.log" );
 1725  
             try
 1726  
             {
 1727  0
                 if ( streamLogs )
 1728  
                 {
 1729  0
                     logger = new FileLogger( outputLog, getLog() );
 1730  
                 }
 1731  
                 else
 1732  
                 {
 1733  0
                     logger = new FileLogger( outputLog );
 1734  
                 }
 1735  
 
 1736  0
                 getLog().debug( "build log initialized in: " + outputLog );
 1737  
             }
 1738  0
             catch ( IOException e )
 1739  
             {
 1740  0
                 throw new MojoExecutionException( "Error initializing build logfile in: " + outputLog, e );
 1741  0
             }
 1742  
         }
 1743  
 
 1744  0
         return logger;
 1745  
     }
 1746  
 
 1747  
     /**
 1748  
      * Gets the system properties to use for the specified project.
 1749  
      *
 1750  
      * @param basedir  The base directory of the project, must not be <code>null</code>.
 1751  
      * @param filename The filename to the properties file to load, may be <code>null</code> to use the default path
 1752  
      *                 given by {@link #testPropertiesFile}.
 1753  
      * @return The system properties to use, may be empty but never <code>null</code>.
 1754  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1755  
      *          If the properties file exists but could not be read.
 1756  
      */
 1757  
     private Properties getSystemProperties( final File basedir, final String filename )
 1758  
         throws MojoExecutionException
 1759  
     {
 1760  0
         Properties collectedTestProperties = new Properties();
 1761  
         
 1762  0
         if ( testProperties != null )
 1763  
         {
 1764  0
             collectedTestProperties.putAll( testProperties );
 1765  
         }
 1766  
 
 1767  0
         if ( properties != null )
 1768  
         {
 1769  
             // MINVOKER-118: property can have empty value, which is not accepted by collectedTestProperties
 1770  0
             for ( Map.Entry<String, String> entry : properties.entrySet() )
 1771  
             {
 1772  0
                 if ( entry.getValue() != null )
 1773  
                 {
 1774  0
                     collectedTestProperties.put( entry.getKey(), entry.getValue() );
 1775  
                 }
 1776  
             }
 1777  
         }
 1778  
 
 1779  0
         File propertiesFile = null;
 1780  0
         if ( filename != null )
 1781  
         {
 1782  0
             propertiesFile = new File( basedir, filename );
 1783  
         }
 1784  0
         else if ( testPropertiesFile != null )
 1785  
         {
 1786  0
             propertiesFile = new File( basedir, testPropertiesFile );
 1787  
         }
 1788  
 
 1789  0
         if ( propertiesFile != null && propertiesFile.isFile() )
 1790  
         {
 1791  0
             InputStream fin = null;
 1792  
             try
 1793  
             {
 1794  0
                 fin = new FileInputStream( propertiesFile );
 1795  
 
 1796  0
                 Properties loadedProperties = new Properties();
 1797  0
                 loadedProperties.load( fin );
 1798  0
                 collectedTestProperties.putAll( loadedProperties );
 1799  
             }
 1800  0
             catch ( IOException e )
 1801  
             {
 1802  0
                 throw new MojoExecutionException( "Error reading system properties from " + propertiesFile );
 1803  
             }
 1804  
             finally
 1805  
             {
 1806  0
                 IOUtil.close( fin );
 1807  0
             }
 1808  
         }
 1809  
 
 1810  0
         return collectedTestProperties;
 1811  
     }
 1812  
 
 1813  
     /**
 1814  
      * Verifies the invocation result.
 1815  
      *
 1816  
      * @param result            The invocation result to check, must not be <code>null</code>.
 1817  
      * @param invocationIndex   The index of the invocation for which to check the exit code, must not be negative.
 1818  
      * @param invokerProperties The invoker properties used to check the exit code, must not be <code>null</code>.
 1819  
      * @param logger            The build logger, may be <code>null</code> if logging is disabled.
 1820  
      * @throws org.apache.maven.shared.scriptinterpreter.RunFailureException
 1821  
      *          If the invocation result indicates a build failure.
 1822  
      */
 1823  
     private void verify( InvocationResult result, int invocationIndex, InvokerProperties invokerProperties,
 1824  
                          FileLogger logger )
 1825  
         throws RunFailureException
 1826  
     {
 1827  0
         if ( result.getExecutionException() != null )
 1828  
         {
 1829  0
             throw new RunFailureException(
 1830  
                 "The Maven invocation failed. " + result.getExecutionException().getMessage(), BuildJob.Result.ERROR );
 1831  
         }
 1832  0
         else if ( !invokerProperties.isExpectedResult( result.getExitCode(), invocationIndex ) )
 1833  
         {
 1834  0
             StringBuilder buffer = new StringBuilder( 256 );
 1835  0
             buffer.append( "The build exited with code " ).append( result.getExitCode() ).append( ". " );
 1836  0
             if ( logger != null )
 1837  
             {
 1838  0
                 buffer.append( "See " );
 1839  0
                 buffer.append( logger.getOutputFile().getAbsolutePath() );
 1840  0
                 buffer.append( " for details." );
 1841  
             }
 1842  
             else
 1843  
             {
 1844  0
                 buffer.append( "See console output for details." );
 1845  
             }
 1846  0
             throw new RunFailureException( buffer.toString(), BuildJob.Result.FAILURE_BUILD );
 1847  
         }
 1848  0
     }
 1849  
 
 1850  
     /**
 1851  
      * Gets the goal list for the specified project.
 1852  
      *
 1853  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1854  
      * @return The list of goals to run when building the project, may be empty but never <code>null</code>.
 1855  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1856  
      *          If the profile file could not be read.
 1857  
      */
 1858  
     List<String> getGoals( final File basedir )
 1859  
         throws MojoExecutionException
 1860  
     {
 1861  
         try
 1862  
         {
 1863  10
             return getTokens( basedir, goalsFile, goals );
 1864  
         }
 1865  0
         catch ( IOException e )
 1866  
         {
 1867  0
             throw new MojoExecutionException( "error reading goals", e );
 1868  
         }
 1869  
     }
 1870  
 
 1871  
     /**
 1872  
      * Gets the profile list for the specified project.
 1873  
      *
 1874  
      * @param basedir The base directory of the project, must not be <code>null</code>.
 1875  
      * @return The list of profiles to activate when building the project, may be empty but never <code>null</code>.
 1876  
      * @throws org.apache.maven.plugin.MojoExecutionException
 1877  
      *          If the profile file could not be read.
 1878  
      */
 1879  
     List<String> getProfiles( File basedir )
 1880  
         throws MojoExecutionException
 1881  
     {
 1882  
         try
 1883  
         {
 1884  6
             return getTokens( basedir, profilesFile, profiles );
 1885  
         }
 1886  0
         catch ( IOException e )
 1887  
         {
 1888  0
             throw new MojoExecutionException( "error reading profiles", e );
 1889  
         }
 1890  
     }
 1891  
 
 1892  
     /**
 1893  
      * Gets the build jobs that should be processed. Note that the order of the returned build jobs is significant.
 1894  
      *
 1895  
      * @return The build jobs to process, may be empty but never <code>null</code>.
 1896  
      * @throws java.io.IOException If the projects directory could not be scanned.
 1897  
      */
 1898  
     BuildJob[] getBuildJobs()
 1899  
         throws IOException
 1900  
     {
 1901  
         BuildJob[] buildJobs;
 1902  
 
 1903  6
         if ( ( pom != null ) && pom.exists() )
 1904  
         {
 1905  0
             buildJobs = new BuildJob[]{ new BuildJob( pom.getAbsolutePath(), BuildJob.Type.NORMAL ) };
 1906  
         }
 1907  6
         else if ( invokerTest != null )
 1908  
         {
 1909  6
             String[] testRegexes = StringUtils.split( invokerTest, "," );
 1910  6
             List<String> includes = new ArrayList<String>( testRegexes.length );
 1911  6
             List<String> excludes = new ArrayList<String>();
 1912  
 
 1913  14
             for ( String regex : testRegexes )
 1914  
             {
 1915  
                 // user just use -Dinvoker.test=MWAR191,MNG111 to use a directory thats the end is not pom.xml
 1916  8
                 if ( regex.startsWith( "!" ) )
 1917  
                 {
 1918  0
                     excludes.add( regex.substring( 1 ) );
 1919  
                 }
 1920  
                 else
 1921  
                 {
 1922  8
                     includes.add( regex );
 1923  
                 }
 1924  
             }
 1925  
 
 1926  
             // it would be nice if we could figure out what types these are... but perhaps
 1927  
             // not necessary for the -Dinvoker.test=xxx t
 1928  6
             buildJobs = scanProjectsDirectory( includes, excludes, BuildJob.Type.DIRECT );
 1929  6
         }
 1930  
         else
 1931  
         {
 1932  0
             List<String> excludes =
 1933  
                 ( pomExcludes != null ) ? new ArrayList<String>( pomExcludes ) : new ArrayList<String>();
 1934  0
             if ( this.settingsFile != null )
 1935  
             {
 1936  0
                 String exclude = relativizePath( this.settingsFile, projectsDirectory.getCanonicalPath() );
 1937  0
                 if ( exclude != null )
 1938  
                 {
 1939  0
                     excludes.add( exclude.replace( '\\', '/' ) );
 1940  0
                     getLog().debug( "Automatically excluded " + exclude + " from project scanning" );
 1941  
                 }
 1942  
             }
 1943  
 
 1944  0
             BuildJob[] setupPoms = scanProjectsDirectory( setupIncludes, excludes, BuildJob.Type.SETUP );
 1945  0
             getLog().debug( "Setup projects: " + Arrays.asList( setupPoms ) );
 1946  
 
 1947  0
             BuildJob[] normalPoms = scanProjectsDirectory( pomIncludes, excludes, BuildJob.Type.NORMAL );
 1948  
 
 1949  0
             Map<String, BuildJob> uniquePoms = new LinkedHashMap<String, BuildJob>();
 1950  0
             for ( BuildJob setupPom : setupPoms )
 1951  
             {
 1952  0
                 uniquePoms.put( setupPom.getProject(), setupPom );
 1953  
             }
 1954  0
             for ( BuildJob normalPom : normalPoms )
 1955  
             {
 1956  0
                 if ( !uniquePoms.containsKey( normalPom.getProject() ) )
 1957  
                 {
 1958  0
                     uniquePoms.put( normalPom.getProject(), normalPom );
 1959  
                 }
 1960  
             }
 1961  
 
 1962  0
             buildJobs = uniquePoms.values().toArray( new BuildJob[uniquePoms.size()] );
 1963  
         }
 1964  
 
 1965  6
         relativizeProjectPaths( buildJobs );
 1966  
 
 1967  6
         return buildJobs;
 1968  
     }
 1969  
 
 1970  
     /**
 1971  
      * Scans the projects directory for projects to build. Both (POM) files and mere directories will be matched by the
 1972  
      * scanner patterns. If the patterns match a directory which contains a file named "pom.xml", the results will
 1973  
      * include the path to this file rather than the directory path in order to avoid duplicate invocations of the same
 1974  
      * project.
 1975  
      *
 1976  
      * @param includes The include patterns for the scanner, may be <code>null</code>.
 1977  
      * @param excludes The exclude patterns for the scanner, may be <code>null</code> to exclude nothing.
 1978  
      * @param type     The type to assign to the resulting build jobs, must not be <code>null</code>.
 1979  
      * @return The build jobs matching the patterns, never <code>null</code>.
 1980  
      * @throws java.io.IOException If the project directory could not be scanned.
 1981  
      */
 1982  
     private BuildJob[] scanProjectsDirectory( List<String> includes, List<String> excludes, String type )
 1983  
         throws IOException
 1984  
     {
 1985  6
         if ( !projectsDirectory.isDirectory() )
 1986  
         {
 1987  0
             return new BuildJob[0];
 1988  
         }
 1989  
 
 1990  6
         DirectoryScanner scanner = new DirectoryScanner();
 1991  6
         scanner.setBasedir( projectsDirectory.getCanonicalFile() );
 1992  6
         scanner.setFollowSymlinks( false );
 1993  6
         if ( includes != null )
 1994  
         {
 1995  6
             scanner.setIncludes( includes.toArray( new String[includes.size()] ) );
 1996  
         }
 1997  6
         if ( excludes != null )
 1998  
         {
 1999  6
             scanner.setExcludes( excludes.toArray( new String[excludes.size()] ) );
 2000  
         }
 2001  6
         scanner.addDefaultExcludes();
 2002  6
         scanner.scan();
 2003  
 
 2004  6
         Map<String, BuildJob> matches = new LinkedHashMap<String, BuildJob>();
 2005  
 
 2006  6
         for ( String includedFile : scanner.getIncludedFiles() )
 2007  
         {
 2008  0
             matches.put( includedFile, new BuildJob( includedFile, type ) );
 2009  
         }
 2010  
 
 2011  20
         for ( String includedDir : scanner.getIncludedDirectories() )
 2012  
         {
 2013  14
             String includedFile = includedDir + File.separatorChar + "pom.xml";
 2014  14
             if ( new File( scanner.getBasedir(), includedFile ).isFile() )
 2015  
             {
 2016  12
                 matches.put( includedFile, new BuildJob( includedFile, type ) );
 2017  
             }
 2018  
             else
 2019  
             {
 2020  2
                 matches.put( includedDir, new BuildJob( includedDir, type ) );
 2021  
             }
 2022  
         }
 2023  
 
 2024  6
         return matches.values().toArray( new BuildJob[matches.size()] );
 2025  
     }
 2026  
 
 2027  
     /**
 2028  
      * Relativizes the project paths of the specified build jobs against the directory specified by
 2029  
      * {@link #projectsDirectory} (if possible). If a project path does not denote a sub path of the projects directory,
 2030  
      * it is returned as is.
 2031  
      *
 2032  
      * @param buildJobs The build jobs whose project paths should be relativized, must not be <code>null</code> nor
 2033  
      *                  contain <code>null</code> elements.
 2034  
      * @throws java.io.IOException If any path could not be relativized.
 2035  
      */
 2036  
     private void relativizeProjectPaths( BuildJob[] buildJobs )
 2037  
         throws IOException
 2038  
     {
 2039  6
         String projectsDirPath = projectsDirectory.getCanonicalPath();
 2040  
 
 2041  20
         for ( BuildJob buildJob : buildJobs )
 2042  
         {
 2043  14
             String projectPath = buildJob.getProject();
 2044  
 
 2045  14
             File file = new File( projectPath );
 2046  
 
 2047  14
             if ( !file.isAbsolute() )
 2048  
             {
 2049  14
                 file = new File( projectsDirectory, projectPath );
 2050  
             }
 2051  
 
 2052  14
             String relativizedPath = relativizePath( file, projectsDirPath );
 2053  
 
 2054  14
             if ( relativizedPath == null )
 2055  
             {
 2056  0
                 relativizedPath = projectPath;
 2057  
             }
 2058  
 
 2059  14
             buildJob.setProject( relativizedPath );
 2060  
         }
 2061  6
     }
 2062  
 
 2063  
     /**
 2064  
      * Relativizes the specified path against the given base directory. Besides relativization, the returned path will
 2065  
      * also be normalized, e.g. directory references like ".." will be removed.
 2066  
      *
 2067  
      * @param path    The path to relativize, must not be <code>null</code>.
 2068  
      * @param basedir The (canonical path of the) base directory to relativize against, must not be <code>null</code>.
 2069  
      * @return The relative path in normal form or <code>null</code> if the input path does not denote a sub path of the
 2070  
      *         base directory.
 2071  
      * @throws java.io.IOException If the path could not be relativized.
 2072  
      */
 2073  
     private String relativizePath( File path, String basedir )
 2074  
         throws IOException
 2075  
     {
 2076  14
         String relativizedPath = path.getCanonicalPath();
 2077  
 
 2078  14
         if ( relativizedPath.startsWith( basedir ) )
 2079  
         {
 2080  14
             relativizedPath = relativizedPath.substring( basedir.length() );
 2081  14
             if ( relativizedPath.startsWith( File.separator ) )
 2082  
             {
 2083  14
                 relativizedPath = relativizedPath.substring( File.separator.length() );
 2084  
             }
 2085  
 
 2086  14
             return relativizedPath;
 2087  
         }
 2088  
         else
 2089  
         {
 2090  0
             return null;
 2091  
         }
 2092  
     }
 2093  
 
 2094  
     /**
 2095  
      * Returns the map-based value source used to interpolate POMs and other stuff.
 2096  
      *
 2097  
      * @return The map-based value source for interpolation, never <code>null</code>.
 2098  
      */
 2099  
     private Map<String, Object> getInterpolationValueSource()
 2100  
     {
 2101  12
         Map<String, Object> props = new HashMap<String, Object>();
 2102  12
         if ( interpolationsProperties != null )
 2103  
         {
 2104  6
             props.putAll( (Map) interpolationsProperties );
 2105  
         }
 2106  12
         if ( filterProperties != null )
 2107  
         {
 2108  0
             props.putAll( filterProperties );
 2109  
         }
 2110  12
         props.put( "basedir", this.project.getBasedir().getAbsolutePath() );
 2111  12
         props.put( "baseurl", toUrl( this.project.getBasedir().getAbsolutePath() ) );
 2112  12
         if ( settings.getLocalRepository() != null )
 2113  
         {
 2114  0
             props.put( "localRepository", settings.getLocalRepository() );
 2115  0
             props.put( "localRepositoryUrl", toUrl( settings.getLocalRepository() ) );
 2116  
         }
 2117  12
         return new CompositeMap( this.project, props );
 2118  
     }
 2119  
 
 2120  
     /**
 2121  
      * Converts the specified filesystem path to a URL. The resulting URL has no trailing slash regardless whether the
 2122  
      * path denotes a file or a directory.
 2123  
      *
 2124  
      * @param filename The filesystem path to convert, must not be <code>null</code>.
 2125  
      * @return The <code>file:</code> URL for the specified path, never <code>null</code>.
 2126  
      */
 2127  
     private static String toUrl( String filename )
 2128  
     {
 2129  
         /*
 2130  
          * NOTE: Maven fails to properly handle percent-encoded "file:" URLs (WAGON-111) so don't use File.toURI() here
 2131  
          * as-is but use the decoded path component in the URL.
 2132  
          */
 2133  12
         String url = "file://" + new File( filename ).toURI().getPath();
 2134  12
         if ( url.endsWith( "/" ) )
 2135  
         {
 2136  12
             url = url.substring( 0, url.length() - 1 );
 2137  
         }
 2138  12
         return url;
 2139  
     }
 2140  
 
 2141  
     /**
 2142  
      * Gets goal/profile names for the specified project, either directly from the plugin configuration or from an
 2143  
      * external token file.
 2144  
      *
 2145  
      * @param basedir       The base directory of the test project, must not be <code>null</code>.
 2146  
      * @param filename      The (simple) name of an optional file in the project base directory from which to read
 2147  
      *                      goals/profiles, may be <code>null</code>.
 2148  
      * @param defaultTokens The list of tokens to return in case the specified token file does not exist, may be
 2149  
      *                      <code>null</code>.
 2150  
      * @return The list of goal/profile names, may be empty but never <code>null</code>.
 2151  
      * @throws java.io.IOException If the token file exists but could not be parsed.
 2152  
      */
 2153  
     private List<String> getTokens( File basedir, String filename, List<String> defaultTokens )
 2154  
         throws IOException
 2155  
     {
 2156  16
         List<String> tokens = ( defaultTokens != null ) ? defaultTokens : new ArrayList<String>();
 2157  
 
 2158  16
         if ( StringUtils.isNotEmpty( filename ) )
 2159  
         {
 2160  16
             File tokenFile = new File( basedir, filename );
 2161  
 
 2162  16
             if ( tokenFile.exists() )
 2163  
             {
 2164  8
                 tokens = readTokens( tokenFile );
 2165  
             }
 2166  
         }
 2167  
 
 2168  16
         return tokens;
 2169  
     }
 2170  
 
 2171  
     /**
 2172  
      * Reads the tokens from the specified file. Tokens are separated either by line terminators or commas. During
 2173  
      * parsing, the file contents will be interpolated.
 2174  
      *
 2175  
      * @param tokenFile The file to read the tokens from, must not be <code>null</code>.
 2176  
      * @return The list of tokens, may be empty but never <code>null</code>.
 2177  
      * @throws java.io.IOException If the token file could not be read.
 2178  
      */
 2179  
     private List<String> readTokens( final File tokenFile )
 2180  
         throws IOException
 2181  
     {
 2182  8
         List<String> result = new ArrayList<String>();
 2183  
 
 2184  8
         BufferedReader reader = null;
 2185  
         try
 2186  
         {
 2187  8
             Map<String, Object> composite = getInterpolationValueSource();
 2188  8
             reader = new BufferedReader( new InterpolationFilterReader( newReader( tokenFile ), composite ) );
 2189  
 
 2190  8
             String line = null;
 2191  18
             while ( ( line = reader.readLine() ) != null )
 2192  
             {
 2193  10
                 result.addAll( collectListFromCSV( line ) );
 2194  
             }
 2195  
         }
 2196  
         finally
 2197  
         {
 2198  8
             IOUtil.close( reader );
 2199  8
         }
 2200  
 
 2201  8
         return result;
 2202  
     }
 2203  
 
 2204  
     /**
 2205  
      * Gets a list of comma separated tokens from the specified line.
 2206  
      *
 2207  
      * @param csv The line with comma separated tokens, may be <code>null</code>.
 2208  
      * @return The list of tokens from the line, may be empty but never <code>null</code>.
 2209  
      */
 2210  
     private List<String> collectListFromCSV( final String csv )
 2211  
     {
 2212  10
         final List<String> result = new ArrayList<String>();
 2213  
 
 2214  10
         if ( ( csv != null ) && ( csv.trim().length() > 0 ) )
 2215  
         {
 2216  10
             final StringTokenizer st = new StringTokenizer( csv, "," );
 2217  
 
 2218  24
             while ( st.hasMoreTokens() )
 2219  
             {
 2220  14
                 result.add( st.nextToken().trim() );
 2221  
             }
 2222  
         }
 2223  
 
 2224  10
         return result;
 2225  
     }
 2226  
 
 2227  
     /**
 2228  
      * Interpolates the specified POM/settings file to a temporary file. The destination file may be same as the input
 2229  
      * file, i.e. interpolation can be performed in-place.
 2230  
      *
 2231  
      * @param originalFile     The XML file to interpolate, must not be <code>null</code>.
 2232  
      * @param interpolatedFile The target file to write the interpolated contents of the original file to, must not be
 2233  
      *                         <code>null</code>.
 2234  
      * @throws org.apache.maven.plugin.MojoExecutionException
 2235  
      *          If the target file could not be created.
 2236  
      */
 2237  
     void buildInterpolatedFile( File originalFile, File interpolatedFile )
 2238  
         throws MojoExecutionException
 2239  
     {
 2240  4
         getLog().debug( "Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath() );
 2241  
 
 2242  
         try
 2243  
         {
 2244  
             String xml;
 2245  
 
 2246  4
             Reader reader = null;
 2247  
             try
 2248  
             {
 2249  
                 // interpolation with token @...@
 2250  4
                 Map<String, Object> composite = getInterpolationValueSource();
 2251  4
                 reader = ReaderFactory.newXmlReader( originalFile );
 2252  4
                 reader = new InterpolationFilterReader( reader, composite, "@", "@" );
 2253  4
                 xml = IOUtil.toString( reader );
 2254  
             }
 2255  
             finally
 2256  
             {
 2257  4
                 IOUtil.close( reader );
 2258  4
             }
 2259  
 
 2260  4
             Writer writer = null;
 2261  
             try
 2262  
             {
 2263  4
                 interpolatedFile.getParentFile().mkdirs();
 2264  4
                 writer = WriterFactory.newXmlWriter( interpolatedFile );
 2265  4
                 writer.write( xml );
 2266  4
                 writer.flush();
 2267  
             }
 2268  
             finally
 2269  
             {
 2270  4
                 IOUtil.close( writer );
 2271  4
             }
 2272  
         }
 2273  0
         catch ( IOException e )
 2274  
         {
 2275  0
             throw new MojoExecutionException( "Failed to interpolate file " + originalFile.getPath(), e );
 2276  4
         }
 2277  4
     }
 2278  
 
 2279  
     /**
 2280  
      * Gets the (interpolated) invoker properties for an integration test.
 2281  
      *
 2282  
      * @param projectDirectory The base directory of the IT project, must not be <code>null</code>.
 2283  
      * @return The invoker properties, may be empty but never <code>null</code>.
 2284  
      * @throws org.apache.maven.plugin.MojoExecutionException
 2285  
      *          If an I/O error occurred during reading the properties.
 2286  
      */
 2287  
     private InvokerProperties getInvokerProperties( final File projectDirectory )
 2288  
         throws MojoExecutionException
 2289  
     {
 2290  0
         Properties props = new Properties();
 2291  0
         if ( invokerPropertiesFile != null )
 2292  
         {
 2293  0
             File propertiesFile = new File( projectDirectory, invokerPropertiesFile );
 2294  0
             if ( propertiesFile.isFile() )
 2295  
             {
 2296  0
                 InputStream in = null;
 2297  
                 try
 2298  
                 {
 2299  0
                     in = new FileInputStream( propertiesFile );
 2300  0
                     props.load( in );
 2301  
                 }
 2302  0
                 catch ( IOException e )
 2303  
                 {
 2304  0
                     throw new MojoExecutionException( "Failed to read invoker properties: " + propertiesFile, e );
 2305  
                 }
 2306  
                 finally
 2307  
                 {
 2308  0
                     IOUtil.close( in );
 2309  0
                 }
 2310  
             }
 2311  
 
 2312  0
             Interpolator interpolator = new RegexBasedInterpolator();
 2313  0
             interpolator.addValueSource( new MapBasedValueSource( getInterpolationValueSource() ) );
 2314  0
             for ( String key : (Set<String>) ( (Map) props ).keySet() )
 2315  
             {
 2316  0
                 String value = props.getProperty( key );
 2317  
                 try
 2318  
                 {
 2319  0
                     value = interpolator.interpolate( value, "" );
 2320  
                 }
 2321  0
                 catch ( InterpolationException e )
 2322  
                 {
 2323  0
                     throw new MojoExecutionException( "Failed to interpolate invoker properties: " + propertiesFile,
 2324  
                                                       e );
 2325  0
                 }
 2326  0
                 props.setProperty( key, value );
 2327  0
             }
 2328  
         }
 2329  0
         return new InvokerProperties( props );
 2330  
     }
 2331  
 
 2332  
     protected boolean isParallelRun()
 2333  
     {
 2334  0
         return parallelThreads > 1;
 2335  
     }
 2336  
 
 2337  
 }