1 | package org.springframework.batch.core.repository.dao; |
2 | |
3 | import java.sql.ResultSet; |
4 | import java.sql.SQLException; |
5 | import java.sql.Timestamp; |
6 | import java.sql.Types; |
7 | import java.util.Iterator; |
8 | import java.util.List; |
9 | import java.util.Map; |
10 | import java.util.Map.Entry; |
11 | |
12 | import org.springframework.batch.core.Job; |
13 | import org.springframework.batch.core.JobInstance; |
14 | import org.springframework.batch.core.JobParameters; |
15 | import org.springframework.beans.factory.InitializingBean; |
16 | import org.springframework.jdbc.core.RowMapper; |
17 | import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; |
18 | import org.springframework.util.Assert; |
19 | |
20 | /** |
21 | * Jdbc implementation of {@link JobInstanceDao}. Uses sequences (via Spring's |
22 | * {@link DataFieldMaxValueIncrementer} abstraction) to create all primary keys |
23 | * before inserting a new row. Objects are checked to ensure all mandatory |
24 | * fields to be stored are not null. If any are found to be null, an |
25 | * IllegalArgumentException will be thrown. This could be left to JdbcTemplate, |
26 | * however, the exception will be fairly vague, and fails to highlight which |
27 | * field caused the exception. |
28 | * |
29 | * @author Lucas Ward |
30 | * @author Dave Syer |
31 | * @author Robert Kasanicky |
32 | */ |
33 | public class JdbcJobInstanceDao extends AbstractJdbcBatchMetadataDao implements JobInstanceDao, InitializingBean { |
34 | |
35 | private static final String CREATE_JOB_INSTANCE = "INSERT into %PREFIX%JOB_INSTANCE(JOB_INSTANCE_ID, JOB_NAME, JOB_KEY, VERSION)" |
36 | + " values (?, ?, ?, ?)"; |
37 | |
38 | private static final String CREATE_JOB_PARAMETERS = "INSERT into %PREFIX%JOB_PARAMS(JOB_INSTANCE_ID, KEY_NAME, TYPE_CD, " |
39 | + "STRING_VAL, DATE_VAL, LONG_VAL, DOUBLE_VAL) values (?, ?, ?, ?, ?, ?, ?)"; |
40 | |
41 | private static final String FIND_JOBS = "SELECT JOB_INSTANCE_ID from %PREFIX%JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?"; |
42 | |
43 | private DataFieldMaxValueIncrementer jobIncrementer; |
44 | |
45 | /** |
46 | * In this jdbc implementation a job id is obtained by asking the |
47 | * jobIncrementer (which is likely a sequence) for the nextLong, and then |
48 | * passing the Id and parameter values into an INSERT statement. |
49 | * |
50 | * @see JobInstanceDao#createJobInstance(Job, JobParameters) |
51 | * @throws IllegalArgumentException if any {@link JobParameters} fields are |
52 | * null. |
53 | */ |
54 | public JobInstance createJobInstance(Job job, JobParameters jobParameters) { |
55 | |
56 | Assert.notNull(job, "Job must not be null."); |
57 | Assert.hasLength(job.getName(), "Job must have a name"); |
58 | Assert.notNull(jobParameters, "JobParameters must not be null."); |
59 | |
60 | Assert.state(getJobInstance(job, jobParameters) == null, "JobInstance must not already exist"); |
61 | |
62 | Long jobId = new Long(jobIncrementer.nextLongValue()); |
63 | |
64 | JobInstance jobInstance = new JobInstance(jobId, jobParameters, job); |
65 | jobInstance.incrementVersion(); |
66 | |
67 | Object[] parameters = new Object[] { jobId, job.getName(), createJobKey(jobParameters), jobInstance.getVersion() }; |
68 | getJdbcTemplate().update(getQuery(CREATE_JOB_INSTANCE), parameters, |
69 | new int[] { Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.INTEGER }); |
70 | |
71 | insertJobParameters(jobId, jobParameters); |
72 | |
73 | return jobInstance; |
74 | } |
75 | |
76 | private String createJobKey(JobParameters jobParameters) { |
77 | |
78 | Map props = jobParameters.getParameters(); |
79 | StringBuffer stringBuffer = new StringBuffer(); |
80 | for (Iterator it = props.entrySet().iterator(); it.hasNext();) { |
81 | Entry entry = (Entry) it.next(); |
82 | stringBuffer.append(entry.toString() + ";"); |
83 | } |
84 | |
85 | return stringBuffer.toString(); |
86 | } |
87 | |
88 | /** |
89 | * Convenience method that inserts all parameters from the provided |
90 | * JobParameters. |
91 | * |
92 | */ |
93 | private void insertJobParameters(Long jobId, JobParameters jobParameters) { |
94 | |
95 | Map parameters = jobParameters.getStringParameters(); |
96 | |
97 | if (!parameters.isEmpty()) { |
98 | for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) { |
99 | Entry entry = (Entry) it.next(); |
100 | insertParameter(jobId, ParameterType.STRING, entry.getKey().toString(), entry.getValue()); |
101 | } |
102 | } |
103 | |
104 | parameters = jobParameters.getLongParameters(); |
105 | |
106 | if (!parameters.isEmpty()) { |
107 | for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) { |
108 | Entry entry = (Entry) it.next(); |
109 | insertParameter(jobId, ParameterType.LONG, entry.getKey().toString(), entry.getValue()); |
110 | } |
111 | } |
112 | |
113 | parameters = jobParameters.getDoubleParameters(); |
114 | |
115 | if (!parameters.isEmpty()) { |
116 | for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) { |
117 | Entry entry = (Entry) it.next(); |
118 | insertParameter(jobId, ParameterType.DOUBLE, entry.getKey().toString(), entry.getValue()); |
119 | } |
120 | } |
121 | |
122 | parameters = jobParameters.getDateParameters(); |
123 | |
124 | if (!parameters.isEmpty()) { |
125 | for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) { |
126 | Entry entry = (Entry) it.next(); |
127 | insertParameter(jobId, ParameterType.DATE, entry.getKey().toString(), entry.getValue()); |
128 | } |
129 | } |
130 | } |
131 | |
132 | /** |
133 | * Convenience method that inserts an individual records into the |
134 | * JobParameters table. |
135 | */ |
136 | private void insertParameter(Long jobId, ParameterType type, String key, Object value) { |
137 | |
138 | Object[] args = new Object[0]; |
139 | int[] argTypes = new int[] { Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.TIMESTAMP, |
140 | Types.BIGINT, Types.DOUBLE }; |
141 | |
142 | if (type == ParameterType.STRING) { |
143 | args = new Object[] { jobId, key, type, value, new Timestamp(0L), new Long(0), new Double(0) }; |
144 | } |
145 | else if (type == ParameterType.LONG) { |
146 | args = new Object[] { jobId, key, type, "", new Timestamp(0L), value, new Double(0) }; |
147 | } |
148 | else if (type == ParameterType.DOUBLE) { |
149 | args = new Object[] { jobId, key, type, "", new Timestamp(0L), new Long(0), value }; |
150 | } |
151 | else if (type == ParameterType.DATE) { |
152 | args = new Object[] { jobId, key, type, "", value, new Long(0), new Double(0) }; |
153 | } |
154 | |
155 | getJdbcTemplate().update(getQuery(CREATE_JOB_PARAMETERS), args, argTypes); |
156 | } |
157 | |
158 | /** |
159 | * The job table is queried for <strong>any</strong> jobs that match the |
160 | * given identifier, adding them to a list via the RowMapper callback. |
161 | * |
162 | * @see JobInstanceDao#getJobInstance(Job, JobParameters) |
163 | * @throws IllegalArgumentException if any {@link JobParameters} fields are |
164 | * null. |
165 | */ |
166 | public JobInstance getJobInstance(final Job job, final JobParameters jobParameters) { |
167 | |
168 | Assert.notNull(job, "Job must not be null."); |
169 | Assert.hasLength(job.getName(), "Job must have a name"); |
170 | Assert.notNull(jobParameters, "JobParameters must not be null."); |
171 | |
172 | Object[] parameters = new Object[] { job.getName(), createJobKey(jobParameters) }; |
173 | |
174 | RowMapper rowMapper = new RowMapper() { |
175 | public Object mapRow(ResultSet rs, int rowNum) throws SQLException { |
176 | JobInstance jobInstance = new JobInstance(new Long(rs.getLong(1)), jobParameters, job); |
177 | return jobInstance; |
178 | } |
179 | }; |
180 | |
181 | List instances = getJdbcTemplate().query(getQuery(FIND_JOBS), parameters, rowMapper); |
182 | |
183 | if (instances.isEmpty()) { |
184 | return null; |
185 | } else { |
186 | Assert.state(instances.size() == 1); |
187 | return (JobInstance) instances.get(0); |
188 | } |
189 | } |
190 | |
191 | /** |
192 | * Setter for {@link DataFieldMaxValueIncrementer} to be used when |
193 | * generating primary keys for {@link JobInstance} instances. |
194 | * |
195 | * @param jobIncrementer the {@link DataFieldMaxValueIncrementer} |
196 | */ |
197 | public void setJobIncrementer(DataFieldMaxValueIncrementer jobIncrementer) { |
198 | this.jobIncrementer = jobIncrementer; |
199 | } |
200 | |
201 | public void afterPropertiesSet() throws Exception { |
202 | super.afterPropertiesSet(); |
203 | Assert.notNull(jobIncrementer); |
204 | } |
205 | |
206 | private static class ParameterType { |
207 | |
208 | private final String type; |
209 | |
210 | private ParameterType(String type) { |
211 | this.type = type; |
212 | } |
213 | |
214 | public String toString() { |
215 | return type; |
216 | } |
217 | |
218 | public static final ParameterType STRING = new ParameterType("STRING"); |
219 | |
220 | public static final ParameterType DATE = new ParameterType("DATE"); |
221 | |
222 | public static final ParameterType LONG = new ParameterType("LONG"); |
223 | |
224 | public static final ParameterType DOUBLE = new ParameterType("DOUBLE"); |
225 | |
226 | private static final ParameterType[] VALUES = { STRING, DATE, LONG, DOUBLE }; |
227 | |
228 | public static ParameterType getType(String typeAsString) { |
229 | |
230 | for (int i = 0; i < VALUES.length; i++) { |
231 | if (VALUES[i].toString().equals(typeAsString)) { |
232 | return (ParameterType) VALUES[i]; |
233 | } |
234 | } |
235 | |
236 | return null; |
237 | } |
238 | } |
239 | } |