/*
* 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.domain;
import com.rits.cloning.Cloner;
import com.thoughtworks.go.config.StageConfig;
import com.thoughtworks.go.server.domain.StageStatusHandler;
import com.thoughtworks.go.util.Clock;
import org.apache.log4j.Logger;
import org.joda.time.DateTimeUtils;
import org.joda.time.Duration;
import java.sql.Timestamp;
import java.util.Date;
public class Stage extends PersistentObject {
private static final Logger LOG = Logger.getLogger(Stage.class);
private Long pipelineId;
private String name;
private JobInstances jobInstances = new JobInstances();
private String approvedBy;
private int orderId;
private Timestamp createdTime;
private Timestamp lastTransitionedTime;
private String approvalType;
private boolean fetchMaterials = StageConfig.DEFAULT_FETCH_MATERIALS;
private StageResult result = DEFAULT_RESULT;
private int counter;
private StageIdentifier identifier;
private Long completedByTransitionId;
private StageState state;
private boolean latestRun = true;
private boolean cleanWorkingDir = StageConfig.DEFAULT_CLEAN_WORKING_DIR;
private Integer rerunOfCounter;
private boolean artifactsDeleted;
private static final StageResult DEFAULT_RESULT = StageResult.Unknown;
private static final Cloner CLONER = new Cloner();
private String configVersion = null;
public Stage() {
}
public Stage(String name, JobInstances jobInstances, String approvedBy, String approvalType, final Clock clock) {
this(name, jobInstances, approvedBy, approvalType, StageConfig.DEFAULT_FETCH_MATERIALS, StageConfig.DEFAULT_CLEAN_WORKING_DIR, clock);
}
public Stage(String name, JobInstances jobInstances, String approvedBy, String approvalType, boolean fetchMaterials, boolean cleanWorkingDir, final Clock clock) {
this.name = name;
this.jobInstances = jobInstances;
this.approvedBy = approvedBy;
this.approvalType = approvalType;
this.fetchMaterials = fetchMaterials;
this.cleanWorkingDir = cleanWorkingDir;
this.createdTime = new Timestamp(clock.currentTimeMillis());
}
public Stage(String name, JobInstances jobInstances, String approvedBy, String approvalType, boolean fetchMaterials, boolean cleanWorkingDir, String configVersion, final Clock clock) {
this(name, jobInstances, approvedBy, approvalType, fetchMaterials, cleanWorkingDir, clock);
this.configVersion = configVersion;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public JobInstances getJobInstances() {
return jobInstances;
}
public void setJobInstances(JobInstances jobInstances) {
this.jobInstances = jobInstances;
}
public Long getPipelineId() {
return pipelineId;
}
public void setPipelineId(Long pipelineId) {
this.pipelineId = pipelineId;
}
public Stage mostRecent(Stage completed) {
return this;
}
public StageState stageState() {
return jobInstances.stageState();
}
public StageState getState() {
return state;
}
public String status() {
return stageState().status();
}
public Date scheduledDate() {
return new Date(createdTime.getTime());
}
public Date completedDate() {
if (completedByTransitionId != null && lastTransitionedTime != null) {
return new Date(lastTransitionedTime.getTime());
} else if (lastTransitionedTime == null) {//TODO: Should not be null anymore. Keeping this for Legacy code so that we dont end up fixing a lot of tests. We should remove this eventually.
Date mostRecent = null;
for (JobStateTransition transition : jobInstances.stateTransitions()) {
if (transition.getCurrentState() == JobState.Completed && (mostRecent == null || transition.getStateChangeTime().after(mostRecent))) {
mostRecent = transition.getStateChangeTime();
}
}
return mostRecent;
}
return null;
}
public Date latestTransitionDate() {
Date latest = jobInstances.latestTransitionDate();
if (latest == null) {
return createdTime;
}
return latest;
}
public boolean isCompletedAndPassed() {
return this.stageState().completed() && passed();
}
public boolean passed() {
return this.stageState().equals(StageState.Passed);
}
public boolean failed() {
return this.stageState().equals(StageState.Failed);
}
public boolean isActive() {
return this.stageState().isActive();
}
public String getApprovedBy() {
return approvedBy;
}
public void setApprovedBy(String approvedBy) {
this.approvedBy = approvedBy;
}
public boolean isApproved() {
return approvedBy != null;
}
public void fail() {
for (JobInstance job : getJobInstances()) {
job.fail();
}
}
public Timestamp getLastTransitionedTime() {
return lastTransitionedTime;
}
public void setLastTransitionedTime(Timestamp lastTransitionedTime) {
this.lastTransitionedTime = lastTransitionedTime;
}
public Timestamp getCreatedTime() {
return createdTime;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public String toString() {
return "<Stage"
+ " id='" + id + "'"
+ " name='" + name + "'"
+ " stageCounter='" + counter + "'"
+ " createdTime='" + createdTime + "'"
+ " result='" + result + "'"
+ ">";
}
public StageResult getResult() {
return result;
}
public synchronized void calculateResult() {
this.state = this.stageState();
this.result = state.stageResult();
if (state.completed()) {
long latestTransitionId = jobInstances.latestTransitionId();
if (latestTransitionId != JobStateTransition.NOT_PERSISTED) {
LOG.info("Stage is being completed by transition id: " + latestTransitionId);
if (completedByTransitionId != null) {
LOG.warn("Completing transition id for stage is being changed from " + completedByTransitionId + " to " + latestTransitionId);
}
this.completedByTransitionId = latestTransitionId;
}
}
}
public String getApprovalType() {
return approvalType;
}
public void setCounter(int counter) {
this.counter = counter;
if (getIdentifier() != null) {
getIdentifier().setStageCounter(String.valueOf(counter));
}
}
public int getCounter() {
return this.counter;
}
public void setIdentifier(StageIdentifier identifier) {
this.identifier = identifier;
}
public StageIdentifier getIdentifier() {
return identifier;
}
public JobInstance findJob(String name) {
return jobInstances.getByName(name);
}
// This is to differentiate b/w scheduled & building
public boolean isScheduled() {
if (counter == 1) {
return areAllJobsInScheduledState();
}
return false;
}
// This is to differentiate b/w re-run & building
public boolean isReRun() {
if (counter > 1) {
// stage re-run || job re-run
return areAllJobsInScheduledState() || (rerunOfCounter != null && areAllReRunJobsInScheduledState());
}
return false;
}
private boolean areAllJobsInScheduledState() {
for (JobInstance instance : jobInstances) {
if (instance.getState() != JobState.Scheduled) {
return false;
}
}
return true;
}
private boolean areAllReRunJobsInScheduledState() {
for (JobInstance instance : jobInstances) {
if (instance.isRerun() && instance.getState() != JobState.Scheduled) {
return false;
}
}
return true;
}
public boolean isCompleted() {
return stageState().completed();
}
public void statusHandling(StageStatusHandler stageStatusHandler) {
if (this.stageState().completedNormally()) {
stageStatusHandler.onNormalCompletion(stageState(), result);
}
}
public RunDuration getDuration() {
if (!state.completed()) {
return RunDuration.IN_PROGRESS_DURATION;
}
Date start = new Date(createdTime.getTime());
Date end = new Date(lastTransitionedTime.getTime());
return new RunDuration.ActualDuration(new Duration(end.getTime() - start.getTime()));
}
private Date latest(JobState stage) {
Date date = null;
for (JobStateTransition transition : jobInstances.stateTransitions()) {
if (transition.getCurrentState().equals(stage)) {
Date time = transition.getStateChangeTime();
if (date == null || time.after(date)) {
date = time;
}
}
}
return date;
}
private Date earliest(JobState stage) {
Date date = null;
for (JobStateTransition transition : jobInstances.stateTransitions()) {
if (transition.getCurrentState().equals(stage)) {
Date time = transition.getStateChangeTime();
if (date == null || time.before(date)) {
date = time;
}
}
}
return date;
}
public String stageLocator() {
return identifier.stageLocator();
}
public String stageLocatorForDisplay() {
return identifier.stageLocatorForDisplay();
}
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Stage stage = (Stage) o;
if (counter != stage.counter) {
return false;
}
if (orderId != stage.orderId) {
return false;
}
if (fetchMaterials != stage.fetchMaterials) {
return false;
}
if (cleanWorkingDir != stage.cleanWorkingDir) {
return false;
}
if (pipelineId != null ? !pipelineId.equals(stage.pipelineId) : stage.pipelineId != null) {
return false;
}
if (approvalType != null ? !approvalType.equals(stage.approvalType) : stage.approvalType != null) {
return false;
}
if (approvedBy != null ? !approvedBy.equals(stage.approvedBy) : stage.approvedBy != null) {
return false;
}
if (createdTime != null ? !createdTime.equals(stage.createdTime) : stage.createdTime != null) {
return false;
}
//TODO: ChrisS We need to fix this once all the tests are saving JobInstances correctly.
//see PipelineDao.saveWithStages to see why we can't do this
// if (jobInstances != null ? !jobInstances.equals(stage.jobInstances) : stage.jobInstances != null) {
// return false;
// }
if (name != null ? !name.equals(stage.name) : stage.name != null) {
return false;
}
if (result != stage.result) {
return false;
}
return true;
}
public int hashCode() {
int result1 = pipelineId != null ? pipelineId.hashCode() : 0;
result1 = 31 * result1 + (name != null ? name.hashCode() : 0);
result1 = 31 * result1 + (jobInstances != null ? jobInstances.hashCode() : 0);
result1 = 31 * result1 + (approvedBy != null ? approvedBy.hashCode() : 0);
result1 = 31 * result1 + orderId;
result1 = 31 * result1 + (createdTime != null ? createdTime.hashCode() : 0);
result1 = 31 * result1 + (approvalType != null ? approvalType.hashCode() : 0);
result1 = 31 * result1 + (result != null ? result.hashCode() : 0);
result1 = 31 * result1 + counter;
result1 = 31 * result1 + (identifier != null ? identifier.hashCode() : 0);
result1 = 31 * result1 + (fetchMaterials ? 1 : 0);
result1 = 31 * result1 + (cleanWorkingDir ? 1 : 0);
return result1;
}
public JobInstance getFirstJob() {
return jobInstances.first();
}
public JobInstances jobsWithResult(JobResult... results) {
return jobInstances.filterByResult(results);
}
@Deprecated
/**
* for ibatis only
*/
public void setCompletedByTransitionId(Long completedByTransitionId) {
this.completedByTransitionId = completedByTransitionId;
}
public Long getCompletedByTransitionId() {
return completedByTransitionId;
}
public void building() {
if (state != null) {
LOG.warn(String.format("Expected stage [%s] to have no state, but was %s", identifier, state), new Exception().fillInStackTrace());
}
state = StageState.Building;
}
public boolean isLatestRun() {
return latestRun;
}
/**
* for ibatis
*/
public void setLatestRun(boolean latestRun) {
this.latestRun = latestRun;
}
public boolean shouldFetchMaterials() {
return fetchMaterials;
}
public boolean shouldCleanWorkingDir() {
return cleanWorkingDir;
}
public Stage createClone() {
return CLONER.deepClone(this);
}
public void prepareForRerunOf(SchedulingContext context, String latestConfigVersion) {
setId(-1);
if (rerunOfCounter == null) {
setRerunOfCounter(counter);
}
this.configVersion = latestConfigVersion;
setApprovedBy(context.getApprovedBy());
setLatestRun(true);
resetResult();
setCreatedTime(new Timestamp(DateTimeUtils.currentTimeMillis()));
jobInstances.resetJobsIds();
}
private void resetResult() {
result = DEFAULT_RESULT;
}
public boolean hasRerunJobs() {
return rerunOfCounter != null;
}
public Integer getRerunOfCounter() {
return rerunOfCounter;
}
public void setRerunOfCounter(Integer rerunOfCounter) {
this.rerunOfCounter = rerunOfCounter;
}
public boolean isArtifactsDeleted() {
return artifactsDeleted;
}
public void setArtifactsDeleted(boolean artifactsDeleted) {
this.artifactsDeleted = artifactsDeleted;
}
public String getConfigVersion() {
return this.configVersion;
}
/**
* for tests only
*/
@Deprecated
public void setConfigVersion(String configVersion) {
this.configVersion = configVersion;
}
public void setCreatedTime(Timestamp timestamp) {
this.createdTime = timestamp;
}
}