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.jdbc.support.nativejdbc.NativeJdbcExtractor; 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 private String defaultSchema; 047 048 049 /** 050 * Constructor used to initialize with provided database meta-data. 051 * @param databaseMetaData meta-data to be used 052 */ 053 public OracleTableMetaDataProvider(DatabaseMetaData databaseMetaData) throws SQLException { 054 this(databaseMetaData, false); 055 } 056 057 /** 058 * Constructor used to initialize with provided database meta-data. 059 * @param databaseMetaData meta-data to be used 060 * @param includeSynonyms whether to include synonyms 061 */ 062 public OracleTableMetaDataProvider(DatabaseMetaData databaseMetaData, boolean includeSynonyms) 063 throws SQLException { 064 065 super(databaseMetaData); 066 this.includeSynonyms = includeSynonyms; 067 this.defaultSchema = lookupDefaultSchema(databaseMetaData); 068 } 069 070 071 /* 072 * Oracle-based implementation for detecting the current schema. 073 */ 074 private static String lookupDefaultSchema(DatabaseMetaData databaseMetaData) { 075 try { 076 CallableStatement cstmt = null; 077 try { 078 Connection con = databaseMetaData.getConnection(); 079 if (con == null) { 080 logger.debug("Cannot check default schema - no Connection from DatabaseMetaData"); 081 return null; 082 } 083 cstmt = con.prepareCall("{? = call sys_context('USERENV', 'CURRENT_SCHEMA')}"); 084 cstmt.registerOutParameter(1, Types.VARCHAR); 085 cstmt.execute(); 086 return cstmt.getString(1); 087 } 088 finally { 089 if (cstmt != null) { 090 cstmt.close(); 091 } 092 } 093 } 094 catch (SQLException ex) { 095 logger.debug("Exception encountered during default schema lookup", ex); 096 return null; 097 } 098 } 099 100 @Override 101 protected String getDefaultSchema() { 102 if (this.defaultSchema != null) { 103 return defaultSchema; 104 } 105 return super.getDefaultSchema(); 106 } 107 108 109 @Override 110 public void initializeWithTableColumnMetaData(DatabaseMetaData databaseMetaData, 111 String catalogName, String schemaName, String tableName) throws SQLException { 112 113 if (!this.includeSynonyms) { 114 logger.debug("Defaulting to no synonyms in table meta-data lookup"); 115 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 116 return; 117 } 118 119 Connection con = databaseMetaData.getConnection(); 120 if (con == null) { 121 logger.warn("Unable to include synonyms in table meta-data lookup - no Connection from DatabaseMetaData"); 122 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 123 return; 124 } 125 126 NativeJdbcExtractor nativeJdbcExtractor = getNativeJdbcExtractor(); 127 if (nativeJdbcExtractor != null) { 128 con = nativeJdbcExtractor.getNativeConnection(con); 129 } 130 131 boolean isOracleCon = false; 132 try { 133 Class<?> oracleConClass = con.getClass().getClassLoader().loadClass("oracle.jdbc.OracleConnection"); 134 isOracleCon = oracleConClass.isInstance(con); 135 if (!isOracleCon) { 136 con = (Connection) con.unwrap(oracleConClass); 137 isOracleCon = oracleConClass.isInstance(con); 138 } 139 } 140 catch (ClassNotFoundException ex) { 141 if (logger.isInfoEnabled()) { 142 logger.info("Could not find Oracle JDBC API: " + ex); 143 } 144 } 145 catch (SQLException ex) { 146 // No OracleConnection found by unwrap 147 } 148 149 if (!isOracleCon) { 150 if (logger.isWarnEnabled()) { 151 logger.warn("Unable to include synonyms in table meta-data lookup - no Oracle Connection: " + con); 152 } 153 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 154 return; 155 } 156 157 logger.debug("Including synonyms in table meta-data lookup"); 158 Method setIncludeSynonyms; 159 Boolean originalValueForIncludeSynonyms; 160 161 try { 162 Method getIncludeSynonyms = con.getClass().getMethod("getIncludeSynonyms"); 163 ReflectionUtils.makeAccessible(getIncludeSynonyms); 164 originalValueForIncludeSynonyms = (Boolean) getIncludeSynonyms.invoke(con); 165 166 setIncludeSynonyms = con.getClass().getMethod("setIncludeSynonyms", boolean.class); 167 ReflectionUtils.makeAccessible(setIncludeSynonyms); 168 setIncludeSynonyms.invoke(con, Boolean.TRUE); 169 } 170 catch (Throwable ex) { 171 throw new InvalidDataAccessApiUsageException("Could not prepare Oracle Connection", ex); 172 } 173 174 super.initializeWithTableColumnMetaData(databaseMetaData, catalogName, schemaName, tableName); 175 176 try { 177 setIncludeSynonyms.invoke(con, originalValueForIncludeSynonyms); 178 } 179 catch (Throwable ex) { 180 throw new InvalidDataAccessApiUsageException("Could not reset Oracle Connection", ex); 181 } 182 } 183 184}