View Javadoc

1   /*
2    * Copyright 2006-2008 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  
17  package org.springframework.batch.core.repository.dao;
18  
19  import java.sql.PreparedStatement;
20  import java.sql.ResultSet;
21  import java.sql.SQLException;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  
27  import org.springframework.batch.core.JobExecution;
28  import org.springframework.batch.core.StepExecution;
29  import org.springframework.batch.item.ExecutionContext;
30  import org.springframework.jdbc.core.PreparedStatementSetter;
31  import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
32  import org.springframework.jdbc.support.lob.DefaultLobHandler;
33  import org.springframework.jdbc.support.lob.LobHandler;
34  import org.springframework.util.Assert;
35  
36  /**
37   * JDBC DAO for {@link ExecutionContext}.
38   * 
39   * Stores execution context data related to both Step and Job using
40   * a different table for each.
41   * 
42   * @author Lucas Ward
43   * @author Robert Kasanicky
44   * @author Thomas Risberg
45   */
46  public class JdbcExecutionContextDao extends AbstractJdbcBatchMetadataDao implements ExecutionContextDao {
47  
48  	private static final String FIND_JOB_EXECUTION_CONTEXT = "SELECT SHORT_CONTEXT, SERIALIZED_CONTEXT "
49  			+ "FROM %PREFIX%JOB_EXECUTION_CONTEXT WHERE JOB_EXECUTION_ID = ?";
50  
51  	private static final String INSERT_JOB_EXECUTION_CONTEXT = "INSERT INTO %PREFIX%JOB_EXECUTION_CONTEXT "
52  			+ "(SHORT_CONTEXT, SERIALIZED_CONTEXT, JOB_EXECUTION_ID) " + "VALUES(?, ?, ?)";
53  
54  	private static final String UPDATE_JOB_EXECUTION_CONTEXT = "UPDATE %PREFIX%JOB_EXECUTION_CONTEXT "
55  			+ "SET SHORT_CONTEXT = ?, SERIALIZED_CONTEXT = ? " + "WHERE JOB_EXECUTION_ID = ?";
56  
57  	private static final String FIND_STEP_EXECUTION_CONTEXT = "SELECT SHORT_CONTEXT, SERIALIZED_CONTEXT "
58  			+ "FROM %PREFIX%STEP_EXECUTION_CONTEXT WHERE STEP_EXECUTION_ID = ?";
59  
60  	private static final String INSERT_STEP_EXECUTION_CONTEXT = "INSERT INTO %PREFIX%STEP_EXECUTION_CONTEXT "
61  			+ "(SHORT_CONTEXT, SERIALIZED_CONTEXT, STEP_EXECUTION_ID) " + "VALUES(?, ?, ?)";
62  
63  	private static final String UPDATE_STEP_EXECUTION_CONTEXT = "UPDATE %PREFIX%STEP_EXECUTION_CONTEXT "
64  			+ "SET SHORT_CONTEXT = ?, SERIALIZED_CONTEXT = ? " + "WHERE STEP_EXECUTION_ID = ?";
65  
66  	private static final int DEFAULT_MAX_VARCHAR_LENGTH = 2500;
67  
68  	private int shortContextLength = DEFAULT_MAX_VARCHAR_LENGTH;
69  
70  	private LobHandler lobHandler = new DefaultLobHandler();
71  
72  	private ExecutionContextStringSerializer serializer;
73  
74  	/**
75  	 * The maximum size that an execution context can have and still be stored
76  	 * completely in short form in the column <code>SHORT_CONTEXT</code>.
77  	 * Anything longer than this will overflow into large-object storage, and
78  	 * the first part only will be retained in the short form for readability.
79  	 * Default value is 2500. Clients using multi-bytes charsets on the database
80  	 * server may need to reduce this value to as little as half the value of
81  	 * the column size.
82  	 * @param shortContextLength
83  	 */
84  	public void setShortContextLength(int shortContextLength) {
85  		this.shortContextLength = shortContextLength;
86  	}
87  
88  	public ExecutionContext getExecutionContext(JobExecution jobExecution) {
89  		Long executionId = jobExecution.getId();
90  		Assert.notNull(executionId, "ExecutionId must not be null.");
91  
92  		List<ExecutionContext> results = getJdbcTemplate().query(getQuery(FIND_JOB_EXECUTION_CONTEXT),
93  				new ExecutionContextRowMapper(), executionId);
94  		if (results.size() > 0) {
95  			return results.get(0);
96  		}
97  		else {
98  			return new ExecutionContext();
99  		}
100 	}
101 
102 	public ExecutionContext getExecutionContext(StepExecution stepExecution) {
103 		Long executionId = stepExecution.getId();
104 		Assert.notNull(executionId, "ExecutionId must not be null.");
105 
106 		List<ExecutionContext> results = getJdbcTemplate().query(getQuery(FIND_STEP_EXECUTION_CONTEXT),
107 				new ExecutionContextRowMapper(), executionId);
108 		if (results.size() > 0) {
109 			return results.get(0);
110 		}
111 		else {
112 			return new ExecutionContext();
113 		}
114 	}
115 
116 	public void updateExecutionContext(final JobExecution jobExecution) {
117 		Long executionId = jobExecution.getId();
118 		ExecutionContext executionContext = jobExecution.getExecutionContext();
119 		Assert.notNull(executionId, "ExecutionId must not be null.");
120 		Assert.notNull(executionContext, "The ExecutionContext must not be null.");
121 
122 		String serializedContext = serializeContext(executionContext);
123 
124 		persistSerializedContext(executionId, serializedContext, UPDATE_JOB_EXECUTION_CONTEXT);
125 	}
126 
127 	public void updateExecutionContext(final StepExecution stepExecution) {
128 
129 		Long executionId = stepExecution.getId();
130 		ExecutionContext executionContext = stepExecution.getExecutionContext();
131 		Assert.notNull(executionId, "ExecutionId must not be null.");
132 		Assert.notNull(executionContext, "The ExecutionContext must not be null.");
133 
134 		String serializedContext = serializeContext(executionContext);
135 
136 		persistSerializedContext(executionId, serializedContext, UPDATE_STEP_EXECUTION_CONTEXT);
137 	}
138 
139 	public void saveExecutionContext(JobExecution jobExecution) {
140 
141 		Long executionId = jobExecution.getId();
142 		ExecutionContext executionContext = jobExecution.getExecutionContext();
143 		Assert.notNull(executionId, "ExecutionId must not be null.");
144 		Assert.notNull(executionContext, "The ExecutionContext must not be null.");
145 
146 		String serializedContext = serializeContext(executionContext);
147 
148 		persistSerializedContext(executionId, serializedContext, INSERT_JOB_EXECUTION_CONTEXT);
149 	}
150 
151 	public void saveExecutionContext(StepExecution stepExecution) {
152 		Long executionId = stepExecution.getId();
153 		ExecutionContext executionContext = stepExecution.getExecutionContext();
154 		Assert.notNull(executionId, "ExecutionId must not be null.");
155 		Assert.notNull(executionContext, "The ExecutionContext must not be null.");
156 
157 		String serializedContext = serializeContext(executionContext);
158 
159 		persistSerializedContext(executionId, serializedContext, INSERT_STEP_EXECUTION_CONTEXT);
160 	}
161 
162 	public void setLobHandler(LobHandler lobHandler) {
163 		this.lobHandler = lobHandler;
164 	}
165 
166 	@Override
167 	public void afterPropertiesSet() throws Exception {
168 		super.afterPropertiesSet();
169 		serializer = new XStreamExecutionContextStringSerializer();
170 		((XStreamExecutionContextStringSerializer) serializer).afterPropertiesSet();
171 	}
172 
173 	/**
174 	 * @param executionId
175 	 * @param serializedContext
176 	 * @param sql with parameters (shortContext, longContext, executionId)
177 	 */
178 	private void persistSerializedContext(final Long executionId, String serializedContext, String sql) {
179 
180 		final String shortContext;
181 		final String longContext;
182 		if (serializedContext.length() > shortContextLength) {
183 			// Overestimate length of ellipsis to be on the safe side with
184 			// 2-byte chars
185 			shortContext = serializedContext.substring(0, shortContextLength - 8) + " ...";
186 			longContext = serializedContext;
187 		}
188 		else {
189 			shortContext = serializedContext;
190 			longContext = null;
191 		}
192 
193 		getJdbcTemplate().getJdbcOperations().update(getQuery(sql), new PreparedStatementSetter() {
194 			public void setValues(PreparedStatement ps) throws SQLException {
195 				ps.setString(1, shortContext);
196 				if (longContext != null) {
197 					lobHandler.getLobCreator().setClobAsString(ps, 2, longContext);
198 				}
199 				else {
200 					ps.setNull(2, getClobTypeToUse());
201 				}
202 				ps.setLong(3, executionId);
203 			}
204 		});
205 	}
206 
207 	private String serializeContext(ExecutionContext ctx) {
208 		Map<String, Object> m = new HashMap<String, Object>();
209 		for (Entry<String, Object> me : ctx.entrySet()) {
210 			m.put(me.getKey(), me.getValue());
211 		}
212 		return serializer.serialize(m);
213 	}
214 
215 	private class ExecutionContextRowMapper implements ParameterizedRowMapper<ExecutionContext> {
216 		public ExecutionContext mapRow(ResultSet rs, int i) throws SQLException {
217 			ExecutionContext executionContext = new ExecutionContext();
218 			String serializedContext = rs.getString("SERIALIZED_CONTEXT");
219 			if (serializedContext == null) {
220 				serializedContext = rs.getString("SHORT_CONTEXT");
221 			}
222 			Map<String, Object> map = serializer.deserialize(serializedContext);
223 			for (Map.Entry<String, Object> entry : map.entrySet()) {
224 				executionContext.put(entry.getKey(), entry.getValue());
225 			}
226 			return executionContext;
227 		}
228 	}
229 
230 }