All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.fizzed.blaze.ivy.IvyDependencyResolver Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Fizzed, Inc.
 *
 * 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 com.fizzed.blaze.ivy;

import com.fizzed.blaze.core.Dependency;
import com.fizzed.blaze.core.DependencyResolveException;
import com.fizzed.blaze.core.DependencyResolver;
import com.fizzed.blaze.Config;
import com.fizzed.blaze.Context;
import com.fizzed.blaze.internal.ConfigHelper;
import com.fizzed.blaze.internal.DependencyHelper;
import java.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetAddress;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import static java.util.Optional.ofNullable;
import java.util.Set;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.cache.ArtifactOrigin;
import org.apache.ivy.core.cache.CacheDownloadOptions;
import org.apache.ivy.core.cache.CacheMetadataOptions;
import org.apache.ivy.core.cache.CacheResourceOptions;
import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
import org.apache.ivy.core.cache.ModuleDescriptorWriter;
import org.apache.ivy.core.cache.RepositoryCacheManager;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.matcher.PatternMatcher;
import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
import org.apache.ivy.plugins.repository.Repository;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.repository.ResourceDownloader;
import org.apache.ivy.plugins.resolver.ChainResolver;
import org.apache.ivy.plugins.resolver.FileSystemResolver;
import org.apache.ivy.plugins.resolver.IBiblioResolver;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.DefaultMessageLogger;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.url.CredentialsStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Dependency resolver implemented with Ivy.
 */
public class IvyDependencyResolver implements DependencyResolver {
    static private final Logger log = LoggerFactory.getLogger(IvyDependencyResolver.class);
    
    @Override
    public List resolve(
            Context context,
            List resolvedDependencies,
            List dependencies) throws DependencyResolveException, ParseException, IOException {
        
        Objects.requireNonNull(context, "context may not be null");
        Objects.requireNonNull(resolvedDependencies, "resolvedDependencies may not be null");
        Objects.requireNonNull(dependencies, "dependencies may not be null");
        
        log.trace("Already resolved dependencies {}", resolvedDependencies);
        log.trace("Dependencies to resolve {}", dependencies);
        
        // customize logging (ivy uses a terrible approach)
        Message.setDefaultLogger(new FilteringIvyLogger());

        
        //
        // maven settings file?
        //
        
        MavenSettings mavenSettings = null;
        Path mavenSettingsFile = context.userDir().resolve(".m2/settings.xml");
        if (Files.exists(mavenSettingsFile)) {
            try {
                mavenSettings = MavenSettings.parse(mavenSettingsFile);
                log.debug("Using maven settings {}", mavenSettingsFile);
            }
            catch (Exception e) {
                log.error("Unable to cleanly parse {}", mavenSettingsFile, e);
            }
        }
        
        //
        // load up any credentials?
        //
        
        final IvyAuthenticator authenticator = new IvyAuthenticator();
        
        Authenticator.setDefault(authenticator);
        
        
        // creates an Ivy instance with settings
        Ivy ivy = Ivy.newInstance();
        IvySettings ivySettings = ivy.getSettings();
        
        // TODO: ivy truly is a piece of junk - unable to figure out how to NOT
        // cache a SNAPSHOT version so this is the workaround for now - allowing you
        // to delete the entire cache
        if (context.config().value(Config.KEY_DEPENDENCY_CLEAN, Boolean.class).getOr(Config.DEFAULT_DEPENDENCY_CLEAN)) {
            log.info("Cleaning dependency cache...");
            ivy.getResolutionCacheManager().clean();
        }
        
        // remove any SNAPSHOT artifacts from cache
        dependencies.stream().forEach((d) -> {
            // cache pattern ~/.ivy2/cache/com.fizzed/blaze-ssh/jars/blaze-ssh-0.13.1-SNAPSHOT.jar
            if (d.getVersion().endsWith("-SNAPSHOT")) {
                Path cachedFile = context.userDir().resolve(".ivy2/cache/" + d.getGroupId() + "/" + d.getArtifactId() + "/jars/" + d.getArtifactId() + "-" + d.getVersion() + ".jar");
                if (Files.exists(cachedFile)) {
                    log.trace("Deleting cached snapshot dependency {}", cachedFile);
                    try {
                        Files.delete(cachedFile);
                    } catch (IOException e) {
                        log.trace("Unable to delete", e.getMessage());
                    }
                }
            }
        });
        
        /**
        DefaultRepositoryCacheManager cacheManager = new DefaultRepositoryCacheManager();
        cacheManager.setChangingPattern(".*-SNAPSHOT");
        cacheManager.setChangingMatcher(PatternMatcher.EXACT_OR_REGEXP);
        cacheManager.setCheckmodified(true);
        
        ivySettings.addRepositoryCacheManager(cacheManager);
        */
//        DefaultRepositoryCacheManager cacheManager
//            = (DefaultRepositoryCacheManager)ivySettings.getRepositoryCacheManagers()[0];
//        cacheManager.clean();
        
        // maven central resolver
        IBiblioResolver mavenCentralResolver = new IBiblioResolver();
        mavenCentralResolver.setM2compatible(true);
        mavenCentralResolver.setName("mavenCentral");
        mavenCentralResolver.setUseMavenMetadata(true);
        
        // any additional upstream repositories?
        final List additionalResolvers = new ArrayList<>();
        
        final List repositoryUrls = context != null && context.config() != null
            ? context.config().valueList(Config.KEY_REPOSITORIES).orNull() : null;
        
        if (repositoryUrls != null) {
            for (String repositoryUrl : repositoryUrls) {
                MavenRepositoryUrl mru = MavenRepositoryUrl.parse(repositoryUrl);
                
                // are there credentials for this too?
                MavenServer mavenServer = ofNullable(mavenSettings)
                    .map(v -> v.findServerById(mru.getId()))
                    .orElse(null);
                
                log.debug("Adding extra maven repo: {}->{}", mru.getId(), mru.getUrl());
                
                if (mavenServer != null) {
                    String host = mru.getUrl().getHost();
                    log.debug("Using maven settings credentials for id={}, host={}, username={}",
                        mru.getId(), host, mavenServer.getUsername());
                    
                    authenticator.addCredentials(host, mavenServer.getUsername(), mavenServer.getPassword());
                }
                
                IBiblioResolver additionalResolver = new IBiblioResolver();
                additionalResolver.setM2compatible(true);
                additionalResolver.setName(mru.getId());
                additionalResolver.setUseMavenMetadata(true);
                additionalResolver.setRoot(mru.getUrl().toString());
                additionalResolvers.add(additionalResolver);
            }
        }
        
        // maven local resolver
        FileSystemResolver mavenLocalResolver = new FileSystemResolver();
        mavenLocalResolver.setName("mavenLocal");
        mavenLocalResolver.setLocal(true);
        
        // it'd be sweet to use the context here, eh?
        //File userHomeDir = new File(System.getProperty("user.home"));
        File userHomeDir = context.userDir().toFile();
        
        mavenLocalResolver.addArtifactPattern(userHomeDir.getAbsolutePath() + "/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision](-[classifier]).[ext]");
        mavenLocalResolver.addIvyPattern(userHomeDir.getAbsolutePath() + "/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision].pom");
        mavenLocalResolver.setM2compatible(true);
        //mavenLocalResolver.setForce(true);
        mavenLocalResolver.setChangingMatcher(PatternMatcher.REGEXP);
        mavenLocalResolver.setChangingPattern(".*-SNAPSHOT");
        mavenLocalResolver.setCheckmodified(true);
        
        // chain resolvers together
        ChainResolver chainResolver = new ChainResolver();
        chainResolver.setName("default");
        chainResolver.add(mavenLocalResolver);
        chainResolver.add(mavenCentralResolver);
        chainResolver.setDual(true);
        additionalResolvers.forEach(v -> {
            chainResolver.add(v);
        });
        
        //chainResolver.setChangingMatcher(PatternMatcher.REGEXP);
        //chainResolver.setChangingPattern(".*-SNAPSHOT");
        //chainResolver.setCheckmodified(true);
        
        ivySettings.addResolver(chainResolver);
        ivy.getSettings().setDefaultResolver(chainResolver.getName());
        
        // fake uber module (this project)
        DefaultModuleDescriptor md =
                DefaultModuleDescriptor.newDefaultInstance(
                        ModuleRevisionId.newInstance("blaze", "blaze", "resolver"));

        // build list of transitive dependencies to resolve
        dependencies.stream()
            .map((d) -> {
                boolean isChanging = d.getVersion().endsWith("-SNAPSHOT");
                return new DefaultDependencyDescriptor(md,
                    ModuleRevisionId.newInstance(d.getGroupId(), d.getArtifactId(), d.getVersion()), false, isChanging, true);
            })
            .forEach((dd) -> {
                dd.addDependencyConfiguration("default", "default");
                md.addDependency(dd);
            });

        String[] confs = new String[] { "default" };

        ResolveOptions resolveOptions = new ResolveOptions().setConfs(confs);

        resolveOptions.setRefresh(true);
        
        //resolveOptions.setValidate(true);
        //resolveOptions.setCheckIfChanged(true);
        //resolveOptions.setRefresh(true);
        //resolveOptions.setValidate(true);
        //resolveOptions.setTransitive(true);
        
        ResolveReport report = ivy.resolve(md, resolveOptions);
        
        if (report.hasError()) {
            // grab first message
            String firstMessage = (String)report.getAllProblemMessages().get(0);
            throw new DependencyResolveException(firstMessage);
        }        

        
        // filter out artifacts that were already resolved and added to classpath
        final Set alreadyResolved = DependencyHelper.toGroupArtifactSet(resolvedDependencies);
        
        // ivy triggers duplicate exclusion calls, this will make sure we only display it once
        final Set alreadyExcluded = new HashSet<>();
        
        // filter and build list of local jar files to use in classpath
        List jarFiles = new ArrayList<>();
        for (ArtifactDownloadReport adr : report.getAllArtifactsReports()) {
            Artifact artifact = adr.getArtifact();
            
            String key = artifact.getModuleRevisionId().getOrganisation()
                            + ":" + artifact.getModuleRevisionId().getName();
            
            log.trace("Potentially filtering {} with key {}", artifact, key);
                
            if (alreadyResolved.contains(key)) {
                log.debug("Excluding {} (already added to classpath)", artifact);
            } else if (adr.getLocalFile() != null) {
                jarFiles.add(adr.getLocalFile());
            }
        }

        return jarFiles;
    }
    
    public class FilteringIvyLogger extends DefaultMessageLogger {
        
        public FilteringIvyLogger() {
            super(Message.MSG_VERBOSE);
        }
        
        @Override
        public void log(String msg, int level) {
            // lots of smart filtering for only the useful messages from ivy
            String trimmedMessage = msg.trim();
            
            if (level < Message.MSG_INFO) {
                log.error(trimmedMessage);
            } else if (trimmedMessage.startsWith("downloading ")) {
                // uppercase the d to match our other logging
                log.info("D{}", trimmedMessage.substring(1));
            } else {
                if (ConfigHelper.isSuperDebugEnabled()) {
                    log.trace(trimmedMessage);
                }
            }
        }

        @Override
        public void rawlog(String msg, int level) {
            log(msg, level);
        }

        @Override
        public void doProgress() {
            //System.out.print(".");
        }

        @Override
        public void doEndProgress(String msg) {
            //System.out.println(msg);
        }
    }
    
    public class DebugIvyLogger extends DefaultMessageLogger {
        
        public DebugIvyLogger() {
            super(Message.MSG_VERBOSE);
        }
        
        @Override
        public void log(String msg, int level) {
            log.debug(msg);
        }

        @Override
        public void rawlog(String msg, int level) {
            log(msg, level);
        }

        @Override
        public void doProgress() {
            // do nothing
        }

        @Override
        public void doEndProgress(String msg) {
            // do nothing
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy