1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.jdbc.core.metadata;
18
19 import java.sql.DatabaseMetaData;
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22 import java.sql.Types;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28
29 import org.springframework.dao.InvalidDataAccessApiUsageException;
30 import org.springframework.jdbc.core.SqlInOutParameter;
31 import org.springframework.jdbc.core.SqlOutParameter;
32 import org.springframework.jdbc.core.SqlParameter;
33 import org.springframework.util.StringUtils;
34
35
36
37
38
39
40
41
42 public class GenericCallMetaDataProvider implements CallMetaDataProvider {
43
44
45 protected static final Log logger = LogFactory.getLog(CallMetaDataProvider.class);
46
47 private boolean procedureColumnMetaDataUsed = false;
48
49 private String userName;
50
51 private boolean supportsCatalogsInProcedureCalls = true;
52
53 private boolean supportsSchemasInProcedureCalls = true;
54
55 private boolean storesUpperCaseIdentifiers = true;
56
57 private boolean storesLowerCaseIdentifiers = false;
58
59 private List<CallParameterMetaData> callParameterMetaData = new ArrayList<CallParameterMetaData>();
60
61
62
63
64
65
66 protected GenericCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException {
67 this.userName = databaseMetaData.getUserName();
68 }
69
70
71 @Override
72 public void initializeWithMetaData(DatabaseMetaData databaseMetaData) throws SQLException {
73 try {
74 setSupportsCatalogsInProcedureCalls(databaseMetaData.supportsCatalogsInProcedureCalls());
75 }
76 catch (SQLException ex) {
77 if (logger.isWarnEnabled()) {
78 logger.warn("Error retrieving 'DatabaseMetaData.supportsCatalogsInProcedureCalls' - " + ex.getMessage());
79 }
80 }
81 try {
82 setSupportsSchemasInProcedureCalls(databaseMetaData.supportsSchemasInProcedureCalls());
83 }
84 catch (SQLException ex) {
85 if (logger.isWarnEnabled()) {
86 logger.warn("Error retrieving 'DatabaseMetaData.supportsSchemasInProcedureCalls' - " + ex.getMessage());
87 }
88 }
89 try {
90 setStoresUpperCaseIdentifiers(databaseMetaData.storesUpperCaseIdentifiers());
91 }
92 catch (SQLException ex) {
93 if (logger.isWarnEnabled()) {
94 logger.warn("Error retrieving 'DatabaseMetaData.storesUpperCaseIdentifiers' - " + ex.getMessage());
95 }
96 }
97 try {
98 setStoresLowerCaseIdentifiers(databaseMetaData.storesLowerCaseIdentifiers());
99 }
100 catch (SQLException ex) {
101 if (logger.isWarnEnabled()) {
102 logger.warn("Error retrieving 'DatabaseMetaData.storesLowerCaseIdentifiers' - " + ex.getMessage());
103 }
104 }
105 }
106
107 @Override
108 public void initializeWithProcedureColumnMetaData(DatabaseMetaData databaseMetaData, String catalogName,
109 String schemaName, String procedureName) throws SQLException {
110
111 this.procedureColumnMetaDataUsed = true;
112 processProcedureColumns(databaseMetaData, catalogName, schemaName, procedureName);
113 }
114
115 @Override
116 public List<CallParameterMetaData> getCallParameterMetaData() {
117 return this.callParameterMetaData;
118 }
119
120 @Override
121 public String procedureNameToUse(String procedureName) {
122 if (procedureName == null) {
123 return null;
124 }
125 else if (isStoresUpperCaseIdentifiers()) {
126 return procedureName.toUpperCase();
127 }
128 else if (isStoresLowerCaseIdentifiers()) {
129 return procedureName.toLowerCase();
130 }
131 else {
132 return procedureName;
133 }
134 }
135
136 @Override
137 public String catalogNameToUse(String catalogName) {
138 if (catalogName == null) {
139 return null;
140 }
141 else if (isStoresUpperCaseIdentifiers()) {
142 return catalogName.toUpperCase();
143 }
144 else if (isStoresLowerCaseIdentifiers()) {
145 return catalogName.toLowerCase();
146 }
147 else {
148 return catalogName;
149 }
150 }
151
152 @Override
153 public String schemaNameToUse(String schemaName) {
154 if (schemaName == null) {
155 return null;
156 }
157 else if (isStoresUpperCaseIdentifiers()) {
158 return schemaName.toUpperCase();
159 }
160 else if (isStoresLowerCaseIdentifiers()) {
161 return schemaName.toLowerCase();
162 }
163 else {
164 return schemaName;
165 }
166 }
167
168 @Override
169 public String metaDataCatalogNameToUse(String catalogName) {
170 if (isSupportsCatalogsInProcedureCalls()) {
171 return catalogNameToUse(catalogName);
172 }
173 else {
174 return null;
175 }
176 }
177
178 @Override
179 public String metaDataSchemaNameToUse(String schemaName) {
180 if (isSupportsSchemasInProcedureCalls()) {
181 return schemaNameToUse(schemaName);
182 }
183 else {
184 return null;
185 }
186 }
187
188 @Override
189 public String parameterNameToUse(String parameterName) {
190 if (parameterName == null) {
191 return null;
192 }
193 else if (isStoresUpperCaseIdentifiers()) {
194 return parameterName.toUpperCase();
195 }
196 else if (isStoresLowerCaseIdentifiers()) {
197 return parameterName.toLowerCase();
198 }
199 else {
200 return parameterName;
201 }
202 }
203
204 @Override
205 public boolean byPassReturnParameter(String parameterName) {
206 return false;
207 }
208
209 @Override
210 public SqlParameter createDefaultOutParameter(String parameterName, CallParameterMetaData meta) {
211 return new SqlOutParameter(parameterName, meta.getSqlType());
212 }
213
214 @Override
215 public SqlParameter createDefaultInOutParameter(String parameterName, CallParameterMetaData meta) {
216 return new SqlInOutParameter(parameterName, meta.getSqlType());
217 }
218
219 @Override
220 public SqlParameter createDefaultInParameter(String parameterName, CallParameterMetaData meta) {
221 return new SqlParameter(parameterName, meta.getSqlType());
222 }
223
224 @Override
225 public String getUserName() {
226 return this.userName;
227 }
228
229 @Override
230 public boolean isReturnResultSetSupported() {
231 return true;
232 }
233
234 @Override
235 public boolean isRefCursorSupported() {
236 return false;
237 }
238
239 @Override
240 public int getRefCursorSqlType() {
241 return Types.OTHER;
242 }
243
244 @Override
245 public boolean isProcedureColumnMetaDataUsed() {
246 return this.procedureColumnMetaDataUsed;
247 }
248
249
250
251
252
253 protected void setSupportsCatalogsInProcedureCalls(boolean supportsCatalogsInProcedureCalls) {
254 this.supportsCatalogsInProcedureCalls = supportsCatalogsInProcedureCalls;
255 }
256
257
258
259
260 @Override
261 public boolean isSupportsCatalogsInProcedureCalls() {
262 return this.supportsCatalogsInProcedureCalls;
263 }
264
265
266
267
268 protected void setSupportsSchemasInProcedureCalls(boolean supportsSchemasInProcedureCalls) {
269 this.supportsSchemasInProcedureCalls = supportsSchemasInProcedureCalls;
270 }
271
272
273
274
275 @Override
276 public boolean isSupportsSchemasInProcedureCalls() {
277 return this.supportsSchemasInProcedureCalls;
278 }
279
280
281
282
283 protected void setStoresUpperCaseIdentifiers(boolean storesUpperCaseIdentifiers) {
284 this.storesUpperCaseIdentifiers = storesUpperCaseIdentifiers;
285 }
286
287
288
289
290 protected boolean isStoresUpperCaseIdentifiers() {
291 return this.storesUpperCaseIdentifiers;
292 }
293
294
295
296
297 protected void setStoresLowerCaseIdentifiers(boolean storesLowerCaseIdentifiers) {
298 this.storesLowerCaseIdentifiers = storesLowerCaseIdentifiers;
299 }
300
301
302
303
304 protected boolean isStoresLowerCaseIdentifiers() {
305 return this.storesLowerCaseIdentifiers;
306 }
307
308
309
310
311
312 private void processProcedureColumns(DatabaseMetaData databaseMetaData, String catalogName, String schemaName, String procedureName) {
313 ResultSet procs = null;
314 String metaDataCatalogName = metaDataCatalogNameToUse(catalogName);
315 String metaDataSchemaName = metaDataSchemaNameToUse(schemaName);
316 String metaDataProcedureName = procedureNameToUse(procedureName);
317 if (logger.isDebugEnabled()) {
318 logger.debug("Retrieving metadata for " + metaDataCatalogName + "/" +
319 metaDataSchemaName + "/" + metaDataProcedureName);
320 }
321 try {
322 procs = databaseMetaData.getProcedures(metaDataCatalogName, metaDataSchemaName, metaDataProcedureName);
323 List<String> found = new ArrayList<String>();
324 while (procs.next()) {
325 found.add(procs.getString("PROCEDURE_CAT") + "." + procs.getString("PROCEDURE_SCHEM") +
326 "." + procs.getString("PROCEDURE_NAME"));
327 }
328 procs.close();
329 if (found.size() > 1) {
330 throw new InvalidDataAccessApiUsageException("Unable to determine the correct call signature - " +
331 "multiple procedures/functions/signatures for " + metaDataProcedureName + " found " + found);
332 }
333 if (found.size() < 1) {
334 if (metaDataProcedureName.contains(".") && !StringUtils.hasText(metaDataCatalogName)) {
335 String packageName = metaDataProcedureName.substring(0, metaDataProcedureName.indexOf("."));
336 throw new InvalidDataAccessApiUsageException("Unable to determine the correct call signature for " +
337 metaDataProcedureName + " - package name should be specified separately using " +
338 "'.withCatalogName(\"" + packageName + "\")'");
339 }
340 }
341
342 procs = databaseMetaData.getProcedureColumns(
343 metaDataCatalogName, metaDataSchemaName, metaDataProcedureName, null);
344 while (procs.next()) {
345 String columnName = procs.getString("COLUMN_NAME");
346 int columnType = procs.getInt("COLUMN_TYPE");
347 if (columnName == null && (
348 columnType == DatabaseMetaData.procedureColumnIn ||
349 columnType == DatabaseMetaData.procedureColumnInOut ||
350 columnType == DatabaseMetaData.procedureColumnOut)) {
351 if (logger.isDebugEnabled()) {
352 logger.debug("Skipping metadata for: " + columnType + " " + procs.getInt("DATA_TYPE") +
353 " " + procs.getString("TYPE_NAME") + " " + procs.getBoolean("NULLABLE") +
354 " (probably a member of a collection)"
355 );
356 }
357 }
358 else {
359 CallParameterMetaData meta = new CallParameterMetaData(columnName, columnType,
360 procs.getInt("DATA_TYPE"), procs.getString("TYPE_NAME"), procs.getBoolean("NULLABLE")
361 );
362 this.callParameterMetaData.add(meta);
363 if (logger.isDebugEnabled()) {
364 logger.debug("Retrieved metadata: " + meta.getParameterName() + " " +
365 meta.getParameterType() + " " + meta.getSqlType() + " " +
366 meta.getTypeName() + " " + meta.isNullable());
367 }
368 }
369 }
370 }
371 catch (SQLException ex) {
372 if (logger.isWarnEnabled()) {
373 logger.warn("Error while retrieving metadata for procedure columns: " + ex);
374 }
375 }
376 finally {
377 try {
378 if (procs != null) {
379 procs.close();
380 }
381 }
382 catch (SQLException ex) {
383 if (logger.isWarnEnabled()) {
384 logger.warn("Problem closing ResultSet for procedure column metadata: " + ex);
385 }
386 }
387 }
388 }
389
390 }