diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/core/databaseaccess/CorePlatform.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/core/databaseaccess/CorePlatform.java index b8c078298b4..5bac61ea3dc 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/core/databaseaccess/CorePlatform.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/core/databaseaccess/CorePlatform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2023 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 v. 2.0 which is available at @@ -16,6 +16,7 @@ import org.eclipse.persistence.exceptions.ConversionException; import org.eclipse.persistence.internal.core.helper.CoreConversionManager; +import org.eclipse.persistence.internal.sessions.AbstractSession; public interface CorePlatform { @@ -29,6 +30,18 @@ public interface CorePlatform */ T convertObject(Object sourceObject, Class javaClass); + /** + * Convert the object to the appropriate type by invoking the appropriate + * ConversionManager method. + * @param sourceObject the object that must be converted + * @param javaClass the class that the object must be converted to + * @param session current database session + * @exception ConversionException all exceptions will be thrown as this type. + * @return the newly converted object + */ + T convertObject(Object sourceObject, Class javaClass, AbstractSession session) throws ConversionException; + + /** * The platform hold its own instance of conversion manager to allow customization. */ diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java index 7cf21ee81d8..7d7ed7d8cb8 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java @@ -252,6 +252,20 @@ public T convertObject(Object sourceObject, Class javaClass) throws Conve return getConversionManager().convertObject(sourceObject, javaClass); } + /** + * Convert the object to the appropriate type by invoking the appropriate + * ConversionManager method. + * @param sourceObject the object that must be converted + * @param javaClass the class that the object must be converted to + * @param session current database session + * @exception ConversionException all exceptions will be thrown as this type. + * @return the newly converted object + */ + @Override + public T convertObject(Object sourceObject, Class javaClass, AbstractSession session) throws ConversionException { + return convertObject(sourceObject, javaClass); + } + /** * Copy the state into the new platform. */ diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/converters/TypeConversionConverter.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/converters/TypeConversionConverter.java index 7dd05ada9e6..bd8410701bc 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/converters/TypeConversionConverter.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/mappings/converters/TypeConversionConverter.java @@ -22,6 +22,7 @@ import org.eclipse.persistence.internal.descriptors.ClassNameConversionRequired; import org.eclipse.persistence.internal.security.PrivilegedAccessHelper; import org.eclipse.persistence.internal.security.PrivilegedClassForName; +import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.DirectCollectionMapping; import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; @@ -209,7 +210,12 @@ public void setObjectClassName(String objectClassName) { @Override public Object convertObjectValueToDataValue(Object attributeValue, Session session) { try { - return session.getDatasourcePlatform().convertObject(attributeValue, getDataClass()); + if (session.isConnected()) { + //Should handle conversions where DB connection is needed like String -> java.sql.Clob + return session.getDatasourcePlatform().convertObject(attributeValue, getDataClass(), (AbstractSession)session); + } else { + return session.getDatasourcePlatform().convertObject(attributeValue, getDataClass()); + } } catch (ConversionException e) { throw ConversionException.couldNotBeConverted(mapping, mapping.getDescriptor(), e); } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/Oracle23Platform.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/Oracle23Platform.java index c2746fe7799..9aed79aa0e6 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/Oracle23Platform.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/Oracle23Platform.java @@ -14,8 +14,62 @@ // Oracle - initial API and implementation package org.eclipse.persistence.platform.database; +import org.eclipse.persistence.exceptions.ConversionException; +import org.eclipse.persistence.exceptions.DatabaseException; +import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; +import org.eclipse.persistence.internal.helper.ClassConstants; +import org.eclipse.persistence.internal.sessions.AbstractSession; + +import java.sql.Clob; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Hashtable; + +import static org.eclipse.persistence.internal.helper.StringHelper.EMPTY_STRING; + public class Oracle23Platform extends Oracle21Platform { + public Oracle23Platform() { super(); } + + @Override + protected Hashtable, FieldTypeDefinition> buildFieldTypes() { + Hashtable, FieldTypeDefinition> fieldTypes = super.buildFieldTypes(); + fieldTypes.put(java.time.LocalDateTime.class, new FieldTypeDefinition("TIMESTAMP", 9)); + fieldTypes.put(java.time.LocalTime.class, new FieldTypeDefinition("TIMESTAMP", 9)); + return fieldTypes; + } + + /** + * INTERNAL: + * Check whether current platform is Oracle 23c or later. + * @return Always returns {@code true} for instances of Oracle 23c platform. + * @since 4.0.2 + */ + @Override + public boolean isOracle23() { + return true; + } + + /** + * INTERNAL: + * Allow for conversion from the Oracle type to the Java type. Used in cases when DB connection is needed like BLOB, CLOB. + */ + @Override + public T convertObject(Object sourceObject, Class javaClass, AbstractSession session) throws ConversionException, DatabaseException { + //Handle special case when empty String ("") is passed from the entity into CLOB type column + if (ClassConstants.CLOB.equals(javaClass) && sourceObject instanceof String && EMPTY_STRING.equals(sourceObject)) { + Connection connection = session.getAccessor().getConnection(); + Clob clob = null; + try { + clob = connection.createClob(); + clob.setString(1, (String)sourceObject); + } catch (SQLException e) { + throw ConversionException.couldNotBeConvertedToClass(sourceObject, ClassConstants.CLOB, e); + } + return (T) clob; + } + return super.convertObject(sourceObject, javaClass); + } } diff --git a/foundation/org.eclipse.persistence.oracle/src/main/java/org/eclipse/persistence/platform/database/oracle/Oracle23Platform.java b/foundation/org.eclipse.persistence.oracle/src/main/java/org/eclipse/persistence/platform/database/oracle/Oracle23Platform.java index 39ef018ad31..90345354e32 100644 --- a/foundation/org.eclipse.persistence.oracle/src/main/java/org/eclipse/persistence/platform/database/oracle/Oracle23Platform.java +++ b/foundation/org.eclipse.persistence.oracle/src/main/java/org/eclipse/persistence/platform/database/oracle/Oracle23Platform.java @@ -14,6 +14,19 @@ // Oracle - initial API and implementation package org.eclipse.persistence.platform.database.oracle; +import org.eclipse.persistence.exceptions.ConversionException; +import org.eclipse.persistence.exceptions.DatabaseException; +import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; +import org.eclipse.persistence.internal.helper.ClassConstants; +import org.eclipse.persistence.internal.sessions.AbstractSession; + +import java.sql.Clob; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Hashtable; + +import static org.eclipse.persistence.internal.helper.StringHelper.EMPTY_STRING; + /** *

Purpose: * Supports certain new Oracle 23c data types, and usage of certain Oracle JDBC specific APIs. @@ -29,7 +42,6 @@ public Oracle23Platform() { super(); } - /** * INTERNAL: * Check whether current platform is Oracle 23c or later. @@ -40,4 +52,33 @@ public Oracle23Platform() { public boolean isOracle23() { return true; } + + @Override + protected Hashtable, FieldTypeDefinition> buildFieldTypes() { + Hashtable, FieldTypeDefinition> fieldTypes = super.buildFieldTypes(); + fieldTypes.put(java.time.LocalDateTime.class, new FieldTypeDefinition("TIMESTAMP", 9)); + fieldTypes.put(java.time.LocalTime.class, new FieldTypeDefinition("TIMESTAMP", 9)); + return fieldTypes; + } + + /** + * INTERNAL: + * Allow for conversion from the Oracle type to the Java type. Used in cases when DB connection is needed like BLOB, CLOB. + */ + @Override + public T convertObject(Object sourceObject, Class javaClass, AbstractSession session) throws ConversionException, DatabaseException { + //Handle special case when empty String ("") is passed from the entity into CLOB type column + if (ClassConstants.CLOB.equals(javaClass) && sourceObject instanceof String && EMPTY_STRING.equals(sourceObject)) { + Connection connection = session.getAccessor().getConnection(); + Clob clob = null; + try { + clob = connection.createClob(); + clob.setString(1, (String)sourceObject); + } catch (SQLException e) { + throw ConversionException.couldNotBeConvertedToClass(sourceObject, ClassConstants.CLOB, e); + } + return (T) clob; + } + return super.convertObject(sourceObject, javaClass); + } } diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/oraclefeatures/TestOracleLOBLocatorFeature.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/oraclefeatures/TestOracleLOBLocatorFeature.java index aa64b7c6c61..86192cc7d85 100644 --- a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/oraclefeatures/TestOracleLOBLocatorFeature.java +++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/oraclefeatures/TestOracleLOBLocatorFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2023 Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the @@ -157,12 +157,13 @@ public void testOracleWithoutLOBLocatorWithEmptyClob() throws Exception { // After Oracle 11, the lob locator is disabled by default (requiring a Session Customizer to reenable it) // So the test should fail because Eclipselink will try store a null value instead of an empty_blob()/empty_clob() // and violate the NOT NULL constraint. + // In Oracle23Platform case when empty String ("") is stored into CLOB column is handled by java.sql.Clob Set notAllowedPlatforms = new HashSet(); notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle8Platform"); notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle9Platform"); notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle10Platform"); - - + notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle23Platform"); + notAllowedPlatforms.add("org.eclipse.persistence.platform.database.oracle.Oracle23Platform"); if (!checkIsOracle() || notAllowedPlatforms.contains(getPlatform(emfNoSessionCustomizer).getClass().getName())) { // Skip if not testing against Oracle return; diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/converters/LobMetadata.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/converters/LobMetadata.java index f0a0924f79b..e8d1fa3d972 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/converters/LobMetadata.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/metadata/converters/LobMetadata.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2023 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 v. 2.0 which is available at @@ -120,10 +120,14 @@ public void process(DatabaseMapping mapping, MappingAccessor accessor, MetadataC // referenceClass type. if (isValidClobType(referenceClass)) { setFieldClassification(mapping, java.sql.Clob.class, isForMapKey); - setConverter(mapping, new TypeConversionConverter(mapping), isForMapKey); + TypeConversionConverter typeConversionConverter = new TypeConversionConverter(mapping); + typeConversionConverter.setDataClass(java.sql.Clob.class); + setConverter(mapping, typeConversionConverter, isForMapKey); } else if (isValidBlobType(referenceClass)) { setFieldClassification(mapping, java.sql.Blob.class, isForMapKey); - setConverter(mapping, new TypeConversionConverter(mapping), isForMapKey); + TypeConversionConverter typeConversionConverter = new TypeConversionConverter(mapping); + typeConversionConverter.setDataClass(java.sql.Blob.class); + setConverter(mapping, typeConversionConverter, isForMapKey); } else if (referenceClass.extendsInterface(Serializable.class)) { setFieldClassification(mapping, java.sql.Blob.class, isForMapKey); setConverter(mapping, new SerializedObjectConverter(mapping), isForMapKey);