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;
018
019import java.sql.CallableStatement;
020import java.sql.Connection;
021import java.sql.ResultSet;
022import java.sql.SQLException;
023import java.util.HashMap;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027
028import org.springframework.dao.InvalidDataAccessApiUsageException;
029import org.springframework.lang.Nullable;
030
031/**
032 * Helper class that efficiently creates multiple {@link CallableStatementCreator}
033 * objects with different parameters based on an SQL statement and a single
034 * set of parameter declarations.
035 *
036 * @author Rod Johnson
037 * @author Thomas Risberg
038 * @author Juergen Hoeller
039 */
040public class CallableStatementCreatorFactory {
041
042        /** The SQL call string, which won't change when the parameters change. */
043        private final String callString;
044
045        /** List of SqlParameter objects. May not be {@code null}. */
046        private final List<SqlParameter> declaredParameters;
047
048        private int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
049
050        private boolean updatableResults = false;
051
052
053        /**
054         * Create a new factory. Will need to add parameters via the
055         * {@link #addParameter} method or have no parameters.
056         * @param callString the SQL call string
057         */
058        public CallableStatementCreatorFactory(String callString) {
059                this.callString = callString;
060                this.declaredParameters = new LinkedList<>();
061        }
062
063        /**
064         * Create a new factory with the given SQL and the given parameters.
065         * @param callString the SQL call string
066         * @param declaredParameters list of {@link SqlParameter} objects
067         */
068        public CallableStatementCreatorFactory(String callString, List<SqlParameter> declaredParameters) {
069                this.callString = callString;
070                this.declaredParameters = declaredParameters;
071        }
072
073
074        /**
075         * Return the SQL call string.
076         * @since 5.1.3
077         */
078        public final String getCallString() {
079                return this.callString;
080        }
081
082        /**
083         * Add a new declared parameter.
084         * <p>Order of parameter addition is significant.
085         * @param param the parameter to add to the list of declared parameters
086         */
087        public void addParameter(SqlParameter param) {
088                this.declaredParameters.add(param);
089        }
090
091        /**
092         * Set whether to use prepared statements that return a specific type of ResultSet.
093         * specific type of ResultSet.
094         * @param resultSetType the ResultSet type
095         * @see java.sql.ResultSet#TYPE_FORWARD_ONLY
096         * @see java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE
097         * @see java.sql.ResultSet#TYPE_SCROLL_SENSITIVE
098         */
099        public void setResultSetType(int resultSetType) {
100                this.resultSetType = resultSetType;
101        }
102
103        /**
104         * Set whether to use prepared statements capable of returning updatable ResultSets.
105         */
106        public void setUpdatableResults(boolean updatableResults) {
107                this.updatableResults = updatableResults;
108        }
109
110
111        /**
112         * Return a new CallableStatementCreator instance given this parameters.
113         * @param params list of parameters (may be {@code null})
114         */
115        public CallableStatementCreator newCallableStatementCreator(@Nullable Map<String, ?> params) {
116                return new CallableStatementCreatorImpl(params != null ? params : new HashMap<>());
117        }
118
119        /**
120         * Return a new CallableStatementCreator instance given this parameter mapper.
121         * @param inParamMapper the ParameterMapper implementation that will return a Map of parameters
122         */
123        public CallableStatementCreator newCallableStatementCreator(ParameterMapper inParamMapper) {
124                return new CallableStatementCreatorImpl(inParamMapper);
125        }
126
127
128        /**
129         * CallableStatementCreator implementation returned by this class.
130         */
131        private class CallableStatementCreatorImpl implements CallableStatementCreator, SqlProvider, ParameterDisposer {
132
133                @Nullable
134                private ParameterMapper inParameterMapper;
135
136                @Nullable
137                private Map<String, ?> inParameters;
138
139                /**
140                 * Create a new CallableStatementCreatorImpl.
141                 * @param inParamMapper the ParameterMapper implementation for mapping input parameters
142                 */
143                public CallableStatementCreatorImpl(ParameterMapper inParamMapper) {
144                        this.inParameterMapper = inParamMapper;
145                }
146
147                /**
148                 * Create a new CallableStatementCreatorImpl.
149                 * @param inParams list of SqlParameter objects
150                 */
151                public CallableStatementCreatorImpl(Map<String, ?> inParams) {
152                        this.inParameters = inParams;
153                }
154
155                @Override
156                public CallableStatement createCallableStatement(Connection con) throws SQLException {
157                        // If we were given a ParameterMapper, we must let the mapper do its thing to create the Map.
158                        if (this.inParameterMapper != null) {
159                                this.inParameters = this.inParameterMapper.createMap(con);
160                        }
161                        else {
162                                if (this.inParameters == null) {
163                                        throw new InvalidDataAccessApiUsageException(
164                                                        "A ParameterMapper or a Map of parameters must be provided");
165                                }
166                        }
167
168                        CallableStatement cs = null;
169                        if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) {
170                                cs = con.prepareCall(callString);
171                        }
172                        else {
173                                cs = con.prepareCall(callString, resultSetType,
174                                                updatableResults ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
175                        }
176
177                        int sqlColIndx = 1;
178                        for (SqlParameter declaredParam : declaredParameters) {
179                                if (!declaredParam.isResultsParameter()) {
180                                        // So, it's a call parameter - part of the call string.
181                                        // Get the value - it may still be null.
182                                        Object inValue = this.inParameters.get(declaredParam.getName());
183                                        if (declaredParam instanceof ResultSetSupportingSqlParameter) {
184                                                // It's an output parameter: SqlReturnResultSet parameters already excluded.
185                                                // It need not (but may be) supplied by the caller.
186                                                if (declaredParam instanceof SqlOutParameter) {
187                                                        if (declaredParam.getTypeName() != null) {
188                                                                cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getTypeName());
189                                                        }
190                                                        else {
191                                                                if (declaredParam.getScale() != null) {
192                                                                        cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getScale());
193                                                                }
194                                                                else {
195                                                                        cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType());
196                                                                }
197                                                        }
198                                                        if (declaredParam.isInputValueProvided()) {
199                                                                StatementCreatorUtils.setParameterValue(cs, sqlColIndx, declaredParam, inValue);
200                                                        }
201                                                }
202                                        }
203                                        else {
204                                                // It's an input parameter; must be supplied by the caller.
205                                                if (!this.inParameters.containsKey(declaredParam.getName())) {
206                                                        throw new InvalidDataAccessApiUsageException(
207                                                                        "Required input parameter '" + declaredParam.getName() + "' is missing");
208                                                }
209                                                StatementCreatorUtils.setParameterValue(cs, sqlColIndx, declaredParam, inValue);
210                                        }
211                                        sqlColIndx++;
212                                }
213                        }
214
215                        return cs;
216                }
217
218                @Override
219                public String getSql() {
220                        return callString;
221                }
222
223                @Override
224                public void cleanupParameters() {
225                        if (this.inParameters != null) {
226                                StatementCreatorUtils.cleanupParameters(this.inParameters.values());
227                        }
228                }
229
230                @Override
231                public String toString() {
232                        return "CallableStatementCreator: sql=[" + callString + "]; parameters=" + this.inParameters;
233                }
234        }
235
236}