[MSHARED-1007] Add MavenHome and MavenExecutable options to InvocationRequest
diff --git a/src/main/java/org/apache/maven/shared/invoker/DefaultInvocationRequest.java b/src/main/java/org/apache/maven/shared/invoker/DefaultInvocationRequest.java
index 93be2ba..0f6d516 100644
--- a/src/main/java/org/apache/maven/shared/invoker/DefaultInvocationRequest.java
+++ b/src/main/java/org/apache/maven/shared/invoker/DefaultInvocationRequest.java
@@ -107,6 +107,10 @@
 
     private boolean quiet;
 
+    private File mavenHome;
+
+    private File mavenExecutable;
+
     /**
      * <p>getBaseDirectory.</p>
      *
@@ -724,4 +728,42 @@
         this.quiet = quiet;
         return this;
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public File getMavenHome()
+    {
+        return mavenHome;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public InvocationRequest setMavenHome( File mavenHome )
+    {
+        this.mavenHome = mavenHome;
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public File getMavenExecutable()
+    {
+        return mavenExecutable;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public InvocationRequest setMavenExecutable( File mavenExecutable )
+    {
+        this.mavenExecutable = mavenExecutable;
+        return this;
+    }
 }
diff --git a/src/main/java/org/apache/maven/shared/invoker/InvocationRequest.java b/src/main/java/org/apache/maven/shared/invoker/InvocationRequest.java
index 91c98d3..fd603a2 100644
--- a/src/main/java/org/apache/maven/shared/invoker/InvocationRequest.java
+++ b/src/main/java/org/apache/maven/shared/invoker/InvocationRequest.java
@@ -763,4 +763,45 @@
      */
     void setTimeoutInSeconds( int timeoutInSeconds );
 
+    /**
+     * Gets the path to the base directory of the Maven installation used to invoke Maven.
+     *
+     * @return The path to the base directory of the Maven installation or <code>null</code> if using the default
+     *         Maven installation.
+     *
+     * @since 3.1.1
+     */
+    File getMavenHome();
+
+    /**
+     * Sets the path to the base directory of the Maven installation used to invoke Maven. This parameter may be left
+     * unspecified to use the default Maven installation which will be discovered by evaluating the system property
+     * <code>maven.home</code> and the environment variable <code>M2_HOME</code>.
+     *
+     * @param mavenHome The path to the base directory of the Maven installation, may be <code>null</code> to use the
+     *            default Maven installation.
+     * @return This invocation request
+     *
+     * @since 3.1.1
+     */
+    InvocationRequest setMavenHome( File mavenHome );
+
+    /**
+     * Get the customized File of the Maven executable.
+     *
+     * @return the custom Maven executable, otherwise {@code null}
+     *
+     * @since 3.1.1
+     */
+    File getMavenExecutable();
+
+    /**
+     * {@code mavenExecutable} can either be a file relative to ${maven.home}/bin/ or an absolute file.
+     *
+     * @param mavenExecutable the executable
+     * @return This invocation request
+     *
+     * @since 3.1.1
+     */
+    InvocationRequest setMavenExecutable( File mavenExecutable );
 }
diff --git a/src/main/java/org/apache/maven/shared/invoker/MavenCommandLineBuilder.java b/src/main/java/org/apache/maven/shared/invoker/MavenCommandLineBuilder.java
index 205ed05..5767a4b 100644
--- a/src/main/java/org/apache/maven/shared/invoker/MavenCommandLineBuilder.java
+++ b/src/main/java/org/apache/maven/shared/invoker/MavenCommandLineBuilder.java
@@ -64,26 +64,22 @@
     public Commandline build( InvocationRequest request )
         throws CommandLineConfigurationException
     {
+
+        setupMavenHome( request );
+        checkRequiredState();
+
         try
         {
-            checkRequiredState();
+           setupMavenExecutable( request );
         }
         catch ( IOException e )
         {
             throw new CommandLineConfigurationException( e.getMessage(), e );
         }
-        File mvn;
-        try
-        {
-            mvn = findMavenExecutable();
-        }
-        catch ( IOException e )
-        {
-            throw new CommandLineConfigurationException( e.getMessage(), e );
-        }
+
         Commandline cli = new Commandline();
 
-        cli.setExecutable( mvn.getAbsolutePath() );
+        cli.setExecutable( mavenExecutable.getAbsolutePath() );
 
         // handling for OS-level envars
         setShellEnvironment( request, cli );
@@ -119,28 +115,13 @@
 
     /**
      * <p>checkRequiredState.</p>
-     *
-     * @throws java.io.IOException if any.
      */
     protected void checkRequiredState()
-        throws IOException
     {
         if ( logger == null )
         {
             throw new IllegalStateException( "A logger instance is required." );
         }
-
-        if ( ( mavenHome == null ) && ( System.getProperty( "maven.home" ) == null ) )
-        // can be restored with 1.5
-        // && ( System.getenv( "M2_HOME" ) != null ) )
-        {
-            if ( !getSystemEnvVars().containsKey( "M2_HOME" ) )
-            {
-                throw new IllegalStateException( "Maven application directory was not "
-                    + "specified, and ${maven.home} is not provided in the system "
-                    + "properties. Specify at least one of these." );
-            }
-        }
     }
 
     /**
@@ -221,10 +202,8 @@
      *
      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
-     * @throws org.apache.maven.shared.invoker.CommandLineConfigurationException if any.
      */
     protected void setShellEnvironment( InvocationRequest request, Commandline cli )
-        throws CommandLineConfigurationException
     {
         if ( request.isShellEnvironmentInherited() )
         {
@@ -600,46 +579,61 @@
 
     }
 
-    /**
-     * <p>findMavenExecutable.</p>
-     *
-     * @return a {@link java.io.File} object.
-     * @throws org.apache.maven.shared.invoker.CommandLineConfigurationException if any.
-     * @throws java.io.IOException if any.
-     */
-    protected File findMavenExecutable()
-        throws CommandLineConfigurationException, IOException
+    void setupMavenHome( InvocationRequest request )
     {
+        if ( request.getMavenHome() != null )
+        {
+            mavenHome = request.getMavenHome();
+        }
+
         if ( mavenHome == null )
         {
             String mavenHomeProperty = System.getProperty( "maven.home" );
+            if ( mavenHomeProperty == null && getSystemEnvVars().getProperty( "M2_HOME" ) != null )
+            {
+                mavenHomeProperty = getSystemEnvVars().getProperty( "M2_HOME" );
+            }
+
             if ( mavenHomeProperty != null )
             {
                 mavenHome = new File( mavenHomeProperty );
-                if ( !mavenHome.isDirectory() )
-                {
-                    File binDir = mavenHome.getParentFile();
-                    if ( binDir != null && "bin".equals( binDir.getName() ) )
-                    {
-                        // ah, they specified the mvn
-                        // executable instead...
-                        mavenHome = binDir.getParentFile();
-                    }
-                    else
-                    {
-                        throw new IllegalStateException( "${maven.home} is not specified as a directory: '"
-                            + mavenHomeProperty + "'." );
-                    }
-                }
-            }
-
-            if ( ( mavenHome == null ) && ( getSystemEnvVars().getProperty( "M2_HOME" ) != null ) )
-            {
-                mavenHome = new File( getSystemEnvVars().getProperty( "M2_HOME" ) );
             }
         }
 
-        logger.debug( "Using ${maven.home} of: '" + mavenHome + "'." );
+        if ( mavenHome != null && !mavenHome.isDirectory() )
+        {
+            File binDir = mavenHome.getParentFile();
+            if ( binDir != null && "bin".equals( binDir.getName() ) )
+            {
+                // ah, they specified the mvn
+                // executable instead...
+                mavenHome = binDir.getParentFile();
+            }
+        }
+
+        if ( mavenHome != null && !mavenHome.isDirectory() )
+        {
+            throw new IllegalStateException( "maven home is not specified as a directory: '" + mavenHome + "'." );
+        }
+
+        logger.debug( "Using maven.home of: '" + mavenHome + "'." );
+    }
+
+
+    /**
+     * <p>setupMavenExecutable.</p>
+     *
+     * @param request a Invoker request
+     * @throws org.apache.maven.shared.invoker.CommandLineConfigurationException if any.
+     * @throws java.io.IOException if any.
+     */
+    protected void setupMavenExecutable( InvocationRequest request )
+        throws CommandLineConfigurationException, IOException
+    {
+        if ( request.getMavenExecutable() != null )
+        {
+            mavenExecutable = request.getMavenExecutable();
+        }
 
         if ( mavenExecutable == null || !mavenExecutable.isAbsolute() )
         {
@@ -680,8 +674,6 @@
                 throw new CommandLineConfigurationException( "Maven executable not found at: " + mavenExecutable );
             }
         }
-
-        return mavenExecutable;
     }
 
     private Properties getSystemEnvVars()
diff --git a/src/test/java/org/apache/maven/shared/invoker/MavenCommandLineBuilderTest.java b/src/test/java/org/apache/maven/shared/invoker/MavenCommandLineBuilderTest.java
index 32fcdc6..f7b2b0c 100644
--- a/src/test/java/org/apache/maven/shared/invoker/MavenCommandLineBuilderTest.java
+++ b/src/test/java/org/apache/maven/shared/invoker/MavenCommandLineBuilderTest.java
@@ -45,7 +45,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
 
 public class MavenCommandLineBuilderTest
 {
@@ -228,7 +228,7 @@
     }
 
     @Test
-    public void testShouldFailIfLoggerSetToNull() throws Exception
+    public void testShouldFailIfLoggerSetToNull()
     {
         mclb.setLogger( null );
 
@@ -259,10 +259,32 @@
         }
 
         mclb.setMavenHome( dummyMavenHomeBin.getParentFile() );
+        mclb.setupMavenExecutable( newRequest() );
 
-        File mavenExe = mclb.findMavenExecutable();
+        assertEquals( check.getCanonicalPath(), mclb.getMavenExecutable().getCanonicalPath() );
+    }
 
-        assertEquals( check.getCanonicalPath(), mavenExe.getCanonicalPath() );
+    @Test
+    public void testShouldFindDummyMavenExecutableWithMavenHomeFromRequest()
+        throws Exception
+    {
+        File dummyMavenHomeBin = temporaryFolder.newFolder( "invoker-tests", "dummy-maven-home", "bin" );
+
+        File check;
+        if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+        {
+            check = createDummyFile( dummyMavenHomeBin, "mvn.bat" );
+        }
+        else
+        {
+            check = createDummyFile( dummyMavenHomeBin, "mvn" );
+        }
+
+        // default value should be not used
+        mclb.setMavenHome( new File( "not-present-1234" ) );
+        mclb.build( newRequest().setMavenHome( dummyMavenHomeBin.getParentFile() ) );
+
+        assertEquals( check.getCanonicalPath(), mclb.getMavenExecutable().getCanonicalPath() );
     }
 
     @Test
@@ -869,16 +891,49 @@
     }
 
     @Test
-    public void testMvnCommand()
+    public void testMvnExecutableFromInvoker()
         throws Exception
     {
-        assumeThat( "Test only works when called with surefire", System.getProperty( "maven.home" ),
-                    is( notNullValue() ) );
+        assumeTrue( "Test only works when maven home can be assigned",
+            System.getProperty( "maven.home" ) != null || System.getenv( "M2_HOME" ) != null );
+
         File mavenExecutable = new File( "mvnDebug" );
+
         mclb.setMavenExecutable( mavenExecutable );
-        File executable = mclb.findMavenExecutable();
-        assertTrue( "Expected executable to exist", executable.exists() );
-        assertTrue( "Expected executable to be absolute", executable.isAbsolute() );
+        mclb.build( newRequest() );
+
+        assertTrue( "Expected executable to exist", mclb.getMavenExecutable().exists() );
+        assertTrue( "Expected executable to be absolute", mclb.getMavenExecutable().isAbsolute() );
+        assertTrue( "Expected mvnDebug as command mvnDebug", mclb.getMavenExecutable().getName().contains( "mvnDebug" ) );
+    }
+
+    @Test
+    public void testMvnExecutableFormRequest()
+        throws Exception
+    {
+        assumeTrue( "Test only works when maven home can be assigned",
+            System.getProperty( "maven.home" ) != null || System.getenv( "M2_HOME" ) != null );
+
+        File mavenExecutable = new File( "mvnDebug" );
+
+        mclb.build( newRequest().setMavenExecutable( mavenExecutable ) );
+
+        assertTrue( "Expected executable to exist", mclb.getMavenExecutable().exists() );
+        assertTrue( "Expected executable to be absolute", mclb.getMavenExecutable().isAbsolute() );
+        assertTrue( "Expected mvnDebug as command", mclb.getMavenExecutable().getName().contains( "mvnDebug" ) );
+    }
+
+    @Test
+    public void testDefaultMavenCommand()
+        throws Exception
+    {
+        assumeTrue( "Test only works when maven home can be assigned",
+            System.getProperty( "maven.home" ) != null || System.getenv( "M2_HOME" ) != null );
+
+        mclb.build( newRequest() );
+
+        assertTrue( "Expected executable to exist", mclb.getMavenExecutable().exists() );
+        assertTrue( "Expected executable to be absolute", mclb.getMavenExecutable().isAbsolute() );
     }
 
     @Test