com.thoughtworks.go.server.service.dd.FanInGraph.java Source code

Java tutorial

Introduction

Here is the source code for com.thoughtworks.go.server.service.dd.FanInGraph.java

Source

/*
 * Copyright 2019 ThoughtWorks, 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.thoughtworks.go.server.service.dd;

import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.CruiseConfig;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.config.materials.ScmMaterialConfig;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig;
import com.thoughtworks.go.domain.MaterialRevision;
import com.thoughtworks.go.domain.MaterialRevisions;
import com.thoughtworks.go.domain.PipelineTimelineEntry;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.presentation.pipelinehistory.PipelineInstanceModel;
import com.thoughtworks.go.server.dao.PipelineDao;
import com.thoughtworks.go.server.domain.PipelineTimeline;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.service.MaterialConfigConverter;
import com.thoughtworks.go.server.service.NoCompatibleUpstreamRevisionsException;
import com.thoughtworks.go.server.service.NoModificationsPresentForDependentMaterialException;
import com.thoughtworks.go.util.Pair;
import com.thoughtworks.go.util.SystemEnvironment;
import org.apache.commons.collections4.CollectionUtils;

import java.util.*;

import static com.thoughtworks.go.server.service.dd.DependencyFanInNode.RevisionAlteration.ALL_OPTIONS_EXHAUSTED;

public class FanInGraph {
    private static final int REVISION_BUFFER_SIZE = 5;

    private final PipelineDao pipelineDao;
    private final CruiseConfig cruiseConfig;
    private final MaterialRepository materialRepository;
    private MaterialConfigConverter materialConfigConverter;

    private final Map<String, FanInNode> nodes = new HashMap<>();
    private final Map<String, MaterialConfig> fingerprintScmMaterialMap = new HashMap<>();
    private final Map<String, DependencyMaterialConfig> fingerprintDepMaterialMap = new HashMap<>();
    private final Map<DependencyMaterialConfig, Set<String>> dependencyMaterialFingerprintMap = new HashMap<>();

    private final DependencyFanInNode root;
    private final CaseInsensitiveString pipelineName;
    private final SystemEnvironment systemEnvironment;
    private FanInEventListener fanInEventListener;

    public FanInGraph(CruiseConfig cruiseConfig, CaseInsensitiveString root, MaterialRepository materialRepository,
            PipelineDao pipelineDao, SystemEnvironment systemEnvironment,
            MaterialConfigConverter materialConfigConverter) {
        this.cruiseConfig = cruiseConfig;
        this.materialRepository = materialRepository;
        this.pipelineDao = pipelineDao;
        this.pipelineName = root;
        this.systemEnvironment = systemEnvironment;
        this.materialConfigConverter = materialConfigConverter;

        PipelineConfig target = cruiseConfig.pipelineConfigByName(root);
        this.root = (DependencyFanInNode) FanInNodeFactory
                .create(new DependencyMaterialConfig(target.name(), target.get(0).name()));

        buildGraph(target);
    }

    private void buildGraph(PipelineConfig target) {
        nodes.put(this.root.materialConfig.getFingerprint(), this.root);
        final Set<String> scmMaterials = new HashSet<>();
        buildRestOfTheGraph(this.root, target, scmMaterials, new HashSet<>());
        dependencyMaterialFingerprintMap.put((DependencyMaterialConfig) this.root.materialConfig, scmMaterials);
    }

    private void buildRestOfTheGraph(DependencyFanInNode root, PipelineConfig target, Set<String> scmMaterialSet,
            Set<DependencyMaterialConfig> visitedNodes) {
        for (MaterialConfig material : target.materialConfigs()) {
            FanInNode node = createNode(material);
            root.children.add(node);
            node.parents.add(root);
            if (node instanceof DependencyFanInNode) {
                DependencyMaterialConfig dependencyMaterial = (DependencyMaterialConfig) material;
                fingerprintDepMaterialMap.put(dependencyMaterial.getFingerprint(), dependencyMaterial);
                handleDependencyMaterial(scmMaterialSet, dependencyMaterial, (DependencyFanInNode) node,
                        visitedNodes);
            } else {
                handleScmMaterial(scmMaterialSet, material);
            }
        }
    }

    private void handleScmMaterial(Set<String> scmMaterialSet, MaterialConfig material) {
        final String fingerprint = material.getFingerprint();
        scmMaterialSet.add(fingerprint);
        fingerprintScmMaterialMap.put(fingerprint, material);
    }

    private void handleDependencyMaterial(Set<String> scmMaterialSet, DependencyMaterialConfig depMaterial,
            DependencyFanInNode node, Set<DependencyMaterialConfig> visitedNodes) {
        if (visitedNodes.contains(depMaterial)) {
            scmMaterialSet.addAll(dependencyMaterialFingerprintMap.get(depMaterial));
            return;
        }
        visitedNodes.add(depMaterial);

        final Set<String> scmMaterialFingerprintSet = new HashSet<>();
        buildRestOfTheGraph(node, cruiseConfig.pipelineConfigByName(depMaterial.getPipelineName()),
                scmMaterialFingerprintSet, visitedNodes);
        dependencyMaterialFingerprintMap.put(depMaterial, scmMaterialFingerprintSet);
        scmMaterialSet.addAll(scmMaterialFingerprintSet);
    }

    private FanInNode createNode(MaterialConfig material) {
        FanInNode node = nodes.get(material.getFingerprint());
        if (node == null) {
            node = FanInNodeFactory.create(material);
            nodes.put(material.getFingerprint(), node);
        }
        return node;
    }

    @Deprecated
    public void setFanInEventListener(FanInEventListener fanInEventListener) {
        this.fanInEventListener = fanInEventListener;
    }

    //Used in test Only
    List<ScmMaterialConfig> getScmMaterials() {
        List<ScmMaterialConfig> scmMaterials = new ArrayList<>();
        for (FanInNode node : nodes.values()) {
            if (node.materialConfig instanceof ScmMaterialConfig) {
                scmMaterials.add((ScmMaterialConfig) node.materialConfig);
            }
        }
        return scmMaterials;
    }

    public Map<DependencyMaterialConfig, Set<MaterialConfig>> getPipelineScmDepMap() {
        Map<DependencyMaterialConfig, Set<MaterialConfig>> dependencyMaterialListMap = new HashMap<>();

        for (Map.Entry<DependencyMaterialConfig, Set<String>> materialSetEntry : dependencyMaterialFingerprintMap
                .entrySet()) {
            Set<MaterialConfig> scmMaterials = new HashSet<>();
            for (String fingerprint : materialSetEntry.getValue()) {
                scmMaterials.add(fingerprintScmMaterialMap.get(fingerprint));
            }
            dependencyMaterialListMap.put(materialSetEntry.getKey(), scmMaterials);
        }

        return dependencyMaterialListMap;
    }

    public MaterialRevisions computeRevisions(MaterialRevisions actualRevisions,
            PipelineTimeline pipelineTimeline) {
        assertAllDirectDependenciesArePresentInInput(actualRevisions, pipelineName);

        Pair<List<RootFanInNode>, List<DependencyFanInNode>> scmAndDepMaterialsChildren = getScmAndDepMaterialsChildren();
        List<RootFanInNode> scmChildren = scmAndDepMaterialsChildren.first();
        List<DependencyFanInNode> depChildren = scmAndDepMaterialsChildren.last();

        if (depChildren.isEmpty()) {
            //No fanin required all are SCMs
            return actualRevisions;
        }

        FanInGraphContext context = buildContext(pipelineTimeline);
        root.initialize(context);

        initChildren(depChildren, pipelineName, context);

        if (fanInEventListener != null) {
            fanInEventListener.iterationComplete(0, depChildren);
        }

        iterateAndMakeAllUniqueScmRevisionsForChildrenSame(depChildren, pipelineName, context);

        List<MaterialRevision> finalRevisionsForScmChildren = createFinalRevisionsForScmChildren(
                root.latestPipelineTimelineEntry(context), scmChildren, depChildren);

        List<MaterialRevision> finalRevisionsForDepChildren = createFinalRevisionsForDepChildren(depChildren);

        return new MaterialRevisions(CollectionUtils.union(
                getMaterialsFromCurrentPipeline(finalRevisionsForScmChildren, actualRevisions),
                finalRevisionsForDepChildren));
    }

    //This whole method is repeated for reporting and it does not use actual revisions for determining final revisions
    //Used in rails view
    //Do not delete
    //Ramraj ge salute
    //Srikant & Sachin
    @Deprecated
    public Collection<MaterialRevision> computeRevisionsForReporting(CaseInsensitiveString pipelineName,
            PipelineTimeline pipelineTimeline) {
        Pair<List<RootFanInNode>, List<DependencyFanInNode>> scmAndDepMaterialsChildren = getScmAndDepMaterialsChildren();
        List<RootFanInNode> scmChildren = scmAndDepMaterialsChildren.first();
        List<DependencyFanInNode> depChildren = scmAndDepMaterialsChildren.last();

        if (depChildren.isEmpty()) {
            //No fanin required all are SCMs
            return null;
        }

        FanInGraphContext context = buildContext(pipelineTimeline);
        root.initialize(context);

        initChildren(depChildren, pipelineName, context);

        iterateAndMakeAllUniqueScmRevisionsForChildrenSame(depChildren, pipelineName, context);

        List<MaterialRevision> finalRevisionsForScmChildren = createFinalRevisionsForScmChildren(
                root.latestPipelineTimelineEntry(context), scmChildren, depChildren);

        List<MaterialRevision> finalRevisionsForDepChildren = createFinalRevisionsForDepChildren(depChildren);

        return CollectionUtils.union(finalRevisionsForScmChildren, finalRevisionsForDepChildren);
    }

    private List<MaterialRevision> createFinalRevisionsForDepChildren(List<DependencyFanInNode> depChildren) {
        List<MaterialRevision> finalRevisions = new ArrayList<>();
        for (DependencyFanInNode child : depChildren) {
            final List<Modification> modifications = materialRepository.modificationFor(child.currentRevision);
            if (modifications.isEmpty()) {
                throw new NoModificationsPresentForDependentMaterialException(child.currentRevision.stageLocator());
            }
            finalRevisions.add(
                    new MaterialRevision(materialConfigConverter.toMaterial(child.materialConfig), modifications));
        }
        return finalRevisions;
    }

    private List<MaterialRevision> createFinalRevisionsForScmChildren(PipelineTimelineEntry latestRootNodeInstance,
            List<RootFanInNode> scmChildren, List<DependencyFanInNode> depChildren) {
        Set<FaninScmMaterial> scmMaterialsFromDepChildren = scmMaterialsOfDepChildren(depChildren);
        List<MaterialRevision> finalRevisions = new ArrayList<>();

        for (RootFanInNode child : scmChildren) {
            child.setScmRevision(scmMaterialsFromDepChildren);

            MaterialConfig materialConfig = child.materialConfig;
            Material material = materialConfigConverter.toMaterial(materialConfig);
            MaterialRevision revision = new MaterialRevision(material);
            if (latestRootNodeInstance != null) {
                PipelineInstanceModel pipeline = pipelineDao.findPipelineHistoryByNameAndCounter(
                        latestRootNodeInstance.getPipelineName(), latestRootNodeInstance.getCounter());
                for (MaterialRevision materialRevision : pipeline.getCurrentRevisions()) {
                    if (materialRevision.getMaterial().getFingerprint()
                            .equals(child.materialConfig.getFingerprint())) {
                        List<Modification> modificationsSince = materialRepository
                                .findModificationsSinceAndUptil(material, materialRevision, child.scmRevision);
                        revision.addModifications(modificationsSince);
                        break;
                    }
                }
            }

            if (revision.getModifications().isEmpty() && child.scmRevision == null) {
                MaterialRevisions latestRevisions = materialRepository
                        .findLatestRevisions(new MaterialConfigs(materialConfig));
                finalRevisions.addAll(latestRevisions.getRevisions());
                continue;
            }

            if (revision.getModifications().isEmpty()) {
                revision = new MaterialRevision(material,
                        materialRepository.findModificationWithRevision(material, child.scmRevision.revision));
            }

            finalRevisions.add(revision);
        }
        return finalRevisions;
    }

    private Set<FaninScmMaterial> scmMaterialsOfDepChildren(List<DependencyFanInNode> depChildren) {
        Set<FaninScmMaterial> allScmMaterials = new HashSet<>();
        for (DependencyFanInNode child : depChildren) {
            allScmMaterials.addAll(child.stageIdentifierScmMaterialForCurrentRevision());
        }
        return allScmMaterials;
    }

    private Pair<List<RootFanInNode>, List<DependencyFanInNode>> getScmAndDepMaterialsChildren() {
        List<RootFanInNode> scmMaterials = new ArrayList<>();
        List<DependencyFanInNode> depMaterials = new ArrayList<>();
        for (FanInNode child : root.children) {
            if (child instanceof RootFanInNode) {
                scmMaterials.add((RootFanInNode) child);
            } else {
                depMaterials.add((DependencyFanInNode) child);
            }
        }
        return new Pair<>(scmMaterials, depMaterials);
    }

    private void iterateAndMakeAllUniqueScmRevisionsForChildrenSame(List<DependencyFanInNode> depChildren,
            CaseInsensitiveString pipelineName, FanInGraphContext context) {
        StageIdFaninScmMaterialPair revisionToSet = getRevisionToSet();
        int i = 1;
        while (revisionToSet != null) {
            for (DependencyFanInNode child : depChildren) {
                final DependencyFanInNode.RevisionAlteration revisionAlteration = child.setRevisionTo(revisionToSet,
                        context);
                if (revisionAlteration == ALL_OPTIONS_EXHAUSTED) {
                    throw NoCompatibleUpstreamRevisionsException.failedToFindCompatibleRevision(pipelineName,
                            child.materialConfig);
                }
            }

            if (fanInEventListener != null) {
                fanInEventListener.iterationComplete(i, depChildren);
            }

            i++;
            revisionToSet = getRevisionToSet();
        }
    }

    private void initChildren(List<DependencyFanInNode> depChildren, CaseInsensitiveString pipelineName,
            FanInGraphContext context) {
        for (DependencyFanInNode child : depChildren) {
            child.populateRevisions(pipelineName, context);
        }
    }

    private void assertAllDirectDependenciesArePresentInInput(MaterialRevisions actualRevisions,
            CaseInsensitiveString pipelineName) {
        Collection<String> actualRevFingerprints = CollectionUtils.collect(actualRevisions.iterator(),
                actualRevision -> actualRevision.getMaterial().getFingerprint());

        for (FanInNode child : root.children) {
            //The dependency material that is not in 'passed' state will not be found in actual revisions
            if (!actualRevFingerprints.contains(child.materialConfig.getFingerprint())) {
                throw NoCompatibleUpstreamRevisionsException.doesNotHaveValidRevisions(pipelineName,
                        child.materialConfig);
            }
        }
    }

    private StageIdFaninScmMaterialPair getRevisionToSet() {
        List<StageIdFaninScmMaterialPair> pIdScmMaterialList = buildPipelineIdScmMaterialMap();

        Collection<StageIdFaninScmMaterialPair> scmRevisionsThatDiffer = findScmRevisionsThatDiffer(
                pIdScmMaterialList);

        if (!scmRevisionsThatDiffer.isEmpty()) {
            return getSmallestScmRevision(scmRevisionsThatDiffer);
        }

        return null;
    }

    private Collection<StageIdFaninScmMaterialPair> findScmRevisionsThatDiffer(
            List<StageIdFaninScmMaterialPair> pIdScmMaterialList) {
        for (final StageIdFaninScmMaterialPair pIdScmPair : pIdScmMaterialList) {
            final Collection<StageIdFaninScmMaterialPair> matWithSameFingerprint = CollectionUtils
                    .select(pIdScmMaterialList, pIdScmPair::equals);

            boolean diffRevFound = false;
            for (StageIdFaninScmMaterialPair pair : matWithSameFingerprint) {
                if (pair.stageIdentifier == pIdScmPair.stageIdentifier) {
                    continue;
                }
                if (pair.faninScmMaterial.revision.equals(pIdScmPair.faninScmMaterial.revision)) {
                    continue;
                }
                diffRevFound = true;
                break;
            }

            if (diffRevFound) {
                return matWithSameFingerprint;
            }
        }

        return Collections.EMPTY_LIST;
    }

    private StageIdFaninScmMaterialPair getSmallestScmRevision(
            Collection<StageIdFaninScmMaterialPair> scmWithDiffVersions) {
        ArrayList<StageIdFaninScmMaterialPair> materialPairList = new ArrayList<>(scmWithDiffVersions);
        materialPairList.sort((pair1, pair2) -> {
            final PipelineTimelineEntry.Revision rev1 = pair1.faninScmMaterial.revision;
            final PipelineTimelineEntry.Revision rev2 = pair2.faninScmMaterial.revision;
            return rev1.date.compareTo(rev2.date);
        });
        return materialPairList.get(0);
    }

    private List<StageIdFaninScmMaterialPair> buildPipelineIdScmMaterialMap() {
        List<StageIdFaninScmMaterialPair> stageIdScmPairs = new ArrayList<>();
        for (FanInNode child : root.children) {
            if (child instanceof DependencyFanInNode) {
                stageIdScmPairs.addAll(((DependencyFanInNode) child).getCurrentFaninScmMaterials());
            }
        }
        return stageIdScmPairs;
    }

    private FanInGraphContext buildContext(PipelineTimeline pipelineTimeline) {
        FanInGraphContext context = new FanInGraphContext();
        context.revBatchCount = REVISION_BUFFER_SIZE;
        context.pipelineTimeline = pipelineTimeline;
        context.fingerprintScmMaterialMap = fingerprintScmMaterialMap;
        context.pipelineScmDepMap = getPipelineScmDepMap();
        context.fingerprintDepMaterialMap = fingerprintDepMaterialMap;
        context.pipelineDao = pipelineDao;
        context.maxBackTrackLimit = systemEnvironment.get(SystemEnvironment.RESOLVE_FANIN_MAX_BACK_TRACK_LIMIT);
        return context;
    }

    private Collection<MaterialRevision> getMaterialsFromCurrentPipeline(
            List<MaterialRevision> finalRevisionsForScmChildren, MaterialRevisions actualRevisions) {
        List<MaterialRevision> updatedRevisions = new ArrayList<>();
        for (MaterialRevision revisionsForScmChild : finalRevisionsForScmChildren) {
            MaterialRevision originalRevision = actualRevisions
                    .findRevisionUsingMaterialFingerprintFor(revisionsForScmChild.getMaterial());
            updatedRevisions.add(new MaterialRevision(originalRevision.getMaterial(),
                    revisionsForScmChild.isChanged(), revisionsForScmChild.getModifications()));
        }
        return updatedRevisions;
    }
}