001/*
002 * Copyright 2002-2018 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 *      https://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
017package org.springframework.jdbc.core.simple;
018
019import java.util.ArrayList;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import javax.sql.DataSource;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.dao.InvalidDataAccessApiUsageException;
030import org.springframework.jdbc.core.CallableStatementCreator;
031import org.springframework.jdbc.core.CallableStatementCreatorFactory;
032import org.springframework.jdbc.core.JdbcTemplate;
033import org.springframework.jdbc.core.RowMapper;
034import org.springframework.jdbc.core.SqlParameter;
035import org.springframework.jdbc.core.metadata.CallMetaDataContext;
036import org.springframework.jdbc.core.namedparam.SqlParameterSource;
037import org.springframework.util.Assert;
038import org.springframework.util.StringUtils;
039
040/**
041 * Abstract class to provide base functionality for easy stored procedure calls
042 * based on configuration options and database meta-data.
043 *
044 * <p>This class provides the base SPI for {@link SimpleJdbcCall}.
045 *
046 * @author Thomas Risberg
047 * @author Juergen Hoeller
048 * @since 2.5
049 */
050public abstract class AbstractJdbcCall {
051
052        /** Logger available to subclasses */
053        protected final Log logger = LogFactory.getLog(getClass());
054
055        /** Lower-level class used to execute SQL */
056        private final JdbcTemplate jdbcTemplate;
057
058        /** Context used to retrieve and manage database meta-data */
059        private final CallMetaDataContext callMetaDataContext = new CallMetaDataContext();
060
061        /** List of SqlParameter objects */
062        private final List<SqlParameter> declaredParameters = new ArrayList<SqlParameter>();
063
064        /** List of RefCursor/ResultSet RowMapper objects */
065        private final Map<String, RowMapper<?>> declaredRowMappers = new LinkedHashMap<String, RowMapper<?>>();
066
067        /**
068         * Has this operation been compiled? Compilation means at least checking
069         * that a DataSource or JdbcTemplate has been provided.
070         */
071        private volatile boolean compiled = false;
072
073        /** The generated string used for call statement */
074        private String callString;
075
076        /**
077         * A delegate enabling us to create CallableStatementCreators
078         * efficiently, based on this class's declared parameters.
079         */
080        private CallableStatementCreatorFactory callableStatementFactory;
081
082
083        /**
084         * Constructor to be used when initializing using a {@link DataSource}.
085         * @param dataSource the DataSource to be used
086         */
087        protected AbstractJdbcCall(DataSource dataSource) {
088                this.jdbcTemplate = new JdbcTemplate(dataSource);
089        }
090
091        /**
092         * Constructor to be used when initializing using a {@link JdbcTemplate}.
093         * @param jdbcTemplate the JdbcTemplate to use
094         */
095        protected AbstractJdbcCall(JdbcTemplate jdbcTemplate) {
096                Assert.notNull(jdbcTemplate, "JdbcTemplate must not be null");
097                this.jdbcTemplate = jdbcTemplate;
098        }
099
100
101        /**
102         * Get the configured {@link JdbcTemplate}.
103         */
104        public JdbcTemplate getJdbcTemplate() {
105                return this.jdbcTemplate;
106        }
107
108        /**
109         * Set the name of the stored procedure.
110         */
111        public void setProcedureName(String procedureName) {
112                this.callMetaDataContext.setProcedureName(procedureName);
113        }
114
115        /**
116         * Get the name of the stored procedure.
117         */
118        public String getProcedureName() {
119                return this.callMetaDataContext.getProcedureName();
120        }
121
122        /**
123         * Set the names of in parameters to be used.
124         */
125        public void setInParameterNames(Set<String> inParameterNames) {
126                this.callMetaDataContext.setLimitedInParameterNames(inParameterNames);
127        }
128
129        /**
130         * Get the names of in parameters to be used.
131         */
132        public Set<String> getInParameterNames() {
133                return this.callMetaDataContext.getLimitedInParameterNames();
134        }
135
136        /**
137         * Set the catalog name to use.
138         */
139        public void setCatalogName(String catalogName) {
140                this.callMetaDataContext.setCatalogName(catalogName);
141        }
142
143        /**
144         * Get the catalog name used.
145         */
146        public String getCatalogName() {
147                return this.callMetaDataContext.getCatalogName();
148        }
149
150        /**
151         * Set the schema name to use.
152         */
153        public void setSchemaName(String schemaName) {
154                this.callMetaDataContext.setSchemaName(schemaName);
155        }
156
157        /**
158         * Get the schema name used.
159         */
160        public String getSchemaName() {
161                return this.callMetaDataContext.getSchemaName();
162        }
163
164        /**
165         * Specify whether this call is a function call.
166         * The default is {@code false}.
167         */
168        public void setFunction(boolean function) {
169                this.callMetaDataContext.setFunction(function);
170        }
171
172        /**
173         * Is this call a function call?
174         */
175        public boolean isFunction() {
176                return this.callMetaDataContext.isFunction();
177        }
178
179        /**
180         * Specify whether the call requires a return value.
181         * The default is {@code false}.
182         */
183        public void setReturnValueRequired(boolean returnValueRequired) {
184                this.callMetaDataContext.setReturnValueRequired(returnValueRequired);
185        }
186
187        /**
188         * Does the call require a return value?
189         */
190        public boolean isReturnValueRequired() {
191                return this.callMetaDataContext.isReturnValueRequired();
192        }
193
194        /**
195         * Specify whether parameters should be bound by name.
196         * The default is {@code false}.
197         * @since 4.2
198         */
199        public void setNamedBinding(boolean namedBinding) {
200                this.callMetaDataContext.setNamedBinding(namedBinding);
201        }
202
203        /**
204         * Should parameters be bound by name?
205         * @since 4.2
206         */
207        public boolean isNamedBinding() {
208                return this.callMetaDataContext.isNamedBinding();
209        }
210
211        /**
212         * Specify whether the parameter meta-data for the call should be used.
213         * The default is {@code true}.
214         */
215        public void setAccessCallParameterMetaData(boolean accessCallParameterMetaData) {
216                this.callMetaDataContext.setAccessCallParameterMetaData(accessCallParameterMetaData);
217        }
218
219        /**
220         * Get the call string that should be used based on parameters and meta-data.
221         */
222        public String getCallString() {
223                return this.callString;
224        }
225
226        /**
227         * Get the {@link CallableStatementCreatorFactory} being used
228         */
229        protected CallableStatementCreatorFactory getCallableStatementFactory() {
230                return this.callableStatementFactory;
231        }
232
233
234        /**
235         * Add a declared parameter to the list of parameters for the call.
236         * <p>Only parameters declared as {@code SqlParameter} and {@code SqlInOutParameter} will
237         * be used to provide input values. This is different from the {@code StoredProcedure}
238         * class which - for backwards compatibility reasons - allows input values to be provided
239         * for parameters declared as {@code SqlOutParameter}.
240         * @param parameter the {@link SqlParameter} to add
241         */
242        public void addDeclaredParameter(SqlParameter parameter) {
243                Assert.notNull(parameter, "The supplied parameter must not be null");
244                if (!StringUtils.hasText(parameter.getName())) {
245                        throw new InvalidDataAccessApiUsageException(
246                                        "You must specify a parameter name when declaring parameters for \"" + getProcedureName() + "\"");
247                }
248                this.declaredParameters.add(parameter);
249                if (logger.isDebugEnabled()) {
250                        logger.debug("Added declared parameter for [" + getProcedureName() + "]: " + parameter.getName());
251                }
252        }
253
254        /**
255         * Add a {@link org.springframework.jdbc.core.RowMapper} for the specified parameter or column.
256         * @param parameterName name of parameter or column
257         * @param rowMapper the RowMapper implementation to use
258         */
259        public void addDeclaredRowMapper(String parameterName, RowMapper<?> rowMapper) {
260                this.declaredRowMappers.put(parameterName, rowMapper);
261                if (logger.isDebugEnabled()) {
262                        logger.debug("Added row mapper for [" + getProcedureName() + "]: " + parameterName);
263                }
264        }
265
266
267        //-------------------------------------------------------------------------
268        // Methods handling compilation issues
269        //-------------------------------------------------------------------------
270
271        /**
272         * Compile this JdbcCall using provided parameters and meta-data plus other settings.
273         * <p>This finalizes the configuration for this object and subsequent attempts to compile are
274         * ignored. This will be implicitly called the first time an un-compiled call is executed.
275         * @throws org.springframework.dao.InvalidDataAccessApiUsageException if the object hasn't
276         * been correctly initialized, for example if no DataSource has been provided
277         */
278        public final synchronized void compile() throws InvalidDataAccessApiUsageException {
279                if (!isCompiled()) {
280                        if (getProcedureName() == null) {
281                                throw new InvalidDataAccessApiUsageException("Procedure or Function name is required");
282                        }
283                        try {
284                                this.jdbcTemplate.afterPropertiesSet();
285                        }
286                        catch (IllegalArgumentException ex) {
287                                throw new InvalidDataAccessApiUsageException(ex.getMessage());
288                        }
289                        compileInternal();
290                        this.compiled = true;
291                        if (logger.isDebugEnabled()) {
292                                logger.debug("SqlCall for " + (isFunction() ? "function" : "procedure") +
293                                                " [" + getProcedureName() + "] compiled");
294                        }
295                }
296        }
297
298        /**
299         * Delegate method to perform the actual compilation.
300         * <p>Subclasses can override this template method to perform their own compilation.
301         * Invoked after this base class's compilation is complete.
302         */
303        protected void compileInternal() {
304                this.callMetaDataContext.initializeMetaData(getJdbcTemplate().getDataSource());
305
306                // Iterate over the declared RowMappers and register the corresponding SqlParameter
307                for (Map.Entry<String, RowMapper<?>> entry : this.declaredRowMappers.entrySet()) {
308                        SqlParameter resultSetParameter =
309                                        this.callMetaDataContext.createReturnResultSetParameter(entry.getKey(), entry.getValue());
310                        this.declaredParameters.add(resultSetParameter);
311                }
312                this.callMetaDataContext.processParameters(this.declaredParameters);
313
314                this.callString = this.callMetaDataContext.createCallString();
315                if (logger.isDebugEnabled()) {
316                        logger.debug("Compiled stored procedure. Call string is [" + this.callString + "]");
317                }
318
319                this.callableStatementFactory =
320                                new CallableStatementCreatorFactory(getCallString(), this.callMetaDataContext.getCallParameters());
321                this.callableStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());
322
323                onCompileInternal();
324        }
325
326        /**
327         * Hook method that subclasses may override to react to compilation.
328         * This implementation does nothing.
329         */
330        protected void onCompileInternal() {
331        }
332
333        /**
334         * Is this operation "compiled"?
335         * @return whether this operation is compiled and ready to use
336         */
337        public boolean isCompiled() {
338                return this.compiled;
339        }
340
341        /**
342         * Check whether this operation has been compiled already;
343         * lazily compile it if not already compiled.
344         * <p>Automatically called by {@code doExecute}.
345         */
346        protected void checkCompiled() {
347                if (!isCompiled()) {
348                        logger.debug("JdbcCall call not compiled before execution - invoking compile");
349                        compile();
350                }
351        }
352
353
354        //-------------------------------------------------------------------------
355        // Methods handling execution
356        //-------------------------------------------------------------------------
357
358        /**
359         * Delegate method that executes the call using the passed-in {@link SqlParameterSource}.
360         * @param parameterSource parameter names and values to be used in call
361         * @return Map of out parameters
362         */
363        protected Map<String, Object> doExecute(SqlParameterSource parameterSource) {
364                checkCompiled();
365                Map<String, Object> params = matchInParameterValuesWithCallParameters(parameterSource);
366                return executeCallInternal(params);
367        }
368
369        /**
370         * Delegate method that executes the call using the passed-in array of parameters.
371         * @param args array of parameter values. The order of values must match the order
372         * declared for the stored procedure.
373         * @return Map of out parameters
374         */
375        protected Map<String, Object> doExecute(Object... args) {
376                checkCompiled();
377                Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
378                return executeCallInternal(params);
379        }
380
381        /**
382         * Delegate method that executes the call using the passed-in Map of parameters.
383         * @param args Map of parameter name and values
384         * @return Map of out parameters
385         */
386        protected Map<String, Object> doExecute(Map<String, ?> args) {
387                checkCompiled();
388                Map<String, ?> params = matchInParameterValuesWithCallParameters(args);
389                return executeCallInternal(params);
390        }
391
392        /**
393         * Delegate method to perform the actual call processing.
394         */
395        private Map<String, Object> executeCallInternal(Map<String, ?> args) {
396                CallableStatementCreator csc = getCallableStatementFactory().newCallableStatementCreator(args);
397                if (logger.isDebugEnabled()) {
398                        logger.debug("The following parameters are used for call " + getCallString() + " with " + args);
399                        int i = 1;
400                        for (SqlParameter param : getCallParameters()) {
401                                logger.debug(i + ": " +  param.getName() + ", SQL type "+ param.getSqlType() + ", type name " +
402                                                param.getTypeName() + ", parameter class [" + param.getClass().getName() + "]");
403                                i++;
404                        }
405                }
406                return getJdbcTemplate().call(csc, getCallParameters());
407        }
408
409
410        /**
411         * Get the name of a single out parameter or return value.
412         * Used for functions or procedures with one out parameter.
413         */
414        protected String getScalarOutParameterName() {
415                return this.callMetaDataContext.getScalarOutParameterName();
416        }
417
418        /**
419         * Get a List of all the call parameters to be used for call.
420         * This includes any parameters added based on meta-data processing.
421         */
422        protected List<SqlParameter> getCallParameters() {
423                return this.callMetaDataContext.getCallParameters();
424        }
425
426        /**
427         * Match the provided in parameter values with registered parameters and
428         * parameters defined via meta-data processing.
429         * @param parameterSource the parameter vakues provided as a {@link SqlParameterSource}
430         * @return Map with parameter names and values
431         */
432        protected Map<String, Object> matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
433                return this.callMetaDataContext.matchInParameterValuesWithCallParameters(parameterSource);
434        }
435
436        /**
437         * Match the provided in parameter values with registered parameters and
438         * parameters defined via meta-data processing.
439         * @param args the parameter values provided as an array
440         * @return Map with parameter names and values
441         */
442        private Map<String, ?> matchInParameterValuesWithCallParameters(Object[] args) {
443                return this.callMetaDataContext.matchInParameterValuesWithCallParameters(args);
444        }
445
446        /**
447         * Match the provided in parameter values with registered parameters and
448         * parameters defined via meta-data processing.
449         * @param args the parameter values provided in a Map
450         * @return Map with parameter names and values
451         */
452        protected Map<String, ?> matchInParameterValuesWithCallParameters(Map<String, ?> args) {
453                return this.callMetaDataContext.matchInParameterValuesWithCallParameters(args);
454        }
455
456}