MavenRepository.java

/*
 * Copyright 2017 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed 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.
 */

package org.appformer.maven.integration;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.maven.model.DeploymentRepository;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.project.MavenProject;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.Server;
import org.appformer.maven.integration.embedder.MavenEmbedder;
import org.appformer.maven.integration.embedder.MavenProjectLoader;
import org.appformer.maven.integration.embedder.MavenSettings;
import org.appformer.maven.support.AFReleaseId;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.collection.CollectResult;
import org.eclipse.aether.collection.DependencyCollectionException;
import org.eclipse.aether.deployment.DeployRequest;
import org.eclipse.aether.deployment.DeploymentException;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.graph.DependencyVisitor;
import org.eclipse.aether.installation.InstallRequest;
import org.eclipse.aether.installation.InstallationException;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.util.artifact.SubArtifact;
import org.eclipse.aether.version.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.appformer.maven.integration.embedder.MavenProjectLoader.parseMavenPom;

public class MavenRepository {

    private static final String SESSION_CHECKS = "updateCheckManager.checks";

    private static final Logger log = LoggerFactory.getLogger( MavenRepository.class );

    public static MavenRepository defaultMavenRepository;

    private final Aether aether;
    private final Collection<RemoteRepository> remoteRepositoriesForRequest;

    protected MavenRepository( Aether aether ) {
        this.aether = aether;
        remoteRepositoriesForRequest = initRemoteRepositoriesForRequest();
    }

    protected MavenRepositoryConfiguration getMavenRepositoryConfiguration() {
        return MavenSettings.getMavenRepositoryConfiguration();
    }

    Collection<RemoteRepository> getRemoteRepositoriesForRequest() {
        return remoteRepositoriesForRequest;
    }

    public static synchronized MavenRepository getMavenRepository() {
        if ( defaultMavenRepository == null ) {
            Aether defaultAether = Aether.getAether();
            defaultMavenRepository = new MavenRepository( defaultAether );
        }
        return defaultMavenRepository;
    }

    private Collection<RemoteRepository> initRemoteRepositoriesForRequest() {
        final MavenRepositoryConfiguration repositoryUtils = getMavenRepositoryConfiguration();
        Collection<RemoteRepository> remoteRepos = new HashSet<RemoteRepository>();
        remoteRepos.addAll( repositoryUtils.getRemoteRepositoriesForRequest() );

        for ( RemoteRepository repo : aether.getRepositories() ) {
            remoteRepos.add( repositoryUtils.resolveMirroredRepo( repo ) );
        }
        return remoteRepos;
    }

    public static MavenRepository getMavenRepository( MavenProject mavenProject ) {
        return new MavenRepository( new Aether( mavenProject ) );
    }

    public List<DependencyDescriptor> getArtifactDependecies( String artifactName ) {
        Artifact artifact = new DefaultArtifact( artifactName );
        CollectRequest collectRequest = new CollectRequest();
        Dependency root = new Dependency( artifact, "" );
        collectRequest.setRoot( root );
        for ( RemoteRepository repo : remoteRepositoriesForRequest ) {
            collectRequest.addRepository( repo );
        }
        CollectResult collectResult;
        try {
            collectResult = aether.getSystem().collectDependencies( aether.getSession(), collectRequest );
        } catch ( DependencyCollectionException e ) {
            throw new RuntimeException( e );
        }
        CollectDependencyVisitor visitor = new CollectDependencyVisitor();
        collectResult.getRoot().accept( visitor );

        List<DependencyDescriptor> descriptors = new ArrayList<DependencyDescriptor>();
        for ( DependencyNode node : visitor.getDependencies() ) {
            // skip root to not add artifact as dependency
            if ( node.getDependency().equals( root ) ) {
                continue;
            }
            descriptors.add( new DependencyDescriptor( node.getDependency().getArtifact() ) );
        }
        return descriptors;
    }

    public Artifact resolveArtifact( AFReleaseId releaseId ) {
        String artifactName = releaseId.toString();
        if ( DependencyDescriptor.isRangedVersion( releaseId.getVersion() ) ) {
            Version v = resolveVersion( artifactName );
            if ( v == null ) {
                return null;
            }
            artifactName = releaseId.getGroupId() + ":" + releaseId.getArtifactId() + ":" + v;
        }
        return resolveArtifact( artifactName );
    }

    public Artifact resolveArtifact( String artifactName ) {
        return resolveArtifact( artifactName, true );
    }

    public Artifact resolveArtifact( String artifactName,
                                     boolean logUnresolvedArtifact ) {
        Artifact artifact = new DefaultArtifact( artifactName );
        ArtifactRequest artifactRequest = new ArtifactRequest();
        artifactRequest.setArtifact( artifact );
        for ( RemoteRepository repo : remoteRepositoriesForRequest ) {
            artifactRequest.addRepository( repo );
        }

        RepositorySystemSession session = aether.getSession();
        Object sessionChecks = null;
        boolean isSnapshot = artifactName.endsWith( "-SNAPSHOT" );
        if (artifactName.endsWith( "-SNAPSHOT" )) {
            // ensure to always update snapshots
            sessionChecks = session.getData().get( SESSION_CHECKS );
            session.getData().set( SESSION_CHECKS, null );
        }

        try {
            ArtifactResult artifactResult = aether.getSystem().resolveArtifact( session, artifactRequest );
            return artifactResult.getArtifact();
        } catch ( ArtifactResolutionException e ) {
            if ( logUnresolvedArtifact ) {
                if ( log.isDebugEnabled() ) {
                    log.debug( "Unable to resolve artifact: " + artifactName, e );
                } else {
                    log.warn( "Unable to resolve artifact: " + artifactName );
                }
            }
            return null;
        } finally {
            if (sessionChecks != null) {
                session.getData().set( SESSION_CHECKS, sessionChecks );
            }
        }
    }

    public Version resolveVersion( String artifactName ) {
        Artifact artifact = new DefaultArtifact( artifactName );
        VersionRangeRequest versionRequest = new VersionRangeRequest();
        versionRequest.setArtifact( artifact );
        for ( RemoteRepository repo : remoteRepositoriesForRequest ) {
            versionRequest.addRepository( repo );
        }
        try {
            VersionRangeResult versionRangeResult = aether.getSystem().resolveVersionRange( aether.getSession(), versionRequest );
            return versionRangeResult.getHighestVersion();
        } catch ( VersionRangeResolutionException e ) {
            if ( log.isDebugEnabled() ) {
                log.debug( "Unable to resolve version range for artifact: " + artifactName, e );
            } else {
                log.warn( "Unable to resolve version range for artifact: " + artifactName );
            }
            return null;
        }
    }

    protected RemoteRepository getRemoteRepositoryFromDistributionManagement( File pomfile ) {
        MavenProject mavenProject = parseMavenPom( pomfile );
        DistributionManagement distMan = mavenProject.getDistributionManagement();
        if (distMan == null) {
            return null;
        }
        DeploymentRepository deployRepo = distMan.getSnapshotRepository() != null && mavenProject.getVersion().endsWith( "SNAPSHOT" ) ?
                                          distMan.getSnapshotRepository() :
                                          distMan.getRepository();
        if (deployRepo == null) {
            return null;
        }

        RemoteRepository.Builder remoteRepoBuilder = new RemoteRepository.Builder( deployRepo.getId(), deployRepo.getLayout(), deployRepo.getUrl() )
                .setSnapshotPolicy( new RepositoryPolicy( true,
                                                          RepositoryPolicy.UPDATE_POLICY_DAILY,
                                                          RepositoryPolicy.CHECKSUM_POLICY_WARN ) )
                .setReleasePolicy( new RepositoryPolicy( true,
                                                         RepositoryPolicy.UPDATE_POLICY_ALWAYS,
                                                         RepositoryPolicy.CHECKSUM_POLICY_WARN ) );

        Server server = MavenSettings.getSettings().getServer( deployRepo.getId() );
        if ( server != null ) {
            MavenEmbedder embedder = MavenProjectLoader.newMavenEmbedder( false );
            try {
                Authentication authentication = embedder.getMavenSession().getRepositorySession()
                                                        .getAuthenticationSelector()
                                                        .getAuthentication( remoteRepoBuilder.build() );
                remoteRepoBuilder.setAuthentication( authentication );
            } finally {
                embedder.dispose();
            }
        }

        return remoteRepoBuilder.build();
    }

    /**
     * Deploys a jar on a remote repository.
     *
     * @param repository The remote repository where the kjar will be deployed
     * @param releaseId The releaseId with which the deployment will be made
     * @param jar The jar to be deployed
     * @param pomfile The pom file to be deployed together with the kjar
     */
    public void deployArtifact( RemoteRepository repository,
                                AFReleaseId releaseId,
                                File jar,
                                File pomfile ) {
        Artifact jarArtifact = new DefaultArtifact( releaseId.getGroupId(), releaseId.getArtifactId(), "jar", releaseId.getVersion() );
        jarArtifact = jarArtifact.setFile( jar );

        Artifact pomArtifact = new SubArtifact( jarArtifact, "", "pom" );
        pomArtifact = pomArtifact.setFile( pomfile );

        DeployRequest deployRequest = new DeployRequest();
        deployRequest
                .addArtifact( jarArtifact )
                .addArtifact( pomArtifact )
                .setRepository( repository );

        try {
            aether.getSystem().deploy( aether.getSession(), deployRequest );
        } catch ( DeploymentException e ) {
            throw new RuntimeException( e );
        }
    }

    protected File bytesToFile(AFReleaseId releaseId, byte[] bytes, String extension ) {
        File file = new File( System.getProperty( "java.io.tmpdir" ), toFileName( releaseId, null ) + extension );
        try (FileOutputStream fos = new FileOutputStream( file )) {
            fos.write( bytes );
            fos.flush();
        } catch ( IOException e ) {
            log.error("Error while converting bytes to file for releaseId: {} and extensions {}.", releaseId, extension);
            throw new RuntimeException( e );
        }
        return file;
    }


    /**
     * Installs the given jar into the local repository.
     *
     * @param releaseId The releaseId with which the kjar will be installed
     * @param jarContent A byte array containing the kjar to be installed
     * @param pomContent A byte array containing the pom file to be installed together with the kjar
     */
    public void installArtifact( AFReleaseId releaseId,
                                 byte[] jarContent,
                                 byte[] pomContent ) {
        File jarFile = bytesToFile( releaseId, jarContent, ".jar" );
        File pomFile = bytesToFile( releaseId, pomContent, ".pom" );
        installArtifact( releaseId, jarFile, pomFile );
    }

    /**
     * Installs the given jar into the local repository.
     *
     * @param releaseId The releaseId with which the kjar will be installed
     * @param jar The jar to be installed
     * @param pomfile The pom file to be installed together with the kjar
     */
    public void installArtifact( AFReleaseId releaseId,
                                 File jar,
                                 File pomfile ) {
        Artifact jarArtifact = new DefaultArtifact( releaseId.getGroupId(), releaseId.getArtifactId(), "jar", releaseId.getVersion() );
        jarArtifact = jarArtifact.setFile( jar );

        Artifact pomArtifact = new SubArtifact( jarArtifact, "", "pom" );
        pomArtifact = pomArtifact.setFile( pomfile );

        InstallRequest installRequest = new InstallRequest();
        installRequest
                .addArtifact( jarArtifact )
                .addArtifact( pomArtifact );

        try {
            aether.getSystem().install( aether.getSession(), installRequest );
        } catch (InstallationException e) {
            throw new RuntimeException( e );
        }
    }

    public void deployPomArtifact( String groupId,
                                   String artifactId,
                                   String version,
                                   File pomfile ) {
        Artifact pomArtifact = new DefaultArtifact( groupId, artifactId, "pom", version );
        pomArtifact = pomArtifact.setFile( pomfile );

        DeployRequest deployRequest = new DeployRequest();
        deployRequest
                .addArtifact( pomArtifact )
                .setRepository( aether.getLocalRepository() );

        try {
            aether.getSystem().deploy( aether.getSession(), deployRequest );
        } catch ( DeploymentException e ) {
            throw new RuntimeException( e );
        }
    }

    private static class CollectDependencyVisitor implements DependencyVisitor {

        private final List<DependencyNode> dependencies = new ArrayList<DependencyNode>();

        public boolean visitEnter( DependencyNode node ) {
            dependencies.add( node );
            return true;
        }

        public boolean visitLeave( DependencyNode node ) {
            return true;
        }

        public List<DependencyNode> getDependencies() {
            return dependencies;
        }
    }

    public static String toFileName( AFReleaseId releaseId,
                                     String classifier ) {
        if ( classifier != null ) {
            return releaseId.getArtifactId() + "-" + releaseId.getVersion() + "-" + classifier;
        }

        return releaseId.getArtifactId() + "-" + releaseId.getVersion();
    }
    
    /**
     * Utility method specifically suggested for testing purposes only.
     */
    public void removeLocalArtifact(AFReleaseId releaseId) {
        // Taken by analogy of build-helper-maven-plugin
        Artifact artifact = new DefaultArtifact(releaseId.getGroupId(), releaseId.getArtifactId(), null, releaseId.getVersion());
        
        LocalRepository localRepo = new LocalRepository( getMavenRepositoryConfiguration().getLocalRepository() );
        
        DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
        LocalRepositoryManager localRepositoryManager = aether.getSystem().newLocalRepositoryManager( session, localRepo );
        session.setLocalRepositoryManager( localRepositoryManager );
        session.setOffline( true );
        
        String pathForLocalArtifact = localRepositoryManager.getPathForLocalArtifact(artifact);
        
        File localArtifactDir = new File( localRepo.getBasedir(), pathForLocalArtifact )    // File .jar
                                .getParentFile()                                            // Directory of specific version, corresponding in ReleaseId
                                ;

        if (!localArtifactDir.exists()) {
            log.warn("The expected local maven repo dir for {} does not exist {}, nothing to delete.", releaseId, localArtifactDir);
            return;
        }
        log.info("Erasing directory from local maven repository {}", localArtifactDir);
        try {
            FileUtils.deleteDirectory(localArtifactDir);
        } catch (Exception e) {
            log.error("Error while trying to erase directory from local maven repository {}", localArtifactDir);
        }
    }
}