EMMA Coverage Report (generated Fri Jan 30 13:20:29 EST 2009)
[all classes][org.springframework.batch.core.repository.support]

COVERAGE SUMMARY FOR SOURCE FILE [SimpleJobRepository.java]

nameclass, %method, %block, %line, %
SimpleJobRepository.java100% (1/1)88%  (7/8)93%  (243/260)96%  (67/70)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SimpleJobRepository100% (1/1)88%  (7/8)93%  (243/260)96%  (67/70)
SimpleJobRepository (): void 0%   (0/1)0%   (0/3)0%   (0/2)
createJobExecution (Job, JobParameters): JobExecution 100% (1/1)86%  (87/101)95%  (21/22)
SimpleJobRepository (JobInstanceDao, JobExecutionDao, StepExecutionDao): void 100% (1/1)100% (12/12)100% (5/5)
getLastStepExecution (JobInstance, Step): StepExecution 100% (1/1)100% (63/63)100% (17/17)
getStepExecutionCount (JobInstance, Step): int 100% (1/1)100% (27/27)100% (8/8)
saveOrUpdate (JobExecution): void 100% (1/1)100% (20/20)100% (6/6)
saveOrUpdate (StepExecution): void 100% (1/1)100% (24/24)100% (7/7)
saveOrUpdateExecutionContext (StepExecution): void 100% (1/1)100% (10/10)100% (3/3)

1/*
2 * Copyright 2006-2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 
17package org.springframework.batch.core.repository.support;
18 
19import java.util.ArrayList;
20import java.util.Iterator;
21import java.util.List;
22 
23import org.springframework.batch.core.BatchStatus;
24import org.springframework.batch.core.Job;
25import org.springframework.batch.core.JobExecution;
26import org.springframework.batch.core.JobInstance;
27import org.springframework.batch.core.JobParameters;
28import org.springframework.batch.core.Step;
29import org.springframework.batch.core.StepExecution;
30import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
31import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
32import org.springframework.batch.core.repository.JobRepository;
33import org.springframework.batch.core.repository.JobRestartException;
34import org.springframework.batch.core.repository.dao.JobExecutionDao;
35import org.springframework.batch.core.repository.dao.JobInstanceDao;
36import org.springframework.batch.core.repository.dao.StepExecutionDao;
37import org.springframework.batch.item.ExecutionContext;
38import org.springframework.transaction.annotation.Isolation;
39import org.springframework.util.Assert;
40 
41/**
42 * 
43 * <p>
44 * Implementation of {@link JobRepository} that stores JobInstances,
45 * JobExecutions, and StepExecutions using the injected DAOs.
46 * <p>
47 * 
48 * @author Lucas Ward
49 * @author Dave Syer
50 * @author Robert Kasanicky
51 * 
52 * @see JobRepository
53 * @see JobInstanceDao
54 * @see JobExecutionDao
55 * @see StepExecutionDao
56 * 
57 */
58public class SimpleJobRepository implements JobRepository {
59 
60        private JobInstanceDao jobInstanceDao;
61 
62        private JobExecutionDao jobExecutionDao;
63 
64        private StepExecutionDao stepExecutionDao;
65 
66        /**
67         * Provide default constructor with low visibility in case user wants to use
68         * use aop:proxy-target-class="true" for transaction interceptor.
69         */
70        SimpleJobRepository() {
71        }
72 
73        public SimpleJobRepository(JobInstanceDao jobInstanceDao, JobExecutionDao jobExecutionDao,
74                        StepExecutionDao stepExecutionDao) {
75                super();
76                this.jobInstanceDao = jobInstanceDao;
77                this.jobExecutionDao = jobExecutionDao;
78                this.stepExecutionDao = stepExecutionDao;
79        }
80 
81        /**
82         * <p>
83         * Create a {@link JobExecution} based on the passed in {@link Job} and
84         * {@link JobParameters}. However, unique identification of a job can only
85         * come from the database, and therefore must come from JobDao by either
86         * creating a new job instance or finding an existing one, which will ensure
87         * that the id of the job instance is populated with the correct value.
88         * </p>
89         * 
90         * <p>
91         * There are two ways in which the method determines if a job should be
92         * created or an existing one should be returned. The first is
93         * restartability. The {@link Job} restartable property will be checked
94         * first. If it is false, a new job will be created, regardless of whether
95         * or not one exists. If it is true, the {@link JobInstanceDao} will be
96         * checked to determine if the job already exists, if it does, it's steps
97         * will be populated (there must be at least 1) and a new
98         * {@link JobExecution} will be returned. If no job instance is found, a new
99         * one will be created.
100         * </p>
101         * 
102         * <p>
103         * A check is made to see if any job executions are already running, and an
104         * exception will be thrown if one is detected. To detect a running job
105         * execution we use the {@link JobExecutionDao}:
106         * <ol>
107         * <li>First we find all jobs which match the given {@link JobParameters}
108         * and job name</li>
109         * <li>What happens then depends on how many existing job instances we
110         * find:
111         * <ul>
112         * <li>If there are none, or the {@link Job} is marked restartable, then we
113         * create a new {@link JobInstance}</li>
114         * <li>If there is more than one and the {@link Job} is not marked as
115         * restartable, it is an error. This could be caused by a job whose
116         * restartable flag has changed to be more strict (true not false)
117         * <em>after</em> it has been executed at least once.</li>
118         * <li>If there is precisely one existing {@link JobInstance} then we check
119         * the {@link JobExecution} instances for that job, and if any of them tells
120         * us it is running (see {@link JobExecution#isRunning()}) then it is an
121         * error.</li>
122         * </ul>
123         * </li>
124         * </ol>
125         * If this method is run in a transaction (as it normally would be) with
126         * isolation level at {@link Isolation#REPEATABLE_READ} or better, then this
127         * method should block if another transaction is already executing it (for
128         * the same {@link JobParameters} and job name). The first transaction to
129         * complete in this scenario obtains a valid {@link JobExecution}, and
130         * others throw {@link JobExecutionAlreadyRunningException} (or timeout).
131         * There are no such guarantees if the {@link JobInstanceDao} and
132         * {@link JobExecutionDao} do not respect the transaction isolation levels
133         * (e.g. if using a non-relational data-store, or if the platform does not
134         * support the higher isolation levels).
135         * </p>
136         * 
137         * @see JobRepository#createJobExecution(Job, JobParameters)
138         * 
139         */
140        public JobExecution createJobExecution(Job job, JobParameters jobParameters)
141                        throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
142 
143                Assert.notNull(job, "Job must not be null.");
144                Assert.notNull(jobParameters, "JobParameters must not be null.");
145 
146                /*
147                 * Find all jobs matching the runtime information.
148                 * 
149                 * If this method is transactional, and the isolation level is
150                 * REPEATABLE_READ or better, another launcher trying to start the same
151                 * job in another thread or process will block until this transaction
152                 * has finished.
153                 */
154 
155                JobInstance jobInstance = jobInstanceDao.getJobInstance(job, jobParameters);
156                ExecutionContext executionContext;
157 
158                // existing job instance found
159                if (jobInstance != null) {
160                        if (!job.isRestartable()) {
161                                throw new JobRestartException("JobInstance already exists and is not restartable");
162                        }
163 
164                        List executions = jobExecutionDao.findJobExecutions(jobInstance);
165 
166                        // check for running executions and find the last started
167                        for (Iterator iterator = executions.iterator(); iterator.hasNext();) {
168                                JobExecution execution = (JobExecution) iterator.next();
169                                if (execution.isRunning()) {
170                                        throw new JobExecutionAlreadyRunningException("A job execution for this job is already running: "
171                                                        + jobInstance);
172                                }
173                                if (execution.getStatus() == BatchStatus.COMPLETED) {
174                                        throw new JobInstanceAlreadyCompleteException(
175                                                        "A job instance already exists and is complete for parameters=" + jobParameters
176                                                                        + ".  If you want to run this job again, change the parameters.");
177                                }
178                        }
179                        executionContext = jobExecutionDao.getLastJobExecution(jobInstance).getExecutionContext();
180                }
181                else {
182                        // no job found, create one
183                        jobInstance = jobInstanceDao.createJobInstance(job, jobParameters);
184                        executionContext = new ExecutionContext();
185                }
186 
187                JobExecution jobExecution = new JobExecution(jobInstance);
188                jobExecution.setExecutionContext(executionContext);
189 
190                // Save the JobExecution so that it picks up an ID (useful for clients
191                // monitoring asynchronous executions):
192                saveOrUpdate(jobExecution);
193 
194                return jobExecution;
195 
196        }
197 
198        /**
199         * Save or Update a JobExecution. A JobExecution is considered one
200         * 'execution' of a particular job. Therefore, it must have it's jobId field
201         * set before it is passed into this method. It also has it's own unique
202         * identifier, because it must be updatable separately. If an id isn't found,
203         * a new JobExecution is created, if one is found, the current row is
204         * updated.
205         * 
206         * @param jobExecution to be stored.
207         * @throws IllegalArgumentException if jobExecution is null.
208         */
209        public void saveOrUpdate(JobExecution jobExecution) {
210 
211                Assert.notNull(jobExecution, "JobExecution cannot be null.");
212                Assert.notNull(jobExecution.getJobId(), "JobExecution must have a Job ID set.");
213 
214                if (jobExecution.getId() == null) {
215                        // existing instance
216                        jobExecutionDao.saveJobExecution(jobExecution);
217                }
218                else {
219                        // new execution
220                        jobExecutionDao.updateJobExecution(jobExecution);
221                }
222        }
223 
224        /**
225         * Save or Update the given StepExecution. If it's id is null, it will be
226         * saved and an id will be set, otherwise it will be updated. It should be
227         * noted that assigning an ID randomly will likely cause an exception
228         * depending on the StepDao implementation.
229         * 
230         * @param stepExecution to be saved.
231         * @throws IllegalArgumentException if stepExecution is null.
232         */
233        public void saveOrUpdate(StepExecution stepExecution) {
234 
235                Assert.notNull(stepExecution, "StepExecution cannot be null.");
236                Assert.notNull(stepExecution.getStepName(), "StepExecution's step name cannot be null.");
237                Assert.notNull(stepExecution.getJobExecutionId(), "StepExecution must belong to persisted JobExecution");
238 
239                if (stepExecution.getId() == null) {
240                        stepExecutionDao.saveStepExecution(stepExecution);
241                }
242                else {
243                        // existing execution, update
244                        stepExecutionDao.updateStepExecution(stepExecution);
245                }
246        }
247 
248        /*
249         * (non-Javadoc)
250         * @see org.springframework.batch.core.repository.JobRepository#saveOrUpdateExecutionContext(org.springframework.batch.core.domain.StepExecution)
251         */
252        public void saveOrUpdateExecutionContext(StepExecution stepExecution) {
253                // Until there is an interface change (
254                stepExecutionDao.saveOrUpdateExecutionContext(stepExecution);
255                jobExecutionDao.saveOrUpdateExecutionContext(stepExecution.getJobExecution());
256        }
257 
258        /**
259         * @return the last execution of the step within given job instance
260         */
261        public StepExecution getLastStepExecution(JobInstance jobInstance, Step step) {
262                List jobExecutions = jobExecutionDao.findJobExecutions(jobInstance);
263                List stepExecutions = new ArrayList(jobExecutions.size());
264                for (Iterator iterator = jobExecutions.iterator(); iterator.hasNext();) {
265                        JobExecution jobExecution = (JobExecution) iterator.next();
266                        StepExecution stepExecution = stepExecutionDao.getStepExecution(jobExecution, step);
267                        if (stepExecution != null) {
268                                stepExecutions.add(stepExecution);
269                        }
270                }
271                StepExecution latest = null;
272                for (Iterator iterator = stepExecutions.iterator(); iterator.hasNext();) {
273                        StepExecution stepExecution = (StepExecution) iterator.next();
274                        if (latest == null) {
275                                latest = stepExecution;
276                        }
277                        if (latest.getStartTime().getTime() < stepExecution.getStartTime().getTime()) {
278                                latest = stepExecution;
279                        }
280                }
281                return latest;
282        }
283 
284        /**
285         * @return number of executions of the step within given job instance
286         */
287        public int getStepExecutionCount(JobInstance jobInstance, Step step) {
288                int count = 0;
289                List jobExecutions = jobExecutionDao.findJobExecutions(jobInstance);
290                for (Iterator iterator = jobExecutions.iterator(); iterator.hasNext();) {
291                        JobExecution jobExecution = (JobExecution) iterator.next();
292                        if (stepExecutionDao.getStepExecution(jobExecution, step) != null) {
293                                count++;
294                        }
295                }
296                return count;
297        }
298 
299}

[all classes][org.springframework.batch.core.repository.support]
EMMA 2.0.5312 (C) Vladimir Roubtsov