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

ca.vanzyl.provisio.MavenProvisioner Maven / Gradle / Ivy

/*
 * Copyright (C) 2015-2024 Jason van Zyl
 *
 * 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 ca.vanzyl.provisio;

import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

import ca.vanzyl.provisio.action.artifact.WriteToDiskAction;
import ca.vanzyl.provisio.model.ArtifactSet;
import ca.vanzyl.provisio.model.Directory;
import ca.vanzyl.provisio.model.FileSet;
import ca.vanzyl.provisio.model.ProvisioArtifact;
import ca.vanzyl.provisio.model.ProvisioningAction;
import ca.vanzyl.provisio.model.ProvisioningContext;
import ca.vanzyl.provisio.model.ProvisioningRequest;
import ca.vanzyl.provisio.model.ProvisioningResult;
import ca.vanzyl.provisio.model.Resource;
import ca.vanzyl.provisio.model.ResourceSet;
import ca.vanzyl.provisio.model.Runtime;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.ArtifactType;
import org.eclipse.aether.artifact.DefaultArtifactType;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.graph.DependencyFilter;
import org.eclipse.aether.graph.Exclusion;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.util.artifact.JavaScopes;
import org.eclipse.aether.util.filter.AndDependencyFilter;
import org.eclipse.aether.util.filter.ExclusionsDependencyFilter;
import org.eclipse.aether.util.filter.ScopeDependencyFilter;
import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MavenProvisioner {
    private static final Logger logger = LoggerFactory.getLogger(MavenProvisioner.class);

    private final RepositorySystem repositorySystem;
    private final RepositorySystemSession repositorySystemSession;
    private final List remoteRepositories;

    public MavenProvisioner(
            RepositorySystem repositorySystem,
            RepositorySystemSession repositorySystemSession,
            List remoteRepositories) {
        this.repositorySystem = repositorySystem;
        this.repositorySystemSession = repositorySystemSession;
        this.remoteRepositories = remoteRepositories;
    }

    public ProvisioningResult provision(ProvisioningRequest request) throws Exception {
        Instant now = Instant.now();
        ProvisioningResult result = new ProvisioningResult(request);
        ProvisioningContext context = new ProvisioningContext(request, result);

        processArtifactSets(context);
        processResourceSets(context);
        processFileSets(context);
        processRuntimeActions(context);
        logger.info(
                "Provisioning done in {} sec (total of {} files processed, {} archives produced)",
                Duration.between(now, Instant.now()).toSeconds(),
                context.laidDownFiles(),
                result.getArchives() != null ? result.getArchives().size() : 0);

        return result;
    }

    public Set resolveArtifacts(ProvisioningRequest request) {
        ProvisioningContext context = new ProvisioningContext(request, null);

        Set result = new HashSet<>();
        for (ArtifactSet artifactSet : context.getRequest().getRuntimeModel().getArtifactSets()) {
            result.addAll(resolveArtifactSet(context, artifactSet));
        }

        return result;
    }

    public List getArtifacts(ProvisioningRequest request) {
        ProvisioningContext context = new ProvisioningContext(request, null);

        List result = new ArrayList<>();
        for (ArtifactSet artifactSet : context.getRequest().getRuntimeModel().getArtifactSets()) {
            result.addAll(getArtifactsFromSet(context, artifactSet));
        }

        return result;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // ArtifactSets
    //
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private void processArtifactSets(ProvisioningContext context) throws Exception {
        for (ArtifactSet artifactSet : context.getRequest().getRuntimeModel().getArtifactSets()) {
            processArtifactSet(context, artifactSet);
        }
    }

    private void processArtifactSet(ProvisioningContext context, ArtifactSet artifactSet) throws Exception {
        resolveArtifactSetOutputDirectory(context, artifactSet);
        resolveArtifactSet(context, artifactSet);
        processArtifactsWithActions(context, artifactSet);
        processArtifactSetActions(context, artifactSet);
        //
        // Child ArtifactSets
        //
        if (artifactSet.getArtifactSets() != null) {
            for (ArtifactSet childArtifactSet : artifactSet.getArtifactSets()) {
                processArtifactSet(context, childArtifactSet);
            }
        }
    }

    private void resolveArtifactSetOutputDirectory(ProvisioningContext context, ArtifactSet artifactSet) {
        ArtifactSet parent = artifactSet.getParent();
        if (parent != null) {
            artifactSet.setOutputDirectory(new File(parent.getOutputDirectory(), artifactSet.getDirectory()));
        } else {
            if (artifactSet.getDirectory().equals("root")
                    || artifactSet.getDirectory().equals("/")) {
                artifactSet.setOutputDirectory(context.getRequest().getOutputDirectory());
            } else {
                artifactSet.setOutputDirectory(
                        new File(context.getRequest().getOutputDirectory(), artifactSet.getDirectory()));
            }
        }
        if (!artifactSet.getOutputDirectory().exists()) {
            artifactSet.getOutputDirectory().mkdirs();
        }
    }

    //
    // Process actions that apply across artifact sets
    //
    private void processArtifactSetActions(ProvisioningContext context, ArtifactSet artifactSet) throws Exception {}

    //
    // Process actions that apply to artifacts
    //
    private void processArtifactsWithActions(ProvisioningContext context, ArtifactSet artifactSet) throws Exception {
        for (ProvisioArtifact artifact : artifactSet.getResolvedArtifacts()) {
            if (artifact.getActions() != null) {
                for (ProvisioningAction action : artifact.getActions()) {
                    configureArtifactAction(artifact, action, artifactSet.getOutputDirectory());
                    action.execute(context);
                }
            } else {
                ProvisioningAction action = new WriteToDiskAction(artifact, artifactSet.getOutputDirectory());
                action.execute(context);
            }
        }
    }

    //
    // Resolving artifact sets
    //
    private Set resolveArtifactSet(ProvisioningContext context, ArtifactSet artifactSet) {
        //
        // Resolve versions
        //
        List artifacts;
        if (artifactSet.getReference() != null) {
            Runtime runtime = context.getRequest().getRuntimeModel();
            if (runtime.getArtifactSetReferences() == null) {
                throw new RuntimeException(String.format(
                        "The reference '%s' is being requested but the artifactSet references are null.",
                        artifactSet.getReference()));
            }
            ArtifactSet referenceArtifactSet =
                    runtime.getArtifactSetReferences().get(artifactSet.getReference());
            if (referenceArtifactSet == null) {
                throw new RuntimeException(
                        String.format("The is no '%s' artifactSet reference available.", artifactSet.getReference()));
            }
            artifacts = referenceArtifactSet.getArtifacts();
        } else {
            artifacts = artifactSet.getArtifacts();
        }

        //
        // If the user happens to create an artifactSet in the configuration and then leaves it empty, like the "ext"
        // configuration below:
        //
        // 
        //   
        //   
        //   
        //   
        // 
        //
        Set parentResolved =
                artifactSet.getParent() != null ? artifactSet.getParent().getResolvedArtifacts() : new HashSet<>();
        Set resolvedArtifacts =
                resolveArtifacts(context, artifacts, parentResolved, artifactSet.getExcludes());

        artifactSet.setResolvedArtifacts(resolvedArtifacts);

        //
        // Set Parent = [a, b, c]
        // Set Child = [a, b, c, d, e, f]
        //
        // First we want to collect all the dependencies that an ArtifactSet may yield.
        //
        // We want to use this first in a calculation of overlapping dependencies between ArtifactSets that have a
        // parent-->child relationship. We are making the assumption that a
        // classloader relationship will be setup along the lines of the parent-->child relationship. So we only want to
        // place in the child's directory the artifacts that
        // are not present in the parent.
        //
        ArtifactSet parent = artifactSet.getParent();
        if (parent != null) {
            Set parentArtifacts = artifactSet.getParent().getResolvedArtifacts();
            //
            // contained by childArtifacts and not contained in parentArtifacts
            //
            Set childResolvedArtifacts = resolvedArtifacts.stream()
                    .filter(a -> !parentArtifacts.contains(a))
                    .collect(toSet());
            artifactSet.setResolvedArtifacts(childResolvedArtifacts);
        } else {
            artifactSet.setResolvedArtifacts(resolvedArtifacts);
        }

        return resolvedArtifacts;
    }

    private List getArtifactsFromSet(ProvisioningContext context, ArtifactSet artifactSet) {
        List artifacts;
        if (artifactSet.getReference() != null) {
            Runtime runtime = context.getRequest().getRuntimeModel();
            if (runtime.getArtifactSetReferences() == null) {
                throw new RuntimeException(String.format(
                        "The reference '%s' is being requested but the artifactSet references are null.",
                        artifactSet.getReference()));
            }
            ArtifactSet referenceArtifactSet =
                    runtime.getArtifactSetReferences().get(artifactSet.getReference());
            if (referenceArtifactSet == null) {
                throw new RuntimeException(
                        String.format("The is no '%s' artifactSet reference available.", artifactSet.getReference()));
            }
            artifacts = referenceArtifactSet.getArtifacts();
        } else {
            artifacts = artifactSet.getArtifacts();
        }
        if (artifactSet.getArtifactSets() != null) {
            for (ArtifactSet childSet : artifactSet.getArtifactSets()) {
                artifacts.addAll(getArtifactsFromSet(context, childSet));
            }
        }
        return artifacts;
    }

    public Set resolveArtifact(ProvisioningContext context, ProvisioArtifact artifact) {
        return resolveArtifacts(context, Collections.singletonList(artifact), new HashSet<>(), new ArrayList<>());
    }

    private Set resolveArtifacts(
            ProvisioningContext context,
            List artifacts,
            Set managedArtifacts,
            List excludes) {
        CollectRequest request = new CollectRequest();
        //
        // We need to defend against a null set
        //
        if (artifacts == null) {
            artifacts = new ArrayList<>();
        }

        if (managedArtifacts == null) {
            managedArtifacts = new HashSet<>();
        }

        //
        // Artifacts that have been handed to us that have been resolved or provided locally
        //
        List providedArtifacts = new ArrayList<>();
        for (ProvisioArtifact artifact : artifacts) {
            if (artifact.getReference() != null) {
                Runtime runtime = context.getRequest().getRuntimeModel();
                if (runtime.getArtifactReferences() == null) {
                    throw new RuntimeException(String.format(
                            "The reference '%s' is being requested but the artifact references are null.",
                            artifact.getReference()));
                }
                ProvisioArtifact referenceArtifact =
                        runtime.getArtifactReferences().get(artifact.getReference());
                if (referenceArtifact == null) {
                    throw new RuntimeException(
                            String.format("The is no '%s' artifact reference available.", artifact.getReference()));
                }
                // We need a way to copy the artifact
                if (artifact.getName() != null) {
                    referenceArtifact.setName(artifact.getName());
                }
                providedArtifacts.add(referenceArtifact);
                continue;
            }
            if (artifact.getFile() != null) {
                providedArtifacts.add(artifact);
                continue;
            }
            //
            // :[:[:]]:
            //
            // ArtifactType
            //
            // String id
            // String extension
            // String classifier
            // String language
            // boolean constitutesBuildPath
            // boolean includesDependencies (self-contained so don't attempt to download anything described in the
            // dependency descriptor (POM)
            //
            ArtifactType type = null;

            if (artifact.getExtension().equals("tar.gz")) {
                type = new DefaultArtifactType("tar.gz", "tar.gz", "", "packaging", false, true);
            } else if (artifact.getExtension().equals("zip")) {
                type = new DefaultArtifactType("zip", "zip", "", "packaging", false, true);
            } else if (artifact.getExtension().equals("war")) {
                type = new DefaultArtifactType("war", "war", "", "packaging", false, true);
            } else if (artifact.getExtension().equals("hpi")) {
                type = new DefaultArtifactType("hpi", "hpi", "", "packaging", false, true);
            } else if (artifact.getExtension().equals("jpi")) {
                type = new DefaultArtifactType("jpi", "jpi", "", "packaging", false, true);
            }
            //
            // TODO: Inside Maven this is not null but it should be ??? There is nothing in the type registry for it.
            //
            else if (getArtifactType(artifact.getExtension()) == null) {
                //
                // We are dealing with artifacts that have no entry in the default artifact type registry. These are
                // typically
                // archive types and we only want to retrieve the artifact itself so we set the artifact type
                // accordingly.
                //
                type = new DefaultArtifactType(
                        artifact.getExtension(), artifact.getExtension(), "", "unknown", false, true);
            }
            if (type != null) {
                artifact = artifact.setProperties(type.getProperties());
            }
            Dependency dependency = new Dependency(artifact, "runtime");
            //
            // Equivalent of something like:
            //
            // 
            //   ...
            //   
            //     
            //       org.apache.maven
            //       maven-core
            //       3.3.9
            //       
            //         
            //           org.codehaus.plexus
            //           plexus-utils
            //         
            //       
            //     
            //   
            // 
            //
            if (artifact.getExclusions() != null) {
                Set exclusions = new HashSet<>();
                for (String exclusion : artifact.getExclusions()) {
                    String[] ga = StringUtils.split(exclusion, ":");
                    if (ga.length == 2) {
                        exclusions.add(new Exclusion(ga[0], ga[1], "*", "*"));
                    } else if (ga.length == 1) {
                        exclusions.add(new Exclusion(null, ga[0], "*", "*"));
                    }
                }
                dependency = dependency.setExclusions(exclusions);
            }
            // request.setRoot(dependency);
            request.addDependency(dependency);
        }
        //
        // Add an exclude filter if necessary
        //
        DependencyFilter systemScopeFilter =
                new ScopeDependencyFilter(JavaScopes.SYSTEM, JavaScopes.PROVIDED, JavaScopes.TEST);
        DependencyRequest dependencyRequest = new DependencyRequest(request, null);
        if (excludes != null) {
            List exclusions = new ArrayList<>();
            for (ca.vanzyl.provisio.model.Exclusion exclusion : excludes) {
                exclusions.add(exclusion.getId());
            }
            DependencyFilter exclusionsFilter = new ExclusionsDependencyFilter(exclusions);
            dependencyRequest.setFilter(new AndDependencyFilter(exclusionsFilter, systemScopeFilter));
        } else {
            dependencyRequest.setFilter(systemScopeFilter);
        }

        for (String coordinate : context.getRequest().getManagedDependencies()) {
            Artifact artifact = new ProvisioArtifact(coordinate);
            request.addManagedDependency(new Dependency(artifact, "runtime"));
        }

        //
        // Treat the parent's resolved artifacts as set of managed dependencies for the child
        //
        for (Artifact artifact : managedArtifacts) {
            request.addManagedDependency(new Dependency(artifact, "runtime"));
        }

        List resultArtifacts;
        try {
            resultArtifacts = resolveArtifacts(dependencyRequest);
            //
            // We need to add back in the artifacts that have already been provided
            //
            resultArtifacts.addAll(providedArtifacts);
        } catch (DependencyResolutionException e) {
            throw new ProvisioningException(e.getMessage(), e);
        }

        Map artifactMapKeyedByGa =
                new HashMap<>(artifacts.stream().collect(toMap(this::artifactKey, a -> a)));
        Set resolvedArtifacts = new HashSet<>();
        for (Artifact a : resultArtifacts) {
            String key = artifactKey(a);
            if (a instanceof ProvisioArtifact) {
                artifactMapKeyedByGa.put(key, (ProvisioArtifact) a);
                resolvedArtifacts.add((ProvisioArtifact) a);
            } else {
                ProvisioArtifact provisioArtifact = artifactMapKeyedByGa.get(key);
                if (provisioArtifact == null) {
                    ProvisioArtifact resolvedArtifact = new ProvisioArtifact(a);
                    artifactMapKeyedByGa.put(key, resolvedArtifact);
                    resolvedArtifacts.add(resolvedArtifact);
                } else {
                    ProvisioArtifact resolvedArtifact = provisioArtifact.setFile(a.getFile());
                    artifactMapKeyedByGa.put(key, resolvedArtifact);
                    resolvedArtifacts.add(resolvedArtifact);
                }
            }
        }

        return resolvedArtifacts;
    }

    /**
     * Internal keying of artifacts.
     */
    private String artifactKey(Artifact artifact) {
        return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getClassifier();
    }

    private List resolveArtifacts(DependencyRequest request) throws DependencyResolutionException {
        //
        // We are attempting to encapsulate everything about resolution with this library. The dependency request
        // requires
        // the collect request to have repositories set but this is all injected within this component so we have to set
        // them.
        //
        CollectRequest collectRequest = request.getCollectRequest();
        if (collectRequest.getRepositories() == null
                || collectRequest.getRepositories().isEmpty()) {
            for (RemoteRepository remoteRepository : remoteRepositories) {
                collectRequest.addRepository(remoteRepository);
            }
        }
        DependencyResult result = repositorySystem.resolveDependencies(repositorySystemSession, request);

        if (logger.isDebugEnabled() && result.getRoot() != null) {
            logger.debug("MavenProvisioner -- Collection result for {}", request.getCollectRequest());
            result.getRoot().accept(new DependencyGraphDumper(logger::debug));
        }

        List artifacts = new ArrayList<>();
        for (ArtifactResult ar : result.getArtifactResults()) {
            artifacts.add(ar.getArtifact());
        }
        return artifacts;
    }

    private ArtifactType getArtifactType(String typeId) {
        return repositorySystemSession.getArtifactTypeRegistry().get(typeId);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // ResourceSets
    //
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private void processResourceSets(ProvisioningContext context) throws Exception {
        List resourceSets = context.getRequest().getRuntime().getResourceSets();
        if (resourceSets != null) {
            for (ResourceSet resourceSet : resourceSets) {
                for (Resource resource : resourceSet.getResources()) {
                    File source = new File(resource.getName());
                    if (!source.exists()) {
                        throw new RuntimeException(String.format("The specified file %s does not exist.", source));
                    }
                    File target = new File(context.getRequest().getOutputDirectory(), source.getName());
                    copy(source, target);
                }
            }
        }
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // FileSets
    //
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private void processFileSets(ProvisioningContext context) throws Exception {
        List fileSets = context.getRequest().getRuntime().getFileSets();
        if (fileSets != null) {
            for (FileSet fileSet : fileSets) {
                //
                // Files
                //
                for (ca.vanzyl.provisio.model.File file : fileSet.getFiles()) {
                    if (file.getTouch() != null) {
                        File target = new File(
                                new File(context.getRequest().getOutputDirectory(), fileSet.getDirectory()),
                                file.getTouch());
                        if (!target.getParentFile().exists()) {
                            target.getParentFile().mkdirs();
                        }
                        Files.createFile(target.toPath());
                    } else {
                        File source = new File(file.getPath());
                        if (!source.exists()) {
                            throw new RuntimeException(String.format("The specified file %s does not exist.", source));
                        }
                        File target = new File(
                                new File(context.getRequest().getOutputDirectory(), fileSet.getDirectory()),
                                source.getName());
                        copy(source, target);
                    }
                }
                //
                // Directories
                //
                for (Directory directory : fileSet.getDirectories()) {
                    File sourceDirectory = new File(directory.getPath());
                    File targetDirectory = new File(context.getRequest().getOutputDirectory(), fileSet.getDirectory());
                    copyDirectoryStructure(sourceDirectory, targetDirectory, directory);
                }
            }
        }
    }

    private void copyDirectoryStructure(File sourceDirectory, File targetDirectory, Directory directory)
            throws IOException {
        List includes = directory.getIncludes();
        List excludes = directory.getExcludes();

        String includesString = null;
        if (includes != null && !includes.isEmpty()) {
            includesString = String.join(",", includes);
        }
        String excludesString = null;
        if (excludes != null && !excludes.isEmpty()) {
            excludesString = String.join(",", excludes);
        }

        if (directory.isFlatten()) {
            List paths = FileUtils.getFiles(sourceDirectory, includesString, excludesString);
            for (File source : paths) {
                File target = new File(targetDirectory, source.getName());
                copy(source, target);
            }
        } else {
            List relativePaths = FileUtils.getFileNames(sourceDirectory, includesString, excludesString, false);
            for (String relativePath : relativePaths) {
                File source = new File(sourceDirectory, relativePath);
                File target = new File(targetDirectory, relativePath);
                copy(source, target);
            }
        }
    }

    private void copy(File source, File target) throws IOException {
        if (!target.getParentFile().exists()) {
            target.getParentFile().mkdirs();
        }
        Files.copy(
                source.toPath(),
                target.toPath(),
                StandardCopyOption.COPY_ATTRIBUTES,
                StandardCopyOption.REPLACE_EXISTING);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Actions
    //
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private void processRuntimeActions(ProvisioningContext context) throws Exception {
        List runtimeActions =
                context.getRequest().getRuntime().getActions();
        if (runtimeActions != null) {
            for (ProvisioningAction action : runtimeActions) {
                configureArtifactSetAction(action, context.getRequest().getOutputDirectory());
                action.execute(context);
            }
        }
    }

    // Configuring Actions, this needs to change

    Lookup lookup = new Lookup();

    private void configureArtifactSetAction(ProvisioningAction provisioningAction, File outputDirectory) {
        lookup.setObjectProperty(provisioningAction, "fileSetDirectory", outputDirectory);
        lookup.setObjectProperty(provisioningAction, "outputDirectory", outputDirectory);
        lookup.setObjectProperty(provisioningAction, "runtimeDirectory", outputDirectory);
        lookup.setObjectProperty(provisioningAction, "provisioner", this);
    }

    private void configureArtifactAction(
            ProvisioArtifact artifact, ProvisioningAction provisioningAction, File outputDirectory) {
        lookup.setObjectProperty(provisioningAction, "artifact", artifact);
        lookup.setObjectProperty(provisioningAction, "fileSetDirectory", outputDirectory);
        lookup.setObjectProperty(provisioningAction, "outputDirectory", outputDirectory);
        lookup.setObjectProperty(provisioningAction, "provisioner", this);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy