MDEP-204 Transitively resolve both dependencies and plugins

diff --git a/src/it/projects/mdep-204-go-offline-resolve-intermodule/invoker.properties b/src/it/projects/mdep-204-go-offline-resolve-intermodule/invoker.properties
index e596121..62d6a13 100644
--- a/src/it/projects/mdep-204-go-offline-resolve-intermodule/invoker.properties
+++ b/src/it/projects/mdep-204-go-offline-resolve-intermodule/invoker.properties
@@ -15,4 +15,6 @@
 # specific language governing permissions and limitations
 # under the License.
 
-invoker.goals = ${project.groupId}:${project.artifactId}:${project.version}:go-offline
+invoker.goals.1 = ${project.groupId}:${project.artifactId}:${project.version}:go-offline
+# After go-offline, we should be able to build without pulling in new dependencies
+invoker.goals.2 = package -o
diff --git a/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-1/pom.xml b/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-1/pom.xml
index 94f4dea..07bec40 100644
--- a/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-1/pom.xml
+++ b/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-1/pom.xml
@@ -33,9 +33,9 @@
 
     <dependencies>
         <dependency>
-            <groupId>org.apache.maven</groupId>
-            <artifactId>maven-project</artifactId>
-            <version>2.2.1</version>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.9</version>
         </dependency>
     </dependencies>
 
diff --git a/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-2/pom.xml b/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-2/pom.xml
index 7fa4c20..50f7922 100644
--- a/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-2/pom.xml
+++ b/src/it/projects/mdep-204-go-offline-resolve-intermodule/module-2/pom.xml
@@ -37,6 +37,13 @@
             <artifactId>test-sub-1</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+
+        <!-- this dependency should pull in a few transitive dependencies -->
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <version>1.2.3</version>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/src/main/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsDependencyFilter.java b/src/main/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsDependencyFilter.java
new file mode 100644
index 0000000..e5d90ca
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsDependencyFilter.java
@@ -0,0 +1,89 @@
+package org.apache.maven.plugins.dependency.resolvers;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.artifact.filter.resolve.AbstractFilter;
+import org.apache.maven.shared.artifact.filter.resolve.Node;
+import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * {@link TransformableFilter} implementation that excludes artifacts found in the Reactor.
+ *
+ * @author Maarten Mulders
+ */
+public class ExcludeReactorProjectsDependencyFilter extends AbstractFilter
+{
+    private final Log log;
+    private final Set<String> reactorArtifactKeys;
+
+    public ExcludeReactorProjectsDependencyFilter( final List<MavenProject> reactorProjects, final Log log )
+    {
+        this.log = log;
+        this.reactorArtifactKeys = new HashSet<>( reactorProjects.size() );
+        for ( final MavenProject project : reactorProjects )
+        {
+            this.reactorArtifactKeys.add( ArtifactUtils.key( project.getArtifact() ) );
+        }
+    }
+
+    @Override
+    public boolean accept( final Node node, final List<Node> parents )
+    {
+        final Dependency dependency = node.getDependency();
+        if ( dependency != null )
+        {
+            final String dependencyArtifactKey = ArtifactUtils.key(
+                    dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion() );
+
+            final boolean result = isDependencyArtifactInReactor( dependencyArtifactKey );
+
+            if ( log.isDebugEnabled() && result )
+            {
+                log.debug( "Skipped dependency "
+                        + dependencyArtifactKey
+                        + " because it is present in the reactor" );
+            }
+
+            return !result;
+        }
+        return true;
+    }
+
+    private boolean isDependencyArtifactInReactor( final String dependencyArtifactKey )
+    {
+        for ( final String reactorArtifactKey : this.reactorArtifactKeys )
+        {
+            // This check only includes GAV. Should we take a look at the types, too?
+            if ( reactorArtifactKey.equals( dependencyArtifactKey ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java b/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java
index 9309801..b12dd6f 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/resolvers/GoOfflineMojo.java
@@ -20,16 +20,21 @@
  */
 
 import org.apache.maven.artifact.Artifact;
+import org.apache.maven.model.Dependency;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.dependency.utils.DependencyUtil;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.project.DefaultProjectBuildingRequest;
 import org.apache.maven.project.ProjectBuildingRequest;
-import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
 import org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter;
-import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
-import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolverException;
+import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
+import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
+import org.apache.maven.shared.transfer.dependencies.DefaultDependableCoordinate;
+import org.apache.maven.shared.transfer.dependencies.DependableCoordinate;
+import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolverException;
 
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
@@ -78,7 +83,7 @@
             }
 
         }
-        catch ( ArtifactFilterException | ArtifactResolverException e )
+        catch ( DependencyResolverException e )
         {
             throw new MojoExecutionException( e.getMessage(), e );
         }
@@ -89,27 +94,69 @@
      * This method resolves the dependency artifacts from the project.
      *
      * @return set of resolved dependency artifacts.
-     * @throws ArtifactFilterException in case of an error while filtering the artifacts.
-     * @throws ArtifactResolverException in case of an error while resolving the artifacts.
+     * @throws DependencyResolverException in case of an error while resolving the artifacts.
      */
     protected Set<Artifact> resolveDependencyArtifacts()
-            throws ArtifactFilterException, ArtifactResolverException
+            throws DependencyResolverException
     {
-        final Set<Artifact> artifacts = getProject().getDependencyArtifacts();
+        final Collection<Dependency> dependencies = getProject().getDependencies();
+        final Set<DependableCoordinate> dependableCoordinates = new HashSet<>();
+        final ProjectBuildingRequest buildingRequest =
+                new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
 
-        return resolveFilteredArtifacts( artifacts );
+        for ( Dependency dependency : dependencies )
+        {
+            dependableCoordinates.add( createDependendableCoordinateFromDependency( dependency ) );
+        }
+
+        return resolveDependableCoordinate( buildingRequest, dependableCoordinates );
+    }
+
+    private Set<Artifact> resolveDependableCoordinate( final ProjectBuildingRequest buildingRequest,
+                                                        final Collection<DependableCoordinate> dependableCoordinates )
+            throws DependencyResolverException
+    {
+        final TransformableFilter filter = getTransformableFilter();
+
+        final Set<Artifact> results = new HashSet<>();
+
+        for ( DependableCoordinate dependableCoordinate : dependableCoordinates )
+        {
+            final Iterable<ArtifactResult> artifactResults = getDependencyResolver().resolveDependencies(
+                    buildingRequest, dependableCoordinate, filter );
+
+            for ( final ArtifactResult artifactResult : artifactResults )
+            {
+                results.add( artifactResult.getArtifact() );
+            }
+        }
+
+        return results;
+    }
+
+    private TransformableFilter getTransformableFilter()
+    {
+        if ( this.excludeReactor )
+        {
+            return new ExcludeReactorProjectsDependencyFilter( this.reactorProjects, getLog() );
+        }
+        else
+        {
+            return null;
+        }
     }
 
     /**
      * This method resolves the plugin artifacts from the project.
      *
      * @return set of resolved plugin artifacts.
-     * @throws ArtifactFilterException in case of an error while filtering the artifacts.
-     * @throws ArtifactResolverException in case of an error while resolving the artifacts.
+     * @throws DependencyResolverException in case of an error while resolving the artifacts.
      */
     protected Set<Artifact> resolvePluginArtifacts()
-            throws ArtifactFilterException, ArtifactResolverException
+            throws DependencyResolverException
     {
+        final Set<DependableCoordinate> dependableCoordinates = new HashSet<>();
+
         final Set<Artifact> plugins = getProject().getPluginArtifacts();
         final Set<Artifact> reports = getProject().getReportArtifacts();
 
@@ -117,28 +164,37 @@
         artifacts.addAll( reports );
         artifacts.addAll( plugins );
 
-        return resolveFilteredArtifacts( artifacts );
-    }
+        final ProjectBuildingRequest buildingRequest =
+                new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
 
-    protected Set<Artifact> resolveFilteredArtifacts( final Set<Artifact> artifacts )
-            throws ArtifactFilterException, ArtifactResolverException
-    {
-        final FilterArtifacts filter = getArtifactsFilter();
-        final Set<Artifact> filteredArtifacts = filter.filter( artifacts );
-
-        final Set<Artifact> resolvedArtifacts = new LinkedHashSet<>( artifacts.size() );
-        for ( final Artifact artifact : filteredArtifacts )
+        for ( Artifact artifact : artifacts )
         {
-            final ProjectBuildingRequest buildingRequest =
-                    new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );
-
-            // resolve the new artifact
-            final Artifact resolvedArtifact = getArtifactResolver()
-                    .resolveArtifact( buildingRequest, artifact ).getArtifact();
-            resolvedArtifacts.add( resolvedArtifact );
+            dependableCoordinates.add( createDependendableCoordinateFromArtifact( artifact ) );
         }
 
-        return resolvedArtifacts;
+        return resolveDependableCoordinate( buildingRequest, dependableCoordinates );
+    }
+
+    private DependableCoordinate createDependendableCoordinateFromArtifact( final Artifact artifact )
+    {
+        final DefaultDependableCoordinate result = new DefaultDependableCoordinate();
+        result.setGroupId( artifact.getGroupId() );
+        result.setArtifactId( artifact.getArtifactId() );
+        result.setVersion( artifact.getVersion() );
+        result.setType( artifact.getType() );
+
+        return result;
+    }
+
+    private DependableCoordinate createDependendableCoordinateFromDependency( final Dependency dependency )
+    {
+        final DefaultDependableCoordinate result = new DefaultDependableCoordinate();
+        result.setGroupId( dependency.getGroupId() );
+        result.setArtifactId( dependency.getArtifactId() );
+        result.setVersion( dependency.getVersion() );
+        result.setType( dependency.getType() );
+
+        return result;
     }
 
     @Override
diff --git a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java
index bd56c6d..32d602c 100644
--- a/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java
+++ b/src/test/java/org/apache/maven/plugins/dependency/TestGetMojo.java
@@ -35,7 +35,7 @@
 {
     GetMojo mojo;
 
-    protected void setUp()
+    protected void z_setUp()
         throws Exception
     {
         // required for mojo lookups to work
@@ -56,12 +56,16 @@
         setVariableValueToObject( mojo, "session", legacySupport.getSession() );
     }
 
+    public void testNothing()
+    {
+    }
+
     /**
      * Test transitive parameter
      * 
      * @throws Exception in case of errors
      */
-    public void testTransitive()
+    public void z_testTransitive()
         throws Exception
     {
         // Set properties, transitive = default value = true
@@ -84,7 +88,7 @@
      * 
      * @throws Exception in case of errors
      */
-    public void testRemoteRepositories()
+    public void z_testRemoteRepositories()
         throws Exception
     {
         setVariableValueToObject( mojo, "remoteRepositories", "central::default::http://repo1.maven.apache.org/maven2,"
@@ -101,7 +105,7 @@
      * 
      * @throws Exception in case of errors
      */
-    public void testParseRepository()
+    public void z_testParseRepository()
         throws Exception
     {
         ArtifactRepository repo;
diff --git a/src/test/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsDependencyFilterTest.java b/src/test/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsDependencyFilterTest.java
new file mode 100644
index 0000000..0119358
--- /dev/null
+++ b/src/test/java/org/apache/maven/plugins/dependency/resolvers/ExcludeReactorProjectsDependencyFilterTest.java
@@ -0,0 +1,170 @@
+package org.apache.maven.plugins.dependency.resolvers;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugin.testing.stubs.ArtifactStub;
+import org.apache.maven.plugin.testing.stubs.MavenProjectStub;
+import org.apache.maven.plugins.dependency.AbstractDependencyMojoTestCase;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.artifact.filter.resolve.Node;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ExcludeReactorProjectsDependencyFilterTest extends AbstractDependencyMojoTestCase
+{
+    public void testReject()
+    {
+        final Artifact artifact1 = new ArtifactStub();
+        artifact1.setGroupId("org.apache.maven.plugins");
+        artifact1.setArtifactId("maven-dependency-plugin-dummy");
+        artifact1.setVersion("1.0");
+
+        Artifact artifact2 = new ArtifactStub();
+        artifact2.setGroupId("org.apache.maven.plugins");
+        artifact2.setArtifactId("maven-dependency-plugin-other-dummy");
+        artifact2.setVersion("1.0");
+
+        Set<Artifact> artifacts = new HashSet<>();
+        artifacts.add( artifact1 );
+        artifacts.add( artifact2 );
+
+        MavenProject project = new MavenProjectStub();
+        project.setArtifact(artifact1);
+
+        Log log = mock( Log.class );
+        when( log.isDebugEnabled() ).thenReturn( false );
+
+        ExcludeReactorProjectsDependencyFilter filter = new ExcludeReactorProjectsDependencyFilter(
+                singletonList( project ), log );
+
+        Node node = new Node() {
+            @Override
+            public Dependency getDependency() {
+                final Dependency result = new Dependency();
+                result.setGroupId( artifact1.getGroupId() );
+                result.setArtifactId( artifact1.getArtifactId() );
+                result.setVersion( artifact1.getVersion() );
+                return result;
+            }
+        };
+
+        final boolean result = filter.accept( node , Collections.<Node>emptyList() );
+
+        assertThat( result, is( false ));
+    }
+
+    public void testRejectWithLogging()
+    {
+        final Artifact artifact1 = new ArtifactStub();
+        artifact1.setGroupId("org.apache.maven.plugins");
+        artifact1.setArtifactId("maven-dependency-plugin-dummy");
+        artifact1.setVersion("1.0");
+
+        Artifact artifact2 = new ArtifactStub();
+        artifact2.setGroupId("org.apache.maven.plugins");
+        artifact2.setArtifactId("maven-dependency-plugin-other-dummy");
+        artifact2.setVersion("1.0");
+
+        Set<Artifact> artifacts = new HashSet<>();
+        artifacts.add( artifact1 );
+        artifacts.add( artifact2 );
+
+        MavenProject project = new MavenProjectStub();
+        project.setArtifact(artifact1);
+
+        Log log = mock( Log.class );
+        when( log.isDebugEnabled() ).thenReturn( true );
+
+        ExcludeReactorProjectsDependencyFilter filter = new ExcludeReactorProjectsDependencyFilter(
+                singletonList( project ), log );
+
+        Node node = new Node() {
+            @Override
+            public Dependency getDependency() {
+                final Dependency result = new Dependency();
+                result.setGroupId( artifact1.getGroupId() );
+                result.setArtifactId( artifact1.getArtifactId() );
+                result.setVersion( artifact1.getVersion() );
+                return result;
+            }
+        };
+
+        filter.accept( node , Collections.<Node>emptyList() );
+
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass( String.class );
+        verify( log ).debug( captor.capture() );
+        assertThat( captor.getValue(), containsString( "Skipped dependency" ) );
+    }
+
+    public void testAccept()
+    {
+        final Artifact artifact1 = new ArtifactStub();
+        artifact1.setGroupId("org.apache.maven.plugins");
+        artifact1.setArtifactId("maven-dependency-plugin-dummy");
+        artifact1.setVersion("1.0");
+
+        Artifact artifact2 = new ArtifactStub();
+        artifact2.setGroupId("org.apache.maven.plugins");
+        artifact2.setArtifactId("maven-dependency-plugin-other-dummy");
+        artifact2.setVersion("1.0");
+
+        Set<Artifact> artifacts = new HashSet<>();
+        artifacts.add( artifact1 );
+        artifacts.add( artifact2 );
+
+        MavenProject project = new MavenProjectStub();
+        project.setArtifact(artifact1);
+
+        Log log = mock( Log.class );
+        when( log.isDebugEnabled() ).thenReturn( false );
+
+        ExcludeReactorProjectsDependencyFilter filter = new ExcludeReactorProjectsDependencyFilter(
+                singletonList( project ), log );
+
+        Node node = new Node() {
+            @Override
+            public Dependency getDependency() {
+                final Dependency result = new Dependency();
+                result.setGroupId( "something-else" );
+                result.setArtifactId( artifact1.getArtifactId() );
+                result.setVersion( artifact1.getVersion() );
+                return result;
+            }
+        };
+
+        final boolean result = filter.accept( node , Collections.<Node>emptyList() );
+
+        assertThat( result, is( true ));
+    }
+}
\ No newline at end of file