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