/*
* Copyright 2006-2013 the original author or authors.
*
* 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 org.springframework.batch.core;
import org.springframework.batch.item.ExecutionContext;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Batch domain object representing the execution of a job.
*
* @author Lucas Ward
* @author Michael Minella
*
*/
@SuppressWarnings("serial")
public class JobExecution extends Entity {
private final JobParameters jobParameters;
private JobInstance jobInstance;
private volatile Collection<StepExecution> stepExecutions = new CopyOnWriteArraySet<StepExecution>();
private volatile BatchStatus status = BatchStatus.STARTING;
private volatile Date startTime = null;
private volatile Date createTime = new Date(System.currentTimeMillis());
private volatile Date endTime = null;
private volatile Date lastUpdated = null;
private volatile ExitStatus exitStatus = ExitStatus.UNKNOWN;
private volatile ExecutionContext executionContext = new ExecutionContext();
private transient volatile List<Throwable> failureExceptions = new CopyOnWriteArrayList<Throwable>();
private final String jobConfigurationName;
public JobExecution(JobExecution original) {
this.jobParameters = original.getJobParameters();
this.jobInstance = original.getJobInstance();
this.stepExecutions = original.getStepExecutions();
this.status = original.getStatus();
this.startTime = original.getStartTime();
this.createTime = original.getCreateTime();
this.endTime = original.getEndTime();
this.lastUpdated = original.getLastUpdated();
this.exitStatus = original.getExitStatus();
this.executionContext = original.getExecutionContext();
this.failureExceptions = original.getFailureExceptions();
this.jobConfigurationName = original.getJobConfigurationName();
this.setId(original.getId());
this.setVersion(original.getVersion());
}
/**
* Because a JobExecution isn't valid unless the job is set, this
* constructor is the only valid one from a modeling point of view.
*
* @param job the job of which this execution is a part
*/
public JobExecution(JobInstance job, Long id, JobParameters jobParameters, String jobConfigurationName) {
super(id);
this.jobInstance = job;
this.jobParameters = jobParameters == null ? new JobParameters() : jobParameters;
this.jobConfigurationName = jobConfigurationName;
}
public JobExecution(JobInstance job, JobParameters jobParameters, String jobConfigurationName) {
this(job, null, jobParameters, jobConfigurationName);
}
public JobExecution(Long id, JobParameters jobParameters, String jobConfigurationName) {
this(null, id, jobParameters, jobConfigurationName);
}
/**
* Constructor for transient (unsaved) instances.
*
* @param job the enclosing {@link JobInstance}
*/
public JobExecution(JobInstance job, JobParameters jobParameters) {
this(job, null, jobParameters, null);
}
public JobExecution(Long id, JobParameters jobParameters) {
this(null, id, jobParameters, null);
}
public JobExecution(Long id) {
this(null, id, null, null);
}
public JobParameters getJobParameters() {
return this.jobParameters;
}
public Date getEndTime() {
return endTime;
}
public void setJobInstance(JobInstance jobInstance) {
this.jobInstance = jobInstance;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public BatchStatus getStatus() {
return status;
}
/**
* Set the value of the status field.
*
* @param status the status to set
*/
public void setStatus(BatchStatus status) {
this.status = status;
}
/**
* Upgrade the status field if the provided value is greater than the
* existing one. Clients using this method to set the status can be sure
* that they don't overwrite a failed status with an successful one.
*
* @param status the new status value
*/
public void upgradeStatus(BatchStatus status) {
this.status = this.status.upgradeTo(status);
}
/**
* Convenience getter for for the id of the enclosing job. Useful for DAO
* implementations.
*
* @return the id of the enclosing job
*/
public Long getJobId() {
if (jobInstance != null) {
return jobInstance.getId();
}
return null;
}
/**
* @param exitStatus
*/
public void setExitStatus(ExitStatus exitStatus) {
this.exitStatus = exitStatus;
}
/**
* @return the exitCode
*/
public ExitStatus getExitStatus() {
return exitStatus;
}
/**
* @return the Job that is executing.
*/
public JobInstance getJobInstance() {
return jobInstance;
}
/**
* Accessor for the step executions.
*
* @return the step executions that were registered
*/
public Collection<StepExecution> getStepExecutions() {
return Collections.unmodifiableList(new ArrayList<StepExecution>(stepExecutions));
}
/**
* Register a step execution with the current job execution.
* @param stepName the name of the step the new execution is associated with
*/
public StepExecution createStepExecution(String stepName) {
StepExecution stepExecution = new StepExecution(stepName, this);
this.stepExecutions.add(stepExecution);
return stepExecution;
}
/**
* Test if this {@link JobExecution} indicates that it is running. It should
* be noted that this does not necessarily mean that it has been persisted
* as such yet.
* @return true if the end time is null
*/
public boolean isRunning() {
return endTime == null;
}
/**
* Test if this {@link JobExecution} indicates that it has been signalled to
* stop.
* @return true if the status is {@link BatchStatus#STOPPING}
*/
public boolean isStopping() {
return status == BatchStatus.STOPPING;
}
/**
* Signal the {@link JobExecution} to stop. Iterates through the associated
* {@link StepExecution}s, calling {@link StepExecution#setTerminateOnly()}.
*
*/
public void stop() {
for (StepExecution stepExecution : stepExecutions) {
stepExecution.setTerminateOnly();
}
status = BatchStatus.STOPPING;
}
/**
* Sets the {@link ExecutionContext} for this execution
*
* @param executionContext the context
*/
public void setExecutionContext(ExecutionContext executionContext) {
this.executionContext = executionContext;
}
/**
* Returns the {@link ExecutionContext} for this execution. The content is
* expected to be persisted after each step completion (successful or not).
*
* @return the context
*/
public ExecutionContext getExecutionContext() {
return executionContext;
}
/**
* @return the time when this execution was created.
*/
public Date getCreateTime() {
return createTime;
}
/**
* @param createTime creation time of this execution.
*/
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getJobConfigurationName() {
return this.jobConfigurationName;
}
/**
* Package private method for re-constituting the step executions from
* existing instances.
* @param stepExecution
*/
void addStepExecution(StepExecution stepExecution) {
stepExecutions.add(stepExecution);
}
/**
* Get the date representing the last time this JobExecution was updated in
* the JobRepository.
*
* @return Date representing the last time this JobExecution was updated.
*/
public Date getLastUpdated() {
return lastUpdated;
}
/**
* Set the last time this JobExecution was updated.
*
* @param lastUpdated
*/
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public List<Throwable> getFailureExceptions() {
return failureExceptions;
}
/**
* Add the provided throwable to the failure exception list.
*
* @param t
*/
public synchronized void addFailureException(Throwable t) {
this.failureExceptions.add(t);
}
/**
* Return all failure causing exceptions for this JobExecution, including
* step executions.
*
* @return List<Throwable> containing all exceptions causing failure for
* this JobExecution.
*/
public synchronized List<Throwable> getAllFailureExceptions() {
Set<Throwable> allExceptions = new HashSet<Throwable>(failureExceptions);
for (StepExecution stepExecution : stepExecutions) {
allExceptions.addAll(stepExecution.getFailureExceptions());
}
return new ArrayList<Throwable>(allExceptions);
}
/**
* Deserialize and ensure transient fields are re-instantiated when read
* back
*/
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
failureExceptions = new ArrayList<Throwable>();
}
/*
* (non-Javadoc)
*
* @see org.springframework.batch.core.domain.Entity#toString()
*/
@Override
public String toString() {
return super.toString()
+ String.format(", startTime=%s, endTime=%s, lastUpdated=%s, status=%s, exitStatus=%s, job=[%s], jobParameters=[%s]",
startTime, endTime, lastUpdated, status, exitStatus, jobInstance, jobParameters);
}
/**
* Add some step executions. For internal use only.
* @param stepExecutions step executions to add to the current list
*/
public void addStepExecutions(List<StepExecution> stepExecutions) {
if (stepExecutions!=null) {
this.stepExecutions.removeAll(stepExecutions);
this.stepExecutions.addAll(stepExecutions);
}
}
}