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.metadata; 018 019import java.sql.DatabaseMetaData; 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.sql.Types; 023import java.util.ArrayList; 024import java.util.List; 025 026import org.apache.commons.logging.Log; 027import org.apache.commons.logging.LogFactory; 028 029import org.springframework.dao.InvalidDataAccessApiUsageException; 030import org.springframework.jdbc.core.SqlInOutParameter; 031import org.springframework.jdbc.core.SqlOutParameter; 032import org.springframework.jdbc.core.SqlParameter; 033import org.springframework.util.StringUtils; 034 035/** 036 * A generic implementation of the {@link CallMetaDataProvider} interface. 037 * This class can be extended to provide database specific behavior. 038 * 039 * @author Thomas Risberg 040 * @author Juergen Hoeller 041 * @since 2.5 042 */ 043public class GenericCallMetaDataProvider implements CallMetaDataProvider { 044 045 /** Logger available to subclasses */ 046 protected static final Log logger = LogFactory.getLog(CallMetaDataProvider.class); 047 048 private boolean procedureColumnMetaDataUsed = false; 049 050 private String userName; 051 052 private boolean supportsCatalogsInProcedureCalls = true; 053 054 private boolean supportsSchemasInProcedureCalls = true; 055 056 private boolean storesUpperCaseIdentifiers = true; 057 058 private boolean storesLowerCaseIdentifiers = false; 059 060 private List<CallParameterMetaData> callParameterMetaData = new ArrayList<CallParameterMetaData>(); 061 062 063 /** 064 * Constructor used to initialize with provided database meta-data. 065 * @param databaseMetaData meta-data to be used 066 */ 067 protected GenericCallMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException { 068 this.userName = databaseMetaData.getUserName(); 069 } 070 071 072 @Override 073 public void initializeWithMetaData(DatabaseMetaData databaseMetaData) throws SQLException { 074 try { 075 setSupportsCatalogsInProcedureCalls(databaseMetaData.supportsCatalogsInProcedureCalls()); 076 } 077 catch (SQLException ex) { 078 if (logger.isWarnEnabled()) { 079 logger.warn("Error retrieving 'DatabaseMetaData.supportsCatalogsInProcedureCalls': " + ex.getMessage()); 080 } 081 } 082 try { 083 setSupportsSchemasInProcedureCalls(databaseMetaData.supportsSchemasInProcedureCalls()); 084 } 085 catch (SQLException ex) { 086 if (logger.isWarnEnabled()) { 087 logger.warn("Error retrieving 'DatabaseMetaData.supportsSchemasInProcedureCalls': " + ex.getMessage()); 088 } 089 } 090 try { 091 setStoresUpperCaseIdentifiers(databaseMetaData.storesUpperCaseIdentifiers()); 092 } 093 catch (SQLException ex) { 094 if (logger.isWarnEnabled()) { 095 logger.warn("Error retrieving 'DatabaseMetaData.storesUpperCaseIdentifiers': " + ex.getMessage()); 096 } 097 } 098 try { 099 setStoresLowerCaseIdentifiers(databaseMetaData.storesLowerCaseIdentifiers()); 100 } 101 catch (SQLException ex) { 102 if (logger.isWarnEnabled()) { 103 logger.warn("Error retrieving 'DatabaseMetaData.storesLowerCaseIdentifiers': " + ex.getMessage()); 104 } 105 } 106 } 107 108 @Override 109 public void initializeWithProcedureColumnMetaData(DatabaseMetaData databaseMetaData, String catalogName, 110 String schemaName, String procedureName) throws SQLException { 111 112 this.procedureColumnMetaDataUsed = true; 113 processProcedureColumns(databaseMetaData, catalogName, schemaName, procedureName); 114 } 115 116 @Override 117 public List<CallParameterMetaData> getCallParameterMetaData() { 118 return this.callParameterMetaData; 119 } 120 121 @Override 122 public String procedureNameToUse(String procedureName) { 123 if (procedureName == null) { 124 return null; 125 } 126 else if (isStoresUpperCaseIdentifiers()) { 127 return procedureName.toUpperCase(); 128 } 129 else if (isStoresLowerCaseIdentifiers()) { 130 return procedureName.toLowerCase(); 131 } 132 else { 133 return procedureName; 134 } 135 } 136 137 @Override 138 public String catalogNameToUse(String catalogName) { 139 if (catalogName == null) { 140 return null; 141 } 142 else if (isStoresUpperCaseIdentifiers()) { 143 return catalogName.toUpperCase(); 144 } 145 else if (isStoresLowerCaseIdentifiers()) { 146 return catalogName.toLowerCase(); 147 } 148 else { 149 return catalogName; 150 } 151 } 152 153 @Override 154 public String schemaNameToUse(String schemaName) { 155 if (schemaName == null) { 156 return null; 157 } 158 else if (isStoresUpperCaseIdentifiers()) { 159 return schemaName.toUpperCase(); 160 } 161 else if (isStoresLowerCaseIdentifiers()) { 162 return schemaName.toLowerCase(); 163 } 164 else { 165 return schemaName; 166 } 167 } 168 169 @Override 170 public String metaDataCatalogNameToUse(String catalogName) { 171 if (isSupportsCatalogsInProcedureCalls()) { 172 return catalogNameToUse(catalogName); 173 } 174 else { 175 return null; 176 } 177 } 178 179 @Override 180 public String metaDataSchemaNameToUse(String schemaName) { 181 if (isSupportsSchemasInProcedureCalls()) { 182 return schemaNameToUse(schemaName); 183 } 184 else { 185 return null; 186 } 187 } 188 189 @Override 190 public String parameterNameToUse(String parameterName) { 191 if (parameterName == null) { 192 return null; 193 } 194 else if (isStoresUpperCaseIdentifiers()) { 195 return parameterName.toUpperCase(); 196 } 197 else if (isStoresLowerCaseIdentifiers()) { 198 return parameterName.toLowerCase(); 199 } 200 else { 201 return parameterName; 202 } 203 } 204 205 @Override 206 public boolean byPassReturnParameter(String parameterName) { 207 return false; 208 } 209 210 @Override 211 public SqlParameter createDefaultOutParameter(String parameterName, CallParameterMetaData meta) { 212 return new SqlOutParameter(parameterName, meta.getSqlType()); 213 } 214 215 @Override 216 public SqlParameter createDefaultInOutParameter(String parameterName, CallParameterMetaData meta) { 217 return new SqlInOutParameter(parameterName, meta.getSqlType()); 218 } 219 220 @Override 221 public SqlParameter createDefaultInParameter(String parameterName, CallParameterMetaData meta) { 222 return new SqlParameter(parameterName, meta.getSqlType()); 223 } 224 225 @Override 226 public String getUserName() { 227 return this.userName; 228 } 229 230 @Override 231 public boolean isReturnResultSetSupported() { 232 return true; 233 } 234 235 @Override 236 public boolean isRefCursorSupported() { 237 return false; 238 } 239 240 @Override 241 public int getRefCursorSqlType() { 242 return Types.OTHER; 243 } 244 245 @Override 246 public boolean isProcedureColumnMetaDataUsed() { 247 return this.procedureColumnMetaDataUsed; 248 } 249 250 251 /** 252 * Specify whether the database supports the use of catalog name in procedure calls. 253 */ 254 protected void setSupportsCatalogsInProcedureCalls(boolean supportsCatalogsInProcedureCalls) { 255 this.supportsCatalogsInProcedureCalls = supportsCatalogsInProcedureCalls; 256 } 257 258 /** 259 * Does the database support the use of catalog name in procedure calls? 260 */ 261 @Override 262 public boolean isSupportsCatalogsInProcedureCalls() { 263 return this.supportsCatalogsInProcedureCalls; 264 } 265 266 /** 267 * Specify whether the database supports the use of schema name in procedure calls. 268 */ 269 protected void setSupportsSchemasInProcedureCalls(boolean supportsSchemasInProcedureCalls) { 270 this.supportsSchemasInProcedureCalls = supportsSchemasInProcedureCalls; 271 } 272 273 /** 274 * Does the database support the use of schema name in procedure calls? 275 */ 276 @Override 277 public boolean isSupportsSchemasInProcedureCalls() { 278 return this.supportsSchemasInProcedureCalls; 279 } 280 281 /** 282 * Specify whether the database uses upper case for identifiers. 283 */ 284 protected void setStoresUpperCaseIdentifiers(boolean storesUpperCaseIdentifiers) { 285 this.storesUpperCaseIdentifiers = storesUpperCaseIdentifiers; 286 } 287 288 /** 289 * Does the database use upper case for identifiers? 290 */ 291 protected boolean isStoresUpperCaseIdentifiers() { 292 return this.storesUpperCaseIdentifiers; 293 } 294 295 /** 296 * Specify whether the database uses lower case for identifiers. 297 */ 298 protected void setStoresLowerCaseIdentifiers(boolean storesLowerCaseIdentifiers) { 299 this.storesLowerCaseIdentifiers = storesLowerCaseIdentifiers; 300 } 301 302 /** 303 * Does the database use lower case for identifiers? 304 */ 305 protected boolean isStoresLowerCaseIdentifiers() { 306 return this.storesLowerCaseIdentifiers; 307 } 308 309 310 /** 311 * Process the procedure column meta-data. 312 */ 313 private void processProcedureColumns( 314 DatabaseMetaData databaseMetaData, String catalogName, String schemaName, String procedureName) { 315 316 String metaDataCatalogName = metaDataCatalogNameToUse(catalogName); 317 String metaDataSchemaName = metaDataSchemaNameToUse(schemaName); 318 String metaDataProcedureName = procedureNameToUse(procedureName); 319 if (logger.isDebugEnabled()) { 320 logger.debug("Retrieving meta-data for " + metaDataCatalogName + '/' + 321 metaDataSchemaName + '/' + metaDataProcedureName); 322 } 323 324 ResultSet procs = null; 325 try { 326 procs = databaseMetaData.getProcedures(metaDataCatalogName, metaDataSchemaName, metaDataProcedureName); 327 List<String> found = new ArrayList<String>(); 328 while (procs.next()) { 329 found.add(procs.getString("PROCEDURE_CAT") + '.' + procs.getString("PROCEDURE_SCHEM") + 330 '.' + procs.getString("PROCEDURE_NAME")); 331 } 332 procs.close(); 333 334 if (found.size() > 1) { 335 throw new InvalidDataAccessApiUsageException( 336 "Unable to determine the correct call signature - multiple " + 337 "procedures/functions/signatures for '" + metaDataProcedureName + "': found " + found); 338 } 339 else if (found.isEmpty()) { 340 if (metaDataProcedureName.contains(".") && !StringUtils.hasText(metaDataCatalogName)) { 341 String packageName = metaDataProcedureName.substring(0, metaDataProcedureName.indexOf('.')); 342 throw new InvalidDataAccessApiUsageException( 343 "Unable to determine the correct call signature for '" + metaDataProcedureName + 344 "' - package name should be specified separately using '.withCatalogName(\"" + 345 packageName + "\")'"); 346 } 347 else if ("Oracle".equals(databaseMetaData.getDatabaseProductName())) { 348 if (logger.isDebugEnabled()) { 349 logger.debug("Oracle JDBC driver did not return procedure/function/signature for '" + 350 metaDataProcedureName + "' - assuming a non-exposed synonym"); 351 } 352 } 353 else { 354 throw new InvalidDataAccessApiUsageException( 355 "Unable to determine the correct call signature - no " + 356 "procedure/function/signature for '" + metaDataProcedureName + "'"); 357 } 358 } 359 360 procs = databaseMetaData.getProcedureColumns( 361 metaDataCatalogName, metaDataSchemaName, metaDataProcedureName, null); 362 while (procs.next()) { 363 String columnName = procs.getString("COLUMN_NAME"); 364 int columnType = procs.getInt("COLUMN_TYPE"); 365 if (columnName == null && ( 366 columnType == DatabaseMetaData.procedureColumnIn || 367 columnType == DatabaseMetaData.procedureColumnInOut || 368 columnType == DatabaseMetaData.procedureColumnOut)) { 369 if (logger.isDebugEnabled()) { 370 logger.debug("Skipping meta-data for: " + columnType + " " + procs.getInt("DATA_TYPE") + 371 " " + procs.getString("TYPE_NAME") + " " + procs.getInt("NULLABLE") + 372 " (probably a member of a collection)"); 373 } 374 } 375 else { 376 CallParameterMetaData meta = new CallParameterMetaData(columnName, columnType, 377 procs.getInt("DATA_TYPE"), procs.getString("TYPE_NAME"), 378 procs.getInt("NULLABLE") == DatabaseMetaData.procedureNullable); 379 this.callParameterMetaData.add(meta); 380 if (logger.isDebugEnabled()) { 381 logger.debug("Retrieved meta-data: " + meta.getParameterName() + " " + 382 meta.getParameterType() + " " + meta.getSqlType() + " " + 383 meta.getTypeName() + " " + meta.isNullable()); 384 } 385 } 386 } 387 } 388 catch (SQLException ex) { 389 if (logger.isWarnEnabled()) { 390 logger.warn("Error while retrieving meta-data for procedure columns: " + ex); 391 } 392 } 393 finally { 394 try { 395 if (procs != null) { 396 procs.close(); 397 } 398 } 399 catch (SQLException ex) { 400 if (logger.isWarnEnabled()) { 401 logger.warn("Problem closing ResultSet for procedure column meta-data: " + ex); 402 } 403 } 404 } 405 } 406 407}