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.lang.reflect.Method; 020import java.sql.CallableStatement; 021import java.sql.Connection; 022import java.sql.DatabaseMetaData; 023import java.sql.SQLException; 024import java.sql.Types; 025 026import org.springframework.dao.InvalidDataAccessApiUsageException; 027import org.springframework.lang.Nullable; 028import org.springframework.util.ReflectionUtils; 029 030/** 031 * Oracle-specific implementation of the {@link org.springframework.jdbc.core.metadata.TableMetaDataProvider}. 032 * Supports a feature for including synonyms in the meta-data lookup. Also supports lookup of current schema 033 * using the {@code sys_context}. 034 * 035 * <p>Thanks to Mike Youngstrom and Bruce Campbell for submitting the original suggestion for the Oracle 036 * current schema lookup implementation. 037 * 038 * @author Thomas Risberg 039 * @author Juergen Hoeller 040 * @since 3.0 041 */ 042public class OracleTableMetaDataProvider extends GenericTableMetaDataProvider { 043 044 private final boolean includeSynonyms; 045 046 @Nullable 047 private final String defaultSchema; 048 049 050 /** 051 * Constructor used to initialize with provided database meta-data. 052 * @param databaseMetaData meta-data to be used 053 */ 054 public OracleTableMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException { 055 this(databaseMetaData, false); 056 } 057 058 /** 059 * Constructor used to initialize with provided database meta-data. 060 * @param databaseMetaData meta-data to be used 061 * @param includeSynonyms whether to include synonyms 062 */ 063 public OracleTableMetaDataProvider(DatabaseMetaData databaseMetaData, boolean includeSynonyms) 064 throws SQLException { 065 066 super(databaseMetaData); 067 this.includeSynonyms = includeSynonyms; 068 this.defaultSchema = lookupDefaultSchema(databaseMetaData); 069 } 070 071 072 /* 073 * Oracle-based implementation for detecting the current schema. 074 */ 075 @Nullable 076 private static String lookupDefaultSchema(DatabaseMetaData databaseMetaData) { 077 try { 078 CallableStatement cstmt = null; 079 try { 080 Connection con = databaseMetaData.getConnection(); 081 if (con == null) { 082 logger.debug("Cannot check default schema - no Connection from DatabaseMetaData"); 083 return null; 084 } 085 cstmt = con.prepareCall("{? = call sys_context('USERENV', 'CURRENT_SCHEMA')}"); 086 cstmt.registerOutParameter(1, Types.VARCHAR); 087 cstmt.execute(); 088 return cstmt.getString(1); 089 } 090 finally { 091 if (cstmt != null) { 092 cstmt.close(); 093 } 094 } 095 } 096 catch (SQLException ex) { 097 logger.debug("Exception encountered during default schema lookup", ex); 098 return null; 099 } 100 } 101 102 @Override 103 @Nullable 104 protected String getDefaultSchema() { 105 if (this.defaultSchema != null) { 106 return this.defaultSchema; 107 } 108 return super.getDefaultSchema(); 109 } 110 111 112 @Override 113 public void initializeWithTableColumnMetaData(DatabaseMetaData databaseMetaData, 114 @Nullable String catalogName, @Nullable String schemaName, @Nullable String tableName) 115 throws SQLException { 116 117 if (!this.includeSynonyms) { 118 logger.debug("Defaulting to no synonyms in table meta-data lookup"); 119 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 120 return; 121 } 122 123 Connection con = databaseMetaData.getConnection(); 124 if (con == null) { 125 logger.info("Unable to include synonyms in table meta-data lookup - no Connection from DatabaseMetaData"); 126 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 127 return; 128 } 129 130 try { 131 Class<?> oracleConClass = con.getClass().getClassLoader().loadClass("oracle.jdbc.OracleConnection"); 132 con = (Connection) con.unwrap(oracleConClass); 133 } 134 catch (ClassNotFoundException | SQLException ex) { 135 if (logger.isInfoEnabled()) { 136 logger.info("Unable to include synonyms in table meta-data lookup - no Oracle Connection: " + ex); 137 } 138 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 139 return; 140 } 141 142 logger.debug("Including synonyms in table meta-data lookup"); 143 Method setIncludeSynonyms; 144 Boolean originalValueForIncludeSynonyms; 145 146 try { 147 Method getIncludeSynonyms = con.getClass().getMethod("getIncludeSynonyms"); 148 ReflectionUtils.makeAccessible(getIncludeSynonyms); 149 originalValueForIncludeSynonyms = (Boolean) getIncludeSynonyms.invoke(con); 150 151 setIncludeSynonyms = con.getClass().getMethod("setIncludeSynonyms", boolean.class); 152 ReflectionUtils.makeAccessible(setIncludeSynonyms); 153 setIncludeSynonyms.invoke(con, Boolean.TRUE); 154 } 155 catch (Throwable ex) { 156 throw new InvalidDataAccessApiUsageException("Could not prepare Oracle Connection", ex); 157 } 158 159 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 160 161 try { 162 setIncludeSynonyms.invoke(con, originalValueForIncludeSynonyms); 163 } 164 catch (Throwable ex) { 165 throw new InvalidDataAccessApiUsageException("Could not reset Oracle Connection", ex); 166 } 167 } 168 169}