/*
* Copyright 2017 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.domain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import com.thoughtworks.go.config.CaseInsensitiveString;
import com.thoughtworks.go.config.PipelineConfig;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.domain.MaterialRevision;
import com.thoughtworks.go.domain.MaterialRevisions;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.MaterialConfig;
/**
* @understands configuration of all the upstream pipelines.
*/
public class PipelineConfigDependencyGraph {
private final PipelineConfig current;
private final List<PipelineConfigDependencyGraph> upstreamDependencies;
public PipelineConfigDependencyGraph(PipelineConfig current, PipelineConfigDependencyGraph... upstreamDependencies) {
this.current = current;
this.upstreamDependencies = Arrays.asList(upstreamDependencies);
}
public PipelineConfig getCurrent() {
return current;
}
public List<PipelineConfigDependencyGraph> getUpstreamDependencies() {
return upstreamDependencies;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PipelineConfigDependencyGraph that = (PipelineConfigDependencyGraph) o;
if (current != null ? !current.equals(that.current) : that.current != null) {
return false;
}
if (upstreamDependencies != null ? !upstreamDependencies.equals(that.upstreamDependencies) : that.upstreamDependencies != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = current != null ? current.hashCode() : 0;
result = 31 * result + (upstreamDependencies != null ? upstreamDependencies.hashCode() : 0);
return result;
}
public Queue<PipelineConfigQueueEntry> buildQueue() {
Queue<PipelineConfigQueueEntry> configQueue = new LinkedList<>();
Queue<PipelineConfigDependencyEntry> tmp = new LinkedList<>();
tmp.add(new PipelineConfigDependencyEntry(this, new ArrayList<>()));
while (true) {
PipelineConfigDependencyEntry currentHead = tmp.poll();
if (currentHead == null) {
break;
}
PipelineConfigDependencyGraph current = currentHead.getNode();
List<PipelineConfig> currentPath = currentHead.getPath();
currentPath.add(current.getCurrent());
configQueue.add(new PipelineConfigQueueEntry(current.getCurrent(), new ArrayList<>(currentPath)));
for (PipelineConfigDependencyGraph upstream : current.getUpstreamDependencies()) {
List<PipelineConfig> parentsPath = new ArrayList<>(currentPath);
tmp.add(new PipelineConfigDependencyEntry(upstream, parentsPath));
}
}
return removeHead(configQueue);
}
private Queue<PipelineConfigQueueEntry> removeHead(Queue<PipelineConfigQueueEntry> configQueue) {
configQueue.poll();
return configQueue;
}
public MaterialConfigs unsharedMaterialConfigs() {
MaterialConfigs firstOrderMaterials = new MaterialConfigs();
List<PipelineConfigQueueEntry> queue = new ArrayList<>(buildQueue());
for (MaterialConfig materialConfig : current.materialConfigs()) {
if (!existsOnAnyOfPipelinesIn(queue, materialConfig)) {
firstOrderMaterials.add(materialConfig);
}
}
return firstOrderMaterials;
}
public Set<String> allMaterialFingerprints() {
Set<String> materialFingerPrints = new HashSet<>();
List<PipelineConfigQueueEntry> queue = new ArrayList<>(buildQueue());
for (PipelineConfigQueueEntry pipelineConfigQueueEntry : queue) {
addMaterialsForConfig(materialFingerPrints, Arrays.asList(pipelineConfigQueueEntry.node));
addMaterialsForConfig(materialFingerPrints, pipelineConfigQueueEntry.path);
}
return materialFingerPrints;
}
private void addMaterialsForConfig(Set<String> materialFingerPrints, List<PipelineConfig> pipelineConfigs) {
for (PipelineConfig pipelineConfig : pipelineConfigs) {
for (MaterialConfig material : pipelineConfig.materialConfigs()) {
materialFingerPrints.add(material.getFingerprint());
}
}
}
private boolean existsOnAnyOfPipelinesIn(List<PipelineConfigQueueEntry> queue, MaterialConfig materialConfig) {
for (PipelineConfigQueueEntry pipelineConfigQueueEntry : queue) {
if (pipelineConfigQueueEntry.hasMaterial(materialConfig)) {
return true;
}
}
return false;
}
public boolean isRevisionsOfSharedMaterialsIgnored(MaterialRevisions revisions) {
MaterialConfigs unsharedScmMaterialConfigs = unsharedMaterialConfigs();
List<PipelineConfigQueueEntry> queue = new ArrayList<>(buildQueue());
for (MaterialRevision revision : revisions) {
Material material = revision.getMaterial();
MaterialConfig materialConfig = material.config();
if (unsharedScmMaterialConfigs.hasMaterialWithFingerprint(materialConfig) || revision.isDependencyMaterialRevision()) {
continue;
}
if (isThisMaterialIgnored(queue, revision, materialConfig)) {
return true;
}
}
return false;
}
private boolean isThisMaterialIgnored(List<PipelineConfigQueueEntry> queue, MaterialRevision revision, MaterialConfig materialConfig) {
for (PipelineConfigQueueEntry pipelineConfigQueueEntry : queue) {
if (pipelineConfigQueueEntry.hasMaterial(materialConfig)) {
if (!pipelineConfigQueueEntry.shouldIgnore(revision)) {
return false;
}
}
}
return true;
}
private static class PipelineConfigDependencyEntry {
private PipelineConfigDependencyGraph node;
private List<PipelineConfig> path;
public PipelineConfigDependencyEntry(PipelineConfigDependencyGraph node, List<PipelineConfig> pipelineConfigs) {
this.node = node;
this.path = pipelineConfigs;
}
public PipelineConfigDependencyGraph getNode() {
return node;
}
public List<PipelineConfig> getPath() {
return path;
}
}
public static class PipelineConfigQueueEntry {
private PipelineConfig node;
private List<PipelineConfig> path;
public PipelineConfigQueueEntry(PipelineConfig node, List<PipelineConfig> pipelineConfigs) {
this.node = node;
this.path = pipelineConfigs;
}
public PipelineConfig getNode() {
return node;
}
public List<PipelineConfig> getPath() {
return path;
}
@Override
public String toString() {
return "PipelineConfigQueueEntry{" +
"node=" + node +
", path=" + path +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PipelineConfigQueueEntry that = (PipelineConfigQueueEntry) o;
if (node != null ? !node.equals(that.node) : that.node != null) {
return false;
}
if (path != null ? !path.equals(that.path) : that.path != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = node != null ? node.hashCode() : 0;
result = 31 * result + (path != null ? path.hashCode() : 0);
return result;
}
public List<PipelineConfig> pathWithoutHead() {
List<PipelineConfig> copy = new ArrayList<>(path);
if (!copy.isEmpty()) {
copy.remove(0);
}
return copy;
}
public boolean containsPipelineInPath(String pipelineName) {
for (PipelineConfig pipelineConfig : path) {
if (CaseInsensitiveString.str(pipelineConfig.name()).equalsIgnoreCase(pipelineName)) {
return true;
}
}
return false;
}
public boolean hasMaterial(MaterialConfig materialConfig) {
return node.hasMaterialWithFingerprint(materialConfig);
}
public boolean shouldIgnore(MaterialRevision revision) {
Material material = revision.getMaterial();
for (MaterialConfig materialConfig : node.materialConfigs()) {
if (material.hasSameFingerprint(materialConfig)) {
return revision.getModifications().shouldBeIgnoredByFilterIn(materialConfig);
}
}
throw new RuntimeException("Material not found: " + material);//IMP: because, config can change between BCPS call and build cause production - shilpa/jj
}
}
}