001/*
002 * Copyright 2006-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.batch.core;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Date;
025import java.util.HashSet;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Set;
029import java.util.concurrent.CopyOnWriteArrayList;
030
031import org.springframework.batch.item.ExecutionContext;
032import org.springframework.lang.Nullable;
033
034/**
035 * Batch domain object representing the execution of a job.
036 *
037 * @author Lucas Ward
038 * @author Michael Minella
039 * @author Mahmoud Ben Hassine
040 * @author Dimitrios Liapis
041 *
042 */
043@SuppressWarnings("serial")
044public class JobExecution extends Entity {
045
046        private final JobParameters jobParameters;
047
048        private JobInstance jobInstance;
049
050        private volatile Collection<StepExecution> stepExecutions = Collections.synchronizedSet(new LinkedHashSet<>());
051
052        private volatile BatchStatus status = BatchStatus.STARTING;
053
054        private volatile Date startTime = null;
055
056        private volatile Date createTime = new Date(System.currentTimeMillis());
057
058        private volatile Date endTime = null;
059
060        private volatile Date lastUpdated = null;
061
062        private volatile ExitStatus exitStatus = ExitStatus.UNKNOWN;
063
064        private volatile ExecutionContext executionContext = new ExecutionContext();
065
066        private transient volatile List<Throwable> failureExceptions = new CopyOnWriteArrayList<>();
067
068        private final String jobConfigurationName;
069
070        public JobExecution(JobExecution original) {
071                this.jobParameters = original.getJobParameters();
072                this.jobInstance = original.getJobInstance();
073                this.stepExecutions = original.getStepExecutions();
074                this.status = original.getStatus();
075                this.startTime = original.getStartTime();
076                this.createTime = original.getCreateTime();
077                this.endTime = original.getEndTime();
078                this.lastUpdated = original.getLastUpdated();
079                this.exitStatus = original.getExitStatus();
080                this.executionContext = original.getExecutionContext();
081                this.failureExceptions = original.getFailureExceptions();
082                this.jobConfigurationName = original.getJobConfigurationName();
083                this.setId(original.getId());
084                this.setVersion(original.getVersion());
085        }
086
087        /**
088         * Because a JobExecution isn't valid unless the job is set, this
089         * constructor is the only valid one from a modeling point of view.
090         *
091         * @param job the job of which this execution is a part
092         * @param id {@link Long} that represents the id for the JobExecution.
093         * @param jobParameters {@link JobParameters} instance for this JobExecution.
094         * @param jobConfigurationName {@link String} instance that represents the
095         * job configuration name (used with JSR-352).
096         */
097        public JobExecution(JobInstance job, Long id, @Nullable JobParameters jobParameters, String jobConfigurationName) {
098                super(id);
099                this.jobInstance = job;
100                this.jobParameters = jobParameters == null ? new JobParameters() : jobParameters;
101                this.jobConfigurationName = jobConfigurationName;
102        }
103
104        public JobExecution(JobInstance job, JobParameters jobParameters, String jobConfigurationName) {
105                this(job, null, jobParameters, jobConfigurationName);
106        }
107
108        public JobExecution(Long id, JobParameters jobParameters, String jobConfigurationName) {
109                this(null, id, jobParameters, jobConfigurationName);
110        }
111
112        /**
113         * Constructor for transient (unsaved) instances.
114         *
115         * @param job the enclosing {@link JobInstance}
116         * @param jobParameters {@link JobParameters} instance for this JobExecution.
117         */
118        public JobExecution(JobInstance job, JobParameters jobParameters) {
119                this(job, null, jobParameters, null);
120        }
121
122        public JobExecution(Long id, JobParameters jobParameters) {
123                this(null, id, jobParameters, null);
124        }
125
126        public JobExecution(Long id) {
127                this(null, id, null, null);
128        }
129
130        public JobParameters getJobParameters() {
131                return this.jobParameters;
132        }
133
134        public Date getEndTime() {
135                return endTime;
136        }
137
138        public void setJobInstance(JobInstance jobInstance) {
139                this.jobInstance = jobInstance;
140        }
141
142        public void setEndTime(Date endTime) {
143                this.endTime = endTime;
144        }
145
146        public Date getStartTime() {
147                return startTime;
148        }
149
150        public void setStartTime(Date startTime) {
151                this.startTime = startTime;
152        }
153
154        public BatchStatus getStatus() {
155                return status;
156        }
157
158        /**
159         * Set the value of the status field.
160         *
161         * @param status the status to set
162         */
163        public void setStatus(BatchStatus status) {
164                this.status = status;
165        }
166
167        /**
168         * Upgrade the status field if the provided value is greater than the
169         * existing one. Clients using this method to set the status can be sure
170         * that they don't overwrite a failed status with an successful one.
171         *
172         * @param status the new status value
173         */
174        public void upgradeStatus(BatchStatus status) {
175                this.status = this.status.upgradeTo(status);
176        }
177
178        /**
179         * Convenience getter for for the id of the enclosing job. Useful for DAO
180         * implementations.
181         *
182         * @return the id of the enclosing job
183         */
184        public Long getJobId() {
185                if (jobInstance != null) {
186                        return jobInstance.getId();
187                }
188                return null;
189        }
190
191        /**
192         * @param exitStatus {@link ExitStatus} instance to be used for job execution.
193         */
194        public void setExitStatus(ExitStatus exitStatus) {
195                this.exitStatus = exitStatus;
196        }
197
198        /**
199         * @return the exitCode
200         */
201        public ExitStatus getExitStatus() {
202                return exitStatus;
203        }
204
205        /**
206         * @return the Job that is executing.
207         */
208        public JobInstance getJobInstance() {
209                return jobInstance;
210        }
211
212        /**
213         * Accessor for the step executions.
214         *
215         * @return the step executions that were registered
216         */
217        public Collection<StepExecution> getStepExecutions() {
218                return Collections.unmodifiableList(new ArrayList<>(stepExecutions));
219        }
220
221        /**
222         * Register a step execution with the current job execution.
223         * @param stepName the name of the step the new execution is associated with
224         * @return {@link StepExecution} an empty {@code StepExecution} associated with this
225         *      {@code JobExecution}.
226         */
227        public StepExecution createStepExecution(String stepName) {
228                StepExecution stepExecution = new StepExecution(stepName, this);
229                this.stepExecutions.add(stepExecution);
230                return stepExecution;
231        }
232
233        /**
234         * Test if this {@link JobExecution} indicates that it is running. It should
235         * be noted that this does not necessarily mean that it has been persisted
236         * as such yet.
237         *
238         * @return true if the end time is null and the start time is not null
239         */
240        public boolean isRunning() {
241                return startTime != null && endTime == null;
242        }
243
244        /**
245         * Test if this {@link JobExecution} indicates that it has been signalled to
246         * stop.
247         * @return true if the status is {@link BatchStatus#STOPPING}
248         */
249        public boolean isStopping() {
250                return status == BatchStatus.STOPPING;
251        }
252
253        /**
254         * Signal the {@link JobExecution} to stop. Iterates through the associated
255         * {@link StepExecution}s, calling {@link StepExecution#setTerminateOnly()}.
256         *
257         */
258        public void stop() {
259                for (StepExecution stepExecution : stepExecutions) {
260                        stepExecution.setTerminateOnly();
261                }
262                status = BatchStatus.STOPPING;
263        }
264
265        /**
266         * Sets the {@link ExecutionContext} for this execution
267         *
268         * @param executionContext the context
269         */
270        public void setExecutionContext(ExecutionContext executionContext) {
271                this.executionContext = executionContext;
272        }
273
274        /**
275         * Returns the {@link ExecutionContext} for this execution. The content is
276         * expected to be persisted after each step completion (successful or not).
277         *
278         * @return the context
279         */
280        public ExecutionContext getExecutionContext() {
281                return executionContext;
282        }
283
284        /**
285         * @return the time when this execution was created.
286         */
287        public Date getCreateTime() {
288                return createTime;
289        }
290
291        /**
292         * @param createTime creation time of this execution.
293         */
294        public void setCreateTime(Date createTime) {
295                this.createTime = createTime;
296        }
297
298        public String getJobConfigurationName() {
299                return this.jobConfigurationName;
300        }
301
302        /**
303         * Package private method for re-constituting the step executions from
304         * existing instances.
305         * @param stepExecution execution to be added
306         */
307        void addStepExecution(StepExecution stepExecution) {
308                stepExecutions.add(stepExecution);
309        }
310
311        /**
312         * Get the date representing the last time this JobExecution was updated in
313         * the JobRepository.
314         *
315         * @return Date representing the last time this JobExecution was updated.
316         */
317        public Date getLastUpdated() {
318                return lastUpdated;
319        }
320
321        /**
322         * Set the last time this JobExecution was updated.
323         *
324         * @param lastUpdated {@link Date} instance to mark job execution's lastUpdated attribute.
325         */
326        public void setLastUpdated(Date lastUpdated) {
327                this.lastUpdated = lastUpdated;
328        }
329
330        public List<Throwable> getFailureExceptions() {
331                return failureExceptions;
332        }
333
334        /**
335         * Add the provided throwable to the failure exception list.
336         *
337         * @param t {@link Throwable} instance to be added failure exception list.
338         */
339        public synchronized void addFailureException(Throwable t) {
340                this.failureExceptions.add(t);
341        }
342
343        /**
344         * Return all failure causing exceptions for this JobExecution, including
345         * step executions.
346         *
347         * @return List&lt;Throwable&gt; containing all exceptions causing failure for
348         * this JobExecution.
349         */
350        public synchronized List<Throwable> getAllFailureExceptions() {
351
352                Set<Throwable> allExceptions = new HashSet<>(failureExceptions);
353                for (StepExecution stepExecution : stepExecutions) {
354                        allExceptions.addAll(stepExecution.getFailureExceptions());
355                }
356
357                return new ArrayList<>(allExceptions);
358        }
359
360        /**
361         * Deserialize and ensure transient fields are re-instantiated when read
362         * back.
363         *
364         * @param stream instance of {@link ObjectInputStream}.
365         *
366         * @throws IOException thrown if error occurs during read.
367         * @throws ClassNotFoundException thrown if class is not found.
368         */
369        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
370                stream.defaultReadObject();
371                failureExceptions = new ArrayList<>();
372        }
373
374        /*
375         * (non-Javadoc)
376         *
377         * @see org.springframework.batch.core.domain.Entity#toString()
378         */
379        @Override
380        public String toString() {
381                return super.toString()
382                                + String.format(", startTime=%s, endTime=%s, lastUpdated=%s, status=%s, exitStatus=%s, job=[%s], jobParameters=[%s]",
383                                                startTime, endTime, lastUpdated, status, exitStatus, jobInstance, jobParameters);
384        }
385
386        /**
387         * Add some step executions.  For internal use only.
388         * @param stepExecutions step executions to add to the current list
389         */
390        public void addStepExecutions(List<StepExecution> stepExecutions) {
391                if (stepExecutions!=null) {
392                        this.stepExecutions.removeAll(stepExecutions);
393                        this.stepExecutions.addAll(stepExecutions);
394                }
395        }
396}