View Javadoc
1   /*
2    * Copyright 2002-2014 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.jdbc.core.simple;
18  
19  import java.util.ArrayList;
20  import java.util.LinkedHashMap;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  import javax.sql.DataSource;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  
29  import org.springframework.dao.InvalidDataAccessApiUsageException;
30  import org.springframework.jdbc.core.CallableStatementCreator;
31  import org.springframework.jdbc.core.CallableStatementCreatorFactory;
32  import org.springframework.jdbc.core.JdbcTemplate;
33  import org.springframework.jdbc.core.RowMapper;
34  import org.springframework.jdbc.core.SqlParameter;
35  import org.springframework.jdbc.core.metadata.CallMetaDataContext;
36  import org.springframework.jdbc.core.namedparam.SqlParameterSource;
37  import org.springframework.util.Assert;
38  import org.springframework.util.StringUtils;
39  
40  /**
41   * Abstract class to provide base functionality for easy stored procedure calls
42   * based on configuration options and database metadata.
43   * This class provides the base SPI for {@link SimpleJdbcCall}.
44   *
45   * @author Thomas Risberg
46   * @author Juergen Hoeller
47   * @since 2.5
48   */
49  public abstract class AbstractJdbcCall {
50  
51  	/** Logger available to subclasses */
52  	protected final Log logger = LogFactory.getLog(getClass());
53  
54  	/** Lower-level class used to execute SQL */
55  	private final JdbcTemplate jdbcTemplate;
56  
57  	/** Context used to retrieve and manage database metadata */
58  	private final CallMetaDataContext callMetaDataContext = new CallMetaDataContext();
59  
60  	/** List of SqlParameter objects */
61  	private final List<SqlParameter> declaredParameters = new ArrayList<SqlParameter>();
62  
63  	/** List of RefCursor/ResultSet RowMapper objects */
64  	private final Map<String, RowMapper<?>> declaredRowMappers = new LinkedHashMap<String, RowMapper<?>>();
65  
66  	/**
67  	 * Has this operation been compiled? Compilation means at least checking
68  	 * that a DataSource or JdbcTemplate has been provided.
69  	 */
70  	private boolean compiled = false;
71  
72  	/** The generated string used for call statement */
73  	private String callString;
74  
75  	/**
76  	 * A delegate enabling us to create CallableStatementCreators
77  	 * efficiently, based on this class's declared parameters.
78  	 */
79  	private CallableStatementCreatorFactory callableStatementFactory;
80  
81  
82  	/**
83  	 * Constructor to be used when initializing using a {@link DataSource}.
84  	 * @param dataSource the DataSource to be used
85  	 */
86  	protected AbstractJdbcCall(DataSource dataSource) {
87  		this.jdbcTemplate = new JdbcTemplate(dataSource);
88  	}
89  
90  	/**
91  	 * Constructor to be used when initializing using a {@link JdbcTemplate}.
92  	 * @param jdbcTemplate the JdbcTemplate to use
93  	 */
94  	protected AbstractJdbcCall(JdbcTemplate jdbcTemplate) {
95  		Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
96  		this.jdbcTemplate = jdbcTemplate;
97  	}
98  
99  
100 	/**
101 	 * Get the configured {@link JdbcTemplate}.
102 	 */
103 	public JdbcTemplate getJdbcTemplate() {
104 		return this.jdbcTemplate;
105 	}
106 
107 	/**
108 	 * Set the name of the stored procedure.
109 	 */
110 	public void setProcedureName(String procedureName) {
111 		this.callMetaDataContext.setProcedureName(procedureName);
112 	}
113 
114 	/**
115 	 * Get the name of the stored procedure.
116 	 */
117 	public String getProcedureName() {
118 		return this.callMetaDataContext.getProcedureName();
119 	}
120 
121 	/**
122 	 * Set the names of in parameters to be used.
123 	 */
124 	public void setInParameterNames(Set<String> inParameterNames) {
125 		this.callMetaDataContext.setLimitedInParameterNames(inParameterNames);
126 	}
127 
128 	/**
129 	 * Get the names of in parameters to be used.
130 	 */
131 	public Set<String> getInParameterNames() {
132 		return this.callMetaDataContext.getLimitedInParameterNames();
133 	}
134 
135 	/**
136 	 * Set the catalog name to use.
137 	 */
138 	public void setCatalogName(String catalogName) {
139 		this.callMetaDataContext.setCatalogName(catalogName);
140 	}
141 
142 	/**
143 	 * Get the catalog name used.
144 	 */
145 	public String getCatalogName() {
146 		return this.callMetaDataContext.getCatalogName();
147 	}
148 
149 	/**
150 	 * Set the schema name to use.
151 	 */
152 	public void setSchemaName(String schemaName) {
153 		this.callMetaDataContext.setSchemaName(schemaName);
154 	}
155 
156 	/**
157 	 * Get the schema name used.
158 	 */
159 	public String getSchemaName() {
160 		return this.callMetaDataContext.getSchemaName();
161 	}
162 
163 	/**
164 	 * Specify whether this call is a function call.
165 	 * The default is {@code false}.
166 	 */
167 	public void setFunction(boolean function) {
168 		this.callMetaDataContext.setFunction(function);
169 	}
170 
171 	/**
172 	 * Is this call a function call?
173 	 */
174 	public boolean isFunction() {
175 		return this.callMetaDataContext.isFunction();
176 	}
177 
178 	/**
179 	 * Specify whether the call requires a return value.
180 	 * The default is {@code false}.
181 	 */
182 	public void setReturnValueRequired(boolean returnValueRequired) {
183 		this.callMetaDataContext.setReturnValueRequired(returnValueRequired);
184 	}
185 
186 	/**
187 	 * Does the call require a return value?
188 	 */
189 	public boolean isReturnValueRequired() {
190 		return this.callMetaDataContext.isReturnValueRequired();
191 	}
192 
193 	/**
194 	 * Specify whether the parameter metadata for the call should be used.
195 	 * The default is {@code true}.
196 	 */
197 	public void setAccessCallParameterMetaData(boolean accessCallParameterMetaData) {
198 		this.callMetaDataContext.setAccessCallParameterMetaData(accessCallParameterMetaData);
199 	}
200 
201 	/**
202 	 * Get the call string that should be used based on parameters and meta data.
203 	 */
204 	public String getCallString() {
205 		return this.callString;
206 	}
207 
208 	/**
209 	 * Get the {@link CallableStatementCreatorFactory} being used
210 	 */
211 	protected CallableStatementCreatorFactory getCallableStatementFactory() {
212 		return this.callableStatementFactory;
213 	}
214 
215 
216 	/**
217 	 * Add a declared parameter to the list of parameters for the call.
218 	 * <p>Only parameters declared as {@code SqlParameter} and {@code SqlInOutParameter} will
219 	 * be used to provide input values. This is different from the {@code StoredProcedure}
220 	 * class which - for backwards compatibility reasons - allows input values to be provided
221 	 * for parameters declared as {@code SqlOutParameter}.
222 	 * @param parameter the {@link SqlParameter} to add
223 	 */
224 	public void addDeclaredParameter(SqlParameter parameter) {
225 		Assert.notNull(parameter, "The supplied parameter must not be null");
226 		if (!StringUtils.hasText(parameter.getName())) {
227 			throw new InvalidDataAccessApiUsageException(
228 					"You must specify a parameter name when declaring parameters for \"" + getProcedureName() + "\"");
229 		}
230 		this.declaredParameters.add(parameter);
231 		if (logger.isDebugEnabled()) {
232 			logger.debug("Added declared parameter for [" + getProcedureName() + "]: " + parameter.getName());
233 		}
234 	}
235 
236 	/**
237 	 * Add a {@link org.springframework.jdbc.core.RowMapper} for the specified parameter or column.
238 	 * @param parameterName name of parameter or column
239 	 * @param rowMapper the RowMapper implementation to use
240 	 */
241 	public void addDeclaredRowMapper(String parameterName, RowMapper<?> rowMapper) {
242 		this.declaredRowMappers.put(parameterName, rowMapper);
243 		if (logger.isDebugEnabled()) {
244 			logger.debug("Added row mapper for [" + getProcedureName() + "]: " + parameterName);
245 		}
246 	}
247 
248 
249 	//-------------------------------------------------------------------------
250 	// Methods handling compilation issues
251 	//-------------------------------------------------------------------------
252 
253 	/**
254 	 * Compile this JdbcCall using provided parameters and meta data plus other settings.
255 	 * <p>This finalizes the configuration for this object and subsequent attempts to compile are
256 	 * ignored. This will be implicitly called the first time an un-compiled call is executed.
257 	 * @throws org.springframework.dao.InvalidDataAccessApiUsageException if the object hasn't
258 	 * been correctly initialized, for example if no DataSource has been provided
259 	 */
260 	public synchronized final void compile() throws InvalidDataAccessApiUsageException {
261 		if (!isCompiled()) {
262 			if (getProcedureName() == null) {
263 				throw new InvalidDataAccessApiUsageException("Procedure or Function name is required");
264 			}
265 			try {
266 				this.jdbcTemplate.afterPropertiesSet();
267 			}
268 			catch (IllegalArgumentException ex) {
269 				throw new InvalidDataAccessApiUsageException(ex.getMessage());
270 			}
271 			compileInternal();
272 			this.compiled = true;
273 			if (logger.isDebugEnabled()) {
274 				logger.debug("SqlCall for " + (isFunction() ? "function" : "procedure") +
275 						" [" + getProcedureName() + "] compiled");
276 			}
277 		}
278 	}
279 
280 	/**
281 	 * Delegate method to perform the actual compilation.
282 	 * <p>Subclasses can override this template method to perform their own compilation.
283 	 * Invoked after this base class's compilation is complete.
284 	 */
285 	protected void compileInternal() {
286 		this.callMetaDataContext.initializeMetaData(getJdbcTemplate().getDataSource());
287 
288 		// Iterate over the declared RowMappers and register the corresponding SqlParameter
289 		for (Map.Entry<String, RowMapper<?>> entry : this.declaredRowMappers.entrySet()) {
290 			SqlParameter resultSetParameter =
291 					this.callMetaDataContext.createReturnResultSetParameter(entry.getKey(), entry.getValue());
292 			this.declaredParameters.add(resultSetParameter);
293 		}
294 		this.callMetaDataContext.processParameters(this.declaredParameters);
295 
296 		this.callString = this.callMetaDataContext.createCallString();
297 		if (logger.isDebugEnabled()) {
298 			logger.debug("Compiled stored procedure. Call string is [" + this.callString + "]");
299 		}
300 
301 		this.callableStatementFactory =
302 				new CallableStatementCreatorFactory(getCallString(), this.callMetaDataContext.getCallParameters());
303 		this.callableStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());
304 
305 		onCompileInternal();
306 	}
307 
308 	/**
309 	 * Hook method that subclasses may override to react to compilation.
310 	 * This implementation does nothing.
311 	 */
312 	protected void onCompileInternal() {
313 	}
314 
315 	/**
316 	 * Is this operation "compiled"?
317 	 * @return whether this operation is compiled, and ready to use.
318 	 */
319 	public boolean isCompiled() {
320 		return this.compiled;
321 	}
322 
323 	/**
324 	 * Check whether this operation has been compiled already;
325 	 * lazily compile it if not already compiled.
326 	 * <p>Automatically called by {@code doExecute}.
327 	 */
328 	protected void checkCompiled() {
329 		if (!isCompiled()) {
330 			logger.debug("JdbcCall call not compiled before execution - invoking compile");
331 			compile();
332 		}
333 	}
334 
335 
336 	//-------------------------------------------------------------------------
337 	// Methods handling execution
338 	//-------------------------------------------------------------------------
339 
340 	/**
341 	 * Delegate method that executes the call using the passed-in {@link SqlParameterSource}.
342 	 * @param parameterSource parameter names and values to be used in call
343 	 * @return Map of out parameters
344 	 */
345 	protected Map<String, Object> doExecute(SqlParameterSource parameterSource) {
346 		checkCompiled();
347 		Map<String, Object> params = matchInParameterValuesWithCallParameters(parameterSource);
348 		return executeCallInternal(params);
349 	}
350 
351 	/**
352 	 * Delegate method that executes the call using the passed-in array of parameters.
353 	 * @param args array of parameter values. The order of values must match the order
354 	 * declared for the stored procedure.
355 	 * @return Map of out parameters
356 	 */
357 	protected Map<String, Object> doExecute(Object... args) {
358 		checkCompiled();
359 		Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
360 		return executeCallInternal(params);
361 	}
362 
363 	/**
364 	 * Delegate method that executes the call using the passed-in Map of parameters.
365 	 * @param args Map of parameter name and values
366 	 * @return Map of out parameters
367 	 */
368 	protected Map<String, Object> doExecute(Map<String, ?> args) {
369 		checkCompiled();
370 		Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
371 		return executeCallInternal(params);
372 	}
373 
374 	/**
375 	 * Delegate method to perform the actual call processing.
376 	 */
377 	private Map<String, Object> executeCallInternal(Map<String, ?> args) {
378 		CallableStatementCreator csc = getCallableStatementFactory().newCallableStatementCreator(args);
379 		if (logger.isDebugEnabled()) {
380 			logger.debug("The following parameters are used for call " + getCallString() + " with " + args);
381 			int i = 1;
382 			for (SqlParameter param : getCallParameters()) {
383 				logger.debug(i + ": " +  param.getName() + ", SQL type "+ param.getSqlType() + ", type name " +
384 						param.getTypeName() + ", parameter class [" + param.getClass().getName() + "]");
385 				i++;
386 			}
387 		}
388 		return getJdbcTemplate().call(csc, getCallParameters());
389 	}
390 
391 
392 	/**
393 	 * Get the name of a single out parameter or return value.
394 	 * Used for functions or procedures with one out parameter.
395 	 */
396 	protected String getScalarOutParameterName() {
397 		return this.callMetaDataContext.getScalarOutParameterName();
398 	}
399 
400 	/**
401 	 * Get a List of all the call parameters to be used for call.
402 	 * This includes any parameters added based on meta data processing.
403 	 */
404 	protected List<SqlParameter> getCallParameters() {
405 		return this.callMetaDataContext.getCallParameters();
406 	}
407 
408 	/**
409 	 * Match the provided in parameter values with registered parameters and
410 	 * parameters defined via metadata processing.
411 	 * @param parameterSource the parameter vakues provided as a {@link SqlParameterSource}
412 	 * @return Map with parameter names and values
413 	 */
414 	protected Map<String, Object> matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
415 		return this.callMetaDataContext.matchInParameterValuesWithCallParameters(parameterSource);
416 	}
417 
418 	/**
419 	 * Match the provided in parameter values with registered parameters and
420 	 * parameters defined via metadata processing.
421 	 * @param args the parameter values provided as an array
422 	 * @return Map with parameter names and values
423 	 */
424 	private Map<String, ?> matchInParameterValuesWithCallParameters(Object[] args) {
425 		return this.callMetaDataContext.matchInParameterValuesWithCallParameters(args);
426 	}
427 
428 	/**
429 	 * Match the provided in parameter values with registered parameters and
430 	 * parameters defined via metadata processing.
431 	 * @param args the parameter values provided in a Map
432 	 * @return Map with parameter names and values
433 	 */
434 	protected Map<String, ?> matchInParameterValuesWithCallParameters(Map<String, ?> args) {
435 		return this.callMetaDataContext.matchInParameterValuesWithCallParameters(args);
436 	}
437 
438 }