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.object;
018
019import java.util.List;
020import java.util.Map;
021import javax.sql.DataSource;
022
023import org.springframework.jdbc.core.CallableStatementCreator;
024import org.springframework.jdbc.core.CallableStatementCreatorFactory;
025import org.springframework.jdbc.core.ParameterMapper;
026import org.springframework.jdbc.core.SqlParameter;
027
028/**
029 * RdbmsOperation using a JdbcTemplate and representing a SQL-based
030 * call such as a stored procedure or a stored function.
031 *
032 * <p>Configures a CallableStatementCreatorFactory based on the declared
033 * parameters.
034 *
035 * @author Rod Johnson
036 * @author Thomas Risberg
037 * @see CallableStatementCreatorFactory
038 */
039public abstract class SqlCall extends RdbmsOperation {
040
041        /**
042         * Flag used to indicate that this call is for a function and to
043         * use the {? = call get_invoice_count(?)} syntax.
044         */
045        private boolean function = false;
046
047        /**
048         * Flag used to indicate that the sql for this call should be used exactly as
049         * it is defined. No need to add the escape syntax and parameter place holders.
050         */
051        private boolean sqlReadyForUse = false;
052
053        /**
054         * Call string as defined in java.sql.CallableStatement.
055         * String of form {call add_invoice(?, ?, ?)} or {? = call get_invoice_count(?)}
056         * if isFunction is set to true. Updated after each parameter is added.
057         */
058        private String callString;
059
060        /**
061         * Object enabling us to create CallableStatementCreators
062         * efficiently, based on this class's declared parameters.
063         */
064        private CallableStatementCreatorFactory callableStatementFactory;
065
066
067        /**
068         * Constructor to allow use as a JavaBean.
069         * A DataSource, SQL and any parameters must be supplied before
070         * invoking the {@code compile} method and using this object.
071         * @see #setDataSource
072         * @see #setSql
073         * @see #compile
074         */
075        public SqlCall() {
076        }
077
078        /**
079         * Create a new SqlCall object with SQL, but without parameters.
080         * Must add parameters or settle with none.
081         * @param ds the DataSource to obtain connections from
082         * @param sql the SQL to execute
083         */
084        public SqlCall(DataSource ds, String sql) {
085                setDataSource(ds);
086                setSql(sql);
087        }
088
089
090        /**
091         * Set whether this call is for a function.
092         */
093        public void setFunction(boolean function) {
094                this.function = function;
095        }
096
097        /**
098         * Return whether this call is for a function.
099         */
100        public boolean isFunction() {
101                return this.function;
102        }
103
104        /**
105         * Set whether the SQL can be used as is.
106         */
107        public void setSqlReadyForUse(boolean sqlReadyForUse) {
108                this.sqlReadyForUse = sqlReadyForUse;
109        }
110
111        /**
112         * Return whether the SQL can be used as is.
113         */
114        public boolean isSqlReadyForUse() {
115                return this.sqlReadyForUse;
116        }
117
118
119        /**
120         * Overridden method to configure the CallableStatementCreatorFactory
121         * based on our declared parameters.
122         * @see RdbmsOperation#compileInternal()
123         */
124        @Override
125        protected final void compileInternal() {
126                if (isSqlReadyForUse()) {
127                        this.callString = getSql();
128                }
129                else {
130                        List<SqlParameter> parameters = getDeclaredParameters();
131                        int parameterCount = 0;
132                        if (isFunction()) {
133                                this.callString = "{? = call " + getSql() + "(";
134                                parameterCount = -1;
135                        }
136                        else {
137                                this.callString = "{call " + getSql() + "(";
138                        }
139                        for (SqlParameter parameter : parameters) {
140                                if (!(parameter.isResultsParameter())) {
141                                        if (parameterCount > 0) {
142                                                this.callString += ", ";
143                                        }
144                                        if (parameterCount >= 0) {
145                                                this.callString += "?";
146                                        }
147                                        parameterCount++;
148                                }
149                        }
150                        this.callString += ")}";
151                }
152                if (logger.isDebugEnabled()) {
153                        logger.debug("Compiled stored procedure. Call string is [" + getCallString() + "]");
154                }
155
156                this.callableStatementFactory = new CallableStatementCreatorFactory(getCallString(), getDeclaredParameters());
157                this.callableStatementFactory.setResultSetType(getResultSetType());
158                this.callableStatementFactory.setUpdatableResults(isUpdatableResults());
159                this.callableStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());
160
161                onCompileInternal();
162        }
163
164        /**
165         * Hook method that subclasses may override to react to compilation.
166         * This implementation does nothing.
167         */
168        protected void onCompileInternal() {
169        }
170
171        /**
172         * Get the call string.
173         */
174        public String getCallString() {
175                return this.callString;
176        }
177
178        /**
179         * Return a CallableStatementCreator to perform an operation
180         * with this parameters.
181         * @param inParams parameters. May be {@code null}.
182         */
183        protected CallableStatementCreator newCallableStatementCreator(Map<String, ?> inParams) {
184                return this.callableStatementFactory.newCallableStatementCreator(inParams);
185        }
186
187        /**
188         * Return a CallableStatementCreator to perform an operation
189         * with the parameters returned from this ParameterMapper.
190         * @param inParamMapper parametermapper. May not be {@code null}.
191         */
192        protected CallableStatementCreator newCallableStatementCreator(ParameterMapper inParamMapper) {
193                return this.callableStatementFactory.newCallableStatementCreator(inParamMapper);
194        }
195
196}