/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * 09/14/2011-2.3.1 Guy Pelletier * - 357533: Allow DDL queries to execute even when Multitenant entities are part of the PU ******************************************************************************/ package org.eclipse.persistence.platform.database.oracle; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Constructor; import java.security.AccessController; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.util.Calendar; import java.util.Date; import java.util.Hashtable; import java.util.Map; import java.util.TimeZone; import java.util.Vector; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.eclipse.persistence.exceptions.ConversionException; import org.eclipse.persistence.exceptions.DatabaseException; import org.eclipse.persistence.exceptions.QueryException; import org.eclipse.persistence.expressions.ExpressionOperator; import org.eclipse.persistence.internal.databaseaccess.Accessor; import org.eclipse.persistence.internal.databaseaccess.BindCallCustomParameter; import org.eclipse.persistence.internal.databaseaccess.ConnectionCustomizer; import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.expressions.SpatialExpressionOperators; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.platform.database.XMLTypePlaceholder; import org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPHelper; import org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPLTZWrapper; import org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPTZWrapper; import org.eclipse.persistence.internal.platform.database.oracle.TIMESTAMPTypes; import org.eclipse.persistence.internal.platform.database.oracle.XMLTypeFactory; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedClassForName; import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor; import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.queries.Call; import org.eclipse.persistence.queries.ValueReadQuery; import oracle.jdbc.OracleConnection; import oracle.jdbc.OracleOpaque; import oracle.jdbc.OraclePreparedStatement; import oracle.jdbc.OracleTypes; import oracle.sql.TIMESTAMP; import oracle.sql.TIMESTAMPLTZ; import oracle.sql.TIMESTAMPTZ; /** * <p><b>Purpose:</b> * Supports usage of certain Oracle JDBC specific APIs. * <p> Supports binding NCHAR, NVARCHAR, NCLOB types as required by Oracle JDBC drivers. * <p> Supports Oracle JDBC TIMESTAMP, TIMESTAMPTZ, TIMESTAMPLTZ types. */ public class Oracle9Platform extends Oracle8Platform { public static final Class NCHAR = NCharacter.class; public static final Class NSTRING = NString.class; public static final Class NCLOB = NClob.class; public static final Class XMLTYPE = XMLTypePlaceholder.class; /* Driver version set to connection.getMetaData.getDriverVersion() */ protected transient String driverVersion; /* Indicates whether printCalendar should be used when creating TIMESTAMPTZ. * Bug5614674. It used to be a driver bug and Helper.printCalendar(cal, false) was used to make it work. * It has been fixed in 11. Separate the newer version from the old ones. * */ protected transient boolean shouldPrintCalendar; /* Indicates whether TIMESTAMPTZ.timestampValue returns Timestamp in GMT. * The flag is set to false unless * Oracle jdbc version is 11.1.0.7 or later and * OracleConnection's "oracle.jdbc.timestampTzInGmt" property is set to "true". * Though the property is defined per connection it is safe to assume that all connections * used with the platform are identical because they all created by the same DatabaseLogin * with the same properties. * */ protected transient boolean isTimestampInGmt; /* Indicates whether TIMESTAMPLTZ.toTimestamp returns Timestamp in GMT. * true for version 11.2.0.2 or later. */ protected transient boolean isLtzTimestampInGmt; /* Indicates whether driverVersion, shouldPrintCalendar, isTimestampInGmt have been initialized. * To re-initialize connection data call clearConnectionData method. */ protected transient boolean isConnectionDataInitialized; /** Indicates whether time component of java.sql.Date should be truncated (hours, minutes, seconds all set to zero) * before been passed as a parameter to PreparedStatement. * Starting with version 12.1 oracle jdbc Statement.setDate no longer zeroes sql.Date's entire time component (only milliseconds). * Set this flag to true to make the platform to truncate days/hours/minutes before passing the date to Statement.setDate method. */ protected boolean shouldTruncateDate; private XMLTypeFactory xmlTypeFactory; /** User name retrieved from JDBC connection in {@link #initializeConnectionData(Connection)}. */ private transient String connectionUserName; /** * Please ensure that following declarations stay as it is. Having them ensures that oracle jdbc driver available * in classpath when this class loaded. * If driver is not available, this class will not be initialized and will fail fast instead of failing later on * when classes from driver are utilized */ private static final Class ORACLE_SQL_TIMESTAMP = oracle.sql.TIMESTAMP.class; private static final Class ORACLE_SQL_TIMESTAMPTZ = oracle.sql.TIMESTAMPTZ.class; private static final Class ORACLE_SQL_TIMESTAMPLTZ = oracle.sql.TIMESTAMPLTZ.class; public Oracle9Platform(){ super(); connectionUserName = null; } /** * INTERNAL: * This class used for binding of NCHAR, NSTRING, NCLOB types. */ protected static class NTypeBindCallCustomParameter extends BindCallCustomParameter { public NTypeBindCallCustomParameter(Object obj) { super(obj); } //Bug5200836, use unwrapped connection if it is NType parameter. @Override public boolean shouldUseUnwrappedConnection() { return true; } /** * INTERNAL: * Binds the custom parameter (obj) into the passed PreparedStatement * for the passed DatabaseCall. * Note that parameter numeration for PreparedStatement starts with 1, * therefore statement.set...(index + 1, ...) should be used. * DatabaseCall will carry this object as its parameter: call.getParameters().elementAt(index). * The reason for passing DatabaseCall and DatabasePlatform into this method * is that this method may set obj as a new value of index parameter: * call.getParameters().setElementAt(obj, index); * and call again the method which has called it: * platform.setParameterValueInDatabaseCall(call, statement, index); * so obj will be bound. * * Called only by DatabasePlatform.setParameterValueInDatabaseCall method */ @Override public void set(DatabasePlatform platform, PreparedStatement statement, int index, AbstractSession session) throws SQLException { // Binding starts with a 1 not 0. Make sure that index > 0 ((oracle.jdbc.OraclePreparedStatement)statement).setFormOfUse(index, oracle.jdbc.OraclePreparedStatement.FORM_NCHAR); super.set(platform, statement, index, session); } } /** * Copy the state into the new platform. */ @Override public void copyInto(Platform platform) { super.copyInto(platform); if (!(platform instanceof Oracle9Platform)) { return; } Oracle9Platform oracle9Platform = (Oracle9Platform)platform; oracle9Platform.setShouldTruncateDate(shouldTruncateDate()); } /** * INTERNAL: * Get a timestamp value from a result set. * Overrides the default behavior to specifically return a timestamp. Added * to overcome an issue with the oracle 9.0.1.4 JDBC driver. */ @Override public Object getObjectFromResultSet(ResultSet resultSet, int columnNumber, int type, AbstractSession session) throws java.sql.SQLException { //Bug#3381652 10G Drivers return sql.Date instead of timestamp on DATE field if ((type == Types.TIMESTAMP) || (type == Types.DATE)) { return resultSet.getTimestamp(columnNumber); } else if (type == oracle.jdbc.OracleTypes.TIMESTAMPTZ) { return getTIMESTAMPTZFromResultSet(resultSet, columnNumber, type, session); } else if (type == oracle.jdbc.OracleTypes.TIMESTAMPLTZ) { return getTIMESTAMPLTZFromResultSet(resultSet, columnNumber, type, session); } else if (type == OracleTypes.ROWID) { return resultSet.getString(columnNumber); } else if (type == Types.SQLXML) { SQLXML sqlXml = resultSet.getSQLXML(columnNumber); String str = sqlXml.getString(); sqlXml.free(); // Oracle 12c appends a \n character to the xml string return str.endsWith("\n") ? str.substring(0, str.length() - 1) : str; } else if (type == OracleTypes.OPAQUE) { try { Object result = resultSet.getObject(columnNumber); if (!(result instanceof OracleOpaque)) { // Report Queries can cause result to not be an instance of OPAQUE. return result; } else { return getXMLTypeFactory().getString((OracleOpaque) result); } } catch (SQLException ex) { throw DatabaseException.sqlException(ex, null, session, false); } } else { return super.getObjectFromResultSet(resultSet, columnNumber, type, session); } } /** * INTERNAL: * Get a TIMESTAMPTZ value from a result set. */ public Object getTIMESTAMPTZFromResultSet(ResultSet resultSet, int columnNumber, int type, AbstractSession session) throws java.sql.SQLException { TIMESTAMPTZ tsTZ = (TIMESTAMPTZ)resultSet.getObject(columnNumber); //Need to call timestampValue once here with the connection to avoid null point //exception later when timestampValue is called in converObject() if ((tsTZ != null) && (tsTZ.getLength() != 0)) { Connection connection = getConnection(session, resultSet.getStatement().getConnection()); //Bug#4364359 Add a wrapper to overcome TIMESTAMPTZ not serializable as of jdbc 9.2.0.5 and 10.1.0.2. //It has been fixed in the next version for both streams Timestamp timestampToWrap = tsTZ.timestampValue(connection); TimeZone timezoneToWrap = TIMESTAMPHelper.extractTimeZone(tsTZ.toBytes()); return new TIMESTAMPTZWrapper(timestampToWrap, timezoneToWrap, this.isTimestampInGmt); } return null; } /** * INTERNAL: * Get a TIMESTAMPLTZ value from a result set. */ public Object getTIMESTAMPLTZFromResultSet(ResultSet resultSet, int columnNumber, int type, AbstractSession session) throws java.sql.SQLException { //TIMESTAMPLTZ needs to be converted to Timestamp here because it requires the connection. //However the java object is not know here. The solution is to store Timestamp and the //session timezone in a wrapper class, which will be used later in converObject(). TIMESTAMPLTZ tsLTZ = (TIMESTAMPLTZ)resultSet.getObject(columnNumber); if ((tsLTZ != null) && (tsLTZ.getLength() != 0)) { Connection connection = getConnection(session, resultSet.getStatement().getConnection()); Timestamp timestampToWrap = TIMESTAMPLTZ.toTimestamp(connection, tsLTZ.toBytes()); String sessionTimeZone = ((OracleConnection)connection).getSessionTimeZone(); //Bug#4364359 Add a separate wrapper for TIMESTAMPLTZ. return new TIMESTAMPLTZWrapper(timestampToWrap, sessionTimeZone, this.isLtzTimestampInGmt); } return null; } /** * INTERNAL * Used by SQLCall.appendModify(..) * If the field should be passed to customModifyInDatabaseCall, retun true, * otherwise false. * Methods shouldCustomModifyInDatabaseCall and customModifyInDatabaseCall should be * kept in sync: shouldCustomModifyInDatabaseCall should return true if and only if the field * is handled by customModifyInDatabaseCall. */ @Override public boolean shouldUseCustomModifyForCall(DatabaseField field) { Class type = field.getType(); if ((type != null) && isOracle9Specific(type)) { return true; } return super.shouldUseCustomModifyForCall(field); } /** * INTERNAL: * Allow the use of XMLType operators on this platform. */ @Override protected void initializePlatformOperators() { super.initializePlatformOperators(); addOperator(ExpressionOperator.extractXml()); addOperator(ExpressionOperator.extractValue()); addOperator(ExpressionOperator.existsNode()); addOperator(ExpressionOperator.isFragment()); addOperator(ExpressionOperator.getStringVal()); addOperator(ExpressionOperator.getNumberVal()); addOperator(SpatialExpressionOperators.withinDistance()); addOperator(SpatialExpressionOperators.relate()); addOperator(SpatialExpressionOperators.filter()); addOperator(SpatialExpressionOperators.nearestNeighbor()); } /** * INTERNAL: * Add XMLType as the default database type for org.w3c.dom.Documents. * Add TIMESTAMP, TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE */ @Override protected Hashtable buildFieldTypes() { Hashtable fieldTypes = super.buildFieldTypes(); fieldTypes.put(org.w3c.dom.Document.class, new FieldTypeDefinition("sys.XMLType")); //Bug#3381652 10g database does not accept Time for DATE field fieldTypes.put(java.sql.Time.class, new FieldTypeDefinition("TIMESTAMP", false)); fieldTypes.put(java.sql.Timestamp.class, new FieldTypeDefinition("TIMESTAMP", false)); fieldTypes.put(ORACLE_SQL_TIMESTAMP, new FieldTypeDefinition("TIMESTAMP", false)); fieldTypes.put(ORACLE_SQL_TIMESTAMPTZ, new FieldTypeDefinition("TIMESTAMP WITH TIME ZONE", false)); fieldTypes.put(ORACLE_SQL_TIMESTAMPLTZ, new FieldTypeDefinition("TIMESTAMP WITH LOCAL TIME ZONE", false)); return fieldTypes; } /** * Build the hint string used for first rows. * * Allows it to be overridden * @param max * @return */ @Override protected String buildFirstRowsHint(int max){ return HINT_START + '(' + max + ')'+ HINT_END; } /** * INTERNAL: * Add TIMESTAMP, TIMESTAMP WITH TIME ZONE and TIMESTAMP WITH LOCAL TIME ZONE */ @Override protected Map<String, Class> buildClassTypes() { Map<String, Class> classTypeMapping = super.buildClassTypes(); classTypeMapping.put("TIMESTAMP", ORACLE_SQL_TIMESTAMP); classTypeMapping.put("TIMESTAMP WITH TIME ZONE", ORACLE_SQL_TIMESTAMPTZ); classTypeMapping.put("TIMESTAMP WITH LOCAL TIME ZONE", ORACLE_SQL_TIMESTAMPLTZ); return classTypeMapping; } @Override public Object clone() { Oracle9Platform clone = (Oracle9Platform)super.clone(); clone.clearConnectionData(); return clone; } /** * INTERNAL: * Allow for conversion from the Oracle type to the Java type. */ @Override public Object convertObject(Object sourceObject, Class javaClass) throws ConversionException, DatabaseException { if ((javaClass == null) || ((sourceObject != null) && (sourceObject.getClass() == javaClass))) { return sourceObject; } if (sourceObject == null) { return super.convertObject(sourceObject, javaClass); } Object valueToConvert = sourceObject; //Used in Type Conversion Mapping on write if ((javaClass == TIMESTAMPTypes.TIMESTAMP_CLASS) || (javaClass == TIMESTAMPTypes.TIMESTAMPLTZ_CLASS)) { return sourceObject; } if (javaClass == TIMESTAMPTypes.TIMESTAMPTZ_CLASS) { if (sourceObject instanceof java.util.Date) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(((java.util.Date)sourceObject).getTime()); return cal; } else { return sourceObject; } } if (javaClass == XMLTYPE) { //Don't convert to XMLTypes. This will be done by the //XMLTypeBindCallCustomParameter to ensure the correct //Connection is used return sourceObject; } //Added to overcome an issue with the oracle 9.0.1.1.0 JDBC driver. if (sourceObject instanceof TIMESTAMP) { try { valueToConvert = ((TIMESTAMP)sourceObject).timestampValue(); } catch (SQLException exception) { throw DatabaseException.sqlException(exception); } } else if (sourceObject instanceof TIMESTAMPTZWrapper) { //Bug#4364359 Used when database type is TIMESTAMPTZ. Timestamp and session timezone are wrapped //in TIMESTAMPTZWrapper. Separate Calendar from any other types. if (((javaClass == ClassConstants.CALENDAR) || (javaClass == ClassConstants.GREGORIAN_CALENDAR))) { try { return TIMESTAMPHelper.buildCalendar((TIMESTAMPTZWrapper)sourceObject); } catch (SQLException exception) { throw DatabaseException.sqlException(exception); } } else { //If not using native sql, Calendar will be converted to Timestamp just as //other date time types valueToConvert = ((TIMESTAMPTZWrapper)sourceObject).getTimestamp(); } } else if (sourceObject instanceof TIMESTAMPLTZWrapper) { //Bug#4364359 Used when database type is TIMESTAMPLTZ. Timestamp and session timezone id are wrapped //in TIMESTAMPLTZWrapper. Separate Calendar from any other types. if (((javaClass == ClassConstants.CALENDAR) || (javaClass == ClassConstants.GREGORIAN_CALENDAR))) { try { return TIMESTAMPHelper.buildCalendar((TIMESTAMPLTZWrapper)sourceObject); } catch (SQLException exception) { throw DatabaseException.sqlException(exception); } } else { //If not using native sql, Calendar will be converted to Timestamp just as //other date time types valueToConvert = ((TIMESTAMPLTZWrapper)sourceObject).getTimestamp(); } } return super.convertObject(valueToConvert, javaClass); } /** * INTERNAL: * Appends an Oracle specific Timestamp, if usesNativeSQL is true otherwise use the ODBC format. * Native Format: to_timestamp ('1997-11-06 10:35:45.656' , 'yyyy-mm-dd hh:mm:ss.ff') */ @Override protected void appendTimestamp(java.sql.Timestamp timestamp, Writer writer) throws IOException { if (usesNativeSQL()) { writer.write("to_timestamp('"); writer.write(Helper.printTimestamp(timestamp)); writer.write("','yyyy-mm-dd HH24:MI:SS.FF')"); } else { super.appendTimestamp(timestamp, writer); } } /** * INTERNAL: * Appends an Oracle specific Timestamp with timezone and daylight time * elements if usesNativeSQL is true, otherwise use the ODBC format. * Native Format: * (DST) to_timestamp_tz ('1997-11-06 10:35:45.345 America/Los_Angeles','yyyy-mm-dd hh:mm:ss.ff TZR TZD') * (non-DST) to_timestamp_tz ('1997-11-06 10:35:45.345 America/Los_Angeles','yyyy-mm-dd hh:mm:ss.ff TZR') */ @Override protected void appendCalendar(Calendar calendar, Writer writer) throws IOException { if (usesNativeSQL()) { writer.write("to_timestamp_tz('"); writer.write(TIMESTAMPHelper.printCalendar(calendar)); // append TZD element if the calendar's timezone is in daylight time if (TIMESTAMPHelper.shouldAppendDaylightTime(calendar)) { writer.write("','yyyy-mm-dd HH24:MI:SS.FF TZR TZD')"); } else { writer.write("','yyyy-mm-dd HH24:MI:SS.FF TZR')"); } } else { super.appendCalendar(calendar, writer); } } /** * INTERNAL: */ @Override public void initializeConnectionData(Connection connection) throws SQLException { if (this.isConnectionDataInitialized || (connection == null) || (connection.getMetaData() == null)) { return; } this.driverVersion = connection.getMetaData().getDriverVersion(); // printCalendar for versions greater or equal 9 and less than 10.2.0.4 this.shouldPrintCalendar = Helper.compareVersions("9", this.driverVersion) <= 0 && Helper.compareVersions(this.driverVersion, "10.2.0.4") < 0; if (Helper.compareVersions(this.driverVersion, "11.1.0.7") >= 0) { if( connection instanceof OracleConnection ) { final OracleConnection oraConn = (OracleConnection)connection; String timestampTzInGmtPropStr = oraConn.getProperties().getProperty("oracle.jdbc.timestampTzInGmt", "true"); this.connectionUserName = oraConn.getUserName(); this.isTimestampInGmt = timestampTzInGmtPropStr.equalsIgnoreCase("true"); } else { this.connectionUserName = connection.getMetaData().getUserName(); this.isTimestampInGmt = true; } if (Helper.compareVersions(this.driverVersion, "11.2.0.2") >= 0) { this.isLtzTimestampInGmt = true; } } this.isConnectionDataInitialized = true; } public void clearConnectionData() { this.driverVersion = null; this.isConnectionDataInitialized = false; } /** * INTERNAL: * Clears both implicit and explicit caches of OracleConnection */ @Override public void clearOracleConnectionCache(Connection conn) { if(conn instanceof OracleConnection){ OracleConnection oracleConnection = (OracleConnection)conn; try { if(oracleConnection.getImplicitCachingEnabled()) { oracleConnection.purgeImplicitCache(); } } catch(SQLException ex) { // ignore } try { if(oracleConnection.getExplicitCachingEnabled()) { oracleConnection.purgeExplicitCache(); } } catch(SQLException ex) { // ignore } } } /** * INTERNAL: * Note that index (not index+1) is used in statement.setObject(index, parameter) * Binding starts with a 1 not 0, so make sure that index > 0. * Treat Calendar separately. Bind Calendar as TIMESTAMPTZ. */ @Override public void setParameterValueInDatabaseCall(Object parameter, PreparedStatement statement, int index, AbstractSession session) throws SQLException { if (parameter instanceof Calendar) { Calendar calendar = (Calendar)parameter; Connection conn = getConnection(session, statement.getConnection()); TIMESTAMPTZ tsTZ = TIMESTAMPHelper.buildTIMESTAMPTZ(calendar, conn, this.shouldPrintCalendar); statement.setObject(index, tsTZ); } else if (this.shouldTruncateDate && parameter instanceof java.sql.Date) { // hours, minutes, seconds all set to zero statement.setDate(index, Helper.truncateDateIgnoreMilliseconds((java.sql.Date)parameter)); } else { super.setParameterValueInDatabaseCall(parameter, statement, index, session); } } /** * INTERNAL: * Answer the timestamp from the server. Convert TIMESTAMPTZ to Timestamp */ @Override public java.sql.Timestamp getTimestampFromServer(AbstractSession session, String sessionName) { if (getTimestampQuery() != null) { getTimestampQuery().setSessionName(sessionName); Object ob = session.executeQuery(getTimestampQuery()); return ((TIMESTAMPTZWrapper)ob).getTimestamp(); } return super.getTimestampFromServer(session, sessionName); } /** * INTERNAL: * This method returns the query to select the SYSTIMESTAMP as TIMESTAMPTZ * from the server for Oracle9i. */ @Override public ValueReadQuery getTimestampQuery() { if (timestampQuery == null) { timestampQuery = new ValueReadQuery(); timestampQuery.setSQLString("SELECT SYSTIMESTAMP FROM DUAL"); timestampQuery.setAllowNativeSQLQuery(true); } return timestampQuery; } /** * INTERNAL: * Return the current SYSTIMESTAMP as TIMESTAMPTZ from the server. */ @Override public String serverTimestampString() { return "SYSTIMESTAMP"; } protected Vector buildToTIMESTAMPVec() { Vector vec = new Vector(); vec.addElement(java.util.Date.class); vec.addElement(Timestamp.class); vec.addElement(Calendar.class); vec.addElement(String.class); vec.addElement(Long.class); vec.addElement(Date.class); vec.addElement(Time.class); return vec; } protected Vector buildToNStringCharVec() { Vector vec = new Vector(); vec.addElement(String.class); vec.addElement(Character.class); return vec; } protected Vector buildToNClobVec() { Vector vec = new Vector(); vec.addElement(String.class); vec.addElement(Character[].class); vec.addElement(char[].class); return vec; } /** * PUBLIC: * Return the BLOB/CLOB value limits on thin driver. The default value is 0. * If usesLocatorForLOBWrite is true, locator will be used in case the * lob's size is larger than lobValueLimit. */ @Override public int getLobValueLimits() { return lobValueLimits; } /** * PUBLIC: * Set the BLOB/CLOB value limits on thin driver. The default value is 0. * If usesLocatorForLOBWrite is true, locator will be used in case the * lob's size is larger than lobValueLimit. */ @Override public void setLobValueLimits(int lobValueLimits) { this.lobValueLimits = lobValueLimits; } /** * INTERNAL: * Return if the type is a special oracle type. * bug 3325122 - just checking against the 4 classes is faster than isAssignableFrom MWN. */ protected boolean isOracle9Specific(Class type) { return (type == NCHAR) || (type == NSTRING) || (type == NCLOB) || (type == XMLTYPE); } /** * INTERNAL: * Used in write LOB method only to identify a CLOB. */ @Override protected boolean isClob(Class type) { return NCLOB.equals(type) || super.isClob(type); } /** * INTERNAL: * Used by SQLCall.translate(..) * The binding *must* be performed (NCHAR, NSTRING, NCLOB). * In these special cases the method returns a wrapper object * which knows whether it should be bound or appended and knows how to do that. */ @Override public Object getCustomModifyValueForCall(Call call, Object value, DatabaseField field, boolean shouldBind) { Class type = field.getType(); if ((type != null) && isOracle9Specific(type)) { if(value == null) { return null; } if (NCHAR.equals(type) || NSTRING.equals(type)) { return new NTypeBindCallCustomParameter(value); } else if (NCLOB.equals(type)) { value = convertToDatabaseType(value); if (shouldUseLocatorForLOBWrite()) { if (lobValueExceedsLimit(value)) { ((DatabaseCall)call).addContext(field, value); value = new String(" "); } } return new NTypeBindCallCustomParameter(value); } else if (XMLTYPE.equals(type)) { return getXMLTypeFactory().createXMLTypeBindCallCustomParameter(value); } } return super.getCustomModifyValueForCall(call, value, field, shouldBind); } protected Vector buildFromStringCharVec(Class javaClass) { Vector vec = getConversionManager().getDataTypesConvertedFrom(javaClass); vec.addElement(NCHAR); vec.addElement(NSTRING); if (javaClass == String.class) { vec.addElement(NCLOB); } return vec; } /** * INTERNAL: * Return the list of Classes that can be converted to from the passed in javaClass. * oracle.sql.TIMESTAMP and NCHAR types are added in some lists. * @param javaClass - the class that is converted from * @return - a vector of classes */ @Override public Vector getDataTypesConvertedFrom(Class javaClass) { if (dataTypesConvertedFromAClass == null) { dataTypesConvertedFromAClass = new Hashtable(5); } Vector dataTypes = (Vector) dataTypesConvertedFromAClass.get(javaClass); if (dataTypes != null) { return dataTypes; } dataTypes = super.getDataTypesConvertedFrom(javaClass); if ((javaClass == String.class) || (javaClass == Character.class)) { dataTypes.addElement(NCHAR); dataTypes.addElement(NSTRING); if (javaClass == String.class) { dataTypes.addElement(NCLOB); } } if ((javaClass == char[].class) || (javaClass == Character[].class)) { dataTypes.addElement(NCLOB); } dataTypesConvertedFromAClass.put(javaClass, dataTypes); return dataTypes; } /** * INTERNAL: * Return the list of Classes that can be converted from to the passed in javaClass. * A list is added for oracle.sql.TIMESTAMP and NCHAR types. * @param javaClass - the class that is converted to * @return - a vector of classes */ @Override public Vector getDataTypesConvertedTo(Class javaClass) { if (dataTypesConvertedToAClass == null) { dataTypesConvertedToAClass = new Hashtable(5); } Vector dataTypes = (Vector) dataTypesConvertedToAClass.get(javaClass); if (dataTypes != null) { return dataTypes; } if ((javaClass == NCHAR) || (javaClass == NSTRING)) { dataTypes = buildToNStringCharVec(); } else if (javaClass == NCLOB) { dataTypes = buildToNClobVec(); } else { dataTypes = super.getDataTypesConvertedTo(javaClass); } dataTypesConvertedToAClass.put(javaClass, dataTypes); return dataTypes; } /** * Return the JDBC type for the given database field to be passed to Statement.setNull * The Oracle driver does not like the OPAQUE type so VARCHAR must be used. */ @Override public int getJDBCTypeForSetNull(DatabaseField field) { int type = getJDBCType(field); if (type == OracleTypes.OPAQUE || type == Types_SQLXML) { // VARCHAR seems to work, driver does not like OPAQUE. return java.sql.Types.VARCHAR; } return type; } /** * Return the JDBC type for the Java type. * The Oracle driver does not like the OPAQUE type so VARCHAR must be used. */ @Override public int getJDBCType(Class javaType) { if (javaType == XMLTYPE) { //return OracleTypes.OPAQUE; // VARCHAR seems to work... return java.sql.Types.VARCHAR; } return super.getJDBCType(javaType); } /** * INTERNAL: This gets called on each batch statement execution * Needs to be implemented so that it returns the number of rows successfully modified * by this statement for optimistic locking purposes (if useNativeBatchWriting is enabled, and * the call uses optimistic locking). * * @param isStatementPrepared - flag is set to true if this statement is prepared * @return - number of rows modified/deleted by this statement */ @Override public int executeBatch(Statement statement, boolean isStatementPrepared) throws SQLException { if (usesNativeBatchWriting() && isStatementPrepared) { int rowCount = 0; try { rowCount = ((OraclePreparedStatement)statement).sendBatch(); } finally { ((OraclePreparedStatement) statement).setExecuteBatch(1); } return rowCount; } else { return super.executeBatch(statement, isStatementPrepared); } } /** * INTERNAL: This gets called on each iteration to add parameters to the batch * Needs to be implemented so that it returns the number of rows successfully modified * by this statement for optimistic locking purposes (if useNativeBatchWriting is enabled, and * the call uses optimistic locking). Is used with parameterized SQL * * @return - number of rows modified/deleted by this statement if it was executed (0 if it wasn't) */ @Override public int addBatch(PreparedStatement statement) throws java.sql.SQLException { if (usesNativeBatchWriting()){ return statement.executeUpdate(); }else{ return super.addBatch(statement); } } /** * INTERNAL: Allows setting the batch size on the statement * Is used with parameterized SQL, and should only be passed in prepared statements * * @return - statement to be used for batch writing */ @Override public Statement prepareBatchStatement(Statement statement, int maxBatchWritingSize) throws java.sql.SQLException { if (usesNativeBatchWriting()){ //add max statement setting ((OraclePreparedStatement) statement).setExecuteBatch(maxBatchWritingSize); } return statement; } /** * INTERNAL: * Lazy initialization of xmlTypeFactory allows to avoid loading xdb-dependent * class XMLTypeFactoryImpl unless xdb is used. * @return XMLTypeFactory */ protected XMLTypeFactory getXMLTypeFactory() { if(xmlTypeFactory == null) { String className = "org.eclipse.persistence.internal.platform.database.oracle.xdb.XMLTypeFactoryImpl"; try { if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){ Class xmlTypeFactoryClass = AccessController.doPrivileged(new PrivilegedClassForName(className, true, this.getClass().getClassLoader())); Constructor xmlTypeFactoryConstructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor(xmlTypeFactoryClass, new Class[0], true)); xmlTypeFactory = (XMLTypeFactory)AccessController.doPrivileged(new PrivilegedInvokeConstructor(xmlTypeFactoryConstructor, new Object[0])); }else{ Class xmlTypeFactoryClass = PrivilegedAccessHelper.getClassForName(className, true, this.getClass().getClassLoader()); Constructor xmlTypeFactoryConstructor = PrivilegedAccessHelper.getConstructorFor(xmlTypeFactoryClass, new Class[0], true); xmlTypeFactory = (XMLTypeFactory)PrivilegedAccessHelper.invokeConstructor(xmlTypeFactoryConstructor, new Object[0]); } } catch (Exception e) { throw QueryException.reflectiveCallOnTopLinkClassFailed(className, e); } } return xmlTypeFactory; } /** * INTERNAL: * Indicates whether the passed object is an instance of XDBDocument. * @return boolean */ @Override public boolean isXDBDocument(Object obj) { return getXMLTypeFactory().isXDBDocument(obj); } /** * INTERNAL: * Indicates whether this Oracle platform can unwrap Oracle connection. */ @Override public boolean canUnwrapOracleConnection() { return true; } /** * INTERNAL: * If can unwrap returns unwrapped Oracle connection, otherwise original connection. */ @Override public Connection unwrapOracleConnection(Connection connection) { //Bug#4607977 Use getPhysicalConnection() instead of physicalConnectionWithin() because it's not suppported in 9.2 driver if(connection instanceof oracle.jdbc.internal.OracleConnection){ return ((oracle.jdbc.internal.OracleConnection)connection).getPhysicalConnection(); }else{ return super.unwrapOracleConnection(connection); } } /** * PUBLIC: * Return is this is the Oracle 9 platform. */ @Override public boolean isOracle9() { return true; } /** * INTERNAL: */ @Override public ConnectionCustomizer createConnectionCustomizer(Accessor accessor, AbstractSession session) { Object proxyTypeValue = session.getProperty(PersistenceUnitProperties.ORACLE_PROXY_TYPE); if (proxyTypeValue == null || ((proxyTypeValue instanceof String) && ((String)proxyTypeValue).length() == 0)) { return null; } else { return new OracleJDBC_10_1_0_2ProxyConnectionCustomizer(accessor, session); } } /** * INTERNAL: Return the driver version. */ public String getDriverVersion() { return driverVersion; } /** * INTERNAL: Return if timestamps are returned in GMT by the driver. */ public boolean isTimestampInGmt() { return isTimestampInGmt; } /** * INTERNAL: Return if ltz timestamps are returned in GMT by the driver. */ public boolean isLtzTimestampInGmt() { return isLtzTimestampInGmt; } /** * PUBLIC: * Indicates whether time component of java.sql.Date should be truncated (hours, minutes, seconds all set to zero) * before been passed as a parameter to PreparedStatement. * Starting with version 12.1 oracle jdbc Statement.setDate no longer zeroes sql.Date's entire time component (only milliseconds). * "true" indicates that the platform truncates days/hours/minutes before passing the date to Statement.setDate method. */ public boolean shouldTruncateDate() { return shouldTruncateDate; } /** * PUBLIC: * Indicates whether time component of java.sql.Date should be truncated (hours, minutes, seconds all set to zero) * before been passed as a parameter to PreparedStatement. * Starting with version 12.1 oracle jdbc Statement.setDate no longer zeroes sql.Date's entire time component (only milliseconds). * Set this flag to true to make the platform to truncate days/hours/minutes before passing the date to Statement.setDate method. */ public void setShouldTruncateDate(boolean shouldTruncateDate) { this.shouldTruncateDate = shouldTruncateDate; } /** * INTERNAL: * User name from JDBC connection is stored in {@link #initializeConnectionData(Connection)}. * @return Always returns {@code true} */ @Override public boolean supportsConnectionUserName() { return true; } /** * INTERNAL: * Returns user name retrieved from JDBC connection. {@link #initializeConnectionData(Connection)} shall be called * before this method. * @return User name retrieved from JDBC connection. */ @Override public String getConnectionUserName() { return this.connectionUserName; } }