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