EMMA Coverage Report (generated Tue May 06 07:29:23 PDT 2008)
[all classes][org.springframework.batch.core.repository.dao]

COVERAGE SUMMARY FOR SOURCE FILE [JdbcStepExecutionDao.java]

nameclass, %method, %block, %line, %
JdbcStepExecutionDao.java100% (6/6)96%  (24/25)90%  (1017/1128)88%  (159.8/182)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class JdbcStepExecutionDao$1100% (1/1)100% (2/2)69%  (61/88)83%  (15/18)
processRow (ResultSet): void 100% (1/1)64%  (49/76)82%  (14/17)
JdbcStepExecutionDao$1 (JdbcStepExecutionDao, ExecutionContext, Long): void 100% (1/1)100% (12/12)100% (1/1)
     
class JdbcStepExecutionDao$2100% (1/1)100% (2/2)82%  (119/145)80%  (24/30)
setValues (PreparedStatement, LobCreator): void 100% (1/1)79%  (100/126)79%  (23/29)
JdbcStepExecutionDao$2 (JdbcStepExecutionDao, LobHandler, Long, String, JdbcS... 100% (1/1)100% (19/19)100% (1/1)
     
class JdbcStepExecutionDao$3100% (1/1)100% (2/2)82%  (119/145)80%  (24/30)
setValues (PreparedStatement, LobCreator): void 100% (1/1)79%  (100/126)79%  (23/29)
JdbcStepExecutionDao$3 (JdbcStepExecutionDao, LobHandler, Long, String, JdbcS... 100% (1/1)100% (19/19)100% (1/1)
     
class JdbcStepExecutionDao100% (1/1)92%  (12/13)95%  (574/604)92%  (71.8/78)
setLobHandler (LobHandler): void 0%   (0/1)0%   (0/4)0%   (0/2)
saveOrUpdateExecutionContext (StepExecution): void 100% (1/1)90%  (61/68)90%  (18/20)
<static initializer> 100% (1/1)91%  (10/11)90%  (0.9/1)
updateStepExecution (StepExecution): void 100% (1/1)92%  (195/211)87%  (14/16)
getStepExecution (JobExecution, Step): StepExecution 100% (1/1)98%  (43/44)99%  (5/5)
saveStepExecution (StepExecution): void 100% (1/1)99%  (150/151)100% (8/8)
JdbcStepExecutionDao (): void 100% (1/1)100% (8/8)100% (3/3)
afterPropertiesSet (): void 100% (1/1)100% (5/5)100% (2/2)
findExecutionContext (StepExecution): ExecutionContext 100% (1/1)100% (32/32)100% (6/6)
insertExecutionAttribute (Long, String, Object, JdbcStepExecutionDao$Attribut... 100% (1/1)100% (20/20)100% (3/3)
setStepExecutionIncrementer (DataFieldMaxValueIncrementer): void 100% (1/1)100% (4/4)100% (2/2)
updateExecutionAttribute (Long, String, Object, JdbcStepExecutionDao$Attribut... 100% (1/1)100% (31/31)100% (5/5)
validateStepExecution (StepExecution): void 100% (1/1)100% (15/15)100% (5/5)
     
class JdbcStepExecutionDao$AttributeType100% (1/1)100% (4/4)97%  (68/70)92%  (12/13)
getType (String): JdbcStepExecutionDao$AttributeType 100% (1/1)90%  (19/21)75%  (3/4)
<static initializer> 100% (1/1)100% (40/40)100% (5/5)
JdbcStepExecutionDao$AttributeType (String): void 100% (1/1)100% (6/6)100% (3/3)
toString (): String 100% (1/1)100% (3/3)100% (1/1)
     
class JdbcStepExecutionDao$StepExecutionRowMapper100% (1/1)100% (2/2)100% (76/76)100% (13/13)
JdbcStepExecutionDao$StepExecutionRowMapper (JdbcStepExecutionDao, JobExecuti... 100% (1/1)100% (12/12)100% (4/4)
mapRow (ResultSet, int): Object 100% (1/1)100% (64/64)100% (9/9)

1package org.springframework.batch.core.repository.dao;
2 
3import java.io.Serializable;
4import java.sql.PreparedStatement;
5import java.sql.ResultSet;
6import java.sql.SQLException;
7import java.sql.Types;
8import java.util.Iterator;
9import java.util.List;
10import java.util.Map.Entry;
11 
12import org.apache.commons.lang.SerializationUtils;
13import org.apache.commons.logging.Log;
14import org.apache.commons.logging.LogFactory;
15import org.springframework.batch.core.BatchStatus;
16import org.springframework.batch.core.UnexpectedJobExecutionException;
17import org.springframework.batch.core.JobExecution;
18import org.springframework.batch.core.Step;
19import org.springframework.batch.core.StepExecution;
20import org.springframework.batch.item.ExecutionContext;
21import org.springframework.batch.repeat.ExitStatus;
22import org.springframework.beans.factory.InitializingBean;
23import org.springframework.dao.DataAccessException;
24import org.springframework.dao.OptimisticLockingFailureException;
25import org.springframework.jdbc.core.PreparedStatementCallback;
26import org.springframework.jdbc.core.RowCallbackHandler;
27import org.springframework.jdbc.core.RowMapper;
28import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback;
29import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
30import org.springframework.jdbc.support.lob.DefaultLobHandler;
31import org.springframework.jdbc.support.lob.LobCreator;
32import org.springframework.jdbc.support.lob.LobHandler;
33import org.springframework.util.Assert;
34 
35/**
36 * Jdbc implementation of {@link StepExecutionDao}.<br/>
37 * 
38 * Allows customization of the tables names used by Spring Batch for step meta
39 * data via a prefix property.<br/>
40 * 
41 * Uses sequences or tables (via Spring's {@link DataFieldMaxValueIncrementer}
42 * abstraction) to create all primary keys before inserting a new row. All
43 * objects are checked to ensure all fields to be stored are not null. If any
44 * are found to be null, an IllegalArgumentException will be thrown. This could
45 * be left to JdbcTemplate, however, the exception will be fairly vague, and
46 * fails to highlight which field caused the exception.<br/>
47 * 
48 * @author Lucas Ward
49 * @author Dave Syer
50 * @author Robert Kasanicky
51 * 
52 * @see StepExecutionDao
53 */
54public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implements StepExecutionDao, InitializingBean {
55 
56        private static final Log logger = LogFactory.getLog(JdbcStepExecutionDao.class);
57 
58        private static final String FIND_STEP_EXECUTION_CONTEXT = "SELECT TYPE_CD, KEY_NAME, STRING_VAL, DOUBLE_VAL, LONG_VAL, OBJECT_VAL "
59                        + "from %PREFIX%STEP_EXECUTION_CONTEXT where STEP_EXECUTION_ID = ?";
60 
61        private static final String INSERT_STEP_EXECUTION_CONTEXT = "INSERT into %PREFIX%STEP_EXECUTION_CONTEXT(STEP_EXECUTION_ID, TYPE_CD,"
62                        + " KEY_NAME, STRING_VAL, DOUBLE_VAL, LONG_VAL, OBJECT_VAL) values(?,?,?,?,?,?,?)";
63 
64        private static final String SAVE_STEP_EXECUTION = "INSERT into %PREFIX%STEP_EXECUTION(STEP_EXECUTION_ID, VERSION, STEP_NAME, JOB_EXECUTION_ID, START_TIME, "
65                        + "END_TIME, STATUS, COMMIT_COUNT, ITEM_COUNT, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE) "
66                        + "values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
67 
68        private static final String UPDATE_STEP_EXECUTION_CONTEXT = "UPDATE %PREFIX%STEP_EXECUTION_CONTEXT set "
69                        + "TYPE_CD = ?, STRING_VAL = ?, DOUBLE_VAL = ?, LONG_VAL = ?, OBJECT_VAL = ? where STEP_EXECUTION_ID = ? and KEY_NAME = ?";
70 
71        private static final String UPDATE_STEP_EXECUTION = "UPDATE %PREFIX%STEP_EXECUTION set START_TIME = ?, END_TIME = ?, "
72                        + "STATUS = ?, COMMIT_COUNT = ?, ITEM_COUNT = ?, CONTINUABLE = ? , EXIT_CODE = ?, "
73                        + "EXIT_MESSAGE = ?, VERSION = ? where STEP_EXECUTION_ID = ? and VERSION = ?";
74 
75        private static final String GET_STEP_EXECUTION = "SELECT STEP_EXECUTION_ID, STEP_NAME, START_TIME, END_TIME, STATUS, COMMIT_COUNT,"
76                        + " ITEM_COUNT, CONTINUABLE, EXIT_CODE, EXIT_MESSAGE from %PREFIX%STEP_EXECUTION where STEP_NAME = ? and JOB_EXECUTION_ID = ?";
77 
78        private static final String CURRENT_VERSION_STEP_EXECUTION = "SELECT VERSION FROM %PREFIX%STEP_EXECUTION WHERE STEP_EXECUTION_ID=?";
79 
80        private static final int EXIT_MESSAGE_LENGTH = 250;
81 
82        private LobHandler lobHandler = new DefaultLobHandler();
83 
84        private DataFieldMaxValueIncrementer stepExecutionIncrementer;
85 
86        public ExecutionContext findExecutionContext(final StepExecution stepExecution) {
87 
88                final Long executionId = stepExecution.getId();
89                Assert.notNull(executionId, "ExecutionId must not be null.");
90 
91                final ExecutionContext executionContext = new ExecutionContext();
92 
93                RowCallbackHandler callback = new RowCallbackHandler() {
94 
95                        public void processRow(ResultSet rs) throws SQLException {
96 
97                                String typeCd = rs.getString("TYPE_CD");
98                                AttributeType type = AttributeType.getType(typeCd);
99                                String key = rs.getString("KEY_NAME");
100                                if (type == AttributeType.STRING) {
101                                        executionContext.putString(key, rs.getString("STRING_VAL"));
102                                }
103                                else if (type == AttributeType.LONG) {
104                                        executionContext.putLong(key, rs.getLong("LONG_VAL"));
105                                }
106                                else if (type == AttributeType.DOUBLE) {
107                                        executionContext.putDouble(key, rs.getDouble("DOUBLE_VAL"));
108                                }
109                                else if (type == AttributeType.OBJECT) {
110                                        executionContext.put(key, SerializationUtils.deserialize(rs.getBinaryStream("OBJECT_VAL")));
111                                }
112                                else {
113                                        throw new UnexpectedJobExecutionException("Invalid type found: [" + typeCd
114                                                        + "] for execution id: [" + executionId + "]");
115                                }
116                        }
117                };
118 
119                getJdbcTemplate().query(getQuery(FIND_STEP_EXECUTION_CONTEXT), new Object[] { executionId }, callback);
120 
121                return executionContext;
122        }
123 
124        private void insertExecutionAttribute(final Long executionId, final String key, final Object value,
125                        final AttributeType type) {
126                PreparedStatementCallback callback = new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
127 
128                        protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException,
129                                        DataAccessException {
130 
131                                ps.setLong(1, executionId.longValue());
132                                ps.setString(3, key);
133                                if (type == AttributeType.STRING) {
134                                        ps.setString(2, AttributeType.STRING.toString());
135                                        ps.setString(4, value.toString());
136                                        ps.setDouble(5, 0.0);
137                                        ps.setLong(6, 0);
138                                        lobCreator.setBlobAsBytes(ps, 7, null);
139                                }
140                                else if (type == AttributeType.DOUBLE) {
141                                        ps.setString(2, AttributeType.DOUBLE.toString());
142                                        ps.setString(4, null);
143                                        ps.setDouble(5, ((Double) value).doubleValue());
144                                        ps.setLong(6, 0);
145                                        lobCreator.setBlobAsBytes(ps, 7, null);
146                                }
147                                else if (type == AttributeType.LONG) {
148                                        ps.setString(2, AttributeType.LONG.toString());
149                                        ps.setString(4, null);
150                                        ps.setDouble(5, 0.0);
151                                        ps.setLong(6, ((Long) value).longValue());
152                                        lobCreator.setBlobAsBytes(ps, 7, null);
153                                }
154                                else {
155                                        ps.setString(2, AttributeType.OBJECT.toString());
156                                        ps.setString(4, null);
157                                        ps.setDouble(5, 0.0);
158                                        ps.setLong(6, 0);
159                                        lobCreator.setBlobAsBytes(ps, 7, SerializationUtils.serialize((Serializable) value));
160                                }
161                        }
162                };
163                getJdbcTemplate().execute(getQuery(INSERT_STEP_EXECUTION_CONTEXT), callback);
164        }
165 
166        /**
167         * Save a StepExecution. A unique id will be generated by the
168         * stepExecutionIncrementor, and then set in the StepExecution. All values
169         * will then be stored via an INSERT statement.
170         * 
171         * @see StepExecutionDao#saveStepExecution(StepExecution)
172         */
173        public void saveStepExecution(StepExecution stepExecution) {
174 
175                Assert.isNull(stepExecution.getId(),
176                                "to-be-saved (not updated) StepExecution can't already have an id assigned");
177                Assert.isNull(stepExecution.getVersion(),
178                                "to-be-saved (not updated) StepExecution can't already have a version assigned");
179 
180                validateStepExecution(stepExecution);
181 
182                stepExecution.setId(new Long(stepExecutionIncrementer.nextLongValue()));
183                stepExecution.incrementVersion(); // should be 0 now
184                Object[] parameters = new Object[] { stepExecution.getId(), stepExecution.getVersion(),
185                                stepExecution.getStepName(), stepExecution.getJobExecutionId(), stepExecution.getStartTime(),
186                                stepExecution.getEndTime(), stepExecution.getStatus().toString(), stepExecution.getCommitCount(),
187                                stepExecution.getItemCount(), stepExecution.getExitStatus().isContinuable() ? "Y" : "N",
188                                stepExecution.getExitStatus().getExitCode(), stepExecution.getExitStatus().getExitDescription() };
189                getJdbcTemplate().update(
190                                getQuery(SAVE_STEP_EXECUTION),
191                                parameters,
192                                new int[] { Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.INTEGER, Types.TIMESTAMP,
193                                                Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.CHAR, Types.VARCHAR,
194                                                Types.VARCHAR });
195        }
196 
197        /**
198         * Validate StepExecution. At a minimum, JobId, StartTime, and Status cannot
199         * be null. EndTime can be null for an unfinished job.
200         * 
201         * @param jobExecution
202         * @throws IllegalArgumentException
203         */
204        private void validateStepExecution(StepExecution stepExecution) {
205                Assert.notNull(stepExecution);
206                Assert.notNull(stepExecution.getStepName(), "StepExecution step name cannot be null.");
207                Assert.notNull(stepExecution.getStartTime(), "StepExecution start time cannot be null.");
208                Assert.notNull(stepExecution.getStatus(), "StepExecution status cannot be null.");
209        }
210 
211        /**
212         * Save or update execution attributes. A lob creator must be used, since
213         * any attributes that don't match a provided type must be serialized into a
214         * blob.
215         * 
216         * @see LobCreator
217         */
218        public void saveOrUpdateExecutionContext(final StepExecution stepExecution) {
219 
220                Long executionId = stepExecution.getId();
221                ExecutionContext executionContext = stepExecution.getExecutionContext();
222                Assert.notNull(executionId, "ExecutionId must not be null.");
223                Assert.notNull(executionContext, "The ExecutionContext must not be null.");
224 
225                for (Iterator it = executionContext.entrySet().iterator(); it.hasNext();) {
226                        Entry entry = (Entry) it.next();
227                        final String key = entry.getKey().toString();
228                        final Object value = entry.getValue();
229 
230                        if (value instanceof String) {
231                                updateExecutionAttribute(executionId, key, value, AttributeType.STRING);
232                        }
233                        else if (value instanceof Double) {
234                                updateExecutionAttribute(executionId, key, value, AttributeType.DOUBLE);
235                        }
236                        else if (value instanceof Long) {
237                                updateExecutionAttribute(executionId, key, value, AttributeType.LONG);
238                        }
239                        else {
240                                updateExecutionAttribute(executionId, key, value, AttributeType.OBJECT);
241                        }
242                }
243        }
244 
245        private void updateExecutionAttribute(final Long executionId, final String key, final Object value,
246                        final AttributeType type) {
247 
248                PreparedStatementCallback callback = new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
249 
250                        protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException,
251                                        DataAccessException {
252 
253                                ps.setLong(6, executionId.longValue());
254                                ps.setString(7, key);
255                                if (type == AttributeType.STRING) {
256                                        ps.setString(1, AttributeType.STRING.toString());
257                                        ps.setString(2, value.toString());
258                                        ps.setDouble(3, 0.0);
259                                        ps.setLong(4, 0);
260                                        lobCreator.setBlobAsBytes(ps, 5, null);
261                                }
262                                else if (type == AttributeType.DOUBLE) {
263                                        ps.setString(1, AttributeType.DOUBLE.toString());
264                                        ps.setString(2, null);
265                                        ps.setDouble(3, ((Double) value).doubleValue());
266                                        ps.setLong(4, 0);
267                                        lobCreator.setBlobAsBytes(ps, 5, null);
268                                }
269                                else if (type == AttributeType.LONG) {
270                                        ps.setString(1, AttributeType.LONG.toString());
271                                        ps.setString(2, null);
272                                        ps.setDouble(3, 0.0);
273                                        ps.setLong(4, ((Long) value).longValue());
274                                        lobCreator.setBlobAsBytes(ps, 5, null);
275                                }
276                                else {
277                                        ps.setString(1, AttributeType.OBJECT.toString());
278                                        ps.setString(2, null);
279                                        ps.setDouble(3, 0.0);
280                                        ps.setLong(4, 0);
281                                        lobCreator.setBlobAsBytes(ps, 5, SerializationUtils.serialize((Serializable) value));
282                                }
283                        }
284                };
285 
286                // LobCreating callbacks always return the affect row count for SQL DML
287                // statements, if less than 1 row
288                // is affected, then this row is new and should be inserted.
289                Integer affectedRows = (Integer) getJdbcTemplate().execute(getQuery(UPDATE_STEP_EXECUTION_CONTEXT), callback);
290                if (affectedRows.intValue() < 1) {
291                        insertExecutionAttribute(executionId, key, value, type);
292                }
293        }
294 
295        /*
296         * (non-Javadoc)
297         * @see org.springframework.batch.execution.repository.dao.StepExecutionDao#updateStepExecution(org.springframework.batch.core.domain.StepExecution)
298         */
299        public void updateStepExecution(StepExecution stepExecution) {
300 
301                validateStepExecution(stepExecution);
302                Assert.notNull(stepExecution.getId(), "StepExecution Id cannot be null. StepExecution must saved"
303                                + " before it can be updated.");
304 
305                // Do not check for existence of step execution considering
306                // it is saved at every commit point.
307 
308                String exitDescription = stepExecution.getExitStatus().getExitDescription();
309                if (exitDescription != null && exitDescription.length() > EXIT_MESSAGE_LENGTH) {
310                        exitDescription = exitDescription.substring(0, EXIT_MESSAGE_LENGTH);
311                        logger.debug("Truncating long message before update of StepExecution: " + stepExecution);
312                }
313 
314                // Attempt to prevent concurrent modification errors by blocking here if
315                // someone is already trying to do it.
316                synchronized (stepExecution) {
317 
318                        Integer version = new Integer(stepExecution.getVersion().intValue() + 1);
319                        Object[] parameters = new Object[] { stepExecution.getStartTime(), stepExecution.getEndTime(),
320                                        stepExecution.getStatus().toString(), stepExecution.getCommitCount(), stepExecution.getItemCount(),
321                                        stepExecution.getExitStatus().isContinuable() ? "Y" : "N",
322                                        stepExecution.getExitStatus().getExitCode(), exitDescription, version, stepExecution.getId(),
323                                        stepExecution.getVersion() };
324                        int count = getJdbcTemplate().update(
325                                        getQuery(UPDATE_STEP_EXECUTION),
326                                        parameters,
327                                        new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER,
328                                                        Types.CHAR, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.INTEGER });
329 
330                        // Avoid concurrent modifications...
331                        if (count == 0) {
332                                int curentVersion = getJdbcTemplate().queryForInt(getQuery(CURRENT_VERSION_STEP_EXECUTION),
333                                                new Object[] { stepExecution.getId() });
334                                throw new OptimisticLockingFailureException("Attempt to update step execution id="
335                                                + stepExecution.getId() + " with wrong version (" + stepExecution.getVersion()
336                                                + "), where current version is " + curentVersion);
337                        }
338 
339                        stepExecution.incrementVersion();
340 
341                }
342        }
343 
344        private class StepExecutionRowMapper implements RowMapper {
345 
346                private final JobExecution jobExecution;
347 
348                private final Step step;
349 
350                public StepExecutionRowMapper(JobExecution jobExecution, Step step) {
351                        this.jobExecution = jobExecution;
352                        this.step = step;
353                }
354 
355                public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
356 
357                        StepExecution stepExecution = new StepExecution(step, jobExecution, new Long(rs.getLong(1)));
358                        stepExecution.setStartTime(rs.getTimestamp(3));
359                        stepExecution.setEndTime(rs.getTimestamp(4));
360                        stepExecution.setStatus(BatchStatus.getStatus(rs.getString(5)));
361                        stepExecution.setCommitCount(rs.getInt(6));
362                        stepExecution.setItemCount(rs.getInt(7));
363                        stepExecution.setExitStatus(new ExitStatus("Y".equals(rs.getString(8)), rs.getString(9), rs.getString(10)));
364                        stepExecution.setExecutionContext(findExecutionContext(stepExecution));
365                        return stepExecution;
366                }
367 
368        }
369 
370        public void setLobHandler(LobHandler lobHandler) {
371                this.lobHandler = lobHandler;
372        }
373 
374        public void setStepExecutionIncrementer(DataFieldMaxValueIncrementer stepExecutionIncrementer) {
375                this.stepExecutionIncrementer = stepExecutionIncrementer;
376        }
377 
378        public void afterPropertiesSet() throws Exception {
379                Assert.notNull(stepExecutionIncrementer, "StepExecutionIncrementer cannot be null.");
380        }
381 
382        public static class AttributeType {
383 
384                private final String type;
385 
386                private AttributeType(String type) {
387                        this.type = type;
388                }
389 
390                public String toString() {
391                        return type;
392                }
393 
394                public static final AttributeType STRING = new AttributeType("STRING");
395 
396                public static final AttributeType LONG = new AttributeType("LONG");
397 
398                public static final AttributeType OBJECT = new AttributeType("OBJECT");
399 
400                public static final AttributeType DOUBLE = new AttributeType("DOUBLE");
401 
402                private static final AttributeType[] VALUES = { STRING, OBJECT, LONG, DOUBLE };
403 
404                public static AttributeType getType(String typeAsString) {
405 
406                        for (int i = 0; i < VALUES.length; i++) {
407                                if (VALUES[i].toString().equals(typeAsString)) {
408                                        return (AttributeType) VALUES[i];
409                                }
410                        }
411 
412                        return null;
413                }
414        }
415 
416        public StepExecution getStepExecution(JobExecution jobExecution, Step step) {
417                List executions = getJdbcTemplate().query(getQuery(GET_STEP_EXECUTION),
418                                new Object[] { step.getName(), jobExecution.getId() }, new StepExecutionRowMapper(jobExecution, step));
419 
420                Assert.state(executions.size() <= 1,
421                                "There can be at most one step execution with given name for single job execution");
422                if (executions.isEmpty()) {
423                        return null;
424                }
425                else {
426                        return (StepExecution) executions.get(0);
427                }
428        }
429 
430}

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