package org.apache.maven.project.artifact;

/*
 * 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 java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.InvalidRepositoryException;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
import org.apache.maven.artifact.metadata.ResolutionGroup;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.metadata.ArtifactRepositoryMetadata;
import org.apache.maven.artifact.repository.metadata.Metadata;
import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
import org.apache.maven.artifact.repository.metadata.RepositoryMetadataManager;
import org.apache.maven.artifact.repository.metadata.RepositoryMetadataResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.MultipleArtifactsNotFoundException;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.ExclusionArtifactFilter;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.bridge.MavenRepositorySystem;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Model;
import org.apache.maven.model.Relocation;
import org.apache.maven.model.Repository;
import org.apache.maven.model.building.ModelBuildingException;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.resolution.UnresolvableModelException;
import org.apache.maven.plugin.LegacySupport;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.properties.internal.EnvironmentUtils;
import org.apache.maven.properties.internal.SystemProperties;
import org.apache.maven.repository.internal.MavenWorkspaceReader;
import org.apache.maven.repository.legacy.metadata.DefaultMetadataResolutionRequest;
import org.apache.maven.repository.legacy.metadata.MetadataResolutionRequest;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.logging.Logger;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.repository.WorkspaceReader;
import org.eclipse.aether.transfer.ArtifactNotFoundException;

/**
 * @author Jason van Zyl
 */
@Component( role = ArtifactMetadataSource.class, hint = "maven" )
public class MavenMetadataSource
    implements ArtifactMetadataSource
{
    @Requirement
    private RepositoryMetadataManager repositoryMetadataManager;

    @Requirement
    private ArtifactFactory artifactFactory;

    @Requirement
    private MavenRepositorySystem repositorySystem;

    //TODO This prevents a cycle in the composition which shows us another problem we need to deal with.
    //@Requirement
    private ProjectBuilder projectBuilder;

    @Requirement
    private PlexusContainer container;

    @Requirement
    private Logger logger;

    @Requirement
    private MavenMetadataCache cache;

    @Requirement
    private LegacySupport legacySupport;

    private void injectSession( MetadataResolutionRequest request )
    {
        RepositorySystemSession session = legacySupport.getRepositorySession();

        if ( session != null )
        {
            request.setOffline( session.isOffline() );
            request.setForceUpdate( RepositoryPolicy.UPDATE_POLICY_ALWAYS.equals( session.getUpdatePolicy() ) );
        }
    }

    @Override
    public ResolutionGroup retrieve( Artifact artifact, ArtifactRepository localRepository,
                                     List<ArtifactRepository> remoteRepositories )
        throws ArtifactMetadataRetrievalException
    {
        return retrieve( artifact, localRepository, remoteRepositories, false );
    }

    public ResolutionGroup retrieve( Artifact artifact, ArtifactRepository localRepository,
                                     List<ArtifactRepository> remoteRepositories, boolean resolveManagedVersions )
        throws ArtifactMetadataRetrievalException
    {
        MetadataResolutionRequest request = new DefaultMetadataResolutionRequest();
        injectSession( request );
        request.setArtifact( artifact );
        request.setLocalRepository( localRepository );
        request.setRemoteRepositories( remoteRepositories );
        request.setResolveManagedVersions( resolveManagedVersions );
        return retrieve( request );
    }

    @Override
    public ResolutionGroup retrieve( MetadataResolutionRequest request )
        throws ArtifactMetadataRetrievalException
    {
        Artifact artifact = request.getArtifact();

        //
        // If we have a system scoped artifact then we do not want any searching in local or remote repositories
        // and we want artifact resolution to only return the system scoped artifact itself.
        //
        if ( artifact.getScope() != null && artifact.getScope().equals( Artifact.SCOPE_SYSTEM ) )
        {
            return new ResolutionGroup( null, null, null );
        }

        ResolutionGroup cached =
            cache.get( artifact, request.isResolveManagedVersions(), request.getLocalRepository(),
                       request.getRemoteRepositories() );

        if ( cached != null
        // if the POM has no file, we cached a missing artifact, only return the cached data if no update forced
            && ( !request.isForceUpdate() || hasFile( cached.getPomArtifact() ) ) )
        {
            return cached;
        }

        List<Dependency> dependencies;

        List<Dependency> managedDependencies = null;

        List<ArtifactRepository> pomRepositories = null;

        Artifact pomArtifact;

        Artifact relocatedArtifact = null;

        // TODO hack: don't rebuild model if it was already loaded during reactor resolution
        RepositorySystemSession repositorySession = legacySupport.getRepositorySession();
        final WorkspaceReader workspace = repositorySession.getWorkspaceReader();
        Model model = null;
        if ( workspace instanceof MavenWorkspaceReader )
        {
            model = ( (MavenWorkspaceReader) workspace ).findModel( RepositoryUtils.toArtifact( artifact ) );
        }

        if ( model != null )
        {
            pomArtifact = artifact;
            dependencies = model.getDependencies();
            DependencyManagement dependencyManagement = model.getDependencyManagement();
            managedDependencies = dependencyManagement == null ? null : dependencyManagement.getDependencies();
            MavenSession session = legacySupport.getSession();
            if ( session != null )
            {
                Map<String, MavenProject> projectMap = session.getProjectMap();
                MavenProject project = projectMap == null ? null
                                : projectMap.get( ArtifactUtils.key( artifact.getGroupId(), artifact.getArtifactId(),
                                                                     artifact.getVersion() ) );
                if ( project == null )
                {
                    // if the project is not on the project map, read the repositories from the model itself!
                    pomRepositories = getRepositoriesFromModel( repositorySession, model );
                }
                else
                {
                    pomRepositories = project.getRemoteArtifactRepositories();
                }
            }
            else
            {
                pomRepositories = new ArrayList<>();
            }
        }
        else if ( artifact instanceof ArtifactWithDependencies )
        {
            pomArtifact = artifact;

            dependencies = ( (ArtifactWithDependencies) artifact ).getDependencies();

            managedDependencies = ( (ArtifactWithDependencies) artifact ).getManagedDependencies();
        }
        else
        {
            ProjectRelocation rel = retrieveRelocatedProject( artifact, request );

            if ( rel == null )
            {
                return null;
            }

            pomArtifact = rel.pomArtifact;

            relocatedArtifact = rel.relocatedArtifact;

            if ( rel.project == null )
            {
                // When this happens we have a Maven 1.x POM, or some invalid POM.
                // It should have never found its way into Maven 2.x repository but it did.
                dependencies = Collections.emptyList();
            }
            else
            {
                dependencies = rel.project.getDependencies();

                DependencyManagement depMgmt = rel.project.getDependencyManagement();
                managedDependencies = ( depMgmt != null ) ? depMgmt.getDependencies() : null;

                pomRepositories = rel.project.getRemoteArtifactRepositories();
            }
        }

        Set<Artifact> artifacts = Collections.emptySet();

        if ( !artifact.getArtifactHandler().isIncludesDependencies() )
        {
            artifacts = new LinkedHashSet<>();

            for ( Dependency dependency : dependencies )
            {
                Artifact dependencyArtifact = createDependencyArtifact( dependency, artifact, pomArtifact );

                if ( dependencyArtifact != null )
                {
                    artifacts.add( dependencyArtifact );
                }
            }
        }

        Map<String, Artifact> managedVersions = null;

        if ( managedDependencies != null && request.isResolveManagedVersions() )
        {
            managedVersions = new HashMap<>();

            for ( Dependency managedDependency : managedDependencies )
            {
                Artifact managedArtifact = createDependencyArtifact( managedDependency, null, pomArtifact );

                managedVersions.put( managedDependency.getManagementKey(), managedArtifact );
            }
        }

        List<ArtifactRepository> aggregatedRepositories =
            aggregateRepositories( request.getRemoteRepositories(), pomRepositories );

        ResolutionGroup result =
            new ResolutionGroup( pomArtifact, relocatedArtifact, artifacts, managedVersions, aggregatedRepositories );

        cache.put( artifact, request.isResolveManagedVersions(), request.getLocalRepository(),
                   request.getRemoteRepositories(), result );

        return result;
    }

    private List<ArtifactRepository> getRepositoriesFromModel( RepositorySystemSession repositorySession, Model model )
    {
        List<ArtifactRepository> pomRepositories = new ArrayList<>();
        for ( Repository modelRepository : model.getRepositories() )
        {
            try
            {
                pomRepositories.add( MavenRepositorySystem.buildArtifactRepository( modelRepository ) );
            }
            catch ( InvalidRepositoryException e )
            {
                // can not use this then
            }
        }
        repositorySystem.injectMirror( repositorySession, pomRepositories );
        repositorySystem.injectProxy( repositorySession, pomRepositories );
        repositorySystem.injectAuthentication( repositorySession, pomRepositories );
        return pomRepositories;
    }

    private boolean hasFile( Artifact artifact )
    {
        return artifact != null && artifact.getFile() != null && artifact.getFile().exists();
    }

    private List<ArtifactRepository> aggregateRepositories( List<ArtifactRepository> requestRepositories,
                                                            List<ArtifactRepository> pomRepositories )
    {
        List<ArtifactRepository> repositories = requestRepositories;

        if ( pomRepositories != null && !pomRepositories.isEmpty() )
        {
            Map<String, ArtifactRepository> repos = new LinkedHashMap<>();

            for ( ArtifactRepository repo : requestRepositories )
            {
                if ( !repos.containsKey( repo.getId() ) )
                {
                    repos.put( repo.getId(), repo );
                }
            }

            for ( ArtifactRepository repo : pomRepositories )
            {
                if ( !repos.containsKey( repo.getId() ) )
                {
                    repos.put( repo.getId(), repo );
                }
            }

            repositories = new ArrayList<>( repos.values() );
        }

        return repositories;
    }

    private Artifact createDependencyArtifact( Dependency dependency, Artifact owner, Artifact pom )
        throws ArtifactMetadataRetrievalException
    {
        try
        {
            String inheritedScope = ( owner != null ) ? owner.getScope() : null;

            ArtifactFilter inheritedFilter = ( owner != null ) ? owner.getDependencyFilter() : null;

            return createDependencyArtifact( artifactFactory, dependency, inheritedScope, inheritedFilter );
        }
        catch ( InvalidVersionSpecificationException e )
        {
            throw new ArtifactMetadataRetrievalException( "Invalid version for dependency "
                + dependency.getManagementKey() + ": " + e.getMessage(), e, pom );
        }
    }

    private static Artifact createDependencyArtifact( ArtifactFactory factory, Dependency dependency,
                                                      String inheritedScope, ArtifactFilter inheritedFilter )
        throws InvalidVersionSpecificationException
    {
        String effectiveScope = getEffectiveScope( dependency.getScope(), inheritedScope );

        if ( effectiveScope == null )
        {
            return null;
        }

        VersionRange versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() );

        Artifact dependencyArtifact =
            factory.createDependencyArtifact( dependency.getGroupId(), dependency.getArtifactId(), versionRange,
                                              dependency.getType(), dependency.getClassifier(), effectiveScope,
                                              dependency.isOptional() );

        ArtifactFilter dependencyFilter = inheritedFilter;

        if ( dependencyFilter != null && !dependencyFilter.include( dependencyArtifact ) )
        {
            return null;
        }

        if ( Artifact.SCOPE_SYSTEM.equals( effectiveScope ) )
        {
            dependencyArtifact.setFile( new File( dependency.getSystemPath() ) );
        }

        dependencyArtifact.setDependencyFilter( createDependencyFilter( dependency, dependencyFilter ) );

        return dependencyArtifact;
    }

    private static String getEffectiveScope( String originalScope, String inheritedScope )
    {
        String effectiveScope = Artifact.SCOPE_RUNTIME;

        if ( originalScope == null )
        {
            originalScope = Artifact.SCOPE_COMPILE;
        }

        if ( inheritedScope == null )
        {
            // direct dependency retains its scope
            effectiveScope = originalScope;
        }
        else if ( Artifact.SCOPE_TEST.equals( originalScope ) || Artifact.SCOPE_PROVIDED.equals( originalScope ) )
        {
            // test and provided are not transitive, so exclude them
            effectiveScope = null;
        }
        else if ( Artifact.SCOPE_SYSTEM.equals( originalScope ) )
        {
            // system scope come through unchanged...
            effectiveScope = Artifact.SCOPE_SYSTEM;
        }
        else if ( Artifact.SCOPE_COMPILE.equals( originalScope ) && Artifact.SCOPE_COMPILE.equals( inheritedScope ) )
        {
            // added to retain compile scope. Remove if you want compile inherited as runtime
            effectiveScope = Artifact.SCOPE_COMPILE;
        }
        else if ( Artifact.SCOPE_TEST.equals( inheritedScope ) )
        {
            effectiveScope = Artifact.SCOPE_TEST;
        }
        else if ( Artifact.SCOPE_PROVIDED.equals( inheritedScope ) )
        {
            effectiveScope = Artifact.SCOPE_PROVIDED;
        }

        return effectiveScope;
    }

    private static ArtifactFilter createDependencyFilter( Dependency dependency, ArtifactFilter inheritedFilter )
    {
        ArtifactFilter effectiveFilter = inheritedFilter;

        if ( !dependency.getExclusions().isEmpty() )
        {
            effectiveFilter = new ExclusionArtifactFilter( dependency.getExclusions() );

            if ( inheritedFilter != null )
            {
                effectiveFilter = new AndArtifactFilter( Arrays.asList( inheritedFilter, effectiveFilter ) );
            }
        }

        return effectiveFilter;
    }

    @Override
    public List<ArtifactVersion> retrieveAvailableVersions( Artifact artifact, ArtifactRepository localRepository,
                                                            List<ArtifactRepository> remoteRepositories )
        throws ArtifactMetadataRetrievalException
    {
        MetadataResolutionRequest request = new DefaultMetadataResolutionRequest();
        injectSession( request );
        request.setArtifact( artifact );
        request.setLocalRepository( localRepository );
        request.setRemoteRepositories( remoteRepositories );
        return retrieveAvailableVersions( request );
    }

    @Override
    public List<ArtifactVersion> retrieveAvailableVersions( MetadataResolutionRequest request )
        throws ArtifactMetadataRetrievalException
    {
        RepositoryMetadata metadata = new ArtifactRepositoryMetadata( request.getArtifact() );

        try
        {
            repositoryMetadataManager.resolve( metadata, request );
        }
        catch ( RepositoryMetadataResolutionException e )
        {
            throw new ArtifactMetadataRetrievalException( e.getMessage(), e, request.getArtifact() );
        }

        List<String> availableVersions = request.getLocalRepository().findVersions( request.getArtifact() );

        return retrieveAvailableVersionsFromMetadata( metadata.getMetadata(), availableVersions );
    }

    @Override
    public List<ArtifactVersion> retrieveAvailableVersionsFromDeploymentRepository( Artifact artifact,
                                                                                    ArtifactRepository localRepository,
                                                                              ArtifactRepository deploymentRepository )
        throws ArtifactMetadataRetrievalException
    {
        RepositoryMetadata metadata = new ArtifactRepositoryMetadata( artifact );

        try
        {
            repositoryMetadataManager.resolveAlways( metadata, localRepository, deploymentRepository );
        }
        catch ( RepositoryMetadataResolutionException e )
        {
            throw new ArtifactMetadataRetrievalException( e.getMessage(), e, artifact );
        }

        List<String> availableVersions = localRepository.findVersions( artifact );

        return retrieveAvailableVersionsFromMetadata( metadata.getMetadata(), availableVersions );
    }

    private List<ArtifactVersion> retrieveAvailableVersionsFromMetadata( Metadata repoMetadata,
                                                                         List<String> availableVersions )
    {
        Collection<String> versions = new LinkedHashSet<>();

        if ( ( repoMetadata != null ) && ( repoMetadata.getVersioning() != null ) )
        {
            versions.addAll( repoMetadata.getVersioning().getVersions() );
        }

        versions.addAll( availableVersions );

        List<ArtifactVersion> artifactVersions = new ArrayList<>( versions.size() );

        for ( String version : versions )
        {
            artifactVersions.add( new DefaultArtifactVersion( version ) );
        }

        return artifactVersions;
    }

    // USED BY MAVEN ASSEMBLY PLUGIN
    @Deprecated
    public static Set<Artifact> createArtifacts( ArtifactFactory artifactFactory, List<Dependency> dependencies,
                                                 String inheritedScope, ArtifactFilter dependencyFilter,
                                                 MavenProject project )
        throws InvalidDependencyVersionException
    {
        Set<Artifact> artifacts = new LinkedHashSet<>();

        for ( Dependency d : dependencies )
        {
            Artifact dependencyArtifact;
            try
            {
                dependencyArtifact = createDependencyArtifact( artifactFactory, d, inheritedScope, dependencyFilter );
            }
            catch ( InvalidVersionSpecificationException e )
            {
                throw new InvalidDependencyVersionException( project.getId(), d, project.getFile(), e );
            }

            if ( dependencyArtifact != null )
            {
                artifacts.add( dependencyArtifact );
            }
        }

        return artifacts;
    }

    private ProjectBuilder getProjectBuilder()
    {
        if ( projectBuilder != null )
        {
            return projectBuilder;
        }

        try
        {
            projectBuilder = container.lookup( ProjectBuilder.class );
        }
        catch ( ComponentLookupException e )
        {
            // Won't happen
        }

        return projectBuilder;
    }
    @SuppressWarnings( "checkstyle:methodlength" )
    private ProjectRelocation retrieveRelocatedProject( Artifact artifact, MetadataResolutionRequest repositoryRequest )
        throws ArtifactMetadataRetrievalException
    {
        MavenProject project;

        Artifact pomArtifact;
        Artifact relocatedArtifact = null;
        boolean done = false;
        do
        {
            project = null;

            pomArtifact =
                artifactFactory.createProjectArtifact( artifact.getGroupId(),
                                                        artifact.getArtifactId(),
                                                        artifact.getVersion(), artifact.getScope() );

            if ( "pom".equals( artifact.getType() ) )
            {
                pomArtifact.setFile( artifact.getFile() );
            }

            if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
            {
                done = true;
            }
            else
            {
                try
                {
                    ProjectBuildingRequest configuration = new DefaultProjectBuildingRequest();
                    configuration.setLocalRepository( repositoryRequest.getLocalRepository() );
                    configuration.setRemoteRepositories( repositoryRequest.getRemoteRepositories() );
                    configuration.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
                    configuration.setProcessPlugins( false );
                    configuration.setRepositoryMerging( ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT );
                    configuration.setSystemProperties( getSystemProperties() );
                    configuration.setUserProperties( new Properties() );
                    configuration.setRepositorySession( legacySupport.getRepositorySession() );

                    project = getProjectBuilder().build( pomArtifact, configuration ).getProject();
                }
                catch ( ProjectBuildingException e )
                {
                    ModelProblem missingParentPom = hasMissingParentPom( e );
                    if ( missingParentPom != null )
                    {
                        throw new ArtifactMetadataRetrievalException( "Failed to process POM for "
                            + artifact.getId() + ": " + missingParentPom.getMessage(),
                                                                      missingParentPom.getException(),
                                                                      artifact );
                    }

                    String message;

                    if ( isMissingPom( e ) )
                    {
                        message = "Missing POM for " + artifact.getId();
                    }
                    else if ( isNonTransferrablePom( e ) )
                    {
                        throw new ArtifactMetadataRetrievalException( "Failed to retrieve POM for "
                            + artifact.getId() + ": " + e.getCause().getMessage(), e.getCause(),
                                                                      artifact );
                    }
                    else
                    {
                        message =
                            "Invalid POM for " + artifact.getId()
                                + ", transitive dependencies (if any) will not be available"
                                + ", enable debug logging for more details";
                    }

                    if ( logger.isDebugEnabled() )
                    {
                        message += ": " + e.getMessage();
                    }

                    logger.warn( message );
                }

                if ( project != null )
                {
                    Relocation relocation = null;

                    DistributionManagement distMgmt = project.getDistributionManagement();
                    if ( distMgmt != null )
                    {
                        relocation = distMgmt.getRelocation();

                        artifact.setDownloadUrl( distMgmt.getDownloadUrl() );
                        pomArtifact.setDownloadUrl( distMgmt.getDownloadUrl() );
                    }

                    if ( relocation != null )
                    {
                        if ( relocation.getGroupId() != null )
                        {
                            artifact.setGroupId( relocation.getGroupId() );
                            relocatedArtifact = artifact;
                            project.setGroupId( relocation.getGroupId() );
                        }
                        if ( relocation.getArtifactId() != null )
                        {
                            artifact.setArtifactId( relocation.getArtifactId() );
                            relocatedArtifact = artifact;
                            project.setArtifactId( relocation.getArtifactId() );
                        }
                        if ( relocation.getVersion() != null )
                        {
                            // note: see MNG-3454. This causes a problem, but fixing it may break more.
                            artifact.setVersionRange( VersionRange.createFromVersion( relocation.getVersion() ) );
                            relocatedArtifact = artifact;
                            project.setVersion( relocation.getVersion() );
                        }

                        if ( artifact.getDependencyFilter() != null
                            && !artifact.getDependencyFilter().include( artifact ) )
                        {
                            return null;
                        }

                        // MNG-2861: the artifact data has changed. If the available versions where previously
                        // retrieved, we need to update it.
                        // TODO shouldn't the versions be merged across relocations?
                        List<ArtifactVersion> available = artifact.getAvailableVersions();
                        if ( available != null && !available.isEmpty() )
                        {
                            MetadataResolutionRequest metadataRequest =
                                new DefaultMetadataResolutionRequest( repositoryRequest );
                            metadataRequest.setArtifact( artifact );
                            available = retrieveAvailableVersions( metadataRequest );
                            artifact.setAvailableVersions( available );
                        }

                        String message =
                            "\n  This artifact has been relocated to " + artifact.getGroupId() + ":"
                                + artifact.getArtifactId() + ":" + artifact.getVersion() + ".\n";

                        if ( relocation.getMessage() != null )
                        {
                            message += "  " + relocation.getMessage() + "\n";
                        }

                        if ( artifact.getDependencyTrail() != null && artifact.getDependencyTrail().size() == 1 )
                        {
                            logger.warn( "While downloading " + pomArtifact.getGroupId() + ":"
                                + pomArtifact.getArtifactId() + ":" + pomArtifact.getVersion() + message + "\n" );
                        }
                        else
                        {
                            logger.debug( "While downloading " + pomArtifact.getGroupId() + ":"
                                + pomArtifact.getArtifactId() + ":" + pomArtifact.getVersion() + message + "\n" );
                        }
                    }
                    else
                    {
                        done = true;
                    }
                }
                else
                {
                    done = true;
                }
            }
        }
        while ( !done );

        ProjectRelocation rel = new ProjectRelocation();
        rel.project = project;
        rel.pomArtifact = pomArtifact;
        rel.relocatedArtifact = relocatedArtifact;

        return rel;
    }

    private ModelProblem hasMissingParentPom( ProjectBuildingException e )
    {
        if ( e.getCause() instanceof ModelBuildingException )
        {
            ModelBuildingException mbe = (ModelBuildingException) e.getCause();
            for ( ModelProblem problem : mbe.getProblems() )
            {
                if ( problem.getException() instanceof UnresolvableModelException )
                {
                    return problem;
                }
            }

        }
        return null;
    }

    private boolean isMissingPom( Exception e )
    {
        if ( e.getCause() instanceof MultipleArtifactsNotFoundException )
        {
            return true;
        }
        return e.getCause() instanceof org.eclipse.aether.resolution.ArtifactResolutionException
            && e.getCause().getCause() instanceof ArtifactNotFoundException;
    }

    private boolean isNonTransferrablePom( Exception e )
    {
        if ( e.getCause() instanceof ArtifactResolutionException )
        {
            return true;
        }
        return e.getCause() instanceof org.eclipse.aether.resolution.ArtifactResolutionException
            && !( e.getCause().getCause() instanceof ArtifactNotFoundException );
    }

    private Properties getSystemProperties()
    {
        Properties props = new Properties();

        EnvironmentUtils.addEnvVars( props );

        SystemProperties.addSystemProperties( props );

        return props;
    }

    private static final class ProjectRelocation
    {
        private MavenProject project;

        private Artifact pomArtifact;

        private Artifact relocatedArtifact;
    }

}
