MDEP-648: Add location of listed repository.
diff --git a/pom.xml b/pom.xml
index b48a903..46db606 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,9 @@
     <contributor>
       <name>Maarten Mulders</name>
     </contributor>
+    <contributor>
+      <name>Pim Moerenhout</name>
+    </contributor>
   </contributors>
 
   <properties>
@@ -340,6 +343,12 @@
         </exclusion>
       </exclusions>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven.wagon</groupId>
+      <artifactId>wagon-http-lightweight</artifactId>
+      <version>3.4.0</version>
+      <scope>provided</scope>
+    </dependency>
 
     <dependency>
       <groupId>org.eclipse.jetty</groupId>
diff --git a/src/it/projects/list-repositories-verbose/invoker.properties b/src/it/projects/list-repositories-verbose/invoker.properties
new file mode 100644
index 0000000..48fda22
--- /dev/null
+++ b/src/it/projects/list-repositories-verbose/invoker.properties
@@ -0,0 +1,19 @@
+# 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.
+
+invoker.goals = ${project.groupId}:${project.artifactId}:${project.version}:list-repositories
+invoker.debug = true
\ No newline at end of file
diff --git a/src/it/projects/list-repositories-verbose/pom.xml b/src/it/projects/list-repositories-verbose/pom.xml
new file mode 100644
index 0000000..9082485
--- /dev/null
+++ b/src/it/projects/list-repositories-verbose/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.its.dependency</groupId>
+  <artifactId>test</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <name>Test</name>
+  <description>
+    Test dependency:list-repositories
+  </description>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-project</artifactId>
+      <version>2.0.6</version>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/src/it/projects/list-repositories-verbose/verify.groovy b/src/it/projects/list-repositories-verbose/verify.groovy
new file mode 100644
index 0000000..68bdb6a
--- /dev/null
+++ b/src/it/projects/list-repositories-verbose/verify.groovy
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+File file = new File( basedir, "build.log" );
+assert file.exists();
+
+String buildLog = file.getText( "UTF-8" );
+assert buildLog.contains( 'location: Maven settings (user/global)' );
+
+return true;
diff --git a/src/it/projects/list-repositories/invoker.properties b/src/it/projects/list-repositories/invoker.properties
index 6973bb1..e79fcff 100644
--- a/src/it/projects/list-repositories/invoker.properties
+++ b/src/it/projects/list-repositories/invoker.properties
@@ -16,3 +16,4 @@
 # under the License.

 

 invoker.goals = ${project.groupId}:${project.artifactId}:${project.version}:list-repositories

+invoker.debug = false
\ No newline at end of file
diff --git a/src/it/projects/list-repositories/verify.groovy b/src/it/projects/list-repositories/verify.groovy
new file mode 100644
index 0000000..cb10938
--- /dev/null
+++ b/src/it/projects/list-repositories/verify.groovy
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+File file = new File( basedir, "build.log" );
+assert file.exists();
+
+String buildLog = file.getText( "UTF-8" );
+assert !buildLog.contains( 'location:' );
+
+return true;
diff --git a/src/main/java/org/apache/maven/plugins/dependency/resolvers/ListRepositoriesMojo.java b/src/main/java/org/apache/maven/plugins/dependency/resolvers/ListRepositoriesMojo.java
index deed8d3..f2fb18f 100644
--- a/src/main/java/org/apache/maven/plugins/dependency/resolvers/ListRepositoriesMojo.java
+++ b/src/main/java/org/apache/maven/plugins/dependency/resolvers/ListRepositoriesMojo.java
@@ -19,15 +19,36 @@
  * under the License.

  */

 

+import java.util.HashSet;

+import java.util.List;

+import java.util.Set;

+

+import org.apache.maven.artifact.Artifact;

+import org.apache.maven.artifact.ArtifactUtils;

 import org.apache.maven.artifact.repository.ArtifactRepository;

+import org.apache.maven.model.Model;

+import org.apache.maven.model.Repository;

+import org.apache.maven.model.building.ModelBuildingRequest;

 import org.apache.maven.plugin.MojoExecutionException;

 import org.apache.maven.plugins.annotations.Component;

 import org.apache.maven.plugins.annotations.Mojo;

+import org.apache.maven.plugins.annotations.Parameter;

 import org.apache.maven.plugins.annotations.ResolutionScope;

 import org.apache.maven.plugins.dependency.AbstractDependencyMojo;

-import org.apache.maven.shared.transfer.dependencies.collect.CollectorResult;

-import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollector;

-import org.apache.maven.shared.transfer.dependencies.collect.DependencyCollectorException;

+import org.apache.maven.project.DefaultProjectBuildingRequest;

+import org.apache.maven.project.MavenProject;

+import org.apache.maven.project.ProjectBuilder;

+import org.apache.maven.project.ProjectBuildingRequest;

+import org.apache.maven.settings.Mirror;

+import org.apache.maven.settings.Settings;

+import org.apache.maven.shared.transfer.artifact.ArtifactCoordinate;

+import org.apache.maven.shared.transfer.artifact.DefaultArtifactCoordinate;

+import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResolver;

+import org.apache.maven.shared.transfer.collection.CollectResult;

+import org.apache.maven.shared.transfer.collection.DependencyCollectionException;

+import org.apache.maven.shared.transfer.collection.DependencyCollector;

+import org.apache.maven.shared.transfer.graph.DependencyNode;

+import org.apache.maven.shared.transfer.graph.DependencyVisitor;

 

 /**

  * Goal that resolves all project dependencies and then lists the repositories used by the build and by the transitive

@@ -41,12 +62,46 @@
     extends AbstractDependencyMojo

 {

     /**

+     * Maven Project Builder component.

+     */

+    @Component

+    private ProjectBuilder projectBuilder;

+

+    /**

      * Dependency collector, needed to resolve dependencies.

      */

     @Component( role = DependencyCollector.class )

     private DependencyCollector dependencyCollector;

 

     /**

+     * Component used to resolve artifacts and download their files from remote repositories.

+     */

+    @Component

+    private ArtifactResolver artifactResolver;

+

+    /**

+     * The system settings for Maven. This is the instance resulting from

+     * merging global and user-level settings files.

+     */

+    @Parameter( defaultValue = "${settings}", readonly = true, required = true )

+    private Settings settings;

+

+    /**

+     * Remote repositories used for the project.

+     */

+    @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )

+    private List<ArtifactRepository> remoteRepositories;

+

+    /**

+     * Sets whether the plugin runs in verbose mode. As of plugin version 2.3, the default value is derived from Maven's

+     * global debug flag (compare command line switch <code>-X</code>). <br/>

+     *

+     * @since 3.1.2

+     */

+    @Parameter( property = "verbose" )

+    private boolean verbose;

+

+    /**

      * Displays a list of the repositories used by this build.

      *

      * @throws MojoExecutionException with a message if an error occurs.

@@ -55,22 +110,276 @@
     protected void doExecute()

         throws MojoExecutionException

     {

+

+        for ( ArtifactRepository artifactRepository : remoteRepositories )

+        {

+            verbose( "Maven remote repositories: " + repositoryAsString( artifactRepository ) );

+        }

+

+        final Set<ArtifactRepository> repositories = new HashSet<>();

+        final Set<Artifact> artifacts = new HashSet<>();

+

+        DependencyVisitor visitor = new DependencyVisitor()

+        {

+            @Override

+            public boolean visitEnter( DependencyNode dependencyNode )

+            {

+                repositories.addAll( dependencyNode.getRemoteRepositories() );

+                artifacts.add( dependencyNode.getArtifact() );

+                return true;

+            }

+

+            @Override

+            public boolean visitLeave( DependencyNode dependencyNode )

+            {

+                return true;

+            }

+        };

+

         try

         {

-            CollectorResult collectResult =

-                dependencyCollector.collectDependencies( session.getProjectBuildingRequest(), getProject().getModel() );

+            ProjectBuildingRequest projectBuildingRequest = session.getProjectBuildingRequest();

+

+            CollectResult collectResult =

+                dependencyCollector.collectDependencies( projectBuildingRequest, getProject().getModel() );

+

+            for ( Exception e : collectResult.getExceptions() )

+            {

+                throw new MojoExecutionException( "Collect dependencies failed", e );

+            }

+

+            collectResult.getRoot().accept( visitor );

+

+            verbose( "Artifacts used by the build of " + collectResult.getRoot().getArtifact() + ":" );

+            for ( Artifact artifact : artifacts )

+            {

+                verbose( " " + artifact.toString() );

+            }

 

             this.getLog().info( "Repositories used by this build:" );

-

-            for ( ArtifactRepository repo : collectResult.getRemoteRepositories() )

+            for ( ArtifactRepository repo : repositories )

             {

-                this.getLog().info( repo.toString() );

+                if ( isVerbose() )

+                {

+                    Set<String> locations = new HashSet<String>();

+                    for ( Mirror mirror : settings.getMirrors() )

+                    {

+                        if ( mirror.getId().equals( repo.getId() )

+                            && ( mirror.getUrl().equals( repo.getUrl() ) ) )

+                        {

+                            locations.add( "Maven settings (user/global)" );

+                        }

+                    }

+

+                    Artifact projectArtifact = getProject().getArtifact();

+                    MavenProject project = getMavenProject( ArtifactUtils.key( projectArtifact ) );

+                    traversePom( repo, projectArtifact, project, locations );

+

+                    for ( Artifact artifact : artifacts )

+                    {

+                        MavenProject artifactProject = getMavenProject( ArtifactUtils.key( artifact ) );

+                        traversePom( repo, artifact, artifactProject, locations );

+                    }

+                    writeRepository( repo, locations );

+                }

+                else

+                {

+                    this.getLog().info( repo.toString() );

+                }

             }

         }

-        catch ( DependencyCollectorException e )

+        catch ( DependencyCollectionException e )

         {

-            throw new MojoExecutionException( "Unable to resolve artifacts", e );

+            throw new MojoExecutionException( "Unable to collect", e );

         }

     }

 

+    private void writeRepository( ArtifactRepository artifactRepository, Set<String> locations )

+    {

+        StringBuilder sb = new StringBuilder( 256 );

+        sb.append( artifactRepository.toString() );

+        for ( String location : locations )

+        {

+            sb.append( " location: " ).append( location ).append( System.lineSeparator() );

+        }

+        this.getLog().info( sb.toString() );

+    }

+

+    /**

+     * Parses the given String into GAV artifact coordinate information, adding the given type.

+     *

+     * @param artifactString should respect the format <code>groupId:artifactId[:version]</code>

+     * @param type The extension for the artifact, must not be <code>null</code>.

+     * @return the <code>Artifact</code> object for the <code>artifactString</code> parameter.

+     * @throws MojoExecutionException if the <code>artifactString</code> doesn't respect the format.

+     */

+    private ArtifactCoordinate getArtifactCoordinate( String artifactString, String type )

+        throws MojoExecutionException

+    {

+        if ( org.codehaus.plexus.util.StringUtils.isEmpty( artifactString ) )

+        {

+            throw new IllegalArgumentException( "artifact parameter could not be empty" );

+        }

+

+        String groupId; // required

+        String artifactId; // required

+        String version; // optional

+

+        String[] artifactParts = artifactString.split( ":" );

+        switch ( artifactParts.length )

+        {

+            case 2:

+                groupId = artifactParts[0];

+                artifactId = artifactParts[1];

+                version = Artifact.LATEST_VERSION;

+                break;

+            case 3:

+                groupId = artifactParts[0];

+                artifactId = artifactParts[1];

+                version = artifactParts[2];

+                break;

+            default:

+                throw new MojoExecutionException( "The artifact parameter '" + artifactString

+                    + "' should be conform to: " + "'groupId:artifactId[:version]'." );

+        }

+        return getArtifactCoordinate( groupId, artifactId, version, type );

+    }

+

+    private ArtifactCoordinate getArtifactCoordinate( String groupId, String artifactId, String version, String type )

+    {

+        DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();

+        coordinate.setGroupId( groupId );

+        coordinate.setArtifactId( artifactId );

+        coordinate.setVersion( version );

+        coordinate.setExtension( type );

+        return coordinate;

+    }

+

+    /**

+     * Retrieves the Maven Project associated with the given artifact String, in the form of

+     * <code>groupId:artifactId[:version]</code>. This resolves the POM artifact at those coordinates and then builds

+     * the Maven project from it.

+     *

+     * @param artifactString Coordinates of the Maven project to get.

+     * @return New Maven project.

+     * @throws MojoExecutionException If there was an error while getting the Maven project.

+     */

+    private MavenProject getMavenProject( String artifactString )

+        throws MojoExecutionException

+    {

+        ArtifactCoordinate coordinate = getArtifactCoordinate( artifactString, "pom" );

+        try

+        {

+            ProjectBuildingRequest pbr = new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() );

+            pbr.setRemoteRepositories( remoteRepositories );

+            pbr.setProject( null );

+            pbr.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );

+            pbr.setResolveDependencies( false );

+            Artifact artifact = artifactResolver.resolveArtifact( pbr, coordinate ).getArtifact();

+            return projectBuilder.build( artifact.getFile(), pbr ).getProject();

+        }

+        catch ( Exception e )

+        {

+            throw new MojoExecutionException( "Unable to get the POM for the artifact '" + artifactString

+                + "'. Verify the artifact parameter.", e );

+        }

+    }

+

+    private void traversePom( ArtifactRepository artifactRepository,

+                              Artifact artifact, MavenProject mavenProject, Set<String> locations )

+        throws MojoExecutionException

+    {

+        getLog().debug( "Looking for locations of repository " + repositoryAsString( artifactRepository )

+            + " for " + artifact );

+        if ( mavenProject != null )

+        {

+            for ( Repository repository : mavenProject.getOriginalModel().getRepositories() )

+            {

+                getLog().debug( "Found repository: " + repositoryAsString( repository )

+                    + " @ " + artifact + ":" + mavenProject.getOriginalModel().getPomFile() );

+                if ( isRepositoryEqual( repository, artifactRepository ) )

+                {

+                    locations.add( mavenProject.getModel().getPomFile().toString() );

+                }

+            }

+

+            traverseParentPom( artifactRepository, mavenProject, locations );

+        }

+        else

+        {

+            throw new MojoExecutionException( "No POM for the artifact '" + artifact + "'" );

+        }

+        return;

+    }

+

+    private void traverseParentPom( ArtifactRepository artifactRepository,

+                                    MavenProject mavenProject, Set<String> locations )

+        throws MojoExecutionException

+    {

+        MavenProject parent = mavenProject.getParent();

+        if ( parent != null )

+        {

+            Model originalModel = parent.getOriginalModel();

+            if ( originalModel.getRepositories().size() != 0

+                || originalModel.getPluginRepositories().size() != 0 )

+            {

+                String artifactKey =

+                    ArtifactUtils.key( parent.getGroupId(), parent.getArtifactId(), parent.getVersion() );

+                MavenProject parentPom = getMavenProject( artifactKey );

+

+                for ( Repository repository : originalModel.getRepositories() )

+                {

+                    getLog().debug( "Found parent repository " + repositoryAsString( repository )

+                        + " @ " + parentPom.getArtifact() + ":" + parentPom.getFile() );

+                    if ( isRepositoryEqual( repository, artifactRepository ) )

+                    {

+                        locations.add( parentPom.getFile().toString() );

+                    }

+                }

+            }

+            traverseParentPom( artifactRepository, parent, locations );

+        }

+        return;

+    }

+

+    private String repositoryAsString( Repository repository )

+    {

+        StringBuilder sb = new StringBuilder( 32 );

+        sb.append( repository.getId() );

+        sb.append( " (" );

+        sb.append( repository.getUrl() );

+        sb.append( ")" );

+        return sb.toString();

+    }

+

+    private String repositoryAsString( ArtifactRepository repository )

+    {

+        StringBuilder sb = new StringBuilder( 32 );

+        sb.append( repository.getId() );

+        sb.append( " (" );

+        sb.append( repository.getUrl() );

+        sb.append( ")" );

+        return sb.toString();

+    }

+

+    private boolean isVerbose()

+    {

+        return ( verbose || getLog().isDebugEnabled() );

+    }

+

+    private void verbose( String message )

+    {

+        if ( isVerbose() )

+        {

+            getLog().info( message );

+        }

+    }

+

+    private boolean isRepositoryEqual( Repository repository, ArtifactRepository artifactRepository )

+    {

+        // TODO: Use org.apache.maven.RepositoryUtils in Maven or check also snapshots, etc.

+        return repository.getId().equals( artifactRepository.getId() )

+            && repository.getUrl().equals( artifactRepository.getUrl() );

+    }

+

 }

diff --git a/src/site/apt/usage.apt.vm b/src/site/apt/usage.apt.vm
index a67381c..9f912dc 100644
--- a/src/site/apt/usage.apt.vm
+++ b/src/site/apt/usage.apt.vm
@@ -685,6 +685,17 @@
   This goal is used to list all the repositories that this build depends upon. It will show repositories defined in your settings, 
   poms and declared in transitive dependency poms.
 
+  This goal can be executed from the command line:
+
++-----+
+mvn dependency:list-repositories
++-----+
+
+  Optionally, in verbose or debug mode it will display the location of the listed repository:
+
++-----+
+mvn dependency:list-repositories -Dverbose
++-----+
 
 * <<<dependency:get>>>