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