diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java index d3645348d..f443771e4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDao.java @@ -25,6 +25,7 @@ import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -58,7 +59,8 @@ import org.springframework.util.StringUtils; * @author Dave Syer * @author Robert Kasanicky */ -public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements JobInstanceDao, InitializingBean { +public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements + JobInstanceDao, InitializingBean { private static final String CREATE_JOB_INSTANCE = "INSERT into %PREFIX%JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION)" + " values (?, ?, ?, ?)"; @@ -68,7 +70,8 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements private static final String FIND_JOBS_WITH_NAME = "SELECT JOB_INSTANCE_ID, JOB_NAME from %PREFIX%JOB_INSTANCE where JOB_NAME = ?"; - private static final String FIND_JOBS_WITH_KEY = FIND_JOBS_WITH_NAME + " and JOB_KEY = ?"; + private static final String FIND_JOBS_WITH_KEY = FIND_JOBS_WITH_NAME + + " and JOB_KEY = ?"; private static final String FIND_JOBS_WITH_EMPTY_KEY = "SELECT JOB_INSTANCE_ID, JOB_NAME from %PREFIX%JOB_INSTANCE where JOB_NAME = ? and (JOB_KEY = ? OR JOB_KEY is NULL)"; @@ -92,24 +95,30 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements * then passing the Id and parameter values into an INSERT statement. * * @see JobInstanceDao#createJobInstance(String, JobParameters) - * @throws IllegalArgumentException if any {@link JobParameters} fields are - * null. + * @throws IllegalArgumentException + * if any {@link JobParameters} fields are null. */ - public JobInstance createJobInstance(String jobName, JobParameters jobParameters) { + public JobInstance createJobInstance(String jobName, + JobParameters jobParameters) { Assert.notNull(jobName, "Job name must not be null."); Assert.notNull(jobParameters, "JobParameters must not be null."); - Assert.state(getJobInstance(jobName, jobParameters) == null, "JobInstance must not already exist"); + Assert.state(getJobInstance(jobName, jobParameters) == null, + "JobInstance must not already exist"); Long jobId = jobIncrementer.nextLongValue(); JobInstance jobInstance = new JobInstance(jobId, jobParameters, jobName); jobInstance.incrementVersion(); - Object[] parameters = new Object[] { jobId, jobName, createJobKey(jobParameters), jobInstance.getVersion() }; - getJdbcTemplate().getJdbcOperations().update(getQuery(CREATE_JOB_INSTANCE), parameters, - new int[] { Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.INTEGER }); + Object[] parameters = new Object[] { jobId, jobName, + createJobKey(jobParameters), jobInstance.getVersion() }; + getJdbcTemplate().getJdbcOperations().update( + getQuery(CREATE_JOB_INSTANCE), + parameters, + new int[] { Types.INTEGER, Types.VARCHAR, Types.VARCHAR, + Types.INTEGER }); insertJobParameters(jobId, jobParameters); @@ -120,24 +129,27 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements Map props = jobParameters.getParameters(); StringBuffer stringBuffer = new StringBuffer(); - for (Entry entry : props.entrySet()) { - stringBuffer.append(entry.toString() + ";"); + List keys = new ArrayList(props.keySet()); + Collections.sort(keys); + for (String key : keys) { + stringBuffer.append(key + "=" + props.get(key).toString() + ";"); } - + MessageDigest digest; try { digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException( + "MD5 algorithm not available. Fatal (should be in the JDK)."); } - catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK)."); - } - + try { - byte[] bytes = digest.digest(stringBuffer.toString().getBytes("UTF-8")); + byte[] bytes = digest.digest(stringBuffer.toString().getBytes( + "UTF-8")); return String.format("%032x", new BigInteger(1, bytes)); - } - catch (UnsupportedEncodingException e) { - throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK)."); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException( + "UTF-8 encoding not available. Fatal (should be in the JDK)."); } } @@ -148,9 +160,11 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements */ private void insertJobParameters(Long jobId, JobParameters jobParameters) { - for (Entry entry : jobParameters.getParameters().entrySet()) { + for (Entry entry : jobParameters.getParameters() + .entrySet()) { JobParameter jobParameter = entry.getValue(); - insertParameter(jobId, jobParameter.getType(), entry.getKey(), jobParameter.getValue()); + insertParameter(jobId, jobParameter.getType(), entry.getKey(), + jobParameter.getValue()); } } @@ -158,26 +172,29 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements * Convenience method that inserts an individual records into the * JobParameters table. */ - private void insertParameter(Long jobId, ParameterType type, String key, Object value) { + private void insertParameter(Long jobId, ParameterType type, String key, + Object value) { Object[] args = new Object[0]; - int[] argTypes = new int[] { Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, - Types.BIGINT, Types.DOUBLE }; + int[] argTypes = new int[] { Types.BIGINT, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, Types.BIGINT, + Types.DOUBLE }; if (type == ParameterType.STRING) { - args = new Object[] { jobId, key, type, value, new Timestamp(0L), 0L, 0D }; - } - else if (type == ParameterType.LONG) { - args = new Object[] { jobId, key, type, "", new Timestamp(0L), value, new Double(0) }; - } - else if (type == ParameterType.DOUBLE) { - args = new Object[] { jobId, key, type, "", new Timestamp(0L), 0L, value }; - } - else if (type == ParameterType.DATE) { + args = new Object[] { jobId, key, type, value, new Timestamp(0L), + 0L, 0D }; + } else if (type == ParameterType.LONG) { + args = new Object[] { jobId, key, type, "", new Timestamp(0L), + value, new Double(0) }; + } else if (type == ParameterType.DOUBLE) { + args = new Object[] { jobId, key, type, "", new Timestamp(0L), 0L, + value }; + } else if (type == ParameterType.DATE) { args = new Object[] { jobId, key, type, "", value, 0L, 0D }; } - getJdbcTemplate().getJdbcOperations().update(getQuery(CREATE_JOB_PARAMETERS), args, argTypes); + getJdbcTemplate().getJdbcOperations().update( + getQuery(CREATE_JOB_PARAMETERS), args, argTypes); } /** @@ -185,30 +202,33 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements * given identifier, adding them to a list via the RowMapper callback. * * @see JobInstanceDao#getJobInstance(String, JobParameters) - * @throws IllegalArgumentException if any {@link JobParameters} fields are - * null. + * @throws IllegalArgumentException + * if any {@link JobParameters} fields are null. */ - public JobInstance getJobInstance(final String jobName, final JobParameters jobParameters) { + public JobInstance getJobInstance(final String jobName, + final JobParameters jobParameters) { Assert.notNull(jobName, "Job name must not be null."); Assert.notNull(jobParameters, "JobParameters must not be null."); String jobKey = createJobKey(jobParameters); - ParameterizedRowMapper rowMapper = new JobInstanceRowMapper(jobParameters); + ParameterizedRowMapper rowMapper = new JobInstanceRowMapper( + jobParameters); List instances; if (StringUtils.hasLength(jobKey)) { - instances = getJdbcTemplate().query(getQuery(FIND_JOBS_WITH_KEY), rowMapper, jobName, jobKey); - } - else { - instances = getJdbcTemplate().query(getQuery(FIND_JOBS_WITH_EMPTY_KEY), rowMapper, jobName, jobKey); + instances = getJdbcTemplate().query(getQuery(FIND_JOBS_WITH_KEY), + rowMapper, jobName, jobKey); + } else { + instances = getJdbcTemplate().query( + getQuery(FIND_JOBS_WITH_EMPTY_KEY), rowMapper, jobName, + jobKey); } if (instances.isEmpty()) { return null; - } - else { + } else { Assert.state(instances.size() == 1); return instances.get(0); } @@ -224,9 +244,9 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements public JobInstance getJobInstance(Long instanceId) { try { - return getJdbcTemplate().queryForObject(getQuery(GET_JOB_FROM_ID), new JobInstanceRowMapper(), instanceId); - } - catch (EmptyResultDataAccessException e) { + return getJdbcTemplate().queryForObject(getQuery(GET_JOB_FROM_ID), + new JobInstanceRowMapper(), instanceId); + } catch (EmptyResultDataAccessException e) { return null; } @@ -244,22 +264,20 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements JobParameter value = null; if (type == ParameterType.STRING) { value = new JobParameter(rs.getString(4)); - } - else if (type == ParameterType.LONG) { + } else if (type == ParameterType.LONG) { value = new JobParameter(rs.getLong(6)); - } - else if (type == ParameterType.DOUBLE) { + } else if (type == ParameterType.DOUBLE) { value = new JobParameter(rs.getDouble(7)); - } - else if (type == ParameterType.DATE) { + } else if (type == ParameterType.DATE) { value = new JobParameter(rs.getTimestamp(5)); } // No need to assert that value is not null because it's an enum map.put(rs.getString(2), value); } }; - getJdbcTemplate().getJdbcOperations() - .query(getQuery(FIND_PARAMS_FROM_ID), new Object[] { instanceId }, handler); + getJdbcTemplate().getJdbcOperations().query( + getQuery(FIND_PARAMS_FROM_ID), new Object[] { instanceId }, + handler); return new JobParameters(map); } @@ -271,11 +289,13 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements * () */ public List getJobNames() { - return getJdbcTemplate().query(getQuery(FIND_JOB_NAMES), new ParameterizedRowMapper() { - public String mapRow(ResultSet rs, int rowNum) throws SQLException { - return rs.getString(1); - } - }); + return getJdbcTemplate().query(getQuery(FIND_JOB_NAMES), + new ParameterizedRowMapper() { + public String mapRow(ResultSet rs, int rowNum) + throws SQLException { + return rs.getString(1); + } + }); } /* @@ -284,13 +304,15 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements * @seeorg.springframework.batch.core.repository.dao.JobInstanceDao# * getLastJobInstances(java.lang.String, int) */ - public List getJobInstances(String jobName, final int start, final int count) { + public List getJobInstances(String jobName, final int start, + final int count) { ResultSetExtractor extractor = new ResultSetExtractor() { private List list = new ArrayList(); - public Object extractData(ResultSet rs) throws SQLException, DataAccessException { + public Object extractData(ResultSet rs) throws SQLException, + DataAccessException { int rowNum = 0; while (rowNum < start && rs.next()) { rowNum++; @@ -306,8 +328,9 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements }; @SuppressWarnings("unchecked") - List result = (List) getJdbcTemplate().getJdbcOperations().query( - getQuery(FIND_LAST_JOBS_BY_NAME), new Object[] { jobName }, extractor); + List result = (List) getJdbcTemplate() + .getJdbcOperations().query(getQuery(FIND_LAST_JOBS_BY_NAME), + new Object[] { jobName }, extractor); return result; } @@ -322,10 +345,10 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements public JobInstance getJobInstance(JobExecution jobExecution) { try { - return getJdbcTemplate().queryForObject(getQuery(GET_JOB_FROM_EXECUTION_ID), new JobInstanceRowMapper(), - jobExecution.getId()); - } - catch (EmptyResultDataAccessException e) { + return getJdbcTemplate().queryForObject( + getQuery(GET_JOB_FROM_EXECUTION_ID), + new JobInstanceRowMapper(), jobExecution.getId()); + } catch (EmptyResultDataAccessException e) { return null; } } @@ -334,7 +357,8 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements * Setter for {@link DataFieldMaxValueIncrementer} to be used when * generating primary keys for {@link JobInstance} instances. * - * @param jobIncrementer the {@link DataFieldMaxValueIncrementer} + * @param jobIncrementer + * the {@link DataFieldMaxValueIncrementer} */ public void setJobIncrementer(DataFieldMaxValueIncrementer jobIncrementer) { this.jobIncrementer = jobIncrementer; @@ -349,7 +373,8 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements * @author Dave Syer * */ - private final class JobInstanceRowMapper implements ParameterizedRowMapper { + private final class JobInstanceRowMapper implements + ParameterizedRowMapper { private JobParameters jobParameters; @@ -365,7 +390,8 @@ public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements if (jobParameters == null) { jobParameters = getJobParameters(id); } - JobInstance jobInstance = new JobInstance(rs.getLong(1), jobParameters, rs.getString(2)); + JobInstance jobInstance = new JobInstance(rs.getLong(1), + jobParameters, rs.getString(2)); // should always be at version=0 because they never get updated jobInstance.incrementVersion(); return jobInstance; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java index 0567610c4..64ada8692 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/dao/JdbcJobInstanceDaoTests.java @@ -1,43 +1,70 @@ package org.springframework.batch.core.repository.dao; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.math.BigInteger; import java.security.MessageDigest; +import org.junit.Test; +import org.junit.runner.RunWith; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; - @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "sql-dao-test.xml") public class JdbcJobInstanceDaoTests extends AbstractJobInstanceDaoTests { - + protected JobInstanceDao getJobInstanceDao() { - deleteFromTables("BATCH_JOB_EXECUTION_CONTEXT", "BATCH_STEP_EXECUTION_CONTEXT", "BATCH_STEP_EXECUTION", "BATCH_JOB_EXECUTION", - "BATCH_JOB_PARAMS", "BATCH_JOB_INSTANCE"); + deleteFromTables("BATCH_JOB_EXECUTION_CONTEXT", + "BATCH_STEP_EXECUTION_CONTEXT", "BATCH_STEP_EXECUTION", + "BATCH_JOB_EXECUTION", "BATCH_JOB_PARAMS", "BATCH_JOB_INSTANCE"); return (JobInstanceDao) applicationContext.getBean("jobInstanceDao"); } @Test - public void testFindJobInstanceByExecution(){ - - JobExecutionDao jobExecutionDao = (JobExecutionDao) applicationContext.getBean("jobExecutionDao"); - - JobInstance jobInstance = dao.createJobInstance("testInstance", new JobParameters()); + public void testFindJobInstanceByExecution() { + + JobExecutionDao jobExecutionDao = (JobExecutionDao) applicationContext + .getBean("jobExecutionDao"); + + JobInstance jobInstance = dao.createJobInstance("testInstance", + new JobParameters()); JobExecution jobExecution = new JobExecution(jobInstance, 2L); jobExecutionDao.saveJobExecution(jobExecution); - + JobInstance returnedInstance = dao.getJobInstance(jobExecution); - Assert.assertEquals(jobInstance, returnedInstance); + assertEquals(jobInstance, returnedInstance); } - + + @Test + public void testCreateJobKey() { + + JdbcJobInstanceDao jdbcDao = (JdbcJobInstanceDao) dao; + JobParameters jobParameters = new JobParametersBuilder().addString( + "foo", "bar").addString("bar", "foo").toJobParameters(); + String key = jdbcDao.createJobKey(jobParameters); + assertEquals(32, key.length()); + + } + + @Test + public void testCreateJobKeyOrdering() { + + JdbcJobInstanceDao jdbcDao = (JdbcJobInstanceDao) dao; + JobParameters jobParameters1 = new JobParametersBuilder().addString( + "foo", "bar").addString("bar", "foo").toJobParameters(); + String key1 = jdbcDao.createJobKey(jobParameters1); + JobParameters jobParameters2 = new JobParametersBuilder().addString( + "bar", "foo").addString("foo", "bar").toJobParameters(); + String key2 = jdbcDao.createJobKey(jobParameters2); + assertEquals(key1, key2); + + } + @Test public void testHexing() throws Exception { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -46,9 +73,9 @@ public class JdbcJobInstanceDaoTests extends AbstractJobInstanceDaoTests { for (byte bite : bytes) { output.append(String.format("%02x", bite)); } - assertEquals("Wrong hash: "+output,32, output.length()); + assertEquals("Wrong hash: " + output, 32, output.length()); String value = String.format("%032x", new BigInteger(1, bytes)); - assertEquals("Wrong hash: "+value,32, value.length()); + assertEquals("Wrong hash: " + value, 32, value.length()); assertEquals(value, output.toString()); } }