/******************************************************************************* * 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 * Vikram Bhatia - added method for releasing temporary LOBs after conversion ******************************************************************************/ package org.eclipse.persistence.platform.database.oracle; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; import java.sql.Ref; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Struct; import java.util.Hashtable; import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.databaseaccess.SimpleAppendCallCustomParameter; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.platform.database.OraclePlatform; import org.eclipse.persistence.queries.Call; import oracle.jdbc.OracleBlob; import oracle.jdbc.OracleClob; /** * <p><b>Purpose:</b> * Supports certain new Oracle 8 data types, and usage of certain Oracle JDBC specific APIs. * <p> Supports Oracle thin JDBC driver LOB >4k binding workaround. * <p> Creates BLOB and CLOB type for byte[] and char[] for table creation. * <p> Supports object-relational data-type creation. */ public class Oracle8Platform extends OraclePlatform { /** Locator is required for Oracle thin driver to write LOB value exceeds the limits */ protected boolean usesLocatorForLOBWrite = true; /** The LOB value limits when the Locator is required for the writing */ protected int lobValueLimits = 0; /** * INTERNAL: */ @Override protected Hashtable buildFieldTypes() { Hashtable fieldTypeMapping = super.buildFieldTypes(); fieldTypeMapping.put(Byte[].class, new FieldTypeDefinition("BLOB", false)); fieldTypeMapping.put(Character[].class, new FieldTypeDefinition("CLOB", false)); return fieldTypeMapping; } /** * INTERNAL: * Allow for conversion from the Oralce type to the Java type. */ @Override public void copyInto(Platform platform) { super.copyInto(platform); if (!(platform instanceof Oracle8Platform)) { return; } Oracle8Platform oracle8Platform = (Oracle8Platform)platform; oracle8Platform.setShouldUseLocatorForLOBWrite(shouldUseLocatorForLOBWrite()); oracle8Platform.setLobValueLimits(getLobValueLimits()); } /** * INTERNAL: * Return if the LOB value size is larger than the limit, i.e. 4k. */ protected boolean lobValueExceedsLimit(Object value) { if (value == null) { return false; } int limit = getLobValueLimits(); if (value instanceof byte[]) {//blob return ((byte[])value).length >= limit; } else if (value instanceof String) {//clob return ((String)value).length() >= limit; } else { return false; } } /** * INTERNAL: * This method is used to unwrap the oracle connection wrapped by * the application server. TopLink needs this unwrapped connection for certain * Oracle Specific support. (ie TIMESTAMPTZ, LOB) * This is added as a workaround for bug 4565190 */ @Override public Connection getConnection(AbstractSession session, Connection connection) { if (session.getServerPlatform() != null && (session.getLogin()).shouldUseExternalConnectionPooling()){ // This is added as a workaround for bug 4460996 return session.getServerPlatform().unwrapConnection(connection); } return connection; } /** * INTERNAL * Used by SQLCall.translate(..) * Typically there is no field translation (and this is default implementation). * However on different platforms (Oracle) there are cases such that the values for * binding and appending may be different (BLOB, CLOB). * 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 (ClassConstants.BLOB.equals(type) || ClassConstants.CLOB.equals(type)) { if(value == null) { return null; } value = convertToDatabaseType(value); if (shouldUseLocatorForLOBWrite()) { if (lobValueExceedsLimit(value)) { ((DatabaseCall)call).addContext(field, value); if (ClassConstants.BLOB.equals(type)) { if (shouldBind) { value = new byte[1]; } else { value = new SimpleAppendCallCustomParameter("empty_blob()"); } } else { if (shouldBind) { value = new String(" "); } else { value = new SimpleAppendCallCustomParameter("empty_clob()"); } } } } return value; } return super.getCustomModifyValueForCall(call, value, field, shouldBind); } /** * 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) { if (shouldUseLocatorForLOBWrite()) { Class type = field.getType(); if (ClassConstants.BLOB.equals(type) || ClassConstants.CLOB.equals(type)) { return true; } } return super.shouldUseCustomModifyForCall(field); } /** * INTERNAL: * Write LOB value - works on Oracle 10 and newer */ @Override public void writeLOB(DatabaseField field, Object value, ResultSet resultSet, AbstractSession session) throws SQLException { if (isBlob(field.getType())) { //change for 338585 to use getName instead of getNameDelimited Blob blob = (Blob) resultSet.getObject(field.getName()); blob.setBytes(1, (byte[]) value); //impose the localization session.log(SessionLog.FINEST, SessionLog.SQL, "write_BLOB", Long.valueOf(blob.length()), field.getName()); } else if (isClob(field.getType())) { //change for 338585 to use getName instead of getNameDelimited Clob clob = (Clob) resultSet.getObject(field.getName()); clob.setString(1, (String) value); //impose the localization session.log(SessionLog.FINEST, SessionLog.SQL, "write_CLOB", Long.valueOf(clob.length()), field.getName()); } else { //do nothing for now, open to BFILE or NCLOB types } } /** * INTERNAL: * Used in writeLOB method only to identify a BLOB */ protected boolean isBlob(Class type) { return ClassConstants.BLOB.equals(type); } /** * INTERNAL: * Used in writeLOB method only to identify a CLOB */ protected boolean isClob(Class type) { return ClassConstants.CLOB.equals(type); } /** * INTERNAL: * Indicates whether app. server should unwrap connection * to use lob locator. */ @Override public boolean isNativeConnectionRequiredForLobLocator() { return true; } /** * PUBLIC: * Set if the locator is required for the LOB write. The default is true. * For Oracle thin driver, the locator is recommended for large size * ( >4k for Oracle8, >5.9K for Oracle9) BLOB/CLOB value write. */ public void setShouldUseLocatorForLOBWrite(boolean usesLocatorForLOBWrite) { this.usesLocatorForLOBWrite = usesLocatorForLOBWrite; } /** * PUBLIC: * Return if the locator is required for the LOB write. The default is true. * For Oracle thin driver, the locator is recommended for large size * ( >4k for Oracle8, >5.9K for Oracle9) BLOB/CLOB value write. */ public boolean shouldUseLocatorForLOBWrite() { return usesLocatorForLOBWrite; } /** * 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. */ 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. */ public void setLobValueLimits(int lobValueLimits) { this.lobValueLimits = lobValueLimits; } /** * INTERNAL: * Platforms that support java.sql.Array may override this method. * @return Array */ @Override public Array createArray(String elementDataTypeName, Object[] elements, Connection connection) throws SQLException { return new oracle.sql.ARRAY(new oracle.sql.ArrayDescriptor(elementDataTypeName, connection), connection, elements); } /** * INTERNAL: * Platforms that support java.sql.Struct may override this method. * @return Struct */ @Override public Struct createStruct(String structTypeName, Object[] attributes, Connection connection) throws SQLException { return new oracle.sql.STRUCT(new oracle.sql.StructDescriptor(structTypeName, connection), connection, attributes); } /** * INTERNAL: * Overrides DatabasePlatform method. * @return String */ @Override public Object getRefValue(Ref ref,Connection connection) throws SQLException { ((oracle.sql.REF)ref).setPhysicalConnectionOf(connection); return ((oracle.sql.REF)ref).getValue(); } /** * INTERNAL: * Used by Oracle platforms during reading of ResultSet to free temporary LOBs. */ @Override public void freeTemporaryObject(Object value) throws SQLException { if (value instanceof OracleClob && ((OracleClob) value).isTemporary()) { ((OracleClob) value).free(); } else if (value instanceof OracleBlob && ((OracleBlob) value).isTemporary()) { ((OracleBlob) value).free(); } } }