/*************************GO-LICENSE-START********************************* * Copyright 2014 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. *************************GO-LICENSE-END***********************************/ package com.thoughtworks.go.server.service; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.GsonBuilder; import com.thoughtworks.go.config.CaseInsensitiveString; import com.thoughtworks.go.config.CruiseConfig; import com.thoughtworks.go.config.PipelineConfig; import com.thoughtworks.go.config.materials.Materials; import com.thoughtworks.go.config.materials.dependency.DependencyMaterial; import com.thoughtworks.go.domain.*; import com.thoughtworks.go.domain.buildcause.BuildCause; import com.thoughtworks.go.domain.materials.Material; import com.thoughtworks.go.domain.materials.dependency.DependencyMaterialRevision; import com.thoughtworks.go.server.dao.PipelineSqlMapDao; import com.thoughtworks.go.server.domain.PipelineConfigDependencyGraph; import com.thoughtworks.go.server.domain.PipelineTimeline; import com.thoughtworks.go.server.persistence.MaterialRepository; import com.thoughtworks.go.server.service.dd.DependencyFanInNode; import com.thoughtworks.go.server.service.dd.FanInEventListener; import com.thoughtworks.go.server.service.dd.FanInGraph; import com.thoughtworks.go.server.transaction.TransactionTemplate; import com.thoughtworks.go.util.SystemEnvironment; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import java.util.Collection; import java.util.List; import java.util.Queue; @Service public class PipelineService implements UpstreamPipelineResolver { private static final Logger LOGGER = Logger.getLogger(PipelineService.class); private TransactionTemplate transactionTemplate; private PipelineSqlMapDao pipelineDao; private StageService stageService; private PipelineLockService pipelineLockService; private PipelineTimeline pipelineTimeline; private MaterialRepository materialRepository; private final SystemEnvironment systemEnvironment; private final GoConfigService goConfigService; private MaterialConfigConverter materialConfigConverter; @Autowired public PipelineService(PipelineSqlMapDao pipelineDao, StageService stageService, PipelineLockService pipelineLockService, PipelineTimeline pipelineTimeline, MaterialRepository materialRepository, TransactionTemplate transactionTemplate, SystemEnvironment systemEnvironment, GoConfigService goConfigService, MaterialConfigConverter materialConfigConverter) { this.pipelineDao = pipelineDao; this.stageService = stageService; this.pipelineLockService = pipelineLockService; this.pipelineTimeline = pipelineTimeline; this.materialRepository = materialRepository; this.transactionTemplate = transactionTemplate; this.systemEnvironment = systemEnvironment; this.goConfigService = goConfigService; this.materialConfigConverter = materialConfigConverter; } public Pipeline fullPipelineByBuildId(long buildId) { return pipelineDao.fullPipelineByBuildId(buildId); } public Pipeline pipelineWithModsByStageId(String pipelineName, long stageId) { return pipelineDao.pipelineWithModsByStageId(pipelineName, stageId); } public Pipeline fullPipelineById(long pipelineId) { return pipelineDao.loadPipeline(pipelineId); } public Pipeline mostRecentFullPipelineByName(String pipeLineName) { return pipelineDao.mostRecentPipeline(pipeLineName); } public StageIdentifier findLastSuccessfulStageIdentifier(String pipelineName, String stageName) { return pipelineDao.findLastSuccessfulStageIdentifier(pipelineName, stageName); } public Pipeline save(final Pipeline pipeline) { String mutexPipelineName = PipelinePauseService.mutexForPausePipeline(pipeline.getName()); synchronized (mutexPipelineName) { return (Pipeline) transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { if (pipeline instanceof NullPipeline) { return pipeline; } updateCounter(pipeline); Pipeline pipelineWithId = pipelineDao.save(pipeline); pipelineLockService.lockIfNeeded(pipelineWithId); for (Stage stage : pipeline.getStages()) { stageService.save(pipelineWithId, stage); } pipelineTimeline.update(); return pipelineWithId; } }); } } private void updateCounter(Pipeline pipeline) { Integer lastCount = pipelineDao.getCounterForPipeline(pipeline.getName()); pipeline.updateCounter(lastCount); pipelineDao.insertOrUpdatePipelineCounter(pipeline, lastCount, pipeline.getCounter()); } /** * @deprecated this is evil and should be removed */ public Pipeline wrapBuildDetails(JobInstance job) { Stage stageForBuild = stageService.getStageByBuild(job.getId()); stageForBuild.setJobInstances(new JobInstances(job)); Pipeline pipeline = pipelineDao.pipelineByBuildIdWithMods(job.getId()); pipeline.setStages(new Stages(stageForBuild)); return pipeline; } public Pipeline findPipelineByCounterOrLabel(String pipelineName, String counterOrLabel) { return pipelineDao.findPipelineByCounterOrLabel(pipelineName, counterOrLabel); } public Pipeline fullPipelineByCounterOrLabel(String pipelineName, String counterOrLabel) { Pipeline pipeline = findPipelineByCounterOrLabel(pipelineName, counterOrLabel); pipelineDao.loadAssociations(pipeline, pipelineName); return pipeline; } /* TRIANGLE BEGIN */ public MaterialRevisions getRevisionsBasedOnDependencies(PipelineConfigDependencyGraph graph, MaterialRevisions actualRevisions) { MaterialRevisions computedRevisions = new MaterialRevisions(); overrideCommonMaterialRevisions(graph, actualRevisions, computedRevisions); copyMissingRevisions(actualRevisions, computedRevisions); return restoreOriginalOrder(actualRevisions, computedRevisions); } private void overrideCommonMaterialRevisions(PipelineConfigDependencyGraph graph, MaterialRevisions actualRevisions, MaterialRevisions computedRevisions) { Queue<PipelineConfigDependencyGraph.PipelineConfigQueueEntry> configQueue = graph.buildQueue(); for (MaterialRevision actualRevision : actualRevisions) { if (actualRevision.getMaterial() instanceof DependencyMaterial && actualRevision.isChanged()) { DependencyMaterialRevision revision = (DependencyMaterialRevision) actualRevision.getRevision(); for (PipelineConfigDependencyGraph.PipelineConfigQueueEntry configQueueEntry : configQueue) { populateRevisionsUsingUpstream(actualRevisions, computedRevisions, revision, configQueueEntry); } } } } private void populateRevisionsUsingUpstream(MaterialRevisions actualRevisions, MaterialRevisions newRevisions, DependencyMaterialRevision dmr, PipelineConfigDependencyGraph.PipelineConfigQueueEntry configQueueEntry) { if (!configQueueEntry.containsPipelineInPath(dmr.getPipelineName())) { return; } for (MaterialRevision materialRevision : actualRevisions) { Material material = materialRevision.getMaterial(); if (currentPipelineHasMaterial(configQueueEntry, material) && !alreadyAdded(newRevisions, material)) { List<PipelineConfig> paths = removePathHead(configQueueEntry); if (!paths.isEmpty()) { MaterialRevision revision = getRevisionFor(paths, dmr, material); //revision is null when an upstream is both parent and grandparent if (revision != null) { materialRevision.replaceModifications(revision.getModifications()); newRevisions.addRevision(materialRevision); } } } } } private boolean currentPipelineHasMaterial(PipelineConfigDependencyGraph.PipelineConfigQueueEntry configQueueEntry, Material material) { return configQueueEntry.getNode().hasMaterial(material.config()); } private List<PipelineConfig> removePathHead(PipelineConfigDependencyGraph.PipelineConfigQueueEntry configQueueEntry) { return configQueueEntry.pathWithoutHead(); } private MaterialRevision getRevisionFor(List<PipelineConfig> path, DependencyMaterialRevision initialRevision, Material matchedMaterial) { Pipeline byNameAndCounter = pipelineDao.findPipelineByNameAndCounter(initialRevision.getPipelineName(), initialRevision.getPipelineCounter()); MaterialRevisions revisions = materialRepository.findMaterialRevisionsForPipeline(byNameAndCounter.getId()); path.remove(0); if (path.isEmpty()) { return revisions.findRevisionForFingerPrint(matchedMaterial.getFingerprint()); } return getRevisionFor(path, revisions.findDependencyMaterialRevision(CaseInsensitiveString.str(path.get(0).name())), matchedMaterial); } private void copyMissingRevisions(MaterialRevisions srcRevisions, MaterialRevisions destRevisions) { for (MaterialRevision actualRevision : srcRevisions) { if (!alreadyAdded(destRevisions, actualRevision.getMaterial())) { destRevisions.addRevision(actualRevision); } } } private boolean alreadyAdded(MaterialRevisions newRevisions, Material material) { return newRevisions.containsModificationForFingerprint(material); } private MaterialRevisions restoreOriginalOrder(MaterialRevisions actualRevisions, MaterialRevisions computedRevisions) { MaterialRevisions orderedComputedRevisions = new MaterialRevisions(); for (MaterialRevision actualRevision : actualRevisions) { orderedComputedRevisions.addRevision(computedRevisions.findRevisionFor(actualRevision.getMaterial())); } return orderedComputedRevisions; } /* TRIANGLE END */ /* DIAMOND BEGIN */ public MaterialRevisions getRevisionsBasedOnDependencies(MaterialRevisions actualRevisions, CruiseConfig cruiseConfig, CaseInsensitiveString pipelineName) { FanInGraph fanInGraph = new FanInGraph(cruiseConfig, pipelineName, materialRepository, pipelineDao, systemEnvironment, materialConfigConverter); final MaterialRevisions computedRevisions = fanInGraph.computeRevisions(actualRevisions, pipelineTimeline); fillUpNonOverridableRevisions(actualRevisions, computedRevisions); return restoreOriginalMaterialConfigAndMaterialOrderUsingFingerprint(actualRevisions, computedRevisions); } // This is for debugging purposes public String getRevisionsBasedOnDependenciesForDebug(CaseInsensitiveString pipelineName, final Integer targetIterationCount) { CruiseConfig cruiseConfig = goConfigService.getCurrentConfig(); FanInGraph fanInGraph = new FanInGraph(cruiseConfig, pipelineName, materialRepository, pipelineDao, systemEnvironment, materialConfigConverter); final String[] iterationData = {null}; fanInGraph.setFanInEventListener(new FanInEventListener() { @Override public void iterationComplete(int iterationCount, List<DependencyFanInNode> dependencyFanInNodes) { if (iterationCount == targetIterationCount) { iterationData[0] = new GsonBuilder().setExclusionStrategies(getGsonExclusionStrategy()).create().toJson(dependencyFanInNodes); } } }); PipelineConfig pipelineConfig = goConfigService.pipelineConfigNamed(pipelineName); Materials materials = materialConfigConverter.toMaterials(pipelineConfig.materialConfigs()); MaterialRevisions actualRevisions = new MaterialRevisions(); for (Material material : materials) { actualRevisions.addAll(materialRepository.findLatestModification(material)); } MaterialRevisions materialRevisions = fanInGraph.computeRevisions(actualRevisions, pipelineTimeline); if (iterationData[0] == null) { iterationData[0] = new GsonBuilder().setExclusionStrategies(getGsonExclusionStrategy()).create().toJson(materialRevisions); } return iterationData[0]; } private ExclusionStrategy getGsonExclusionStrategy() { return new ExclusionStrategy() { @Override public boolean shouldSkipField(FieldAttributes f) { return f.getName().equals("materialConfig") || f.getName().equals("parents") || f.getName().equals("children"); } @Override public boolean shouldSkipClass(Class<?> clazz) { return false; } }; } //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> getRevisionsBasedOnDependenciesForReporting(CruiseConfig cruiseConfig, CaseInsensitiveString pipelineName) { FanInGraph fanInGraph = new FanInGraph(cruiseConfig, pipelineName, materialRepository, pipelineDao, systemEnvironment, materialConfigConverter); return fanInGraph.computeRevisionsForReporting(pipelineName, pipelineTimeline); } private void fillUpNonOverridableRevisions(MaterialRevisions actualRevisions, MaterialRevisions computedRevisions) { for (int i = 0; i < actualRevisions.numberOfRevisions(); i++) { MaterialRevision actualRev = actualRevisions.getMaterialRevision(i); MaterialRevision computedRevision = computedRevisions.findRevisionFor(actualRev.getMaterial()); if (computedRevision == null) { computedRevisions.addRevision(actualRev); } } } public BuildCause buildCauseFor(String pipelineName, int pipelineCounter) { return pipelineDao.findBuildCauseOfPipelineByNameAndCounter(pipelineName, pipelineCounter); } private MaterialRevisions restoreOriginalMaterialConfigAndMaterialOrderUsingFingerprint(MaterialRevisions actualRevisions, MaterialRevisions computedRevisions) { MaterialRevisions orderedComputedRevisions = new MaterialRevisions(); for (MaterialRevision actualRevision : actualRevisions) { MaterialRevision revisionForMaterial = computedRevisions.findRevisionUsingMaterialFingerprintFor(actualRevision.getMaterial()); MaterialRevision materialRevisionWithRestoredMaterialConfig = new MaterialRevision(actualRevision.getMaterial(), revisionForMaterial.isChanged(), revisionForMaterial.getModifications()); orderedComputedRevisions.addRevision(materialRevisionWithRestoredMaterialConfig); } return orderedComputedRevisions; } /* DIAMOND END */ public PipelineTimeline getPipelineTimeline() { return pipelineTimeline; } }