001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jdbc.core.simple;
018:
019: import java.util.ArrayList;
020: import java.util.LinkedHashMap;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.Set;
024:
025: import javax.sql.DataSource;
026:
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029:
030: import org.springframework.dao.InvalidDataAccessApiUsageException;
031: import org.springframework.jdbc.core.CallableStatementCreator;
032: import org.springframework.jdbc.core.CallableStatementCreatorFactory;
033: import org.springframework.jdbc.core.JdbcTemplate;
034: import org.springframework.jdbc.core.SqlParameter;
035: import org.springframework.jdbc.core.metadata.CallMetaDataContext;
036: import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
037: import org.springframework.jdbc.core.namedparam.SqlParameterSource;
038:
039: /**
040: * Abstract class to provide base functionality for easy stored procedure calls
041: * based on configuration options and database metadata.
042: * This class provides the base SPI for {@link SimpleJdbcCall}.
043: *
044: * @author Thomas Risberg
045: * @since 2.5
046: */
047: public abstract class AbstractJdbcCall {
048:
049: /** Logger available to subclasses */
050: protected final Log logger = LogFactory.getLog(getClass());
051:
052: /** Lower-level class used to execute SQL */
053: private JdbcTemplate jdbcTemplate = new JdbcTemplate();
054:
055: /** List of SqlParameter objects */
056: private final List<SqlParameter> declaredParameters = new ArrayList<SqlParameter>();
057:
058: /** List of RefCursor/ResultSet RowMapper objects */
059: private final Map<String, ParameterizedRowMapper> declaredRowMappers = new LinkedHashMap<String, ParameterizedRowMapper>();
060:
061: /**
062: * Has this operation been compiled? Compilation means at
063: * least checking that a DataSource and sql have been provided,
064: * but subclasses may also implement their own custom validation.
065: */
066: private boolean compiled = false;
067:
068: /** the generated string used for call statement */
069: private String callString;
070:
071: /** context used to retrieve and manage database metadata */
072: private CallMetaDataContext callMetaDataContext = new CallMetaDataContext();
073:
074: /**
075: * Object enabling us to create CallableStatementCreators
076: * efficiently, based on this class's declared parameters.
077: */
078: private CallableStatementCreatorFactory callableStatementFactory;
079:
080: /**
081: * Constructor to be used when initializing using a {@link DataSource}.
082: * @param dataSource the DataSource to be used
083: */
084: protected AbstractJdbcCall(DataSource dataSource) {
085: this .jdbcTemplate = new JdbcTemplate(dataSource);
086: }
087:
088: /**
089: * Constructor to be used when initializing using a {@link JdbcTemplate}.
090: * @param jdbcTemplate the JdbcTemplate to use
091: */
092: protected AbstractJdbcCall(JdbcTemplate jdbcTemplate) {
093: this .jdbcTemplate = jdbcTemplate;
094: }
095:
096: /**
097: * Get the configured {@link JdbcTemplate}
098: */
099: public JdbcTemplate getJdbcTemplate() {
100: return this .jdbcTemplate;
101: }
102:
103: /**
104: * Get the {@link CallableStatementCreatorFactory} being used
105: */
106: protected CallableStatementCreatorFactory getCallableStatementFactory() {
107: return this .callableStatementFactory;
108: }
109:
110: /**
111: * Set the name of the stored procedure.
112: */
113: public void setProcedureName(String procedureName) {
114: this .callMetaDataContext.setProcedureName(procedureName);
115: }
116:
117: /**
118: * Get the name of the stored procedure.
119: */
120: public String getProcedureName() {
121: return this .callMetaDataContext.getProcedureName();
122: }
123:
124: /**
125: * Set the names of in parameters to be used.
126: */
127: public void setInParameterNames(Set<String> inParameterNames) {
128: this .callMetaDataContext
129: .setLimitedInParameterNames(inParameterNames);
130: }
131:
132: /**
133: * Get the names of in parameters to be used.
134: */
135: public Set<String> getInParameterNames() {
136: return this .callMetaDataContext.getLimitedInParameterNames();
137: }
138:
139: /**
140: * Set the catalog name to use.
141: */
142: public void setCatalogName(String catalogName) {
143: this .callMetaDataContext.setCatalogName(catalogName);
144: }
145:
146: /**
147: * Get the catalog name used.
148: */
149: public String getCatalogName() {
150: return this .callMetaDataContext.getCatalogName();
151: }
152:
153: /**
154: * Set the schema name to use,
155: */
156: public void setSchemaName(String schemaName) {
157: this .callMetaDataContext.setSchemaName(schemaName);
158: }
159:
160: /**
161: * Get the schema name used.
162: */
163: public String getSchemaName() {
164: return this .callMetaDataContext.getSchemaName();
165: }
166:
167: /**
168: * Specify whether this call is a function call.
169: */
170: public void setFunction(boolean function) {
171: this .callMetaDataContext.setFunction(function);
172: }
173:
174: /**
175: * Is this call a function call?
176: */
177: public boolean isFunction() {
178: return this .callMetaDataContext.isFunction();
179: }
180:
181: /**
182: * Specify whether the call requires a rerurn value.
183: */
184: public void setReturnValueRequired(boolean b) {
185: this .callMetaDataContext.setReturnValueRequired(b);
186: }
187:
188: /**
189: * Does the call require a return value?
190: */
191: public boolean isReturnValueRequired() {
192: return this .callMetaDataContext.isReturnValueRequired();
193: }
194:
195: /**
196: * Add a declared parameter to teh list of parameters for the call
197: * @param parameter the {@link SqlParameter} to add
198: */
199: public void addDeclaredParameter(SqlParameter parameter) {
200: this .declaredParameters.add(parameter);
201: if (logger.isDebugEnabled()) {
202: logger.debug("Added declared parameter for ["
203: + getProcedureName() + "]: " + parameter.getName());
204: }
205: }
206:
207: /**
208: * Add a {@link org.springframework.jdbc.core.RowMapper} for the specified parameter or column
209: * @param parameterName name of parameter or column
210: * @param rowMapper the RowMapper implementation to use
211: */
212: public void addDeclaredRowMapper(String parameterName,
213: ParameterizedRowMapper rowMapper) {
214: this .declaredRowMappers.put(parameterName, rowMapper);
215: if (logger.isDebugEnabled()) {
216: logger.debug("Added row mapper for [" + getProcedureName()
217: + "]: " + parameterName);
218: }
219: }
220:
221: /**
222: * Get the call string that should be used based on parameters and meta data
223: */
224: public String getCallString() {
225: return this .callString;
226: }
227:
228: /**
229: * Specify whether the parameter metadata for the call should be used. The default is true.
230: */
231: public void setAccessCallParameterMetaData(
232: boolean accessCallParameterMetaData) {
233: this .callMetaDataContext
234: .setAccessCallParameterMetaData(accessCallParameterMetaData);
235: }
236:
237: //-------------------------------------------------------------------------
238: // Methods handling compilation issues
239: //-------------------------------------------------------------------------
240:
241: /**
242: * Compile this JdbcCall using provided parameters and meta data plus other settings. This
243: * finalizes the configuration for this object and subsequent attempts to compile are ignored.
244: * This will be implicitly called the first time an un-compiled call is executed.
245: * @throws org.springframework.dao.InvalidDataAccessApiUsageException if the object hasn't
246: * been correctly initialized, for example if no DataSource has been provided
247: */
248: public final void compile()
249: throws InvalidDataAccessApiUsageException {
250: if (!isCompiled()) {
251: if (getProcedureName() == null) {
252: throw new InvalidDataAccessApiUsageException(
253: "Procedure or Function name is required");
254: }
255:
256: try {
257: this .jdbcTemplate.afterPropertiesSet();
258: } catch (IllegalArgumentException ex) {
259: throw new InvalidDataAccessApiUsageException(ex
260: .getMessage());
261: }
262:
263: compileInternal();
264: this .compiled = true;
265:
266: if (logger.isDebugEnabled()) {
267: logger.debug("SqlCall for "
268: + (isFunction() ? "function" : "procedure")
269: + " [" + getProcedureName() + "] compiled");
270: }
271: }
272: }
273:
274: /**
275: * Method to perform the actual compilation. Subclasses can override this template method to perform
276: * their own compilation. Invoked after this base class's compilation is complete.
277: */
278: protected void compileInternal() {
279: this .callMetaDataContext.initializeMetaData(getJdbcTemplate()
280: .getDataSource());
281:
282: // iterate over the declared RowMappers and register the corresponding SqlParameter
283: for (Map.Entry<String, ParameterizedRowMapper> entry : this .declaredRowMappers
284: .entrySet()) {
285: SqlParameter resultSetParameter = this .callMetaDataContext
286: .createReturnResultSetParameter(entry.getKey(),
287: entry.getValue());
288: this .declaredParameters.add(resultSetParameter);
289: }
290: callMetaDataContext.processParameters(this .declaredParameters);
291:
292: this .callString = this .callMetaDataContext.createCallString();
293: if (logger.isDebugEnabled()) {
294: logger.debug("Compiled stored procedure. Call string is ["
295: + this .callString + "]");
296: }
297:
298: this .callableStatementFactory = new CallableStatementCreatorFactory(
299: getCallString(), this .callMetaDataContext
300: .getCallParameters());
301: this .callableStatementFactory
302: .setNativeJdbcExtractor(getJdbcTemplate()
303: .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</code>.
327: */
328: protected void checkCompiled() {
329: if (!isCompiled()) {
330: logger
331: .debug("JdbcCall call not compiled before execution - invoking compile");
332: compile();
333: }
334: }
335:
336: //-------------------------------------------------------------------------
337: // Methods handling execution
338: //-------------------------------------------------------------------------
339:
340: /**
341: * Method that provides execution of the call using the passed in {@link SqlParameterSource}
342: *
343: * @param parameterSource parameter names and values to be used in call
344: * @return Map of out parameters
345: */
346: protected Map<String, Object> doExecute(
347: SqlParameterSource parameterSource) {
348: checkCompiled();
349: Map params = null;
350: if (parameterSource instanceof MapSqlParameterSource) {
351: Map<String, Object> sourceValues = ((MapSqlParameterSource) parameterSource)
352: .getValues();
353: params = matchInParameterValuesWithCallParameters(sourceValues);
354: } else {
355: params = matchInParameterValuesWithCallParameters(parameterSource);
356: }
357: return executeCallInternal(params);
358: }
359:
360: /**
361: * Method that provides execution of the call using the passed in Map of parameters
362: * @param args Map of parameter name and values
363: * @return Map of out parameters
364: */
365: protected Map<String, Object> doExecute(Map<String, Object> args) {
366: checkCompiled();
367: Map params = matchInParameterValuesWithCallParameters(args);
368: return executeCallInternal(params);
369: }
370:
371: /**
372: * Mathod to perform the actual call processing
373: */
374: private Map<String, Object> executeCallInternal(Map params) {
375: CallableStatementCreator csc = getCallableStatementFactory()
376: .newCallableStatementCreator(params);
377: if (logger.isDebugEnabled()) {
378: logger.debug("The following parameters are used for call "
379: + getCallString() + " with: " + params);
380: int i = 1;
381: for (SqlParameter p : getCallParameters()) {
382: logger.debug(i++ + ": " + p.getName() + " SQL Type "
383: + p.getSqlType() + " Type Name "
384: + p.getTypeName() + " "
385: + p.getClass().getName());
386: }
387: }
388: Map<String, Object> result = getJdbcTemplate().call(csc,
389: getCallParameters());
390: return result;
391: }
392:
393: /**
394: * Get the name of a single out parameter or return value. Used for functions or procedures with one out parameter
395: */
396: protected String getScalarOutParameterName() {
397: return this .callMetaDataContext.getScalarOutParameterName();
398: }
399:
400: /**
401: * Match the provided in parameter values with regitered parameters and parameters defined via metedata
402: * processing.
403: * @param parameterSource the parameter vakues provided as a {@link SqlParameterSource}
404: * @return Map with parameter names and values
405: */
406: protected Map<String, Object> matchInParameterValuesWithCallParameters(
407: SqlParameterSource parameterSource) {
408: return this .callMetaDataContext
409: .matchInParameterValuesWithCallParameters(parameterSource);
410: }
411:
412: /**
413: * Match the provided in parameter values with regitered parameters and parameters defined via metedata
414: * processing.
415: * @param args the parameter values provided in a Map
416: * @return Map with parameter names and values
417: */
418: protected Map<String, Object> matchInParameterValuesWithCallParameters(
419: Map<String, Object> args) {
420: return this .callMetaDataContext
421: .matchInParameterValuesWithCallParameters(args);
422: }
423:
424: /**
425: * Get a List of all the call parameters to be used for call. This includes any parameters added
426: * based on meta data processing.
427: */
428: protected List<SqlParameter> getCallParameters() {
429: return this.callMetaDataContext.getCallParameters();
430: }
431: }
|