From 601bdc6a52b8a83d7ad07923c5d08633ed7b183b Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Fri, 13 Dec 2024 01:45:50 +0100 Subject: [PATCH 01/81] HHH-17404 : Support reading/writing from/to special source/target in FormatMapper --- .../dialect/OracleLegacyDialect.java | 2 +- .../java/org/hibernate/dialect/Dialect.java | 13 + .../org/hibernate/dialect/OracleDialect.java | 46 +++- .../dialect/OracleDurationJdbcType.java | 28 +++ .../OracleOsonArrayJdbcTypeConstructor.java | 43 ++++ .../OracleOsonJacksonArrayJdbcType.java | 201 +++++++++++++++ .../dialect/OracleOsonJacksonJdbcType.java | 232 ++++++++++++++++++ .../aggregate/OracleAggregateSupport.java | 89 ++++--- .../dialect/type/OracleJsonJdbcType.java | 2 +- .../type/format/AbstractJsonFormatMapper.java | 21 ++ .../hibernate/type/format/FormatMapper.java | 20 ++ .../format/jackson/JacksonIntegration.java | 21 +- .../jackson/JacksonJsonFormatMapper.java | 33 +++ .../jackson/JacksonXmlFormatMapper.java | 22 ++ .../jakartajson/JsonBJsonFormatMapper.java | 1 + .../type/format/jaxb/JaxbXmlFormatMapper.java | 22 ++ 16 files changed, 761 insertions(+), 35 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index 07e85788a6fa..2670fb217ba1 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -992,7 +992,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this ); + return OracleAggregateSupport.valueOf( this ,true); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index f566d03caf27..3988cfa22505 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -5473,6 +5473,19 @@ public Boolean supportsRefCursors() { return null; } + + /** + * Whether this Dialect supports jakarta Temporal annotation while + * serialising or deserialising JSON + * + * @return {@code true} indicates it does; {@code false} indicates it does not + * + * @see org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData#supportsRefCursors + */ + public Boolean getSupportsJakartaTemporalAnnotationInEmbeddable() { + return Boolean.TRUE; + } + /** * Pluggable strategy for determining the {@link Size} to use for * columns of a given SQL type. diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index da62a303db34..10586d03cac4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -104,6 +104,7 @@ import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl; import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; +import org.hibernate.type.format.jackson.JacksonIntegration; import org.hibernate.type.spi.TypeConfiguration; import java.sql.CallableStatement; @@ -116,9 +117,11 @@ import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jboss.logging.Logger; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS; +import static org.hibernate.dialect.DialectLogging.DIALECT_MESSAGE_LOGGER; import static org.hibernate.dialect.type.OracleJdbcHelper.getArrayJdbcTypeConstructor; import static org.hibernate.dialect.type.OracleJdbcHelper.getNestedTableJdbcTypeConstructor; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; @@ -192,6 +195,9 @@ public class OracleDialect extends Dialect { private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 19 ); + private static final String JACKSON_MAPPER_NAME = "jackson"; + private static boolean OracleOsonExtensionUsed = false; + private final OracleUserDefinedTypeExporter userDefinedTypeExporter = new OracleUserDefinedTypeExporter( this ); private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); private final SequenceSupport oracleSequenceSupport = OracleSequenceSupport.getInstance(this); @@ -1007,9 +1013,32 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ); } + final String mapperName = configurationService.getSetting( "hibernate.type.json_format_mapper", + StandardConverters.STRING,JACKSON_MAPPER_NAME); + if ( getVersion().isSameOrAfter( 21 ) ) { - typeContributions.contributeJdbcType( OracleJsonJdbcType.INSTANCE ); - typeContributions.contributeJdbcTypeConstructor( OracleJsonArrayJdbcTypeConstructor.NATIVE_INSTANCE ); + if ( JacksonIntegration.isOracleOsonExtensionAvailable() && JACKSON_MAPPER_NAME.equalsIgnoreCase( mapperName )) { + // We must check that that extension is available and actually used. + typeContributions.contributeJdbcType( OracleOsonJacksonJdbcType.INSTANCE ); + typeContributions.contributeJdbcTypeConstructor( OracleOsonArrayJdbcTypeConstructor.INSTANCE ); + DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, + "Oracle OSON Jackson extension used" ); + // as we speak this is not supported by OSON extension + OracleOsonExtensionUsed = true; + } + else { + if (DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.isDebugEnabled()) { + DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, + "Oracle OSON Jackson extension not used" ); + DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, + "JacksonIntegration.isOracleOsonExtensionAvailable(): " + + JacksonIntegration.isOracleOsonExtensionAvailable()); + DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, + "hibernate.type.json_format_mapper : " + mapperName); + } + typeContributions.contributeJdbcType( OracleJsonJdbcType.INSTANCE ); + typeContributions.contributeJdbcTypeConstructor( OracleJsonArrayJdbcTypeConstructor.NATIVE_INSTANCE ); + } } else { typeContributions.contributeJdbcType( OracleJsonBlobJdbcType.INSTANCE ); @@ -1028,6 +1057,8 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry // Oracle requires a custom binder for binding untyped nulls with the NULL type typeContributions.contributeJdbcType( NullJdbcType.INSTANCE ); typeContributions.contributeJdbcType( ObjectNullAsNullTypeJdbcType.INSTANCE ); + // Oracle Stores the duration is ISO-8601 format. + typeContributions.contributeJdbcType( OracleDurationJdbcType.INSTANCE ); // Until we remove StandardBasicTypes, we have to keep this typeContributions.contributeType( @@ -1053,10 +1084,19 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry jdbcTypeRegistry.addDescriptor( OracleOrdinalEnumJdbcType.INSTANCE ); } } + @Override + public Boolean getSupportsJakartaTemporalAnnotationInEmbeddable() { + return OracleOsonExtensionUsed == false; + } @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this ); + if (OracleOsonExtensionUsed) { + return OracleAggregateSupport.valueOf( this, false ); + } + else { + return OracleAggregateSupport.valueOf( this, true ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java new file mode 100644 index 000000000000..dfbf38ab4140 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect; + +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.VarcharJdbcType; + +import java.sql.Types; + +public class OracleDurationJdbcType extends VarcharJdbcType { + + public static final OracleDurationJdbcType INSTANCE = new OracleDurationJdbcType(); + + public OracleDurationJdbcType() { + } + + @Override + public int getDdlTypeCode() { + return Types.VARCHAR; + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.DURATION; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java new file mode 100644 index 000000000000..b91c6a86f931 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect; + +import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; +import org.hibernate.type.BasicType; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Factory for {@link OracleOsonJacksonArrayJdbcType}. + * @author Emmanuel Jannetti + */ +public class OracleOsonArrayJdbcTypeConstructor implements JdbcTypeConstructor { + public static final JdbcTypeConstructor INSTANCE = new OracleOsonArrayJdbcTypeConstructor(); + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + BasicType<?> elementType, + ColumnTypeInformation columnTypeInformation) { + return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation ); + } + + @Override + public JdbcType resolveType( + TypeConfiguration typeConfiguration, + Dialect dialect, + JdbcType elementType, + ColumnTypeInformation columnTypeInformation) { + return new OracleOsonJacksonArrayJdbcType( elementType ); + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.JSON_ARRAY; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java new file mode 100644 index 000000000000..1bf468b37ab4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -0,0 +1,201 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import oracle.sql.json.OracleJsonDatum; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; + +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * @author Emmanuel Jannetti + */ +public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { + /** + * Singleton access + */ + //public static final OracleOsonJacksonArrayJdbcType INSTANCE = new OracleOsonJacksonArrayJdbcType(); + + private static Method jacksonOsonObjectMapperGetter = null; + + static { + try { + Class jacksonOsonConverter = OracleOsonJacksonJdbcType.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.JacksonOsonConverter" ); + jacksonOsonObjectMapperGetter = jacksonOsonConverter.getMethod( "getObjectMapper" ); + } + catch (ClassNotFoundException | LinkageError | NoSuchMethodException e) { + // should not happen as OracleOsonJacksonJdbcType is loaded + // only when Oracle OSON JDBC extension is present + // see OracleDialect class. + throw new ExceptionInInitializerError( "OracleOsonJacksonArrayJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); + } + } + + public OracleOsonJacksonArrayJdbcType(JdbcType elementJdbcType) { + super(elementJdbcType); + } + + + @Override + public String toString() { + return "OracleOsonJacksonArrayJdbcType"; + } + + @Override + public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { + + final ObjectMapper objectMapper; + try { + objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); + } + catch (IllegalAccessException | InvocationTargetException e) { + // should not happen + throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); + } + + return new BasicBinder<>( javaType, this ) { + + private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + PipedOutputStream out = new PipedOutputStream(); + PipedInputStream in = new PipedInputStream(out); + JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); + mapper.writeToTarget( value, javaType, osonGen, options ); + osonGen.close(); + return in; + } + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + try { + st.setBinaryStream( index, toOson( value, getJavaType(), options ) ); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + try { + st.setBinaryStream( name, toOson( value, getJavaType(), options ) ); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + }; + } + + @Override + public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { + + final ObjectMapper objectMapper; + try { + objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); + } + catch (IllegalAccessException | InvocationTargetException e) { + // should not happen + throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); + } + + return new BasicExtractor<>( javaType, this ) { + + private X fromOson(byte[] osonBytes, FormatMapper mapper, WrapperOptions options) throws Exception { + return mapper.readFromSource( getJavaType(), osonBytes, options); + } + + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + if ( ojd == null ) { + return null; + } + byte[] osonBytes = ojd.shareBytes(); + + try { + return fromOson( osonBytes, mapper ,options); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); + if ( ojd == null ) { + return null; + } + byte[] osonBytes = ojd.shareBytes(); + try { + return fromOson( osonBytes, mapper ,options); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + + OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); + if ( ojd == null ) { + return null; + } + byte[] osonBytes = ojd.shareBytes(); + try { + return fromOson( osonBytes, mapper ,options); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java new file mode 100644 index 000000000000..7dc9f047560d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -0,0 +1,232 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import oracle.sql.json.OracleJsonDatum; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; + +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * @author Emmanuel Jannetti + */ +public class OracleOsonJacksonJdbcType extends OracleJsonJdbcType { + public static final OracleOsonJacksonJdbcType INSTANCE = new OracleOsonJacksonJdbcType( null ); + private static Method jacksonOsonObjectMapperGetter = null; + + static { + try { + Class jacksonOsonConverter = OracleOsonJacksonJdbcType.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.JacksonOsonConverter" ); + jacksonOsonObjectMapperGetter = jacksonOsonConverter.getMethod( "getObjectMapper" ); + } + catch (ClassNotFoundException | LinkageError | NoSuchMethodException e) { + // should not happen as OracleOsonJacksonJdbcType is loaded + // only when Oracle OSON JDBC extension is present + // see OracleDialect class. + throw new ExceptionInInitializerError( "OracleOsonJacksonJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); + } + } + private OracleOsonJacksonJdbcType(EmbeddableMappingType embeddableMappingType) { + super( embeddableMappingType ); + } + + @Override + public String toString() { + return "OracleOsonJacksonJdbcType"; + } + + @Override + public AggregateJdbcType resolveAggregateJdbcType( + EmbeddableMappingType mappingType, + String sqlType, + RuntimeModelCreationContext creationContext) { + return new OracleOsonJacksonJdbcType( mappingType ); + } + + @Override + public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { + + final ObjectMapper objectMapper; + try { + objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); + } + catch (IllegalAccessException | InvocationTargetException e) { + // should not happen + throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); + } + + return new BasicBinder<>( javaType, this ) { + + private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { + + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + PipedOutputStream out = new PipedOutputStream(); + PipedInputStream in = new PipedInputStream(out); + JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); + mapper.writeToTarget( value, javaType, osonGen, options ); + osonGen.close(); + return in; + } + + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + try { + st.setBinaryStream( index, toOson( value, getJavaType(), options ) ); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + try { + st.setBinaryStream( name, toOson( value, getJavaType(), options ) ); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + }; + } + + @Override + public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { + + final ObjectMapper objectMapper; + try { + objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); + } + catch (IllegalAccessException | InvocationTargetException e) { + // should not happen + throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); + } + + return new BasicExtractor<>( javaType, this ) { + + private X fromOson(byte[] osonBytes, FormatMapper mapper, WrapperOptions options) throws Exception { + + if (getEmbeddableMappingType() != null && + getJavaType().getJavaTypeClass() == Object[].class) { + // We are dealing with embeddable (@Embeddable) and we request + // an array of objects. We use JsonParser to fetch values + // and build the array.(as opposed to let Jackson do it as we do not + // have proper obejct definition at that stage). + + } + + JavaType <X> type = getJavaType(); + if (getEmbeddableMappingType() != null) { + // We are dealing with embeddable (@Embeddable) + type = (JavaType<X>) getEmbeddableMappingType().getJavaType(); + } + return mapper.readFromSource( type, osonBytes, options ); + } + + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + if ( ojd == null ) { + return null; + } + byte[] osonBytes = ojd.shareBytes(); + + + + try { + return fromOson( osonBytes, mapper ,options); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + + + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + + OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); + if ( ojd == null ) { + return null; + } + byte[] osonBytes = ojd.shareBytes(); + + try { + return fromOson( osonBytes, mapper ,options); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + + OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); + if ( ojd == null ) { + return null; + } + byte[] osonBytes = ojd.shareBytes(); + try { + return fromOson( osonBytes, mapper ,options); + } + catch (Exception e) { + throw new SQLException( e ); + } + } + + }; + } + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index 8aac39ab3bf1..f69cc49ba888 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -49,12 +49,14 @@ public class OracleAggregateSupport extends AggregateSupportImpl { - private static final AggregateSupport V23_INSTANCE = new OracleAggregateSupport( true, JsonSupport.OSON ); - private static final AggregateSupport V21_INSTANCE = new OracleAggregateSupport( false, JsonSupport.OSON ); - private static final AggregateSupport V19_INSTANCE = new OracleAggregateSupport( false, JsonSupport.MERGEPATCH ); - private static final AggregateSupport V18_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY_AND_PATH ); - private static final AggregateSupport V12_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY ); - private static final AggregateSupport LEGACY_INSTANCE = new OracleAggregateSupport( false, JsonSupport.NONE ); + protected static final AggregateSupport V23_INSTANCE = new OracleAggregateSupport( true, JsonSupport.OSON, true ); + // Special instance used when an Oracle OSON extension is available and used + protected static final AggregateSupport V23_OSON_EXT_INSTANCE = new OracleAggregateSupport( true, JsonSupport.OSON,false); + protected static final AggregateSupport V21_INSTANCE = new OracleAggregateSupport( false, JsonSupport.OSON, true ); + protected static final AggregateSupport V19_INSTANCE = new OracleAggregateSupport( false, JsonSupport.MERGEPATCH , true); + protected static final AggregateSupport V18_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY_AND_PATH, true ); + protected static final AggregateSupport V12_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY , true); + protected static final AggregateSupport LEGACY_INSTANCE = new OracleAggregateSupport( false, JsonSupport.NONE , true); private static final String JSON_QUERY_START = "json_query("; private static final String JSON_QUERY_JSON_END = "' returning json)"; @@ -68,13 +70,15 @@ public class OracleAggregateSupport extends AggregateSupportImpl { private final boolean checkConstraintSupport; private final JsonSupport jsonSupport; + private final boolean dateTypesStoreAsString; - private OracleAggregateSupport(boolean checkConstraintSupport, JsonSupport jsonSupport) { + OracleAggregateSupport(boolean checkConstraintSupport, JsonSupport jsonSupport, boolean dateTypesStoreAsString) { this.checkConstraintSupport = checkConstraintSupport; this.jsonSupport = jsonSupport; + this.dateTypesStoreAsString = dateTypesStoreAsString; } - public static AggregateSupport valueOf(Dialect dialect) { + public static AggregateSupport valueOf(Dialect dialect, boolean useDateStoredAsString) { final DatabaseVersion version = dialect.getVersion(); return switch ( version.getMajor() ) { case 12, 13, 14, 15, 16, 17 -> V12_INSTANCE; @@ -82,8 +86,9 @@ public static AggregateSupport valueOf(Dialect dialect) { case 19, 20 -> V19_INSTANCE; case 21, 22 -> V21_INSTANCE; default -> version.isSameOrAfter( 23 ) - ? OracleAggregateSupport.V23_INSTANCE - : OracleAggregateSupport.LEGACY_INSTANCE; + ? useDateStoredAsString?OracleAggregateSupport.V23_INSTANCE: + OracleAggregateSupport.V23_OSON_EXT_INSTANCE + : OracleAggregateSupport.LEGACY_INSTANCE; }; } @@ -139,31 +144,54 @@ public String aggregateComponentCustomReadExpression( "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' ); case DATE: - return template.replace( - placeholder, - "to_date(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD')" - ); + if (this.dateTypesStoreAsString) { + return template.replace( + placeholder, + "to_date(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD')" + ); + } + else { + // Oracle OSON extension is used, value is not stored as string + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "' returning date)" + ); + } + case TIME: return template.replace( placeholder, "to_timestamp(json_value(" + parentPartExpression + columnExpression + "'),'hh24:mi:ss')" ); case TIMESTAMP: - return template.replace( - placeholder, - "to_timestamp(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9')" - ); + if (this.dateTypesStoreAsString) { + return template.replace( + placeholder, + "to_timestamp(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9')" + ); + } + else { + + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "' returning timestamp)" + ); + } case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_UTC: - return template.replace( - placeholder, - "to_timestamp_tz(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9TZH:TZM')" - ); - case UUID: - return template.replace( - placeholder, - "hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))" - ); + if (this.dateTypesStoreAsString) { + return template.replace( + placeholder, + "to_timestamp_tz(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9TZH:TZM')" + ); + } + else { + // Oracle OSON extension is used, value is not stored as string + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "')" + ); + } case BINARY: case VARBINARY: case LONG32VARBINARY: @@ -187,8 +215,13 @@ public String aggregateComponentCustomReadExpression( final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) column.getJdbcMapping(); final OracleArrayJdbcType jdbcType = (OracleArrayJdbcType) pluralType.getJdbcType(); switch ( jdbcType.getElementJdbcType().getDefaultSqlTypeCode() ) { - case BOOLEAN: + case DATE: + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' + ); + case BOOLEAN: case TIME: case TIMESTAMP: case TIMESTAMP_WITH_TIMEZONE: diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java index 1d1c3055d190..36bc1ed331a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java @@ -24,7 +24,7 @@ public class OracleJsonJdbcType extends OracleJsonBlobJdbcType { */ public static final OracleJsonJdbcType INSTANCE = new OracleJsonJdbcType( null ); - private OracleJsonJdbcType(EmbeddableMappingType embeddableMappingType) { + OracleJsonJdbcType(EmbeddableMappingType embeddableMappingType) { super( embeddableMappingType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java index d32252791a67..c315efa64916 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java @@ -7,6 +7,7 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; +import java.io.IOException; import java.lang.reflect.Type; /** @@ -36,4 +37,24 @@ public final <T> String toString(T value, JavaType<T> javaType, WrapperOptions w protected abstract <T> T fromString(CharSequence charSequence, Type type); protected abstract <T> String toString(T value, Type type); + + @Override + public boolean supportsSourceType(Class<?> sourceType) { + return false; + } + + @Override + public boolean supportsTargetType(Class<?> targetType) { + return false; + } + + @Override + public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) + throws IOException { + } + + @Override + public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java index e22355f6e2e3..bff9726cb0b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java @@ -8,6 +8,8 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; +import java.io.IOException; + /** * A mapper for mapping objects to and from a format. * <ul> @@ -41,4 +43,22 @@ public interface FormatMapper { * Serializes the object to a string. */ <T> String toString(T value, JavaType<T> javaType, WrapperOptions wrapperOptions); + + /** + * Checks that this mapper supports a type as a source type. + * @param sourceType the source type + * @return <code>true</code> if the type is supported, false otherwise. + */ + boolean supportsSourceType(Class<?> sourceType); + + /** + * Checks that this mapper supports a type as a target type. + * @param targetType the target type + * @return <code>true</code> if the type is supported, false otherwise. + */ + boolean supportsTargetType(Class<?> targetType); + + <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException; + + <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java index 5b63787b881b..b0285fb2c49b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java @@ -12,6 +12,7 @@ public final class JacksonIntegration { // when GraalVM native image is initializing them. private static final boolean JACKSON_XML_AVAILABLE = ableToLoadJacksonXMLMapper(); private static final boolean JACKSON_JSON_AVAILABLE = ableToLoadJacksonJSONMapper(); + private static final boolean JACKSON_OSON_AVAILABLE = ableToLoadJacksonOSONGenerator(); private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper() : null; private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER_PORTABLE = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper( false ) : null; private static final JacksonJsonFormatMapper JSON_FORMAT_MAPPER = JACKSON_JSON_AVAILABLE ? new JacksonJsonFormatMapper() : null; @@ -28,8 +29,14 @@ private static boolean ableToLoadJacksonXMLMapper() { return canLoad( "com.fasterxml.jackson.dataformat.xml.XmlMapper" ); } - public static FormatMapper getXMLJacksonFormatMapperOrNull() { - return XML_FORMAT_MAPPER; + /** + * Checks that Jackson is available and that we have the Oracle OSON extension available + * in the classpath. + * @return true if we can load the OSON support, false otherwise. + */ + private static boolean ableToLoadJacksonOSONGenerator() { + return ableToLoadJacksonJSONMapper() && + canLoad( "oracle.jdbc.provider.oson.OsonGenerator" ); } public static FormatMapper getXMLJacksonFormatMapperOrNull(boolean legacyFormat) { @@ -40,6 +47,16 @@ public static FormatMapper getJsonJacksonFormatMapperOrNull() { return JSON_FORMAT_MAPPER; } + /** + * Checks that Oracle OSON extension available + * + * @return true if we can load the OSON support, false otherwise. + */ + public static boolean isOracleOsonExtensionAvailable() { + return JACKSON_OSON_AVAILABLE; + } + + private static boolean canLoad(String name) { try { //N.B. intentionally not using the context classloader diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index d9ecf79a9a4c..0c2045d89adc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -4,11 +4,16 @@ */ package org.hibernate.type.format.jackson; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.format.AbstractJsonFormatMapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.lang.reflect.Type; /** @@ -48,4 +53,32 @@ public <T> String toString(T value, Type type) { throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e ); } } + + @Override + public boolean supportsSourceType(Class<?> sourceType) { + return false; + } + + @Override + public boolean supportsTargetType(Class<?> targetType) { + return false; + } + + @Override + public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) + throws IOException { + + objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ).writeValue( (JsonGenerator) target, value); + + } + + @Override + public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { + + JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); + + T t = objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); + + return t; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java index 170a683d2480..01d2d156cc49 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java @@ -40,6 +40,7 @@ /** * @author Christian Beikov + * @author Emmanuel Jannetti */ public final class JacksonXmlFormatMapper implements FormatMapper { @@ -203,6 +204,27 @@ else if ( javaType.getJavaTypeClass().isArray() ) { return writeValueAsString( value, javaType, javaType.getJavaType() ); } + @Override + public boolean supportsSourceType(Class<?> sourceType) { + return false; + } + + @Override + public boolean supportsTargetType(Class<?> targetType) { + return false; + } + + @Override + public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) + throws IOException { + + } + + @Override + public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { + return null; + } + private <T> String writeValueAsString(Object value, JavaType<T> javaType, Type type) { try { return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jakartajson/JsonBJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jakartajson/JsonBJsonFormatMapper.java index 515c3bb7fc14..67404b2becc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jakartajson/JsonBJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jakartajson/JsonBJsonFormatMapper.java @@ -15,6 +15,7 @@ /** * @author Christian Beikov * @author Yanming Zhou + * @author Emmanuel Jannetti */ public final class JsonBJsonFormatMapper extends AbstractJsonFormatMapper { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jaxb/JaxbXmlFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jaxb/JaxbXmlFormatMapper.java index c0264691252f..4aff790faf84 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jaxb/JaxbXmlFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jaxb/JaxbXmlFormatMapper.java @@ -4,6 +4,7 @@ */ package org.hibernate.type.format.jaxb; +import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.lang.reflect.Array; @@ -497,6 +498,27 @@ else if ( javaType.getJavaTypeClass().isArray() ) { } } + @Override + public boolean supportsSourceType(Class<?> sourceType) { + return false; + } + + @Override + public boolean supportsTargetType(Class<?> targetType) { + return false; + } + + @Override + public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) + throws IOException { + + } + + @Override + public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { + return null; + } + private JAXBElementTransformer createTransformer( StringBuilderSqlAppender appender, Class<?> elementClass, From 2cf095ac0d025066a6c4373428374832ace4c3ed Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Tue, 17 Dec 2024 14:52:23 +0530 Subject: [PATCH 02/81] HHH-17404: Jakarta annotation souuport for Jackson --- .../dialect/OracleOsonJacksonJdbcType.java | 10 +- .../aggregate/OracleAggregateSupport.java | 7 +- .../type/OracleUserDefinedTypeExporter.java | 2 +- .../JacksonJakartaAnnotationIntrospector.java | 107 ++++++++++++++++++ .../jackson/JacksonJsonFormatMapper.java | 5 +- .../jackson/JacksonOsonFormatMapper.java | 79 +++++++++++++ 6 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 7dc9f047560d..ef7e7aa5a7a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -17,7 +17,7 @@ import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.format.FormatMapper; -import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; +import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.InputStream; import java.io.PipedInputStream; @@ -85,7 +85,7 @@ private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions opt // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType(),getJavaType()); PipedOutputStream out = new PipedOutputStream(); PipedInputStream in = new PipedInputStream(out); @@ -159,7 +159,7 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper, getEmbeddableMappingType(),getJavaType()); OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); if ( ojd == null ) { @@ -185,7 +185,7 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType(),getJavaType()); OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); @@ -210,7 +210,7 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType(),getJavaType()); OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); if ( ojd == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index f69cc49ba888..23f6a81a1ee8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -195,6 +195,7 @@ public String aggregateComponentCustomReadExpression( case BINARY: case VARBINARY: case LONG32VARBINARY: + case UUID: // We encode binary data as hex, so we have to decode here if ( determineLength( column ) * 2 < 4000L ) { return template.replace( @@ -230,14 +231,10 @@ public String aggregateComponentCustomReadExpression( case VARBINARY: case LONG32VARBINARY: case UUID: - return template.replace( - placeholder, - jdbcType.getSqlTypeName() + "_from_json(json_query(" + parentPartExpression + columnExpression + "' returning " + jsonTypeName + "))" - ); default: return template.replace( placeholder, - "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' + jdbcType.getSqlTypeName() + "_from_json(json_query(" + parentPartExpression + columnExpression + "' returning " + jsonTypeName + "))" ); } case JSON: diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java index 4808017f80ce..4c37624d4344 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java @@ -337,7 +337,7 @@ private String determineValueExpression(String expression, int elementSqlTypeCod case DATE: return "to_date(" + expression + ",'YYYY-MM-DD')"; case TIME: - return "to_timestamp(" + expression + ",'hh24:mi:ss')"; + return "to_timestamp(CONCAT('1970-01-01 ', \" + expression + \"),'YYYY-MM-DD hh24:mi:ss')"; case TIMESTAMP: return "to_timestamp(" + expression + ",'YYYY-MM-DD\"T\"hh24:mi:ss.FF9')"; case TIMESTAMP_WITH_TIMEZONE: diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java new file mode 100644 index 000000000000..adc17745dcd5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java @@ -0,0 +1,107 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format.jackson; + +import com.fasterxml.jackson.databind.PropertyName; +import com.fasterxml.jackson.databind.introspect.Annotated; +import com.fasterxml.jackson.databind.introspect.AnnotatedField; +import com.fasterxml.jackson.databind.introspect.AnnotatedMember; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; +import com.fasterxml.jackson.databind.util.NameTransformer; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.AttributeMappingsList; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; + +import java.util.HashMap; +import java.util.Map; + +public class JacksonJakartaAnnotationIntrospector extends JacksonAnnotationIntrospector { + + private final EmbeddableMappingType mappingType; + private final Map<String, EmbeddableMappingTypeWithFlattening> mappingTypeMap = new HashMap<>(); + + public JacksonJakartaAnnotationIntrospector(EmbeddableMappingType mappingType) { + this.mappingType = mappingType; + resolveEmbeddableTypes( this.mappingType ); + } + + @Override + public PropertyName findNameForSerialization(Annotated a) { + Column column = _findAnnotation(a, Column.class); + if (column != null && !column.name().isEmpty()) { + return PropertyName.construct(column.name()); + } + return super.findNameForSerialization(a); + } + + @Override + public PropertyName findNameForDeserialization(Annotated a) { + Column column = _findAnnotation(a, Column.class); + if (column != null && !column.name().isEmpty()) { + return PropertyName.construct(column.name()); + } + return super.findNameForDeserialization(a); + } + + @Override + public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) { + if(member instanceof AnnotatedField ) { + Embeddable embeddable = member.getType().getRawClass().getAnnotation(Embeddable.class); + if (embeddable != null) { + String propName = member.getName(); + if(mappingTypeMap.get(propName) != null){ + EmbeddableMappingTypeWithFlattening embeddableMappingTypeWithFlattening = + mappingTypeMap.get( propName ); + if(embeddableMappingTypeWithFlattening.isShouldFlatten()) { + return NameTransformer.simpleTransformer( "","" ); + } + } + } + } + return super.findUnwrappingNameTransformer(member); + } +// theJson + private void resolveEmbeddableTypes(EmbeddableMappingType embeddableMappingType) { + AttributeMappingsList attributeMappings = embeddableMappingType.getAttributeMappings(); + + for (int i = 0; i < attributeMappings.size(); i++){ + AttributeMapping attributeMapping = attributeMappings.get(i); + if ( attributeMapping instanceof EmbeddedAttributeMapping embeddedAttributeMapping ) { + + EmbeddableMappingType attributeEmbeddableMappingType = embeddedAttributeMapping.getMappedType(); + SelectableMapping aggregateMapping = attributeEmbeddableMappingType.getAggregateMapping(); + + mappingTypeMap.put( attributeMapping.getAttributeName(), + new EmbeddableMappingTypeWithFlattening( + attributeEmbeddableMappingType, + aggregateMapping == null )); + + resolveEmbeddableTypes( attributeEmbeddableMappingType); + } + } + } + + static class EmbeddableMappingTypeWithFlattening { + private final EmbeddableMappingType embeddableMappingType; + private final boolean shouldFlatten; + + public EmbeddableMappingTypeWithFlattening(EmbeddableMappingType embeddableMappingType, boolean shouldFlatten) { + this.embeddableMappingType = embeddableMappingType; + this.shouldFlatten = shouldFlatten; + } + + public EmbeddableMappingType getEmbeddableMappingType() { + return embeddableMappingType; + } + + public boolean isShouldFlatten() { + return shouldFlatten; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index 0c2045d89adc..09c365be0b7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.ObjectWriter; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.format.AbstractJsonFormatMapper; @@ -68,7 +69,9 @@ public boolean supportsTargetType(Class<?> targetType) { public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { - objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ).writeValue( (JsonGenerator) target, value); + ObjectWriter writer = objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ); + writer.writeValue( (JsonGenerator) target, value); + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java new file mode 100644 index 000000000000..55055bc43180 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.AbstractJsonFormatMapper; + +import java.io.IOException; +import java.lang.reflect.Type; + +public class JacksonOsonFormatMapper extends AbstractJsonFormatMapper { + + public static final String SHORT_NAME = "jackson"; + + private final ObjectMapper objectMapper; + private final EmbeddableMappingType embeddableMappingType; + + + + public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { + this.objectMapper = objectMapper; + this.embeddableMappingType = embeddableMappingType; + this.objectMapper.setAnnotationIntrospector( new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); + + } + + public <T> JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType, JavaType<T> javaType) { + this.objectMapper = objectMapper; + this.embeddableMappingType = embeddableMappingType; + this.objectMapper.setAnnotationIntrospector( new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); + + } + + @Override + public <T> T fromString(CharSequence charSequence, Type type) { + try { + return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( type ) ); + } + catch (JsonProcessingException e) { + throw new IllegalArgumentException( "Could not deserialize string to java type: " + type, e ); + } + } + + @Override + public <T> String toString(T value, Type type) { + try { + return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value ); + } + catch (JsonProcessingException e) { + throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e ); + } + } + + @Override + public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) + throws IOException { + + ObjectWriter writer = objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ); + writer.writeValue( (JsonGenerator) target, value); + } + + @Override + public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { + JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); + + T t = objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); + + return t; + } +} From 2ae32e5cdc0cb9f00f1f16f1a41d73be1237fb07 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Mon, 16 Dec 2024 16:27:27 +0100 Subject: [PATCH 03/81] HHH-17404: align JDBC dependencies version --- .../test/mapping/embeddable/Aggregate.java | 4 ++ .../embeddable/NestedJsonEmbeddableTest.java | 40 +++++++++++++++++++ hibernate-testing/hibernate-testing.gradle | 8 ++++ 3 files changed, 52 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java index c761c25904af..c0d229411aa8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java @@ -149,6 +149,10 @@ public void setConvertedGender(EntityOfBasics.Gender convertedGender) { this.convertedGender = convertedGender; } + public Boolean getTheBoolean() { + return theBoolean; + } + @Column(name = "ordinal_gender") public EntityOfBasics.Gender getOrdinalGender() { return ordinalGender; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java index 2f5d880bcad5..2e4415a4db56 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java @@ -453,6 +453,30 @@ public static class TheJson { public TheJson() { } + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } + + public SimpleEmbeddable getSimpleEmbeddable() { + return simpleEmbeddable; + } + + public void setSimpleEmbeddable(SimpleEmbeddable simpleEmbeddable) { + this.simpleEmbeddable = simpleEmbeddable; + } + + public EmbeddableAggregate getNested() { + return nested; + } + + public void setNested(EmbeddableAggregate nested) { + this.nested = nested; + } + public TheJson(String stringField, Integer integerField, String leaf, EmbeddableAggregate nested) { this.stringField = stringField; this.simpleEmbeddable = new SimpleEmbeddable( integerField, leaf ); @@ -472,6 +496,22 @@ public static class SimpleEmbeddable { @JdbcTypeCode(SqlTypes.JSON) private DoubleNested doubleNested; + public Integer getIntegerField() { + return integerField; + } + + public void setIntegerField(Integer integerField) { + this.integerField = integerField; + } + + public DoubleNested getDoubleNested() { + return doubleNested; + } + + public void setDoubleNested(DoubleNested doubleNested) { + this.doubleNested = doubleNested; + } + public SimpleEmbeddable() { } diff --git a/hibernate-testing/hibernate-testing.gradle b/hibernate-testing/hibernate-testing.gradle index f64f1e44418c..0c13cdefd35c 100644 --- a/hibernate-testing/hibernate-testing.gradle +++ b/hibernate-testing/hibernate-testing.gradle @@ -46,5 +46,13 @@ dependencies { implementation testLibs.junit5Launcher annotationProcessor project( ':hibernate-processor' ) + + + + runtimeOnly ('com.oracle.database.jdbc:ojdbc-provider-jackson-oson:1.0.2') { + exclude group: 'com.oracle.database.jdbc', module: 'ojdbc8' + } + } + From 98fa356e125a8798567af4dba86b97900114833c Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Tue, 17 Dec 2024 16:03:43 +0100 Subject: [PATCH 04/81] HHH-17404 : use OsonEvent to deal with temporal --- .../dialect/OracleOsonJacksonJdbcType.java | 5 +- .../jackson/JacksonJsonFormatMapper.java | 2 +- .../jackson/JacksonOsonFormatMapper.java | 169 +++++++++++++++++- .../embeddable/EmbeddableAggregate.java | 8 +- 4 files changed, 172 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index ef7e7aa5a7a2..54392574d4cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -140,8 +140,9 @@ private X fromOson(byte[] osonBytes, FormatMapper mapper, WrapperOptions options // We are dealing with embeddable (@Embeddable) and we request // an array of objects. We use JsonParser to fetch values // and build the array.(as opposed to let Jackson do it as we do not - // have proper obejct definition at that stage). - + // have a proper object definition at that stage). + return ((JacksonOsonFormatMapper)mapper).readToArray( + getEmbeddableMappingType(), osonBytes, options ); } JavaType <X> type = getJavaType(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index 09c365be0b7f..b6d77ffcfeb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -21,7 +21,7 @@ * @author Christian Beikov * @author Yanming Zhou */ -public final class JacksonJsonFormatMapper extends AbstractJsonFormatMapper { +public class JacksonJsonFormatMapper extends AbstractJsonFormatMapper { public static final String SHORT_NAME = "jackson"; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 55055bc43180..bee886b91a3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -7,26 +7,56 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import oracle.sql.json.OracleJsonParser; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.format.AbstractJsonFormatMapper; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import java.io.IOException; +import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; -public class JacksonOsonFormatMapper extends AbstractJsonFormatMapper { + +public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { public static final String SHORT_NAME = "jackson"; private final ObjectMapper objectMapper; private final EmbeddableMappingType embeddableMappingType; - - - + private static final Class OsonParserKlass; + private static Method OsonParserKlassCurrentEventMethod = null; + private static Method OsonParserKlassGetLocalDateTimeMethod = null; + private static Method OsonParserKlassReadDurationMethod = null; + private static Method OsonParserKlassReadOffsetDateTimeMethod = null; + static { + try { + + OsonParserKlass = JacksonOsonFormatMapper.class.getClassLoader() + .loadClass( "oracle.jdbc.provider.oson.OsonParser" ); + OsonParserKlassCurrentEventMethod = OsonParserKlass.getMethod( "currentOsonEvent" ); + OsonParserKlassGetLocalDateTimeMethod = OsonParserKlass.getMethod( "getLocalDateTime" ); + OsonParserKlassReadDurationMethod = OsonParserKlass.getMethod( "readDuration" ); + OsonParserKlassReadOffsetDateTimeMethod = OsonParserKlass.getMethod("readOffsetDateTime" ); + } + catch (ClassNotFoundException | LinkageError | NoSuchMethodException e) { + // should not happen as OracleOsonJacksonJdbcType is loaded + // only when Oracle OSON JDBC extension is present + // see OracleDialect class. + throw new ExceptionInInitializerError( + "JacksonOsonFormatMapper class loaded without OSON extension: " + e.getClass() + " " + e.getMessage() ); + } +} public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { + super(objectMapper); this.objectMapper = objectMapper; this.embeddableMappingType = embeddableMappingType; this.objectMapper.setAnnotationIntrospector( new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); @@ -34,12 +64,141 @@ public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType } public <T> JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType, JavaType<T> javaType) { + super(objectMapper); this.objectMapper = objectMapper; this.embeddableMappingType = embeddableMappingType; this.objectMapper.setAnnotationIntrospector( new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); } + + + private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, Object [] finalResult, EmbeddableMappingType embeddableMappingType, WrapperOptions options) + throws IOException { + + JsonToken token = currentToken; + + int selectableIndex = -1; + SelectableMapping mapping = null; + while ( true ) { + if ( token == null ) { + break; + } + switch ( token ) { + case JsonToken.START_ARRAY: + int i = 0; + break; + case JsonToken.VALUE_STRING: + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); + //selectableIndex = embeddableMappingType.getSelectableIndex(currentFieldName ); + //AttributeMapping am = embeddableMappingType.findAttributeMapping( currentFieldName ); + assert selectableIndex >= 0; + mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); + //result.add(osonParser.getText()); + // result.add(mapping.getJdbcMapping().convertToDomainValue( + // mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getText(), options ) )); + + try { + OracleJsonParser.Event event = + (OracleJsonParser.Event)OsonParserKlassCurrentEventMethod.invoke( osonParser ); + switch(event) { + case OracleJsonParser.Event.VALUE_DATE : + + break; + case OracleJsonParser.Event.VALUE_TIMESTAMP: + LocalDateTime local = + (LocalDateTime)OsonParserKlassGetLocalDateTimeMethod.invoke( osonParser ); + finalResult[selectableIndex] = Timestamp.valueOf(local); + break; + case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: + OffsetDateTime offsetDT = (OffsetDateTime)OsonParserKlassReadOffsetDateTimeMethod.invoke( osonParser ); + finalResult[selectableIndex] = offsetDT; + break; + case OracleJsonParser.Event.VALUE_INTERVALDS: + Duration duration = (Duration) OsonParserKlassReadDurationMethod.invoke(osonParser); + finalResult[selectableIndex] = duration; + break; + case OracleJsonParser.Event.VALUE_INTERVALYM: + //break; TODO should not be like that + default : + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ) ); + } + } + catch (Exception e) { + throw new IOException( e ); + } + break; + case JsonToken.VALUE_TRUE: + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); + finalResult[selectableIndex] = Boolean.TRUE.toString(); + break; + case JsonToken.VALUE_FALSE: + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); + finalResult[selectableIndex] = Boolean.TRUE.toString(); + break; + case JsonToken.VALUE_NULL: + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); + finalResult[selectableIndex] = null; + break; + case JsonToken.VALUE_NUMBER_INT: + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); + //selectableIndex = embeddableMappingType.getSelectableIndex(currentFieldName ); + assert selectableIndex >= 0; + mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getIntValue(), options ) ); + break; + case JsonToken.VALUE_NUMBER_FLOAT: + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); + //selectableIndex = embeddableMappingType.getSelectableIndex(currentFieldName ); + assert selectableIndex >= 0; + mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloatValue(), options ) ); + break; + case JsonToken.VALUE_EMBEDDED_OBJECT: + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); + // TODO: think twice about this conversion + finalResult[selectableIndex] = new String( osonParser.getBinaryValue() ); + break; + case JsonToken.START_OBJECT: + if ( osonParser.currentName() == null ) { + // that's the root + consumeValuedToken( osonParser, osonParser.nextToken(), finalResult, + embeddableMappingType, + options ); + } + else { + selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); + if ( selectableIndex != -1 ) { + final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable( + selectableIndex ); + final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() + .getJdbcType(); + final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); + consumeValuedToken( osonParser, osonParser.nextToken(), finalResult, + subMappingType, + options ); + } + } + break; + case JsonToken.END_OBJECT: + return; + } + token = osonParser.nextToken(); + } + + } + + public <T> T readToArray(EmbeddableMappingType embeddableMappingType, Object source, WrapperOptions options) throws IOException { + JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); + Object []finalResult = new Object[embeddableMappingType.getJdbcValueCount()]; + consumeValuedToken(osonParser, osonParser.nextToken(), finalResult, embeddableMappingType, options); + return (T)finalResult; + } + + @Override public <T> T fromString(CharSequence charSequence, Type type) { try { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java index 069ed1ac1559..01d63dcb166a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java @@ -60,7 +60,7 @@ public class EmbeddableAggregate { private byte[] theBinary; private Date theDate; private Date theTime; - private Date theTimestamp; + private Timestamp theTimestamp; private Instant theInstant; private UUID theUuid; private EntityOfBasics.Gender gender; @@ -183,12 +183,12 @@ public void setTheTime(Date theTime) { this.theTime = theTime; } - @Temporal( TemporalType.TIMESTAMP ) - public Date getTheTimestamp() { + //@Temporal( TemporalType.TIMESTAMP ) + public Timestamp getTheTimestamp() { return theTimestamp; } - public void setTheTimestamp(Date theTimestamp) { + public void setTheTimestamp(Timestamp theTimestamp) { this.theTimestamp = theTimestamp; } From c5b744073cb83cfc18ecbaacc9c33b240c0538a9 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Tue, 17 Dec 2024 22:37:02 +0100 Subject: [PATCH 05/81] HHH-17404 : fix Boolean convertion --- .../JacksonJakartaAnnotationIntrospector.java | 26 ++++++++++++++++-- .../jackson/JacksonOsonFormatMapper.java | 27 +++++++++++++------ .../embeddable/NestedJsonEmbeddableTest.java | 24 +++++++++++++++++ 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java index adc17745dcd5..57c81dedf670 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.introspect.Annotated; import com.fasterxml.jackson.databind.introspect.AnnotatedField; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; +import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.databind.util.NameTransformer; import jakarta.persistence.Column; @@ -48,10 +49,19 @@ public PropertyName findNameForDeserialization(Annotated a) { } return super.findNameForDeserialization(a); } - + private String getFieldNameFromGetterName(String fieldName) { + // we assume that method is get|set<camelCase Nme> + // 1. we strip out get|set + // 2. lowercase on first letter + assert fieldName != null; + assert fieldName.substring( 0,3 ).equalsIgnoreCase( "get" ) || + fieldName.substring( 0,3 ).equalsIgnoreCase( "set" ); + fieldName = fieldName.substring( 3 ); + return Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); + } @Override public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) { - if(member instanceof AnnotatedField ) { + if(member instanceof AnnotatedField) { Embeddable embeddable = member.getType().getRawClass().getAnnotation(Embeddable.class); if (embeddable != null) { String propName = member.getName(); @@ -63,6 +73,18 @@ public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) { } } } + } else if (member instanceof AnnotatedMethod) { + Embeddable embeddable = member.getType().getRawClass().getAnnotation(Embeddable.class); + if (embeddable != null) { + String propName = getFieldNameFromGetterName(member.getName()); + if(mappingTypeMap.get(propName) != null){ + EmbeddableMappingTypeWithFlattening embeddableMappingTypeWithFlattening = + mappingTypeMap.get( propName ); + if(embeddableMappingTypeWithFlattening.isShouldFlatten()) { + return NameTransformer.simpleTransformer( "","" ); + } + } + } } return super.findUnwrappingNameTransformer(member); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index bee886b91a3d..8c6657b786c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -25,7 +25,10 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; - +/** + * @author Emmanuel Jannetti + * @auther Bidyadhar Mohanty + */ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { public static final String SHORT_NAME = "jackson"; @@ -121,8 +124,15 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O case OracleJsonParser.Event.VALUE_INTERVALYM: //break; TODO should not be like that default : - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ) ); +// finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( +// mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ) ); +// Object f1 = mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ); +// Object f2 = mapping.getJdbcMapping().convertToDomainValue(mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() )); +// String s1 = osonParser.getText(); +// Object o1 = mapping.getJdbcMapping().convertToDomainValue( osonParser.getText() ); +// Object o2 = mapping.getJdbcMapping().convertToRelationalValue( osonParser.getText() ); +// finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( osonParser.getText() ); + finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ); } } catch (Exception e) { @@ -131,11 +141,11 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O break; case JsonToken.VALUE_TRUE: selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); - finalResult[selectableIndex] = Boolean.TRUE.toString(); + finalResult[selectableIndex] = Boolean.TRUE; break; case JsonToken.VALUE_FALSE: selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); - finalResult[selectableIndex] = Boolean.TRUE.toString(); + finalResult[selectableIndex] = Boolean.FALSE; break; case JsonToken.VALUE_NULL: selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); @@ -159,8 +169,7 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O break; case JsonToken.VALUE_EMBEDDED_OBJECT: selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); - // TODO: think twice about this conversion - finalResult[selectableIndex] = new String( osonParser.getBinaryValue() ); + finalResult[selectableIndex] = osonParser.getBinaryValue() ; break; case JsonToken.START_OBJECT: if ( osonParser.currentName() == null ) { @@ -177,7 +186,9 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() .getJdbcType(); final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); - consumeValuedToken( osonParser, osonParser.nextToken(), finalResult, + finalResult[selectableIndex] = new Object[subMappingType.getJdbcValueCount()]; + consumeValuedToken( osonParser, osonParser.nextToken(), + (Object[]) finalResult[selectableIndex], subMappingType, options ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java index 2e4415a4db56..a1f25a98d226 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java @@ -557,6 +557,14 @@ public DoubleNested(String leaf) { this.theNested = new Nested( leaf ); } + public Nested getTheNested() { + return theNested; + } + + public void setTheNested(Nested theNested) { + this.theNested = theNested; + } + @Override public boolean equals(Object o) { if ( this == o ) { @@ -589,6 +597,14 @@ public Nested(String stringField) { this.theLeaf = new Leaf( stringField ); } + public Leaf getTheLeaf() { + return theLeaf; + } + + public void setTheLeaf(Leaf theLeaf) { + this.theLeaf = theLeaf; + } + @Override public boolean equals(Object o) { if ( this == o ) { @@ -620,6 +636,14 @@ public Leaf(String stringField) { this.stringField = stringField; } + public String getStringField() { + return stringField; + } + + public void setStringField(String stringField) { + this.stringField = stringField; + } + @Override public boolean equals(Object o) { if ( this == o ) { From 578bbd45238c135a569a86934e0ef3c9e5965f90 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 18 Dec 2024 09:03:45 +0100 Subject: [PATCH 06/81] HHH-17404 : fix UUID deserialization --- .../dialect/OracleOsonJacksonJdbcType.java | 7 +++-- .../jackson/JacksonOsonFormatMapper.java | 29 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 54392574d4cb..d7a4e8eaa2b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -19,6 +19,8 @@ import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; @@ -87,11 +89,12 @@ private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions opt // But this do not let use inject our ObjectMapper. For now create our own instance FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType(),getJavaType()); - PipedOutputStream out = new PipedOutputStream(); - PipedInputStream in = new PipedInputStream(out); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + //TODO : really use streams JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); osonGen.close(); + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); return in; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 8c6657b786c4..edb82349fe9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -20,10 +20,12 @@ import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.nio.ByteBuffer; import java.sql.Timestamp; import java.time.Duration; import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.util.UUID; /** * @author Emmanuel Jannetti @@ -106,15 +108,24 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O (OracleJsonParser.Event)OsonParserKlassCurrentEventMethod.invoke( osonParser ); switch(event) { case OracleJsonParser.Event.VALUE_DATE : - + LocalDateTime localDate = + (LocalDateTime)OsonParserKlassGetLocalDateTimeMethod.invoke( osonParser ); + finalResult[selectableIndex] = java.sql.Date.valueOf(localDate.toLocalDate()); break; case OracleJsonParser.Event.VALUE_TIMESTAMP: LocalDateTime local = (LocalDateTime)OsonParserKlassGetLocalDateTimeMethod.invoke( osonParser ); - finalResult[selectableIndex] = Timestamp.valueOf(local); + if ("java.sql.Timestamp".equals( + embeddableMappingType.getJdbcValueSelectable( selectableIndex ) + .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName())) { + finalResult[selectableIndex] = Timestamp.valueOf( local ); + } else { + finalResult[selectableIndex] = local; + } break; case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: - OffsetDateTime offsetDT = (OffsetDateTime)OsonParserKlassReadOffsetDateTimeMethod.invoke( osonParser ); + OffsetDateTime offsetDT = + (OffsetDateTime)OsonParserKlassReadOffsetDateTimeMethod.invoke( osonParser ); finalResult[selectableIndex] = offsetDT; break; case OracleJsonParser.Event.VALUE_INTERVALDS: @@ -169,7 +180,17 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O break; case JsonToken.VALUE_EMBEDDED_OBJECT: selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); - finalResult[selectableIndex] = osonParser.getBinaryValue() ; + byte[] bytes = osonParser.getBinaryValue(); + if ("java.util.UUID".equals( + embeddableMappingType.getJdbcValueSelectable( selectableIndex ) + .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName())) { + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + long mostSignificantBits = byteBuffer.getLong(); + long leastSignificantBits = byteBuffer.getLong(); + finalResult[selectableIndex] = new UUID(mostSignificantBits, leastSignificantBits); + } else { + finalResult[selectableIndex] = bytes; + } break; case JsonToken.START_OBJECT: if ( osonParser.currentName() == null ) { From 77a7fc67efaf252291cbcb1936618f51327da73d Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 18 Dec 2024 17:10:41 +0100 Subject: [PATCH 07/81] HHH-17404 : move to OracleJSONParser --- .../OracleOsonJacksonArrayJdbcType.java | 75 ++---- .../dialect/OracleOsonJacksonJdbcType.java | 81 ++---- .../jackson/JacksonOsonFormatMapper.java | 232 ++++++++---------- 3 files changed, 151 insertions(+), 237 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index 1bf468b37ab4..b59c680c8651 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -16,10 +16,11 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; +import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.CallableStatement; @@ -31,10 +32,6 @@ * @author Emmanuel Jannetti */ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { - /** - * Singleton access - */ - //public static final OracleOsonJacksonArrayJdbcType INSTANCE = new OracleOsonJacksonArrayJdbcType(); private static Method jacksonOsonObjectMapperGetter = null; @@ -82,11 +79,12 @@ private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions opt // But this do not let use inject our ObjectMapper. For now create our own instance FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); - PipedOutputStream out = new PipedOutputStream(); - PipedInputStream in = new PipedInputStream(out); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); osonGen.close(); + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); return in; } @Override @@ -127,74 +125,49 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { - private X fromOson(byte[] osonBytes, FormatMapper mapper, WrapperOptions options) throws Exception { - return mapper.readFromSource( getJavaType(), osonBytes, options); - } - - @Override - protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { // TODO : We should rely on // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper); + return mapper.readFromSource( getJavaType(), osonBytes, options); + } - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - if ( ojd == null ) { + private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { + if ( datum == null ) { return null; } - byte[] osonBytes = ojd.shareBytes(); - + byte[] osonBytes = datum.shareBytes(); try { - return fromOson( osonBytes, mapper ,options); + return fromOson( osonBytes ,options); } catch (Exception e) { throw new SQLException( e ); } } + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction( ojd, options); + } + @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); - if ( ojd == null ) { - return null; - } - byte[] osonBytes = ojd.shareBytes(); - try { - return fromOson( osonBytes, mapper ,options); - } - catch (Exception e) { - throw new SQLException( e ); - } + return doExtraction( ojd, options); } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); - OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); - if ( ojd == null ) { - return null; - } - byte[] osonBytes = ojd.shareBytes(); - try { - return fromOson( osonBytes, mapper ,options); - } - catch (Exception e) { - throw new SQLException( e ); - } + return doExtraction( ojd, options); } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index d7a4e8eaa2b4..e1ccdab50a2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -22,8 +22,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.CallableStatement; @@ -87,10 +85,10 @@ private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions opt // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType(),getJavaType()); + FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType()); ByteArrayOutputStream out = new ByteArrayOutputStream(); - //TODO : really use streams + //TODO : really use streams JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); osonGen.close(); @@ -136,7 +134,12 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { - private X fromOson(byte[] osonBytes, FormatMapper mapper, WrapperOptions options) throws Exception { + private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { + // TODO : We should rely on + // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // + // But this do not let use inject our ObjectMapper. For now create our own instance + FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper, getEmbeddableMappingType()); if (getEmbeddableMappingType() != null && getJavaType().getJavaTypeClass() == Object[].class) { @@ -156,25 +159,13 @@ private X fromOson(byte[] osonBytes, FormatMapper mapper, WrapperOptions options return mapper.readFromSource( type, osonBytes, options ); } - @Override - protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper, getEmbeddableMappingType(),getJavaType()); - - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - if ( ojd == null ) { + private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { + if ( datum == null ) { return null; } - byte[] osonBytes = ojd.shareBytes(); - - - + byte[] osonBytes = datum.shareBytes(); try { - return fromOson( osonBytes, mapper ,options); + return fromOson( osonBytes ,options); } catch (Exception e) { throw new SQLException( e ); @@ -182,51 +173,25 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro } @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - - - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType(),getJavaType()); - + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + // can I use rs.getBinaryStream( paramIndex); ? + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction(ojd,options); + } + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + // can I use rs.getBinaryStream( paramIndex); ? OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); - if ( ojd == null ) { - return null; - } - byte[] osonBytes = ojd.shareBytes(); - - try { - return fromOson( osonBytes, mapper ,options); - } - catch (Exception e) { - throw new SQLException( e ); - } + return doExtraction(ojd,options); } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType(),getJavaType()); - + // can I use rs.getBinaryStream( paramIndex); ? OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); - if ( ojd == null ) { - return null; - } - byte[] osonBytes = ojd.shareBytes(); - try { - return fromOson( osonBytes, mapper ,options); - } - catch (Exception e) { - throw new SQLException( e ); - } + return doExtraction(ojd,options); } }; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index edb82349fe9a..71edacba1234 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -7,9 +7,9 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonParser; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.SelectableMapping; @@ -18,13 +18,11 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import java.io.IOException; -import java.lang.reflect.Method; import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.sql.Timestamp; import java.time.Duration; import java.time.LocalDateTime; -import java.time.OffsetDateTime; import java.util.UUID; /** @@ -37,150 +35,117 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { private final ObjectMapper objectMapper; private final EmbeddableMappingType embeddableMappingType; - private static final Class OsonParserKlass; - private static Method OsonParserKlassCurrentEventMethod = null; - private static Method OsonParserKlassGetLocalDateTimeMethod = null; - private static Method OsonParserKlassReadDurationMethod = null; - private static Method OsonParserKlassReadOffsetDateTimeMethod = null; - static { - try { - - OsonParserKlass = JacksonOsonFormatMapper.class.getClassLoader() - .loadClass( "oracle.jdbc.provider.oson.OsonParser" ); - OsonParserKlassCurrentEventMethod = OsonParserKlass.getMethod( "currentOsonEvent" ); - OsonParserKlassGetLocalDateTimeMethod = OsonParserKlass.getMethod( "getLocalDateTime" ); - OsonParserKlassReadDurationMethod = OsonParserKlass.getMethod( "readDuration" ); - OsonParserKlassReadOffsetDateTimeMethod = OsonParserKlass.getMethod("readOffsetDateTime" ); - } - catch (ClassNotFoundException | LinkageError | NoSuchMethodException e) { - // should not happen as OracleOsonJacksonJdbcType is loaded - // only when Oracle OSON JDBC extension is present - // see OracleDialect class. - throw new ExceptionInInitializerError( - "JacksonOsonFormatMapper class loaded without OSON extension: " + e.getClass() + " " + e.getMessage() ); - } -} - public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { - super(objectMapper); - this.objectMapper = objectMapper; - this.embeddableMappingType = embeddableMappingType; - this.objectMapper.setAnnotationIntrospector( new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); + /** + * Creates a new JacksonOsonFormatMapper + * @param objectMapper the Jackson object mapper + * same as JacksonOsonFormatMapper(objectMapper, null) + */ + public JacksonOsonFormatMapper(ObjectMapper objectMapper) { + this(objectMapper, null); } - public <T> JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType, JavaType<T> javaType) { + /** + * Creates a new JacksonOsonFormatMapper + * @param objectMapper the Jackson object mapper + * @param embeddableMappingType the embeddable mapping definitions + */ + public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { super(objectMapper); this.objectMapper = objectMapper; this.embeddableMappingType = embeddableMappingType; - this.objectMapper.setAnnotationIntrospector( new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); - + if (this.embeddableMappingType != null) { + this.objectMapper.setAnnotationIntrospector( + new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); + } } - - - private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, Object [] finalResult, EmbeddableMappingType embeddableMappingType, WrapperOptions options) + /** + * Process OSON parser tokens + * @param osonParser the OSON parser + * @param currentEvent the current of the parser + * @param finalResult the populated object array + * @param embeddableMappingType the embeddable mapping definitions + * @param options the wrapping options + * @throws IOException + */ + private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Event currentEvent, Object [] finalResult, EmbeddableMappingType embeddableMappingType, WrapperOptions options) throws IOException { - JsonToken token = currentToken; + OracleJsonParser.Event event = currentEvent; int selectableIndex = -1; SelectableMapping mapping = null; + String currentKeyName = null; while ( true ) { - if ( token == null ) { + if ( event == null ) { break; } - switch ( token ) { - case JsonToken.START_ARRAY: + switch ( event ) { + case OracleJsonParser.Event.KEY_NAME: + currentKeyName = osonParser.getString(); + selectableIndex = embeddableMappingType.getSelectableIndex( currentKeyName); + if (selectableIndex >= 0) { + // we may not have a selectable mapping for that key + mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); + } + break; + case OracleJsonParser.Event.START_ARRAY: + case OracleJsonParser.Event.END_ARRAY: int i = 0; break; - case JsonToken.VALUE_STRING: - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); - //selectableIndex = embeddableMappingType.getSelectableIndex(currentFieldName ); - //AttributeMapping am = embeddableMappingType.findAttributeMapping( currentFieldName ); - assert selectableIndex >= 0; - mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); - //result.add(osonParser.getText()); - // result.add(mapping.getJdbcMapping().convertToDomainValue( - // mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getText(), options ) )); - - try { - OracleJsonParser.Event event = - (OracleJsonParser.Event)OsonParserKlassCurrentEventMethod.invoke( osonParser ); - switch(event) { - case OracleJsonParser.Event.VALUE_DATE : - LocalDateTime localDate = - (LocalDateTime)OsonParserKlassGetLocalDateTimeMethod.invoke( osonParser ); - finalResult[selectableIndex] = java.sql.Date.valueOf(localDate.toLocalDate()); - break; - case OracleJsonParser.Event.VALUE_TIMESTAMP: - LocalDateTime local = - (LocalDateTime)OsonParserKlassGetLocalDateTimeMethod.invoke( osonParser ); - if ("java.sql.Timestamp".equals( - embeddableMappingType.getJdbcValueSelectable( selectableIndex ) - .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName())) { - finalResult[selectableIndex] = Timestamp.valueOf( local ); - } else { - finalResult[selectableIndex] = local; - } - break; - case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: - OffsetDateTime offsetDT = - (OffsetDateTime)OsonParserKlassReadOffsetDateTimeMethod.invoke( osonParser ); - finalResult[selectableIndex] = offsetDT; - break; - case OracleJsonParser.Event.VALUE_INTERVALDS: - Duration duration = (Duration) OsonParserKlassReadDurationMethod.invoke(osonParser); - finalResult[selectableIndex] = duration; - break; - case OracleJsonParser.Event.VALUE_INTERVALYM: - //break; TODO should not be like that - default : -// finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( -// mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ) ); -// Object f1 = mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ); -// Object f2 = mapping.getJdbcMapping().convertToDomainValue(mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() )); -// String s1 = osonParser.getText(); -// Object o1 = mapping.getJdbcMapping().convertToDomainValue( osonParser.getText() ); -// Object o2 = mapping.getJdbcMapping().convertToRelationalValue( osonParser.getText() ); -// finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( osonParser.getText() ); - finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getText() ); - } - } - catch (Exception e) { - throw new IOException( e ); + case OracleJsonParser.Event.VALUE_DATE : + LocalDateTime localDate = osonParser.getLocalDateTime(); + finalResult[selectableIndex] = java.sql.Date.valueOf(localDate.toLocalDate()); + break; + case OracleJsonParser.Event.VALUE_TIMESTAMP: + LocalDateTime local = osonParser.getLocalDateTime(); + if ("java.sql.Timestamp".equals( + embeddableMappingType.getJdbcValueSelectable( selectableIndex ) + .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName())) { + finalResult[selectableIndex] = Timestamp.valueOf( local ); + } else { + finalResult[selectableIndex] = local; } break; - case JsonToken.VALUE_TRUE: - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); + case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getOffsetDateTime(), options ) ); + break; + case OracleJsonParser.Event.VALUE_INTERVALDS: + case OracleJsonParser.Event.VALUE_INTERVALYM: + Duration duration = osonParser.getDuration(); + finalResult[selectableIndex] = duration; + break; + case OracleJsonParser.Event.VALUE_STRING: + finalResult[selectableIndex] = + mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getString() ); + break; + case OracleJsonParser.Event.VALUE_TRUE: finalResult[selectableIndex] = Boolean.TRUE; break; - case JsonToken.VALUE_FALSE: - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); + case OracleJsonParser.Event.VALUE_FALSE: finalResult[selectableIndex] = Boolean.FALSE; break; - case JsonToken.VALUE_NULL: - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName()); + case OracleJsonParser.Event.VALUE_NULL: finalResult[selectableIndex] = null; break; - case JsonToken.VALUE_NUMBER_INT: - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); - //selectableIndex = embeddableMappingType.getSelectableIndex(currentFieldName ); - assert selectableIndex >= 0; - mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getIntValue(), options ) ); + case OracleJsonParser.Event.VALUE_DECIMAL: + if (osonParser.isIntegralNumber()) { + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getInt(), options ) ); + } else { + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); + } break; - case JsonToken.VALUE_NUMBER_FLOAT: - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); - //selectableIndex = embeddableMappingType.getSelectableIndex(currentFieldName ); - assert selectableIndex >= 0; - mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); + case OracleJsonParser.Event.VALUE_DOUBLE: + case OracleJsonParser.Event.VALUE_FLOAT: finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloatValue(), options ) ); + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); break; - case JsonToken.VALUE_EMBEDDED_OBJECT: - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); - byte[] bytes = osonParser.getBinaryValue(); + case OracleJsonParser.Event.VALUE_BINARY: + byte[] bytes = osonParser.getBytes(); if ("java.util.UUID".equals( embeddableMappingType.getJdbcValueSelectable( selectableIndex ) .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName())) { @@ -192,15 +157,15 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O finalResult[selectableIndex] = bytes; } break; - case JsonToken.START_OBJECT: - if ( osonParser.currentName() == null ) { + case OracleJsonParser.Event.START_OBJECT: + if ( currentKeyName == null ) { // that's the root - consumeValuedToken( osonParser, osonParser.nextToken(), finalResult, + consumeOsonTokens( osonParser, osonParser.next(), finalResult, embeddableMappingType, options ); } else { - selectableIndex = embeddableMappingType.getSelectableIndex( osonParser.currentName() ); + selectableIndex = embeddableMappingType.getSelectableIndex( currentKeyName ); if ( selectableIndex != -1 ) { final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); @@ -208,29 +173,40 @@ private void consumeValuedToken(JsonParser osonParser, JsonToken currentToken, O .getJdbcType(); final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); finalResult[selectableIndex] = new Object[subMappingType.getJdbcValueCount()]; - consumeValuedToken( osonParser, osonParser.nextToken(), + consumeOsonTokens( osonParser, osonParser.next(), (Object[]) finalResult[selectableIndex], subMappingType, options ); } } break; - case JsonToken.END_OBJECT: + case OracleJsonParser.Event.END_OBJECT: return; + default: + throw new IOException("Unknown OSON event " + event); + } - token = osonParser.nextToken(); + event = osonParser.next(); } } + /** + * Consumes OSON bytes and populate an Object array as described in the embeddable mapping definitions. + * @param embeddableMappingType the embeddable mapping definitions + * @param source the OSON bytes as <code>byte[]</code> + * @param options the wrapping options + * @return the Object array + * @param <T> return type i.e object array + * @throws IOException OSON parsing has failed + */ public <T> T readToArray(EmbeddableMappingType embeddableMappingType, Object source, WrapperOptions options) throws IOException { - JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); Object []finalResult = new Object[embeddableMappingType.getJdbcValueCount()]; - consumeValuedToken(osonParser, osonParser.nextToken(), finalResult, embeddableMappingType, options); + OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( ByteBuffer.wrap( (byte[])source ) ); + consumeOsonTokens(osonParser, osonParser.next(), finalResult, embeddableMappingType, options); return (T)finalResult; } - @Override public <T> T fromString(CharSequence charSequence, Type type) { try { From 02f52ea09280b6c87d2d8cd43123eac75996555c Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 18 Dec 2024 23:47:43 +0100 Subject: [PATCH 08/81] HHH-17404 : implement array csae in embeddable deserialization --- .../dialect/OracleOsonJacksonJdbcType.java | 2 +- .../jackson/JacksonOsonFormatMapper.java | 196 +++++++++++++----- 2 files changed, 145 insertions(+), 53 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index e1ccdab50a2e..d189d3633668 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -147,7 +147,7 @@ private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { // an array of objects. We use JsonParser to fetch values // and build the array.(as opposed to let Jackson do it as we do not // have a proper object definition at that stage). - return ((JacksonOsonFormatMapper)mapper).readToArray( + return ((JacksonOsonFormatMapper)mapper).toObjectArray( getEmbeddableMappingType(), osonBytes, options ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 71edacba1234..38d77329fb3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -13,21 +13,24 @@ import oracle.sql.json.OracleJsonParser; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.type.BasicPluralType; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.UUIDJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import java.io.IOException; import java.lang.reflect.Type; import java.nio.ByteBuffer; +import java.sql.Date; import java.sql.Timestamp; -import java.time.Duration; import java.time.LocalDateTime; -import java.util.UUID; +import java.util.ArrayList; +import java.util.List; /** * @author Emmanuel Jannetti - * @auther Bidyadhar Mohanty + * @author Bidyadhar Mohanty */ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { @@ -60,6 +63,7 @@ public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType } } + /** * Process OSON parser tokens * @param osonParser the OSON parser @@ -67,7 +71,7 @@ public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType * @param finalResult the populated object array * @param embeddableMappingType the embeddable mapping definitions * @param options the wrapping options - * @throws IOException + * @throws IOException error while reading from underlying parser */ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Event currentEvent, Object [] finalResult, EmbeddableMappingType embeddableMappingType, WrapperOptions options) throws IOException { @@ -77,84 +81,174 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve int selectableIndex = -1; SelectableMapping mapping = null; String currentKeyName = null; - while ( true ) { - if ( event == null ) { - break; - } + List<Object> subArrayList = null; + BasicPluralType<?, ?> pluralType = null; + while ( event != null ) { switch ( event ) { case OracleJsonParser.Event.KEY_NAME: currentKeyName = osonParser.getString(); - selectableIndex = embeddableMappingType.getSelectableIndex( currentKeyName); - if (selectableIndex >= 0) { + selectableIndex = embeddableMappingType.getSelectableIndex( currentKeyName ); + if ( selectableIndex >= 0 ) { // we may not have a selectable mapping for that key mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); } break; case OracleJsonParser.Event.START_ARRAY: + // initialize array to gather values + subArrayList = new ArrayList<>(); + assert (mapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) + : "Array event received for non plural type"; + // initialize array's element type + pluralType = (BasicPluralType<?, ?>) mapping.getJdbcMapping(); + break; case OracleJsonParser.Event.END_ARRAY: - int i = 0; + assert (subArrayList != null && pluralType != null) : "Wrong event ordering"; + // flush array values + finalResult[selectableIndex] = pluralType.getJdbcJavaType().wrap( subArrayList, options ); + // reset until we encounter next array elem + subArrayList = null; + pluralType = null; break; - case OracleJsonParser.Event.VALUE_DATE : + case OracleJsonParser.Event.VALUE_DATE: LocalDateTime localDate = osonParser.getLocalDateTime(); - finalResult[selectableIndex] = java.sql.Date.valueOf(localDate.toLocalDate()); + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( Date.valueOf( localDate.toLocalDate() ) ); + } + else { + finalResult[selectableIndex] = Date.valueOf( localDate.toLocalDate() ); + } break; case OracleJsonParser.Event.VALUE_TIMESTAMP: LocalDateTime local = osonParser.getLocalDateTime(); - if ("java.sql.Timestamp".equals( + Object theOne; + if ( "java.sql.Timestamp".equals( embeddableMappingType.getJdbcValueSelectable( selectableIndex ) - .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName())) { - finalResult[selectableIndex] = Timestamp.valueOf( local ); - } else { - finalResult[selectableIndex] = local; + .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() ) ) { + theOne = Timestamp.valueOf( local ); + } + else { + theOne = local; + } + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( theOne ); + } + else { + finalResult[selectableIndex] = theOne; } break; case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getOffsetDateTime(), options ) ); + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( osonParser.getOffsetDateTime() ); + } + else { + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType() + .wrap( osonParser.getOffsetDateTime(), options ) ); + } break; case OracleJsonParser.Event.VALUE_INTERVALDS: case OracleJsonParser.Event.VALUE_INTERVALYM: - Duration duration = osonParser.getDuration(); - finalResult[selectableIndex] = duration; + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( osonParser.getDuration() ); + } + else { + finalResult[selectableIndex] = osonParser.getDuration(); + } break; case OracleJsonParser.Event.VALUE_STRING: - finalResult[selectableIndex] = - mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getString() ); + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( + pluralType.getElementType().getJdbcJavaType().fromString( osonParser.getString() ) ); + } + else { + finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType() + .fromString( osonParser.getString() ); + } break; case OracleJsonParser.Event.VALUE_TRUE: - finalResult[selectableIndex] = Boolean.TRUE; + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( Boolean.TRUE ); + } + else { + finalResult[selectableIndex] = Boolean.TRUE; + } break; case OracleJsonParser.Event.VALUE_FALSE: - finalResult[selectableIndex] = Boolean.FALSE; + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( Boolean.FALSE ); + } + else { + finalResult[selectableIndex] = Boolean.FALSE; + } break; case OracleJsonParser.Event.VALUE_NULL: - finalResult[selectableIndex] = null; + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( null ); + } + else { + finalResult[selectableIndex] = null; + } break; case OracleJsonParser.Event.VALUE_DECIMAL: - if (osonParser.isIntegralNumber()) { - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getInt(), options ) ); - } else { - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( osonParser.isIntegralNumber() ? osonParser.getInt() : osonParser.getFloat() ); + } + else { + // not array case: wrap value directly + if ( osonParser.isIntegralNumber() ) { + theOne = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getInt(), options ) ); + } + else { + theOne = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); + } } break; case OracleJsonParser.Event.VALUE_DOUBLE: + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( osonParser.getDouble() ); + } + else { + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getDouble(), options ) ); + } + break; case OracleJsonParser.Event.VALUE_FLOAT: - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( osonParser.getFloat() ); + } + else { + finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); + } break; case OracleJsonParser.Event.VALUE_BINARY: byte[] bytes = osonParser.getBytes(); - if ("java.util.UUID".equals( - embeddableMappingType.getJdbcValueSelectable( selectableIndex ) - .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName())) { - ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); - long mostSignificantBits = byteBuffer.getLong(); - long leastSignificantBits = byteBuffer.getLong(); - finalResult[selectableIndex] = new UUID(mostSignificantBits, leastSignificantBits); - } else { - finalResult[selectableIndex] = bytes; + if ( "java.util.UUID".equals( + mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() ) ) { + theOne = UUIDJavaType.INSTANCE.wrap( osonParser.getBytes(), options ); + } + else { + theOne = bytes; + } + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( theOne ); + } + else { + finalResult[selectableIndex] = theOne; } break; case OracleJsonParser.Event.START_OBJECT: @@ -162,7 +256,7 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve // that's the root consumeOsonTokens( osonParser, osonParser.next(), finalResult, embeddableMappingType, - options ); + options ); } else { selectableIndex = embeddableMappingType.getSelectableIndex( currentKeyName ); @@ -183,14 +277,15 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve case OracleJsonParser.Event.END_OBJECT: return; default: - throw new IOException("Unknown OSON event " + event); + throw new IOException( "Unknown OSON event " + event ); } - event = osonParser.next(); + event = osonParser.hasNext() ? osonParser.next() : null; } } + /** * Consumes OSON bytes and populate an Object array as described in the embeddable mapping definitions. * @param embeddableMappingType the embeddable mapping definitions @@ -200,7 +295,7 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve * @param <T> return type i.e object array * @throws IOException OSON parsing has failed */ - public <T> T readToArray(EmbeddableMappingType embeddableMappingType, Object source, WrapperOptions options) throws IOException { + public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object source, WrapperOptions options) throws IOException { Object []finalResult = new Object[embeddableMappingType.getJdbcValueCount()]; OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( ByteBuffer.wrap( (byte[])source ) ); consumeOsonTokens(osonParser, osonParser.next(), finalResult, embeddableMappingType, options); @@ -238,9 +333,6 @@ public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, Wrap @Override public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); - - T t = objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); - - return t; + return objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); } } From e85f51b34cecbee046ad67f0fbb30c087075e0b4 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Thu, 19 Dec 2024 08:51:52 +0100 Subject: [PATCH 09/81] HHH-17404 : fix NPE while parsing DECIMAL --- .../format/jackson/JacksonOsonFormatMapper.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 38d77329fb3f..90ba1760a845 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -110,13 +110,21 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve pluralType = null; break; case OracleJsonParser.Event.VALUE_DATE: - LocalDateTime localDate = osonParser.getLocalDateTime(); + LocalDateTime localDateTime = osonParser.getLocalDateTime(); + // check +// if (date) { +// finalResult[selectableIndex] = Date.valueOf( localDateTime.toLocalDate() ); +// } +// else if(LocalDate) +// { +// finalResult[selectableIndex]= localDateTime.toLocalDate(); +// } if ( pluralType != null ) { // dealing with arrays - subArrayList.add( Date.valueOf( localDate.toLocalDate() ) ); + subArrayList.add( Date.valueOf( localDateTime.toLocalDate() ) ); } else { - finalResult[selectableIndex] = Date.valueOf( localDate.toLocalDate() ); + finalResult[selectableIndex] = Date.valueOf( localDateTime.toLocalDate() ); } break; case OracleJsonParser.Event.VALUE_TIMESTAMP: @@ -212,6 +220,7 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve theOne = mapping.getJdbcMapping().convertToDomainValue( mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); } + finalResult[selectableIndex] = theOne; } break; case OracleJsonParser.Event.VALUE_DOUBLE: From efbe98bfe108e81281bf15fdb5ac2194f94ef37b Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Thu, 19 Dec 2024 17:58:47 +0100 Subject: [PATCH 10/81] HHH-17404 : fix dates and URLs --- .../dialect/OracleOsonJacksonJdbcType.java | 15 +++--- .../jackson/JacksonOsonFormatMapper.java | 47 +++++++++++-------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index d189d3633668..747a80ec5541 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; +import oracle.jdbc.OracleType; import oracle.sql.json.OracleJsonDatum; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; @@ -19,9 +20,7 @@ import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.CallableStatement; @@ -79,7 +78,7 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { return new BasicBinder<>( javaType, this ) { - private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { + private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { // TODO : We should rely on // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); @@ -88,19 +87,17 @@ private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions opt FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType()); ByteArrayOutputStream out = new ByteArrayOutputStream(); - //TODO : really use streams JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); - osonGen.close(); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - return in; + osonGen.close(); // until now + return out.toByteArray(); } @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { try { - st.setBinaryStream( index, toOson( value, getJavaType(), options ) ); + st.setObject( index, toOson( value, getJavaType(), options ), OracleType.JSON); } catch (Exception e) { throw new SQLException( e ); @@ -111,7 +108,7 @@ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions o protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { try { - st.setBinaryStream( name, toOson( value, getJavaType(), options ) ); + st.setObject( name, toOson( value, getJavaType(), options ), OracleType.JSON); } catch (Exception e) { throw new SQLException( e ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 90ba1760a845..b208ba0f9302 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -83,6 +83,7 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve String currentKeyName = null; List<Object> subArrayList = null; BasicPluralType<?, ?> pluralType = null; + Object theOne; while ( event != null ) { switch ( event ) { case OracleJsonParser.Event.KEY_NAME: @@ -111,28 +112,31 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve break; case OracleJsonParser.Event.VALUE_DATE: LocalDateTime localDateTime = osonParser.getLocalDateTime(); - // check -// if (date) { -// finalResult[selectableIndex] = Date.valueOf( localDateTime.toLocalDate() ); -// } -// else if(LocalDate) -// { -// finalResult[selectableIndex]= localDateTime.toLocalDate(); -// } + Class underlyingType = null; + if(pluralType!=null) { + underlyingType = pluralType.getElementType().getJavaType(); + } else { + underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); + } + if (java.sql.Date.class.isAssignableFrom( underlyingType )) { + theOne = Date.valueOf( localDateTime.toLocalDate()); + } else if (java.time.LocalDate.class.isAssignableFrom( underlyingType )) { + theOne = localDateTime.toLocalDate(); + } else { + throw new IllegalArgumentException("unexpected date type " + underlyingType); + } if ( pluralType != null ) { // dealing with arrays - subArrayList.add( Date.valueOf( localDateTime.toLocalDate() ) ); + subArrayList.add( theOne ); } else { - finalResult[selectableIndex] = Date.valueOf( localDateTime.toLocalDate() ); + finalResult[selectableIndex] = theOne; } break; case OracleJsonParser.Event.VALUE_TIMESTAMP: LocalDateTime local = osonParser.getLocalDateTime(); - Object theOne; if ( "java.sql.Timestamp".equals( - embeddableMappingType.getJdbcValueSelectable( selectableIndex ) - .getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() ) ) { + mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() ) ) { theOne = Timestamp.valueOf( local ); } else { @@ -174,8 +178,7 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve pluralType.getElementType().getJdbcJavaType().fromString( osonParser.getString() ) ); } else { - finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType() - .fromString( osonParser.getString() ); + finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getString(),options ); } break; case OracleJsonParser.Event.VALUE_TRUE: @@ -244,14 +247,20 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve } break; case OracleJsonParser.Event.VALUE_BINARY: - byte[] bytes = osonParser.getBytes(); - if ( "java.util.UUID".equals( - mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() ) ) { + if(pluralType!=null) { + underlyingType = pluralType.getElementType().getJavaType(); + } + else { + underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); + } + + if (java.util.UUID.class.isAssignableFrom( underlyingType )) { theOne = UUIDJavaType.INSTANCE.wrap( osonParser.getBytes(), options ); } else { - theOne = bytes; + theOne = osonParser.getBytes(); } + if ( pluralType != null ) { // dealing with arrays subArrayList.add( theOne ); From 8652de996553b29feb2b4d9d8c73513bcd6db3ad Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Fri, 20 Dec 2024 14:28:42 +0100 Subject: [PATCH 11/81] HHH-17404 : fix handle of VALUE_INTERVALYM --- .../type/format/jackson/JacksonOsonFormatMapper.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index b208ba0f9302..948ff79acd6b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -162,15 +162,25 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve } break; case OracleJsonParser.Event.VALUE_INTERVALDS: - case OracleJsonParser.Event.VALUE_INTERVALYM: if ( pluralType != null ) { // dealing with arrays subArrayList.add( osonParser.getDuration() ); } else { + // TODO: shall I use mapping.getJdbcMapping().getJdbcJavaType().wrap(...) ? finalResult[selectableIndex] = osonParser.getDuration(); } break; + case OracleJsonParser.Event.VALUE_INTERVALYM: + if ( pluralType != null ) { + // dealing with arrays + subArrayList.add( osonParser.getPeriod() ); + } + else { + // TODO: shall I use mapping.getJdbcMapping().getJdbcJavaType().wrap(...) ? + finalResult[selectableIndex] = osonParser.getPeriod(); + } + break; case OracleJsonParser.Event.VALUE_STRING: if ( pluralType != null ) { // dealing with arrays From 3ef01dce0504d825cf941d2eb168c97da043a003 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Mon, 6 Jan 2025 14:02:28 +0530 Subject: [PATCH 12/81] HHH-17404: Add serializer for Embeddable and OracleDurationJdbcType --- .../org/hibernate/dialect/OracleDialect.java | 3 + .../dialect/OracleDurationJdbcType.java | 76 +++- .../dialect/OracleOsonJacksonJdbcType.java | 3 + .../aggregate/OracleAggregateSupport.java | 43 +- .../jackson/JacksonOsonFormatMapper.java | 421 +++++++++++++++++- 5 files changed, 522 insertions(+), 24 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 10586d03cac4..738fab6ff7b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -142,6 +142,7 @@ import static org.hibernate.type.SqlTypes.DATE; import static org.hibernate.type.SqlTypes.DECIMAL; import static org.hibernate.type.SqlTypes.DOUBLE; +import static org.hibernate.type.SqlTypes.DURATION; import static org.hibernate.type.SqlTypes.FLOAT; import static org.hibernate.type.SqlTypes.GEOMETRY; import static org.hibernate.type.SqlTypes.INTEGER; @@ -851,6 +852,7 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR } // We need the DDL type during runtime to produce the proper encoding in certain functions ddlTypeRegistry.addDescriptor( new DdlTypeImpl( BIT, "number(1,0)", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( DURATION, "interval day to second", this ) ); } @Override @@ -1021,6 +1023,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry // We must check that that extension is available and actually used. typeContributions.contributeJdbcType( OracleOsonJacksonJdbcType.INSTANCE ); typeContributions.contributeJdbcTypeConstructor( OracleOsonArrayJdbcTypeConstructor.INSTANCE ); + DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON Jackson extension used" ); // as we speak this is not supported by OSON extension diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java index dfbf38ab4140..b6a9323aaebb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -5,20 +5,84 @@ package org.hibernate.dialect; import org.hibernate.type.SqlTypes; -import org.hibernate.type.descriptor.jdbc.VarcharJdbcType; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcType; -import java.sql.Types; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Duration; -public class OracleDurationJdbcType extends VarcharJdbcType { +public class OracleDurationJdbcType implements JdbcType { public static final OracleDurationJdbcType INSTANCE = new OracleDurationJdbcType(); - public OracleDurationJdbcType() { + public OracleDurationJdbcType() {} + + @Override + public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setObject( index, javaType.unwrap( value, Duration.class, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setObject( name, javaType.unwrap( value, Duration.class, options ) ); + } + + + }; } @Override - public int getDdlTypeCode() { - return Types.VARCHAR; + public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + // Handle the fact that a duration could also come as number of nanoseconds + final Object nativeValue = rs.getObject( paramIndex ); + if ( nativeValue instanceof Number ) { + return javaType.wrap( nativeValue, options ); + } + return javaType.wrap( rs.getObject( paramIndex, Duration.class ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + // Handle the fact that a duration could also come as number of nanoseconds + final Object nativeValue = statement.getObject( index ); + if ( nativeValue instanceof Number ) { + return javaType.wrap( nativeValue, options ); + } + return javaType.wrap( statement.getObject( index, Duration.class ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + // Handle the fact that a duration could also come as number of nanoseconds + final Object nativeValue = statement.getObject( name ); + if ( nativeValue instanceof Number ) { + return javaType.wrap( nativeValue, options ); + } + return javaType.wrap( statement.getObject( name, Duration.class ), options ); + } + }; + } + + @Override + public int getJdbcTypeCode() { + return SqlTypes.DURATION; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 747a80ec5541..98112a8b291e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -86,6 +86,9 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) // But this do not let use inject our ObjectMapper. For now create our own instance FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType()); + if(getEmbeddableMappingType()!= null) { + return ((JacksonOsonFormatMapper)mapper).toOson(value,javaType,options); + } ByteArrayOutputStream out = new ByteArrayOutputStream(); JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index 23f6a81a1ee8..bea29441e55b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -22,6 +22,7 @@ import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectablePath; import org.hibernate.metamodel.mapping.SqlTypedMapping; +import org.hibernate.query.sqm.CastType; import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; @@ -88,7 +89,7 @@ public static AggregateSupport valueOf(Dialect dialect, boolean useDateStoredAsS default -> version.isSameOrAfter( 23 ) ? useDateStoredAsString?OracleAggregateSupport.V23_INSTANCE: OracleAggregateSupport.V23_OSON_EXT_INSTANCE - : OracleAggregateSupport.LEGACY_INSTANCE; + : OracleAggregateSupport.LEGACY_INSTANCE; }; } @@ -139,10 +140,22 @@ public String aggregateComponentCustomReadExpression( case BIGINT: case CLOB: case NCLOB: - return template.replace( - placeholder, - "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' - ); + CastType castType = column.getJdbcMapping().getCastType(); + switch ( castType ) { + case INTEGER_BOOLEAN: + if (!dateTypesStoreAsString) { + return template.replace( + placeholder, + "decode(json_value(" + parentPartExpression + columnExpression + "'),'true',1,'false',0,null)" + ); + } + default: + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' + ); + } + case DATE: if (this.dateTypesStoreAsString) { return template.replace( @@ -244,10 +257,22 @@ public String aggregateComponentCustomReadExpression( "json_query(" + parentPartExpression + columnExpression + "' returning " + jsonTypeName + ")" ); default: - return template.replace( - placeholder, - "cast(json_value(" + parentPartExpression + columnExpression + "') as " + column.getColumnDefinition() + ')' - ); + CastType castTypeCharBoolean = column.getJdbcMapping().getCastType(); + switch ( castTypeCharBoolean ){ + case YN_BOOLEAN: + if (!dateTypesStoreAsString) { + return template.replace( + placeholder, + "decode(json_value(" + parentPartExpression + columnExpression + "'),'true','Y','false','N',null)" + ); + } + default: + return template.replace( + placeholder, + "cast(json_value(" + parentPartExpression + columnExpression + "') as " + column.getColumnDefinition() + ')' + ); + } + } case NONE: throw new UnsupportedOperationException( "The Oracle version doesn't support JSON aggregates!" ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 948ff79acd6b..cdc5b7b8ce49 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -7,26 +7,67 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; +import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; +import com.fasterxml.jackson.databind.ser.PropertyWriter; +import oracle.jdbc.driver.json.tree.OracleJsonDateImpl; +import oracle.jdbc.driver.json.tree.OracleJsonTimestampImpl; +import oracle.sql.DATE; +import oracle.sql.TIMESTAMP; +import oracle.sql.json.OracleJsonDate; import oracle.sql.json.OracleJsonFactory; +import oracle.sql.json.OracleJsonGenerator; import oracle.sql.json.OracleJsonParser; +import oracle.sql.json.OracleJsonTimestamp; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.type.BasicPluralType; +import org.hibernate.type.BasicType; +import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.JdbcDateJavaType; +import org.hibernate.type.descriptor.java.JdbcTimeJavaType; +import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; import org.hibernate.type.descriptor.java.UUIDJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.lang.reflect.Array; import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.sql.Date; +import java.sql.SQLException; +import java.sql.Time; import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; + +import static org.hibernate.dialect.StructHelper.getEmbeddedPart; /** * @author Emmanuel Jannetti @@ -39,6 +80,9 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { private final ObjectMapper objectMapper; private final EmbeddableMappingType embeddableMappingType; + // fields/Methods to retrieve serializer data + + /** * Creates a new JacksonOsonFormatMapper * @param objectMapper the Jackson object mapper @@ -57,10 +101,6 @@ public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType super(objectMapper); this.objectMapper = objectMapper; this.embeddableMappingType = embeddableMappingType; - if (this.embeddableMappingType != null) { - this.objectMapper.setAnnotationIntrospector( - new JacksonJakartaAnnotationIntrospector( this.embeddableMappingType ) ); - } } @@ -139,6 +179,14 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() ) ) { theOne = Timestamp.valueOf( local ); } + else if ( "java.time.LocalTime".equals( + mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() )) { + theOne = local.toLocalTime(); + } + else if ( "java.sql.Time".equals( + mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() )) { + theOne = Time.valueOf( local.toLocalTime() ); + } else { theOne = local; } @@ -188,7 +236,9 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve pluralType.getElementType().getJdbcJavaType().fromString( osonParser.getString() ) ); } else { - finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getString(),options ); +// finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getString(),options ); +// finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getString(),options )); + finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().fromEncodedString( osonParser.getString(),0,osonParser.getString().length() ); } break; case OracleJsonParser.Event.VALUE_TRUE: @@ -225,11 +275,20 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve } else { // not array case: wrap value directly + if ( osonParser.isIntegralNumber() ) { - theOne = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getInt(), options ) ); + if("java.lang.Double".equals( mapping.getJdbcMapping().getJdbcJavaType().getTypeName() ) ) { + theOne = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getDouble(), options ) ); + + } else { + theOne = mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getInt(), options ) ); + + } } else { + theOne = mapping.getJdbcMapping().convertToDomainValue( mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); } @@ -350,12 +409,353 @@ public <T> String toString(T value, Type type) { } } + + public <X>byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); + serializetoOsonApproach2( value,generator,javaType,options); + generator.close(); + return out.toByteArray(); + } + + private <X> void serializetoOsonApproach2(X value, OracleJsonGenerator generator, JavaType<X> javaType, WrapperOptions options) { + generator.writeStartObject(); + serializetoOsonApproach2Util( value, generator, javaType, options,embeddableMappingType ); + generator.writeEnd(); + } + + private <X> void serializetoOsonApproach2Util(X value, + OracleJsonGenerator generator, + JavaType<X> javaType, + WrapperOptions options, + EmbeddableMappingType embeddableMappingType) { + + final Object[] values = embeddableMappingType.getValues( value ); + for ( int i = 0; i < values.length; i++ ) { + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); + if ( attributeMapping instanceof SelectableMapping ) { + final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); + final BasicType<Object> basicType = (BasicType<Object>) attributeMapping.getMappedType(); + + generator.writeKey( name ); + serializeValue( basicType.convertToRelationalValue( values[i] ), (JavaType<Object>) basicType.getJdbcJavaType(),basicType.getJdbcType(), options,generator); + + } + else if (attributeMapping instanceof EmbeddedAttributeMapping) { + final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); + final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); + if ( values[i] == null ) { + // Skipping the update of the separator is on purpose + continue; + } + if (aggregateMapping == null) { + // flattened case + serializetoOsonApproach2Util( (X) values[i], + generator, + javaType, + options, + embeddableMappingType ); + } + else { + // non flattened case + final String name = aggregateMapping.getSelectableName(); + generator.writeKey( name ); + generator.writeStartObject(); + serializetoOsonApproach2Util( (X) values[i], + generator, + javaType, + options, + embeddableMappingType); + generator.writeEnd(); + + } + + } + } + } + + private void serializeValue(Object value, + JavaType<Object> javaType, + JdbcType jdbcType, + WrapperOptions options, + OracleJsonGenerator generator) { + switch ( jdbcType.getDefaultSqlTypeCode() ) { + case SqlTypes.TINYINT: + case SqlTypes.SMALLINT: + case SqlTypes.INTEGER: + if ( value instanceof Boolean ) { + // BooleanJavaType has this as an implicit conversion + int i = ((Boolean) value) ? 1 : 0; + generator.write( i ); + break; + } + if ( value instanceof Enum ) { + generator.write( ((Enum<?>) value ).ordinal() ); + break; + } + generator.write( javaType.unwrap( value,Integer.class,options ) ); + break; + case SqlTypes.BOOLEAN: + generator.write( (Boolean) value ); + break; + case SqlTypes.BIT: + generator.write( (Integer) value ); + break; + case SqlTypes.BIGINT: + generator.write( (BigInteger) value ); + break; + case SqlTypes.FLOAT: + generator.write( (Float) value ); + break; + case SqlTypes.REAL: + case SqlTypes.DOUBLE: + generator.write( (Double) value ); + break; + case SqlTypes.CHAR: + case SqlTypes.NCHAR: + case SqlTypes.VARCHAR: + case SqlTypes.NVARCHAR: + if ( value instanceof Boolean ) { + String c = ((Boolean) value) ? "Y" : "N"; + generator.write( c ); + break; + } + case SqlTypes.LONGVARCHAR: + case SqlTypes.LONGNVARCHAR: + case SqlTypes.LONG32VARCHAR: + case SqlTypes.LONG32NVARCHAR: + case SqlTypes.CLOB: + case SqlTypes.MATERIALIZED_CLOB: + case SqlTypes.NCLOB: + case SqlTypes.MATERIALIZED_NCLOB: + case SqlTypes.ENUM: + case SqlTypes.NAMED_ENUM: + // correct? + generator.write( javaType.toString(value) ); + break; + case SqlTypes.DATE: + DATE dd = new DATE(javaType.unwrap( value,java.sql.Date.class,options )); + OracleJsonDate jsonDate = new OracleJsonDateImpl(dd.shareBytes()); + generator.write(jsonDate); + break; + case SqlTypes.TIME: + case SqlTypes.TIME_WITH_TIMEZONE: + case SqlTypes.TIME_UTC: + generator.write( javaType.toString(value) ); + break; + case SqlTypes.TIMESTAMP: + TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( value, Timestamp.class, options )); + OracleJsonTimestamp writeTimeStamp = new OracleJsonTimestampImpl(TS.shareBytes()); + generator.write(writeTimeStamp); + break; + case SqlTypes.TIMESTAMP_WITH_TIMEZONE: + try { + OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options ); + generator.write( dateTime ); + } + catch (Exception e) { + Timestamp tswtz = javaType.unwrap( value, Timestamp.class, options ); + TIMESTAMP TSWTZ = new TIMESTAMP(tswtz); + OracleJsonTimestamp writeTimeStampWTZ = new OracleJsonTimestampImpl(TSWTZ.shareBytes()); + generator.write(writeTimeStampWTZ); + } + break; + case SqlTypes.TIMESTAMP_UTC: + if( value instanceof OffsetDateTime ) { + OffsetDateTime odt = javaType.unwrap( value, OffsetDateTime.class, options ); + generator.write( odt ); + break; + } + else if (value instanceof Instant ) { + Instant instant = javaType.unwrap( value, Instant.class, options ); + generator.write(instant.atOffset( ZoneOffset.UTC ) ); + break; + } + generator.write( javaType.toString(value) ); + break; + case SqlTypes.NUMERIC: + case SqlTypes.DECIMAL: + BigDecimal bd = javaType.unwrap( value, BigDecimal.class, options ); + generator.write( bd ); + break; + + case SqlTypes.DURATION: + Duration duration = javaType.unwrap( value, Duration.class, options ); + generator.write( duration ); + break; + case SqlTypes.UUID: + UUID uuid = javaType.unwrap( value, UUID.class, options ); + byte[] bytes = _asBytes( uuid ); + generator.write( bytes ); + break; + case SqlTypes.BINARY: + case SqlTypes.VARBINARY: + case SqlTypes.LONGVARBINARY: + case SqlTypes.LONG32VARBINARY: + case SqlTypes.BLOB: + case SqlTypes.MATERIALIZED_BLOB: + + break; + case SqlTypes.ARRAY: + case SqlTypes.JSON_ARRAY: + final int length = Array.getLength( value ); + generator.writeStartArray(); + if ( length != 0 ) { + //noinspection unchecked + final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) javaType ).getElementJavaType(); + final JdbcType elementJdbcType = ( (ArrayJdbcType) jdbcType ).getElementJdbcType(); + + for ( int i = 0; i < length; i++ ) { + Object arrayElement = Array.get( value, i ); + serializeValue( arrayElement,elementJavaType, elementJdbcType, options, generator ); + } + } + generator.writeEnd(); + break; + default: + throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + jdbcType ); + } + + } + private byte[] _asBytes(UUID uuid) + { + byte[] buffer = new byte[16]; + long hi = uuid.getMostSignificantBits(); + long lo = uuid.getLeastSignificantBits(); + _appendInt((int) (hi >> 32), buffer, 0); + _appendInt((int) hi, buffer, 4); + _appendInt((int) (lo >> 32), buffer, 8); + _appendInt((int) lo, buffer, 12); + return buffer; + } + + private void _appendInt(int value, byte[] buffer, int offset) + { + buffer[offset] = (byte) (value >> 24); + buffer[++offset] = (byte) (value >> 16); + buffer[++offset] = (byte) (value >> 8); + buffer[++offset] = (byte) value; + } + + @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { + com.fasterxml.jackson.databind.JavaType jacksonJavaType = objectMapper.constructType( javaType.getJavaType() ); + + if(embeddableMappingType == null ) { + ObjectWriter writer = objectMapper.writerFor( jacksonJavaType ); + writer.writeValue( (JsonGenerator) target, value); + return; + } + + DefaultSerializerProvider provider =((DefaultSerializerProvider)objectMapper.getSerializerProvider()) + .createInstance( objectMapper.getSerializationConfig(),objectMapper.getSerializerFactory() ); + JsonSerializer<Object> valueSerializer = provider + .findTypedValueSerializer( jacksonJavaType,true, null ); + serializetoOson(value,valueSerializer,embeddableMappingType,(JsonGenerator)target,objectMapper,provider); + ((JsonGenerator)target).flush(); + + } + + private <T> void serializetoOson(T value, JsonSerializer<Object> valueSerializer, EmbeddableMappingType embeddableMappingType, JsonGenerator target, ObjectMapper objectMapper, DefaultSerializerProvider provider) + throws IOException { + target.writeStartObject(); + serializetoOsonUtil(value,valueSerializer,embeddableMappingType,target,objectMapper,provider); + target.writeEndObject(); + + } + + private <T> void serializetoOsonUtil(T value, + JsonSerializer<Object> valueSerializer, + EmbeddableMappingType embeddableMappingType, + JsonGenerator generator, + ObjectMapper mapper, + DefaultSerializerProvider provider) throws IOException { + final Object[] values = embeddableMappingType.getValues( value ); + + Map<String, BeanPropertyWriter> beanPropertyWriterMap = buildBeanPropertyMap(valueSerializer.properties()); + for ( int i = 0; i < values.length; i++ ) { + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); + if ( attributeMapping instanceof SelectableMapping ) { + // basic attribute ?? + final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); + + final BasicType<Object> basicType = (BasicType<Object>) attributeMapping.getMappedType(); + BasicValueConverter<?, ?> valueConverter = basicType.getValueConverter(); + JavaType<?> javaType = + valueConverter!=null ? valueConverter.getRelationalJavaType() : attributeMapping.getJavaType(); + generator.writeFieldName( name ); + + BeanPropertyWriter writer = beanPropertyWriterMap.get( ( (BasicAttributeMapping) attributeMapping ).getAttributeName() ); + JsonSerializer<Object> serializer = + provider.findValueSerializer( objectMapper.constructType( javaType.getJavaType() ), writer ); + JsonSerializer<Object> nullSerializer = provider.findNullValueSerializer( null ); + + try { + assert serializer != null; + if ( values[i] == null ) { + nullSerializer.serialize( null, generator, provider ); + } + else { + serializer.serialize( basicType.convertToRelationalValue( values[i] ),generator,provider); + } + + } + catch (Exception e) { + throw new RuntimeException( e ); + } + } + else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { + if ( values[i] == null ) { + // Skipping the update of the separator is on purpose + continue; + } + final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); + final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); + if ( aggregateMapping == null ){ + + JsonSerializer<Object> serializer = provider + .findTypedValueSerializer( objectMapper.constructType( attributeMapping.getJavaType().getJavaType() ),true,null ); + // flattened case + serializetoOsonUtil( (T) values[i], + serializer, + mappingType, + generator, + mapper, + provider); + } + else { + // non flattened case + final String name = aggregateMapping.getSelectableName(); + generator.writeFieldName( name ); + generator.writeStartObject(); + JsonSerializer<Object> serializer = provider + .findTypedValueSerializer( + objectMapper.constructType( attributeMapping.getJavaType().getJavaType() ), + true,null ); + serializetoOsonUtil( (T)values[i], + serializer, + mappingType, + generator, + mapper, + provider); + generator.writeEndObject(); + } + + } + + } - ObjectWriter writer = objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ); - writer.writeValue( (JsonGenerator) target, value); + } + + private Map<String, BeanPropertyWriter> buildBeanPropertyMap(Iterator<PropertyWriter> properties) { + Map<String,BeanPropertyWriter> result = new HashMap<String,BeanPropertyWriter>(); + while ( properties.hasNext() ) { + BeanPropertyWriter writer = (BeanPropertyWriter) properties.next(); + result.put( writer.getName(), writer ); + } + return result; } @Override @@ -363,4 +763,7 @@ public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); return objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); } + + + } From 05795597d501c68ecb9ba43a8abcb4925cb881bb Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Mon, 23 Dec 2024 12:35:41 +0100 Subject: [PATCH 13/81] HHH-17404 : move to fromString() in case of string event received --- .../type/format/jackson/JacksonOsonFormatMapper.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index cdc5b7b8ce49..08bb2c6e875c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -236,9 +236,7 @@ else if ( "java.sql.Time".equals( pluralType.getElementType().getJdbcJavaType().fromString( osonParser.getString() ) ); } else { -// finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getString(),options ); -// finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getString(),options )); - finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().fromEncodedString( osonParser.getString(),0,osonParser.getString().length() ); + finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getString()); } break; case OracleJsonParser.Event.VALUE_TRUE: From da633142a75f1072e8b01e943494ce46f33bb3a1 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Fri, 3 Jan 2025 19:51:22 +0100 Subject: [PATCH 14/81] HHH-17404 : Add OSON document handler for embeddabe processing --- .../SessionFactoryOptionsBuilder.java | 17 +- .../internal/StrategySelectorBuilder.java | 6 + .../OracleOsonJacksonArrayJdbcType.java | 24 +- .../dialect/OracleOsonJacksonJdbcType.java | 23 +- .../type/format/AbstractJsonFormatMapper.java | 4 +- .../type/format/JsonDocumentHandler.java | 60 ++++ .../format/jackson/JacksonIntegration.java | 5 + .../JacksonJakartaAnnotationIntrospector.java | 3 +- .../jackson/JacksonJsonFormatMapper.java | 33 +- .../jackson/JacksonOsonFormatMapper.java | 324 ++++-------------- .../jackson/JacksonXmlFormatMapper.java | 8 +- .../ObjectArrayOsonDocumentHandler.java | 243 +++++++++++++ 12 files changed, 441 insertions(+), 309 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index b6e451464448..50b4aefc85b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -79,6 +79,7 @@ import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.stat.Statistics; import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonIntegration; import org.hibernate.type.format.jaxb.JaxbXmlFormatMapper; import jakarta.persistence.criteria.Nulls; @@ -112,6 +113,7 @@ import static org.hibernate.jpa.internal.util.CacheModeHelper.interpretCacheMode; import static org.hibernate.jpa.internal.util.ConfigurationHelper.getFlushMode; import static org.hibernate.type.format.jackson.JacksonIntegration.getJsonJacksonFormatMapperOrNull; +import static org.hibernate.type.format.jackson.JacksonIntegration.getOsonJacksonFormatMapperOrNull; import static org.hibernate.type.format.jackson.JacksonIntegration.getXMLJacksonFormatMapperOrNull; import static org.hibernate.type.format.jakartajson.JakartaJsonIntegration.getJakartaJsonBFormatMapperOrNull; @@ -787,12 +789,23 @@ private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( .getDefaultConnectionHandlingMode(); } - private static FormatMapper determineJsonFormatMapper(Object setting, StrategySelector strategySelector) { + private static FormatMapper determineJsonFormatMapper(Object setting, StrategySelector strategySelector, ConfigurationService configurationService) { return strategySelector.resolveDefaultableStrategy( FormatMapper.class, setting, (Callable<FormatMapper>) () -> { - final FormatMapper jsonJacksonFormatMapper = getJsonJacksonFormatMapperOrNull(); + FormatMapper jsonJacksonFormatMapper = null; + configurationService.getSetting( + SESSION_FACTORY_NAME_IS_JNDI, + BOOLEAN, + true + ); + if (JacksonIntegration.isOracleOsonExtensionAvailable()) { + jsonJacksonFormatMapper = getOsonJacksonFormatMapperOrNull(); + } + else { + jsonJacksonFormatMapper = getJsonJacksonFormatMapperOrNull(); + } return jsonJacksonFormatMapper != null ? jsonJacksonFormatMapper : getJakartaJsonBFormatMapperOrNull(); } ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index f2e4c3381516..77150de87f70 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -46,6 +46,7 @@ import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; +import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import org.hibernate.type.format.jackson.JacksonXmlFormatMapper; import org.hibernate.type.format.jaxb.JaxbXmlFormatMapper; import org.hibernate.type.format.jakartajson.JsonBJsonFormatMapper; @@ -311,6 +312,11 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector) JsonBJsonFormatMapper.SHORT_NAME, JsonBJsonFormatMapper.class ); + strategySelector.registerStrategyImplementor( + FormatMapper.class, + JacksonOsonFormatMapper.SHORT_NAME, + JacksonOsonFormatMapper.class + ); } private static void addXmlFormatMappers(StrategySelectorImpl strategySelector) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index b59c680c8651..f75f6977f07a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -15,7 +15,6 @@ import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.format.FormatMapper; -import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayInputStream; @@ -73,11 +72,12 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { return new BasicBinder<>( javaType, this ) { private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonJsonFormatMapper(objectMapper); + FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // TODO : we should not have to do this. + // for now we have to inject the objectMapper. + // As this is not a validated architectural decision, we do not + // modify the interface yet. + ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, null ); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -126,11 +126,13 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper); + FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // TODO : we should not have to do this. + // for now we have to inject the objectMapper. + // As this is not a validated architectural decision, we do not + // modify the interface yet. + ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, null ); + return mapper.readFromSource( getJavaType(), osonBytes, options); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 98112a8b291e..f9d16ec55d96 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -80,11 +80,12 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper,getEmbeddableMappingType()); + FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // TODO : we should not have to do this. + // for now we have to inject the objectMapper. + // As this is not a validated architectural decision, we do not + // modify the interface yet. + ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, getEmbeddableMappingType()); if(getEmbeddableMappingType()!= null) { return ((JacksonOsonFormatMapper)mapper).toOson(value,javaType,options); @@ -135,11 +136,13 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { - // TODO : We should rely on - // FormatMapper fm = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // - // But this do not let use inject our ObjectMapper. For now create our own instance - FormatMapper mapper = new JacksonOsonFormatMapper(objectMapper, getEmbeddableMappingType()); + + FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + // TODO : we should not have to do this. + // for now we have to inject the objectMapper. + // As this is not a validated architectural decision, we do not + // modify the interface yet. + ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, getEmbeddableMappingType() ); if (getEmbeddableMappingType() != null && getJavaType().getJavaTypeClass() == Object[].class) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java index c315efa64916..c6cd1a0b5819 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java @@ -40,12 +40,12 @@ public final <T> String toString(T value, JavaType<T> javaType, WrapperOptions w @Override public boolean supportsSourceType(Class<?> sourceType) { - return false; + return CharSequence.class.isAssignableFrom(sourceType); } @Override public boolean supportsTargetType(Class<?> targetType) { - return false; + return String.class.isAssignableFrom( targetType ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java new file mode 100644 index 000000000000..497985faa064 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ + +/** + * JSON document handler. + * Used to parse JSON documents. Implementors of this will define + * proper callback implementations. + * + * @author Emmanuel Jannetti + */ + +public interface JsonDocumentHandler { + /** + * Callback to be called when the start of an JSON object is encountered. + */ + void startObject(); + + /** + * Callback to be called when the end of an JSON object is encountered. + */ + void endObject(); + + /** + * Callback to be called when the start of an array is encountered. + */ + void startArray(); + + /** + * Callback to be called when the end of an array is encountered. + */ + void endArray(); + + /** + * Callback to be called when the key of JSON attribute is encountered. + */ + void onObjectKey(String key); + + /** + * Callback to be called when null value is encountered. + */ + void onNullValue(); + + /** + * Callback to be called when boolean value is encountered. + */ + void onBooleanValue(boolean value); + + /** + * Callback to be called when string value is encountered. + */ + void onStringValue(String value); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java index b0285fb2c49b..d3efc479f628 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java @@ -16,6 +16,8 @@ public final class JacksonIntegration { private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper() : null; private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER_PORTABLE = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper( false ) : null; private static final JacksonJsonFormatMapper JSON_FORMAT_MAPPER = JACKSON_JSON_AVAILABLE ? new JacksonJsonFormatMapper() : null; + private static final JacksonJsonFormatMapper OSON_FORMAT_MAPPER = JACKSON_OSON_AVAILABLE ? new JacksonOsonFormatMapper() : null; + private JacksonIntegration() { //To not be instantiated: static helpers only @@ -46,6 +48,9 @@ public static FormatMapper getXMLJacksonFormatMapperOrNull(boolean legacyFormat) public static FormatMapper getJsonJacksonFormatMapperOrNull() { return JSON_FORMAT_MAPPER; } + public static FormatMapper getOsonJacksonFormatMapperOrNull() { + return OSON_FORMAT_MAPPER; + } /** * Checks that Oracle OSON extension available diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java index 57c81dedf670..3d1375a856c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java @@ -73,7 +73,8 @@ public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) { } } } - } else if (member instanceof AnnotatedMethod) { + } + else if (member instanceof AnnotatedMethod) { Embeddable embeddable = member.getType().getRawClass().getAnnotation(Embeddable.class); if (embeddable != null) { String propName = getFieldNameFromGetterName(member.getName()); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index b6d77ffcfeb8..85f82149b196 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -4,9 +4,6 @@ */ package org.hibernate.type.format.jackson; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.ObjectWriter; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.format.AbstractJsonFormatMapper; @@ -55,33 +52,29 @@ public <T> String toString(T value, Type type) { } } - @Override - public boolean supportsSourceType(Class<?> sourceType) { - return false; - } - - @Override - public boolean supportsTargetType(Class<?> targetType) { - return false; - } - @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { - ObjectWriter writer = objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ); - writer.writeValue( (JsonGenerator) target, value); - + try { + objectMapper.writerFor( + objectMapper.constructType( javaType.getJavaType() ) ).writeValueAsString( value ); + } + catch (JsonProcessingException e) { + throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType.getJavaType(), e ); + } } @Override public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); - - T t = objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); + try { + return objectMapper.readValue( ((CharSequence)source).toString(), objectMapper.constructType( javaType.getJavaType() ) ); + } + catch (JsonProcessingException e) { + throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType.getJavaType(), e ); + } - return t; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 08bb2c6e875c..4c858b3a10cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -23,24 +23,11 @@ import oracle.sql.json.OracleJsonParser; import oracle.sql.json.OracleJsonTimestamp; import org.hibernate.metamodel.mapping.EmbeddableMappingType; -import org.hibernate.metamodel.mapping.SelectableMapping; -import org.hibernate.metamodel.mapping.ValuedModelPart; -import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.type.BasicPluralType; -import org.hibernate.type.BasicType; -import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.JdbcDateJavaType; -import org.hibernate.type.descriptor.java.JdbcTimeJavaType; -import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; -import org.hibernate.type.descriptor.java.UUIDJavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; -import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; -import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.format.JsonDocumentHandler; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -49,25 +36,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.sql.Date; -import java.sql.SQLException; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.logging.Level; - -import static org.hibernate.dialect.StructHelper.getEmbeddedPart; + /** * @author Emmanuel Jannetti @@ -77,8 +46,7 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { public static final String SHORT_NAME = "jackson"; - private final ObjectMapper objectMapper; - private final EmbeddableMappingType embeddableMappingType; + private ObjectMapper objectMapper; // fields/Methods to retrieve serializer data @@ -89,277 +57,91 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { * same as JacksonOsonFormatMapper(objectMapper, null) */ public JacksonOsonFormatMapper(ObjectMapper objectMapper) { - this(objectMapper, null); - } - - /** - * Creates a new JacksonOsonFormatMapper - * @param objectMapper the Jackson object mapper - * @param embeddableMappingType the embeddable mapping definitions - */ - public JacksonOsonFormatMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { super(objectMapper); this.objectMapper = objectMapper; - this.embeddableMappingType = embeddableMappingType; } - + public JacksonOsonFormatMapper() { + super(); + } /** * Process OSON parser tokens * @param osonParser the OSON parser * @param currentEvent the current of the parser - * @param finalResult the populated object array - * @param embeddableMappingType the embeddable mapping definitions - * @param options the wrapping options * @throws IOException error while reading from underlying parser */ - private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Event currentEvent, Object [] finalResult, EmbeddableMappingType embeddableMappingType, WrapperOptions options) + private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Event currentEvent, JsonDocumentHandler handler) throws IOException { OracleJsonParser.Event event = currentEvent; - int selectableIndex = -1; - SelectableMapping mapping = null; - String currentKeyName = null; - List<Object> subArrayList = null; - BasicPluralType<?, ?> pluralType = null; - Object theOne; while ( event != null ) { switch ( event ) { case OracleJsonParser.Event.KEY_NAME: - currentKeyName = osonParser.getString(); - selectableIndex = embeddableMappingType.getSelectableIndex( currentKeyName ); - if ( selectableIndex >= 0 ) { - // we may not have a selectable mapping for that key - mapping = embeddableMappingType.getJdbcValueSelectable( selectableIndex ); - } + handler.onObjectKey( osonParser.getString() ); break; case OracleJsonParser.Event.START_ARRAY: - // initialize array to gather values - subArrayList = new ArrayList<>(); - assert (mapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) - : "Array event received for non plural type"; - // initialize array's element type - pluralType = (BasicPluralType<?, ?>) mapping.getJdbcMapping(); + handler.startArray(); break; case OracleJsonParser.Event.END_ARRAY: - assert (subArrayList != null && pluralType != null) : "Wrong event ordering"; - // flush array values - finalResult[selectableIndex] = pluralType.getJdbcJavaType().wrap( subArrayList, options ); - // reset until we encounter next array elem - subArrayList = null; - pluralType = null; + handler.endArray(); break; case OracleJsonParser.Event.VALUE_DATE: - LocalDateTime localDateTime = osonParser.getLocalDateTime(); - Class underlyingType = null; - if(pluralType!=null) { - underlyingType = pluralType.getElementType().getJavaType(); - } else { - underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); - } - if (java.sql.Date.class.isAssignableFrom( underlyingType )) { - theOne = Date.valueOf( localDateTime.toLocalDate()); - } else if (java.time.LocalDate.class.isAssignableFrom( underlyingType )) { - theOne = localDateTime.toLocalDate(); - } else { - throw new IllegalArgumentException("unexpected date type " + underlyingType); - } - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( theOne ); - } - else { - finalResult[selectableIndex] = theOne; - } - break; case OracleJsonParser.Event.VALUE_TIMESTAMP: - LocalDateTime local = osonParser.getLocalDateTime(); - if ( "java.sql.Timestamp".equals( - mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() ) ) { - theOne = Timestamp.valueOf( local ); - } - else if ( "java.time.LocalTime".equals( - mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() )) { - theOne = local.toLocalTime(); - } - else if ( "java.sql.Time".equals( - mapping.getJdbcMapping().getJdbcJavaType().getJavaType().getTypeName() )) { - theOne = Time.valueOf( local.toLocalTime() ); - } - else { - theOne = local; - } - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( theOne ); - } - else { - finalResult[selectableIndex] = theOne; - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonDateValue( + osonParser.getLocalDateTime()); break; case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( osonParser.getOffsetDateTime() ); - } - else { - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType() - .wrap( osonParser.getOffsetDateTime(), options ) ); - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getOffsetDateTime()); break; case OracleJsonParser.Event.VALUE_INTERVALDS: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( osonParser.getDuration() ); - } - else { - // TODO: shall I use mapping.getJdbcMapping().getJdbcJavaType().wrap(...) ? - finalResult[selectableIndex] = osonParser.getDuration(); - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getDuration()); break; case OracleJsonParser.Event.VALUE_INTERVALYM: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( osonParser.getPeriod() ); - } - else { - // TODO: shall I use mapping.getJdbcMapping().getJdbcJavaType().wrap(...) ? - finalResult[selectableIndex] = osonParser.getPeriod(); - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getPeriod()); break; case OracleJsonParser.Event.VALUE_STRING: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( - pluralType.getElementType().getJdbcJavaType().fromString( osonParser.getString() ) ); - } - else { - finalResult[selectableIndex] = mapping.getJdbcMapping().getJdbcJavaType().fromString( osonParser.getString()); - } + handler.onStringValue( osonParser.getString() ); break; case OracleJsonParser.Event.VALUE_TRUE: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( Boolean.TRUE ); - } - else { - finalResult[selectableIndex] = Boolean.TRUE; - } + handler.onBooleanValue( true ); break; case OracleJsonParser.Event.VALUE_FALSE: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( Boolean.FALSE ); - } - else { - finalResult[selectableIndex] = Boolean.FALSE; - } + handler.onBooleanValue( false ); break; case OracleJsonParser.Event.VALUE_NULL: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( null ); - } - else { - finalResult[selectableIndex] = null; - } + handler.onNullValue(); break; case OracleJsonParser.Event.VALUE_DECIMAL: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( osonParser.isIntegralNumber() ? osonParser.getInt() : osonParser.getFloat() ); - } - else { - // not array case: wrap value directly - - if ( osonParser.isIntegralNumber() ) { - if("java.lang.Double".equals( mapping.getJdbcMapping().getJdbcJavaType().getTypeName() ) ) { - theOne = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getDouble(), options ) ); - - } else { - theOne = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getInt(), options ) ); - - } - } - else { - - theOne = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); - } - finalResult[selectableIndex] = theOne; + if (osonParser.isIntegralNumber()) { + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getInt()); + } else { + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getFloat()); } break; case OracleJsonParser.Event.VALUE_DOUBLE: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( osonParser.getDouble() ); - } - else { - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getDouble(), options ) ); - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getDouble()); break; case OracleJsonParser.Event.VALUE_FLOAT: - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( osonParser.getFloat() ); - } - else { - finalResult[selectableIndex] = mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType().wrap( osonParser.getFloat(), options ) ); - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getFloat()); break; case OracleJsonParser.Event.VALUE_BINARY: - if(pluralType!=null) { - underlyingType = pluralType.getElementType().getJavaType(); - } - else { - underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); - } - - if (java.util.UUID.class.isAssignableFrom( underlyingType )) { - theOne = UUIDJavaType.INSTANCE.wrap( osonParser.getBytes(), options ); - } - else { - theOne = osonParser.getBytes(); - } - - if ( pluralType != null ) { - // dealing with arrays - subArrayList.add( theOne ); - } - else { - finalResult[selectableIndex] = theOne; - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonBinaryValue( + osonParser.getBytes()); break; case OracleJsonParser.Event.START_OBJECT: - if ( currentKeyName == null ) { - // that's the root - consumeOsonTokens( osonParser, osonParser.next(), finalResult, - embeddableMappingType, - options ); - } - else { - selectableIndex = embeddableMappingType.getSelectableIndex( currentKeyName ); - if ( selectableIndex != -1 ) { - final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable( - selectableIndex ); - final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() - .getJdbcType(); - final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); - finalResult[selectableIndex] = new Object[subMappingType.getJdbcValueCount()]; - consumeOsonTokens( osonParser, osonParser.next(), - (Object[]) finalResult[selectableIndex], - subMappingType, - options ); - } - } + handler.startObject( ); + //consumeOsonTokens( osonParser, osonParser.next(), handler); break; case OracleJsonParser.Event.END_OBJECT: + handler.endObject(); return; default: throw new IOException( "Unknown OSON event " + event ); @@ -377,14 +159,19 @@ else if ( "java.sql.Time".equals( * @param source the OSON bytes as <code>byte[]</code> * @param options the wrapping options * @return the Object array - * @param <T> return type i.e object array + * @param <T> return type i.e., object array * @throws IOException OSON parsing has failed */ public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object source, WrapperOptions options) throws IOException { - Object []finalResult = new Object[embeddableMappingType.getJdbcValueCount()]; + OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( ByteBuffer.wrap( (byte[])source ) ); - consumeOsonTokens(osonParser, osonParser.next(), finalResult, embeddableMappingType, options); - return (T)finalResult; + + ObjectArrayOsonDocumentHandler handler = new ObjectArrayOsonDocumentHandler( embeddableMappingType, + options); + + consumeOsonTokens(osonParser, osonParser.next(), handler); + + return (T)handler.getMappedObjectArray(); } @Override @@ -762,6 +549,25 @@ public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions return objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); } + @Override + public boolean supportsSourceType(Class<?> sourceType) { + return JsonParser.class.isAssignableFrom( sourceType ); + } + + @Override + public boolean supportsTargetType(Class<?> targetType) { + return JsonParser.class.isAssignableFrom( targetType ); + } + public void setJacksonObjectMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { + this.objectMapper = objectMapper; + + // need this until annotation introspector is removed. + if (embeddableMappingType != null) { + this.objectMapper.setAnnotationIntrospector( + new JacksonJakartaAnnotationIntrospector( embeddableMappingType ) ); + } + + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java index 01d2d156cc49..8b70c7138d98 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java @@ -206,23 +206,23 @@ else if ( javaType.getJavaTypeClass().isArray() ) { @Override public boolean supportsSourceType(Class<?> sourceType) { - return false; + return CharSequence.class.isAssignableFrom(sourceType); } @Override public boolean supportsTargetType(Class<?> targetType) { - return false; + return String.class.isAssignableFrom( targetType ); } @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { - + target = toString(value, javaType, options); } @Override public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - return null; + return fromString((CharSequence) source, javaType, options); } private <T> String writeValueAsString(Object value, JavaType<T> javaType, Type type) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java new file mode 100644 index 000000000000..ecc434a67fa5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java @@ -0,0 +1,243 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format.jackson; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.type.BasicPluralType; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.UUIDJavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.format.JsonDocumentHandler; + +import java.sql.Date; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * Implementation of <code>JsonDocumentHandler</code> for OSON document. + * This implementation will produce an Object Array based on + * embeddable mapping + * + */ +public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { + + private Object [] objectArrayResult; + SelectableMapping mapping = null; + String currentKeyName = null; + List<Object> subArrayObjectList = null; + BasicPluralType<?, ?> subArrayObjectTypes = null; + + // mapping definitions are in a tree + // Each mapping definition may contain sub mappings (sub embeddable mapping) + // This stack is used to keep pointer on mapping to be used + // see startObject/endObject methods + Stack<EmbeddableMappingType> embeddableMappingTypes = new Stack<>(); + + WrapperOptions wrapperOptions; + + // index within objectArrayResult + int currentSelectableIndexInResultArray = -1; + + public ObjectArrayOsonDocumentHandler(EmbeddableMappingType embeddableMappingType, WrapperOptions wrapperOptions) { + this.embeddableMappingTypes.push(embeddableMappingType); + this.wrapperOptions = wrapperOptions; + this.objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()]; + } + + /** + * Gets the Object array built from document handling + * @return the array + */ + public Object [] getMappedObjectArray() { + return this.objectArrayResult; + } + + @Override + public void startObject() { + if (currentKeyName != null) { + // we are dealing with a sub-object, allocate space for it. + // otherwise, we have nothing to do. + currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); + assert currentSelectableIndexInResultArray != -1: "Cannot get index of " + currentKeyName; + + final SelectableMapping selectable = embeddableMappingTypes.peek().getJdbcValueSelectable( + currentSelectableIndexInResultArray ); + final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() + .getJdbcType(); + final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); + objectArrayResult[currentSelectableIndexInResultArray] = + new Object[subMappingType.getJdbcValueCount()]; + embeddableMappingTypes.push( subMappingType ); + } + } + + @Override + public void endObject() { + embeddableMappingTypes.pop(); + } + + @Override + public void startArray() { + assert (subArrayObjectList == null && subArrayObjectTypes == null) : "startArray called twice ?"; + + // initialize an array to gather values + subArrayObjectList = new ArrayList<>(); + assert (mapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) + : "Array event received for non plural type"; + // initialize array's element type + subArrayObjectTypes = (BasicPluralType<?, ?>) mapping.getJdbcMapping(); + } + + @Override + public void endArray() { + assert (subArrayObjectList != null && subArrayObjectTypes != null) : "endArray called before startArray"; + // flush array values + objectArrayResult[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, wrapperOptions ); + // reset until we encounter next array element + subArrayObjectList = null; + subArrayObjectTypes = null; + } + + @Override + public void onObjectKey(String key) { + this.currentKeyName = key; + + currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); + if ( currentSelectableIndexInResultArray >= 0 ) { + // we may not have a selectable mapping for that key + mapping = embeddableMappingTypes.peek().getJdbcValueSelectable( currentSelectableIndexInResultArray ); + } + else { + throw new IllegalArgumentException( + String.format( + "Could not find selectable [%s] in embeddable type [%s] for JSON processing.", + currentKeyName, + embeddableMappingTypes.peek().getMappedJavaType().getJavaTypeClass().getName() + ) + ); + } + } + + @Override + public void onNullValue() { + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( null ); + } + else { + objectArrayResult[currentSelectableIndexInResultArray] = null; + } + } + + @Override + public void onBooleanValue(boolean value) { + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( value?Boolean.TRUE:Boolean.FALSE); + } + else { + objectArrayResult[currentSelectableIndexInResultArray] = value?Boolean.TRUE:Boolean.FALSE; + } + } + + @Override + public void onStringValue(String value) { + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( + subArrayObjectTypes.getElementType().getJdbcJavaType().fromString( value ) ); + } + else { + objectArrayResult[currentSelectableIndexInResultArray] = + mapping.getJdbcMapping().getJdbcJavaType().fromString( value); + } + } + + /** + * Callback for OSON values + * @param value the OSON value + * @param <T> the type of the value as returned by OracleJsonParser + */ + public <T> void onOsonValue(T value) { + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( value ); + } + else { + objectArrayResult[currentSelectableIndexInResultArray] = + mapping.getJdbcMapping().convertToDomainValue( + mapping.getJdbcMapping().getJdbcJavaType() + .wrap( value, wrapperOptions ) ); + } + } + + /** + * Callback for OSON binary value + * @param bytes the OSON byters + */ + public void onOsonBinaryValue(byte[] bytes) { + Class underlyingType = null; + Object theOneToBeUsed; + if(subArrayObjectTypes!=null) { + underlyingType = subArrayObjectTypes.getElementType().getJavaType(); + } + else { + underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); + } + + if (java.util.UUID.class.isAssignableFrom( underlyingType )) { + theOneToBeUsed = UUIDJavaType.INSTANCE.wrap( bytes, wrapperOptions ); + } + else { + theOneToBeUsed = bytes; + } + + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( theOneToBeUsed ); + } + else { + objectArrayResult[currentSelectableIndexInResultArray] = theOneToBeUsed; + } + } + + /** + * Callback for OracleJsonParser.Event.VALUE_DATE and OracleJsonParser.Event.VALUE_TIMESTAMP: + * @param localDateTime the time + */ + public void onOsonDateValue(LocalDateTime localDateTime) { + + Class underlyingType = null; + Object theOneToBeUsed = localDateTime; + + if(subArrayObjectTypes!=null) { + underlyingType = subArrayObjectTypes.getElementType().getJavaType(); + } + else { + underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); + } + if (java.sql.Date.class.isAssignableFrom( underlyingType )) { + theOneToBeUsed = Date.valueOf( localDateTime.toLocalDate()); + } + else if (java.time.LocalDate.class.isAssignableFrom( underlyingType )) { + theOneToBeUsed = localDateTime.toLocalDate(); + } + else if (java.sql.Timestamp.class.isAssignableFrom( underlyingType )) { + theOneToBeUsed = Timestamp.valueOf( localDateTime ); + } + + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( theOneToBeUsed ); + } + else { + objectArrayResult[currentSelectableIndexInResultArray] = theOneToBeUsed; + } + } +} From a01918cb950682af0f2f3b722412b10a7a0f5846 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Mon, 6 Jan 2025 14:51:40 +0530 Subject: [PATCH 15/81] HHH-17404: More fixes --- .../dialect/OracleOsonJacksonJdbcType.java | 6 ++--- .../type/format/JsonDocumentHandler.java | 1 + .../jackson/JacksonOsonFormatMapper.java | 27 +++++++++++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index f9d16ec55d96..6bd2e4a7c748 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -87,9 +87,9 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) // modify the interface yet. ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, getEmbeddableMappingType()); - if(getEmbeddableMappingType()!= null) { - return ((JacksonOsonFormatMapper)mapper).toOson(value,javaType,options); - } +// if(getEmbeddableMappingType()!= null) { +// return ((JacksonOsonFormatMapper)mapper).toOson(value,javaType,options,getEmbeddableMappingType()); +// } ByteArrayOutputStream out = new ByteArrayOutputStream(); JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java index 497985faa064..f4a292076360 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java @@ -57,4 +57,5 @@ public interface JsonDocumentHandler { */ void onStringValue(String value); + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 4c858b3a10cb..ca0a49ad608c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -23,10 +23,14 @@ import oracle.sql.json.OracleJsonParser; import oracle.sql.json.OracleJsonTimestamp; import org.hibernate.metamodel.mapping.EmbeddableMappingType; + import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; + +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.format.JsonDocumentHandler; import java.io.ByteArrayOutputStream; @@ -37,6 +41,16 @@ import java.math.BigInteger; import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + /** * @author Emmanuel Jannetti @@ -47,6 +61,7 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { public static final String SHORT_NAME = "jackson"; private ObjectMapper objectMapper; + private EmbeddableMappingType embeddableMappingType; // fields/Methods to retrieve serializer data @@ -195,15 +210,15 @@ public <T> String toString(T value, Type type) { } - public <X>byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) { + public <X>byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options,EmbeddableMappingType embeddableMappingType) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); - serializetoOsonApproach2( value,generator,javaType,options); + serializetoOsonApproach2( value,generator,javaType,options,embeddableMappingType); generator.close(); return out.toByteArray(); } - private <X> void serializetoOsonApproach2(X value, OracleJsonGenerator generator, JavaType<X> javaType, WrapperOptions options) { + private <X> void serializetoOsonApproach2(X value, OracleJsonGenerator generator, JavaType<X> javaType, WrapperOptions options, EmbeddableMappingType embeddableMappingType) { generator.writeStartObject(); serializetoOsonApproach2Util( value, generator, javaType, options,embeddableMappingType ); generator.writeEnd(); @@ -562,11 +577,7 @@ public boolean supportsTargetType(Class<?> targetType) { public void setJacksonObjectMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { this.objectMapper = objectMapper; - // need this until annotation introspector is removed. - if (embeddableMappingType != null) { - this.objectMapper.setAnnotationIntrospector( - new JacksonJakartaAnnotationIntrospector( embeddableMappingType ) ); - } + this.embeddableMappingType = embeddableMappingType; } From bdcad4e8e36a87aba7b08c35c167583eebfb9586 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Tue, 7 Jan 2025 22:27:05 +0530 Subject: [PATCH 16/81] HHH-17404: Serialization for additional types --- .../jackson/JacksonOsonFormatMapper.java | 33 ++++++++++--------- .../ObjectArrayOsonDocumentHandler.java | 8 +++++ 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index ca0a49ad608c..8af5c094fe6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -225,10 +225,10 @@ private <X> void serializetoOsonApproach2(X value, OracleJsonGenerator generator } private <X> void serializetoOsonApproach2Util(X value, - OracleJsonGenerator generator, - JavaType<X> javaType, - WrapperOptions options, - EmbeddableMappingType embeddableMappingType) { + OracleJsonGenerator generator, + JavaType<X> javaType, + WrapperOptions options, + EmbeddableMappingType embeddableMappingType) { final Object[] values = embeddableMappingType.getValues( value ); for ( int i = 0; i < values.length; i++ ) { @@ -296,20 +296,20 @@ private void serializeValue(Object value, generator.write( javaType.unwrap( value,Integer.class,options ) ); break; case SqlTypes.BOOLEAN: - generator.write( (Boolean) value ); + generator.write( javaType.unwrap( value,Boolean.class,options ) ); break; case SqlTypes.BIT: - generator.write( (Integer) value ); + generator.write( javaType.unwrap( value,Integer.class,options ) ); break; case SqlTypes.BIGINT: - generator.write( (BigInteger) value ); + generator.write( javaType.unwrap( value,BigInteger.class,options ) ); break; case SqlTypes.FLOAT: - generator.write( (Float) value ); + generator.write( javaType.unwrap( value,Float.class,options ) ); break; case SqlTypes.REAL: case SqlTypes.DOUBLE: - generator.write( (Double) value ); + generator.write( javaType.unwrap( value,Double.class,options ) ); break; case SqlTypes.CHAR: case SqlTypes.NCHAR: @@ -331,7 +331,7 @@ private void serializeValue(Object value, case SqlTypes.ENUM: case SqlTypes.NAMED_ENUM: // correct? - generator.write( javaType.toString(value) ); + generator.write( javaType.unwrap( value,String.class,options ) ); break; case SqlTypes.DATE: DATE dd = new DATE(javaType.unwrap( value,java.sql.Date.class,options )); @@ -341,7 +341,7 @@ private void serializeValue(Object value, case SqlTypes.TIME: case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_UTC: - generator.write( javaType.toString(value) ); + generator.write( javaType.unwrap( value,String.class,options ) ); break; case SqlTypes.TIMESTAMP: TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( value, Timestamp.class, options )); @@ -371,7 +371,7 @@ else if (value instanceof Instant ) { generator.write(instant.atOffset( ZoneOffset.UTC ) ); break; } - generator.write( javaType.toString(value) ); + generator.write( javaType.unwrap( value,String.class,options ) ); break; case SqlTypes.NUMERIC: case SqlTypes.DECIMAL: @@ -385,16 +385,19 @@ else if (value instanceof Instant ) { break; case SqlTypes.UUID: UUID uuid = javaType.unwrap( value, UUID.class, options ); - byte[] bytes = _asBytes( uuid ); - generator.write( bytes ); + byte[] uuidBytes = _asBytes( uuid ); + generator.write( uuidBytes ); break; case SqlTypes.BINARY: case SqlTypes.VARBINARY: case SqlTypes.LONGVARBINARY: case SqlTypes.LONG32VARBINARY: + byte[] bytes = javaType.unwrap( value, byte[].class, options ); + generator.write( bytes ); + break; case SqlTypes.BLOB: case SqlTypes.MATERIALIZED_BLOB: - + // how to handle break; case SqlTypes.ARRAY: case SqlTypes.JSON_ARRAY: diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java index ecc434a67fa5..cd0f5f9f9df0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java @@ -15,6 +15,7 @@ import java.sql.Date; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.ArrayList; import java.util.List; import java.util.Stack; @@ -231,6 +232,13 @@ else if (java.time.LocalDate.class.isAssignableFrom( underlyingType )) { else if (java.sql.Timestamp.class.isAssignableFrom( underlyingType )) { theOneToBeUsed = Timestamp.valueOf( localDateTime ); } + else if(java.time.LocalTime.class.isAssignableFrom( underlyingType )) { + theOneToBeUsed = localDateTime.toLocalTime(); + } + else if ( java.util.Date.class.isAssignableFrom( underlyingType ) ) { + // better way? + theOneToBeUsed = java.util.Date.from( localDateTime.atZone( ZoneId.of( "UTC" ) ).toInstant()); + } if ( subArrayObjectList != null ) { // dealing with arrays From d8b0288702b16be4c76a7f03ddd1c3762fd5b88a Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Mon, 6 Jan 2025 11:35:53 +0100 Subject: [PATCH 17/81] HHH-17404 : add more date related types to document handler --- .../JacksonJakartaAnnotationIntrospector.java | 130 ------------------ .../ObjectArrayOsonDocumentHandler.java | 13 +- 2 files changed, 10 insertions(+), 133 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java deleted file mode 100644 index 3d1375a856c2..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJakartaAnnotationIntrospector.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.type.format.jackson; - -import com.fasterxml.jackson.databind.PropertyName; -import com.fasterxml.jackson.databind.introspect.Annotated; -import com.fasterxml.jackson.databind.introspect.AnnotatedField; -import com.fasterxml.jackson.databind.introspect.AnnotatedMember; -import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; -import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; -import com.fasterxml.jackson.databind.util.NameTransformer; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.metamodel.mapping.AttributeMappingsList; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; -import org.hibernate.metamodel.mapping.SelectableMapping; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; - -import java.util.HashMap; -import java.util.Map; - -public class JacksonJakartaAnnotationIntrospector extends JacksonAnnotationIntrospector { - - private final EmbeddableMappingType mappingType; - private final Map<String, EmbeddableMappingTypeWithFlattening> mappingTypeMap = new HashMap<>(); - - public JacksonJakartaAnnotationIntrospector(EmbeddableMappingType mappingType) { - this.mappingType = mappingType; - resolveEmbeddableTypes( this.mappingType ); - } - - @Override - public PropertyName findNameForSerialization(Annotated a) { - Column column = _findAnnotation(a, Column.class); - if (column != null && !column.name().isEmpty()) { - return PropertyName.construct(column.name()); - } - return super.findNameForSerialization(a); - } - - @Override - public PropertyName findNameForDeserialization(Annotated a) { - Column column = _findAnnotation(a, Column.class); - if (column != null && !column.name().isEmpty()) { - return PropertyName.construct(column.name()); - } - return super.findNameForDeserialization(a); - } - private String getFieldNameFromGetterName(String fieldName) { - // we assume that method is get|set<camelCase Nme> - // 1. we strip out get|set - // 2. lowercase on first letter - assert fieldName != null; - assert fieldName.substring( 0,3 ).equalsIgnoreCase( "get" ) || - fieldName.substring( 0,3 ).equalsIgnoreCase( "set" ); - fieldName = fieldName.substring( 3 ); - return Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); - } - @Override - public NameTransformer findUnwrappingNameTransformer(AnnotatedMember member) { - if(member instanceof AnnotatedField) { - Embeddable embeddable = member.getType().getRawClass().getAnnotation(Embeddable.class); - if (embeddable != null) { - String propName = member.getName(); - if(mappingTypeMap.get(propName) != null){ - EmbeddableMappingTypeWithFlattening embeddableMappingTypeWithFlattening = - mappingTypeMap.get( propName ); - if(embeddableMappingTypeWithFlattening.isShouldFlatten()) { - return NameTransformer.simpleTransformer( "","" ); - } - } - } - } - else if (member instanceof AnnotatedMethod) { - Embeddable embeddable = member.getType().getRawClass().getAnnotation(Embeddable.class); - if (embeddable != null) { - String propName = getFieldNameFromGetterName(member.getName()); - if(mappingTypeMap.get(propName) != null){ - EmbeddableMappingTypeWithFlattening embeddableMappingTypeWithFlattening = - mappingTypeMap.get( propName ); - if(embeddableMappingTypeWithFlattening.isShouldFlatten()) { - return NameTransformer.simpleTransformer( "","" ); - } - } - } - } - return super.findUnwrappingNameTransformer(member); - } -// theJson - private void resolveEmbeddableTypes(EmbeddableMappingType embeddableMappingType) { - AttributeMappingsList attributeMappings = embeddableMappingType.getAttributeMappings(); - - for (int i = 0; i < attributeMappings.size(); i++){ - AttributeMapping attributeMapping = attributeMappings.get(i); - if ( attributeMapping instanceof EmbeddedAttributeMapping embeddedAttributeMapping ) { - - EmbeddableMappingType attributeEmbeddableMappingType = embeddedAttributeMapping.getMappedType(); - SelectableMapping aggregateMapping = attributeEmbeddableMappingType.getAggregateMapping(); - - mappingTypeMap.put( attributeMapping.getAttributeName(), - new EmbeddableMappingTypeWithFlattening( - attributeEmbeddableMappingType, - aggregateMapping == null )); - - resolveEmbeddableTypes( attributeEmbeddableMappingType); - } - } - } - - static class EmbeddableMappingTypeWithFlattening { - private final EmbeddableMappingType embeddableMappingType; - private final boolean shouldFlatten; - - public EmbeddableMappingTypeWithFlattening(EmbeddableMappingType embeddableMappingType, boolean shouldFlatten) { - this.embeddableMappingType = embeddableMappingType; - this.shouldFlatten = shouldFlatten; - } - - public EmbeddableMappingType getEmbeddableMappingType() { - return embeddableMappingType; - } - - public boolean isShouldFlatten() { - return shouldFlatten; - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java index cd0f5f9f9df0..c95ebd4ee71e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java @@ -13,6 +13,7 @@ import org.hibernate.type.format.JsonDocumentHandler; import java.sql.Date; +import java.sql.Time; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneId; @@ -36,7 +37,7 @@ public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { // mapping definitions are in a tree // Each mapping definition may contain sub mappings (sub embeddable mapping) - // This stack is used to keep pointer on mapping to be used + // This stack is used to keep a pointer on the current mapping to be used // see startObject/endObject methods Stack<EmbeddableMappingType> embeddableMappingTypes = new Stack<>(); @@ -152,11 +153,11 @@ public void onStringValue(String value) { if ( subArrayObjectList != null ) { // dealing with arrays subArrayObjectList.add( - subArrayObjectTypes.getElementType().getJdbcJavaType().fromString( value ) ); + subArrayObjectTypes.getElementType().getJdbcJavaType().fromEncodedString( value ,0,value.length()) ); } else { objectArrayResult[currentSelectableIndexInResultArray] = - mapping.getJdbcMapping().getJdbcJavaType().fromString( value); + mapping.getJdbcMapping().getJdbcJavaType().fromEncodedString( value,0,value.length()); } } @@ -229,6 +230,12 @@ public void onOsonDateValue(LocalDateTime localDateTime) { else if (java.time.LocalDate.class.isAssignableFrom( underlyingType )) { theOneToBeUsed = localDateTime.toLocalDate(); } + else if (java.time.LocalTime.class.isAssignableFrom( underlyingType )) { + theOneToBeUsed = localDateTime.toLocalTime(); + } + else if (java.sql.Time.class.isAssignableFrom( underlyingType )) { + theOneToBeUsed = Time.valueOf( localDateTime.toLocalTime() ); + } else if (java.sql.Timestamp.class.isAssignableFrom( underlyingType )) { theOneToBeUsed = Timestamp.valueOf( localDateTime ); } From fd641691887770400117220fdeb27276372ce411 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Tue, 7 Jan 2025 17:52:28 +0100 Subject: [PATCH 18/81] HHH-17404 : use of BigDecimal in case of DECIMAL OSON event received in document handler --- .../hibernate/type/format/JsonDocumentHandler.java | 5 +++++ .../type/format/jackson/JacksonOsonFormatMapper.java | 11 +++-------- .../jackson/ObjectArrayOsonDocumentHandler.java | 11 +++++++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java index f4a292076360..fd88db2fbd35 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java @@ -8,6 +8,7 @@ * Copyright Red Hat Inc. and Hibernate Authors */ + /** * JSON document handler. * Used to parse JSON documents. Implementors of this will define @@ -57,5 +58,9 @@ public interface JsonDocumentHandler { */ void onStringValue(String value); + /** + * Callback to be called when Number value is encountered. + */ + void onNumberValue(Number value); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 8af5c094fe6f..a86db3aacb87 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -131,13 +131,8 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve handler.onNullValue(); break; case OracleJsonParser.Event.VALUE_DECIMAL: - if (osonParser.isIntegralNumber()) { - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getInt()); - } else { - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getFloat()); - } + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getBigDecimal()); break; case OracleJsonParser.Event.VALUE_DOUBLE: ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( @@ -186,7 +181,7 @@ public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object s consumeOsonTokens(osonParser, osonParser.next(), handler); - return (T)handler.getMappedObjectArray(); + return (T)handler.getObjectArray(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java index c95ebd4ee71e..160df852e1ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java @@ -54,9 +54,9 @@ public ObjectArrayOsonDocumentHandler(EmbeddableMappingType embeddableMappingTyp /** * Gets the Object array built from document handling - * @return the array + * @return the array of objects */ - public Object [] getMappedObjectArray() { + public Object [] getObjectArray() { return this.objectArrayResult; } @@ -65,6 +65,7 @@ public void startObject() { if (currentKeyName != null) { // we are dealing with a sub-object, allocate space for it. // otherwise, we have nothing to do. + // Push the new (sub)mapping definition. currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); assert currentSelectableIndexInResultArray != -1: "Cannot get index of " + currentKeyName; @@ -81,6 +82,7 @@ public void startObject() { @Override public void endObject() { + // go back in the mapping definition tree embeddableMappingTypes.pop(); } @@ -161,6 +163,11 @@ public void onStringValue(String value) { } } + @Override + public void onNumberValue(Number value) { + onOsonValue(value); + } + /** * Callback for OSON values * @param value the OSON value From e2e9607e2248c17ab65536a846e7344f8add2c23 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Tue, 7 Jan 2025 18:30:11 +0100 Subject: [PATCH 19/81] HHH-17404 : fix toString in mapper --- .../jackson/JacksonOsonFormatMapper.java | 28 +++---------------- .../ObjectArrayOsonDocumentHandler.java | 2 ++ 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index a86db3aacb87..e25d740e16fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; @@ -36,7 +35,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Array; -import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; @@ -73,8 +71,11 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { */ public JacksonOsonFormatMapper(ObjectMapper objectMapper) { super(objectMapper); - this.objectMapper = objectMapper; } + + /** + * Creates a new JacksonOsonFormatMapper + */ public JacksonOsonFormatMapper() { super(); } @@ -184,27 +185,6 @@ public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object s return (T)handler.getObjectArray(); } - @Override - public <T> T fromString(CharSequence charSequence, Type type) { - try { - return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( type ) ); - } - catch (JsonProcessingException e) { - throw new IllegalArgumentException( "Could not deserialize string to java type: " + type, e ); - } - } - - @Override - public <T> String toString(T value, Type type) { - try { - return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value ); - } - catch (JsonProcessingException e) { - throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e ); - } - } - - public <X>byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options,EmbeddableMappingType embeddableMappingType) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java index 160df852e1ae..3ce21dbcb743 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java @@ -25,6 +25,8 @@ * Implementation of <code>JsonDocumentHandler</code> for OSON document. * This implementation will produce an Object Array based on * embeddable mapping + * Once All JSON document is handle the mapped Object array can be retrieved using the + * <code>getObjectArray()</code> method. * */ public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { From 4ce35d5652167682a24488fc9b4ea99f6b2603d4 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Wed, 8 Jan 2025 14:42:45 +0530 Subject: [PATCH 20/81] HHH-17404 InternalDS Support for duration --- .../org/hibernate/dialect/OracleDialect.java | 5 +++ .../dialect/OracleDurationJavaType.java | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 738fab6ff7b3..7861da828414 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -92,6 +92,8 @@ import org.hibernate.type.NullType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; +import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; +import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.NullJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType; @@ -833,6 +835,7 @@ protected String columnType(int sqlTypeCode) { protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.registerColumnTypes( typeContributions, serviceRegistry ); final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); + final JavaTypeRegistry javaTypeRegistry = typeContributions.getTypeConfiguration().getJavaTypeRegistry(); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "SYS.XMLTYPE", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "MDSYS.SDO_GEOMETRY", this ) ); @@ -853,6 +856,8 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR // We need the DDL type during runtime to produce the proper encoding in certain functions ddlTypeRegistry.addDescriptor( new DdlTypeImpl( BIT, "number(1,0)", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( DURATION, "interval day to second", this ) ); + javaTypeRegistry.addDescriptor( OracleDurationJavaType.INSTANCE ); + } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java new file mode 100644 index 000000000000..7bff7256d666 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect; + +import oracle.sql.INTERVALDS; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.DurationJavaType; + +import java.time.Duration; + +public class OracleDurationJavaType extends DurationJavaType { + + public static final OracleDurationJavaType INSTANCE = new OracleDurationJavaType(); + + public OracleDurationJavaType() { + super(); + } + + @Override + public <X> Duration wrap(X value, WrapperOptions options) { + if(value == null) { + return null; + } + + if ( value instanceof INTERVALDS ) { + return INTERVALDS.toDuration( ((INTERVALDS) value).toBytes() ); + } + + return super.wrap( value, options ); + } +} From 7c1c121acfb26b885be5b5c8dde6ce914b9cd9ab Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Thu, 9 Jan 2025 13:11:19 +0530 Subject: [PATCH 21/81] HHH-17404-Replace the embeddable serialization approach. --- .../dialect/OracleOsonJacksonJdbcType.java | 7 +- .../aggregate/OracleAggregateSupport.java | 39 +--- .../jackson/JacksonOsonFormatMapper.java | 170 ++++-------------- 3 files changed, 44 insertions(+), 172 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 6bd2e4a7c748..b8f5e1cf33c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -87,9 +87,10 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) // modify the interface yet. ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, getEmbeddableMappingType()); -// if(getEmbeddableMappingType()!= null) { -// return ((JacksonOsonFormatMapper)mapper).toOson(value,javaType,options,getEmbeddableMappingType()); -// } + if(getEmbeddableMappingType()!= null) { + return ((JacksonOsonFormatMapper)mapper).toOson(value,javaType,options,getEmbeddableMappingType()); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index bea29441e55b..d8c7372cee4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -22,7 +22,6 @@ import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectablePath; import org.hibernate.metamodel.mapping.SqlTypedMapping; -import org.hibernate.query.sqm.CastType; import org.hibernate.sql.ast.SqlAstNodeRenderingMode; import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.spi.SqlAppender; @@ -140,21 +139,10 @@ public String aggregateComponentCustomReadExpression( case BIGINT: case CLOB: case NCLOB: - CastType castType = column.getJdbcMapping().getCastType(); - switch ( castType ) { - case INTEGER_BOOLEAN: - if (!dateTypesStoreAsString) { - return template.replace( - placeholder, - "decode(json_value(" + parentPartExpression + columnExpression + "'),'true',1,'false',0,null)" - ); - } - default: - return template.replace( - placeholder, - "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' - ); - } + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' + ); case DATE: if (this.dateTypesStoreAsString) { @@ -257,21 +245,10 @@ public String aggregateComponentCustomReadExpression( "json_query(" + parentPartExpression + columnExpression + "' returning " + jsonTypeName + ")" ); default: - CastType castTypeCharBoolean = column.getJdbcMapping().getCastType(); - switch ( castTypeCharBoolean ){ - case YN_BOOLEAN: - if (!dateTypesStoreAsString) { - return template.replace( - placeholder, - "decode(json_value(" + parentPartExpression + columnExpression + "'),'true','Y','false','N',null)" - ); - } - default: - return template.replace( - placeholder, - "cast(json_value(" + parentPartExpression + columnExpression + "') as " + column.getColumnDefinition() + ')' - ); - } + return template.replace( + placeholder, + "cast(json_value(" + parentPartExpression + columnExpression + "') as " + column.getColumnDefinition() + ')' + ); } case NONE: diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index e25d740e16fc..526daa657bbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -6,12 +6,8 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; -import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider; -import com.fasterxml.jackson.databind.ser.PropertyWriter; import oracle.jdbc.driver.json.tree.OracleJsonDateImpl; import oracle.jdbc.driver.json.tree.OracleJsonTimestampImpl; import oracle.sql.DATE; @@ -22,9 +18,12 @@ import oracle.sql.json.OracleJsonParser; import oracle.sql.json.OracleJsonTimestamp; import org.hibernate.metamodel.mapping.EmbeddableMappingType; - +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; +import org.hibernate.type.BasicType; +import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; @@ -38,15 +37,12 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; - +import java.sql.Time; import java.sql.Timestamp; import java.time.Duration; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneOffset; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; import java.util.UUID; @@ -188,22 +184,22 @@ public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object s public <X>byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options,EmbeddableMappingType embeddableMappingType) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); - serializetoOsonApproach2( value,generator,javaType,options,embeddableMappingType); + serializetoOson( value,generator,javaType,options,embeddableMappingType); generator.close(); return out.toByteArray(); } - private <X> void serializetoOsonApproach2(X value, OracleJsonGenerator generator, JavaType<X> javaType, WrapperOptions options, EmbeddableMappingType embeddableMappingType) { + private <X> void serializetoOson(X value, OracleJsonGenerator generator, JavaType<X> javaType, WrapperOptions options, EmbeddableMappingType embeddableMappingType) { generator.writeStartObject(); - serializetoOsonApproach2Util( value, generator, javaType, options,embeddableMappingType ); + serializetoOsonUtil( value, generator, javaType, options,embeddableMappingType ); generator.writeEnd(); } - private <X> void serializetoOsonApproach2Util(X value, - OracleJsonGenerator generator, - JavaType<X> javaType, - WrapperOptions options, - EmbeddableMappingType embeddableMappingType) { + private <X> void serializetoOsonUtil(X value, + OracleJsonGenerator generator, + JavaType<X> javaType, + WrapperOptions options, + EmbeddableMappingType embeddableMappingType) { final Object[] values = embeddableMappingType.getValues( value ); for ( int i = 0; i < values.length; i++ ) { @@ -213,7 +209,16 @@ private <X> void serializetoOsonApproach2Util(X value, final BasicType<Object> basicType = (BasicType<Object>) attributeMapping.getMappedType(); generator.writeKey( name ); - serializeValue( basicType.convertToRelationalValue( values[i] ), (JavaType<Object>) basicType.getJdbcJavaType(),basicType.getJdbcType(), options,generator); + + if (values[i] == null) { + generator.writeNull(); + continue; + } + serializeValue( basicType.convertToRelationalValue( values[i] ), + (JavaType<Object>) basicType.getJdbcJavaType(), + basicType.getJdbcType(), + options, + generator); } else if (attributeMapping instanceof EmbeddedAttributeMapping) { @@ -225,7 +230,7 @@ else if (attributeMapping instanceof EmbeddedAttributeMapping) { } if (aggregateMapping == null) { // flattened case - serializetoOsonApproach2Util( (X) values[i], + serializetoOsonUtil( (X) values[i], generator, javaType, options, @@ -236,7 +241,7 @@ else if (attributeMapping instanceof EmbeddedAttributeMapping) { final String name = aggregateMapping.getSelectableName(); generator.writeKey( name ); generator.writeStartObject(); - serializetoOsonApproach2Util( (X) values[i], + serializetoOsonUtil( (X) values[i], generator, javaType, options, @@ -316,7 +321,8 @@ private void serializeValue(Object value, case SqlTypes.TIME: case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_UTC: - generator.write( javaType.unwrap( value,String.class,options ) ); + Time time = javaType.unwrap( value, Time.class,options ); + generator.write( time.toString() ); break; case SqlTypes.TIMESTAMP: TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( value, Timestamp.class, options )); @@ -367,12 +373,11 @@ else if (value instanceof Instant ) { case SqlTypes.VARBINARY: case SqlTypes.LONGVARBINARY: case SqlTypes.LONG32VARBINARY: - byte[] bytes = javaType.unwrap( value, byte[].class, options ); - generator.write( bytes ); - break; case SqlTypes.BLOB: case SqlTypes.MATERIALIZED_BLOB: // how to handle + byte[] bytes = javaType.unwrap( value, byte[].class, options ); + generator.write( bytes ); break; case SqlTypes.ARRAY: case SqlTypes.JSON_ARRAY: @@ -420,120 +425,9 @@ private void _appendInt(int value, byte[] buffer, int offset) public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { com.fasterxml.jackson.databind.JavaType jacksonJavaType = objectMapper.constructType( javaType.getJavaType() ); + ObjectWriter writer = objectMapper.writerFor( jacksonJavaType ); + writer.writeValue( (JsonGenerator) target, value); - if(embeddableMappingType == null ) { - ObjectWriter writer = objectMapper.writerFor( jacksonJavaType ); - writer.writeValue( (JsonGenerator) target, value); - return; - } - - DefaultSerializerProvider provider =((DefaultSerializerProvider)objectMapper.getSerializerProvider()) - .createInstance( objectMapper.getSerializationConfig(),objectMapper.getSerializerFactory() ); - JsonSerializer<Object> valueSerializer = provider - .findTypedValueSerializer( jacksonJavaType,true, null ); - serializetoOson(value,valueSerializer,embeddableMappingType,(JsonGenerator)target,objectMapper,provider); - ((JsonGenerator)target).flush(); - - } - - private <T> void serializetoOson(T value, JsonSerializer<Object> valueSerializer, EmbeddableMappingType embeddableMappingType, JsonGenerator target, ObjectMapper objectMapper, DefaultSerializerProvider provider) - throws IOException { - target.writeStartObject(); - serializetoOsonUtil(value,valueSerializer,embeddableMappingType,target,objectMapper,provider); - target.writeEndObject(); - - } - - private <T> void serializetoOsonUtil(T value, - JsonSerializer<Object> valueSerializer, - EmbeddableMappingType embeddableMappingType, - JsonGenerator generator, - ObjectMapper mapper, - DefaultSerializerProvider provider) throws IOException { - final Object[] values = embeddableMappingType.getValues( value ); - - Map<String, BeanPropertyWriter> beanPropertyWriterMap = buildBeanPropertyMap(valueSerializer.properties()); - for ( int i = 0; i < values.length; i++ ) { - final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); - if ( attributeMapping instanceof SelectableMapping ) { - // basic attribute ?? - final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); - - final BasicType<Object> basicType = (BasicType<Object>) attributeMapping.getMappedType(); - BasicValueConverter<?, ?> valueConverter = basicType.getValueConverter(); - JavaType<?> javaType = - valueConverter!=null ? valueConverter.getRelationalJavaType() : attributeMapping.getJavaType(); - generator.writeFieldName( name ); - - BeanPropertyWriter writer = beanPropertyWriterMap.get( ( (BasicAttributeMapping) attributeMapping ).getAttributeName() ); - JsonSerializer<Object> serializer = - provider.findValueSerializer( objectMapper.constructType( javaType.getJavaType() ), writer ); - JsonSerializer<Object> nullSerializer = provider.findNullValueSerializer( null ); - - try { - assert serializer != null; - if ( values[i] == null ) { - nullSerializer.serialize( null, generator, provider ); - } - else { - serializer.serialize( basicType.convertToRelationalValue( values[i] ),generator,provider); - } - - } - catch (Exception e) { - throw new RuntimeException( e ); - } - } - else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { - if ( values[i] == null ) { - // Skipping the update of the separator is on purpose - continue; - } - final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); - final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); - if ( aggregateMapping == null ){ - - JsonSerializer<Object> serializer = provider - .findTypedValueSerializer( objectMapper.constructType( attributeMapping.getJavaType().getJavaType() ),true,null ); - // flattened case - serializetoOsonUtil( (T) values[i], - serializer, - mappingType, - generator, - mapper, - provider); - } - else { - // non flattened case - final String name = aggregateMapping.getSelectableName(); - generator.writeFieldName( name ); - generator.writeStartObject(); - JsonSerializer<Object> serializer = provider - .findTypedValueSerializer( - objectMapper.constructType( attributeMapping.getJavaType().getJavaType() ), - true,null ); - serializetoOsonUtil( (T)values[i], - serializer, - mappingType, - generator, - mapper, - provider); - generator.writeEndObject(); - } - - } - - } - - } - - private Map<String, BeanPropertyWriter> buildBeanPropertyMap(Iterator<PropertyWriter> properties) { - Map<String,BeanPropertyWriter> result = new HashMap<String,BeanPropertyWriter>(); - while ( properties.hasNext() ) { - BeanPropertyWriter writer = (BeanPropertyWriter) properties.next(); - result.put( writer.getName(), writer ); - } - return result; } @Override From e2b8ae5e8f07f7b1d84e9300b60c6bcd5af50791 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 8 Jan 2025 10:57:32 +0100 Subject: [PATCH 22/81] HHH-17404 : fix issue with sub-embeddables --- .../jackson/JacksonOsonFormatMapper.java | 5 +- .../ObjectArrayOsonDocumentHandler.java | 56 +++++++++++-------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 526daa657bbd..4b460c9ef037 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -144,12 +144,11 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve osonParser.getBytes()); break; case OracleJsonParser.Event.START_OBJECT: - handler.startObject( ); - //consumeOsonTokens( osonParser, osonParser.next(), handler); + handler.startObject(); break; case OracleJsonParser.Event.END_OBJECT: handler.endObject(); - return; + break; default: throw new IOException( "Unknown OSON event " + event ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java index 3ce21dbcb743..b8eb3a7c1542 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java @@ -31,17 +31,24 @@ */ public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { + // final result of mapped obejct array private Object [] objectArrayResult; - SelectableMapping mapping = null; + // current mapping to be used + SelectableMapping currentSelectableMapping = null; String currentKeyName = null; List<Object> subArrayObjectList = null; BasicPluralType<?, ?> subArrayObjectTypes = null; // mapping definitions are in a tree // Each mapping definition may contain sub mappings (sub embeddable mapping) - // This stack is used to keep a pointer on the current mapping to be used - // see startObject/endObject methods + // This stack is used to keep a pointer on the current mapping to be used to assign correct types. + // see startObject()/endObject() methods Stack<EmbeddableMappingType> embeddableMappingTypes = new Stack<>(); + // As for mapping definitions, when "sub embeddable" is encountered, the array + // that needs to be filled with Objects is the one we allocate in the final result array slot. + // We use a stack to keep track of array ref + Stack<Object[]> objectArrays = new Stack<>(); + WrapperOptions wrapperOptions; @@ -52,6 +59,7 @@ public ObjectArrayOsonDocumentHandler(EmbeddableMappingType embeddableMappingTyp this.embeddableMappingTypes.push(embeddableMappingType); this.wrapperOptions = wrapperOptions; this.objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()]; + this.objectArrays.push( this.objectArrayResult ); } /** @@ -65,10 +73,10 @@ public ObjectArrayOsonDocumentHandler(EmbeddableMappingType embeddableMappingTyp @Override public void startObject() { if (currentKeyName != null) { - // we are dealing with a sub-object, allocate space for it. + // We are dealing with a sub-object, allocate space for it then, // otherwise, we have nothing to do. // Push the new (sub)mapping definition. - currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); + this.currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); assert currentSelectableIndexInResultArray != -1: "Cannot get index of " + currentKeyName; final SelectableMapping selectable = embeddableMappingTypes.peek().getJdbcValueSelectable( @@ -76,16 +84,18 @@ public void startObject() { final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() .getJdbcType(); final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); - objectArrayResult[currentSelectableIndexInResultArray] = + this.objectArrays.peek()[currentSelectableIndexInResultArray] = new Object[subMappingType.getJdbcValueCount()]; - embeddableMappingTypes.push( subMappingType ); + this.embeddableMappingTypes.push( subMappingType ); + this.objectArrays.push( (Object[]) this.objectArrays.peek()[currentSelectableIndexInResultArray] ); } } @Override public void endObject() { // go back in the mapping definition tree - embeddableMappingTypes.pop(); + this.embeddableMappingTypes.pop(); + this.objectArrays.pop(); } @Override @@ -94,17 +104,17 @@ public void startArray() { // initialize an array to gather values subArrayObjectList = new ArrayList<>(); - assert (mapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) + assert (currentSelectableMapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) : "Array event received for non plural type"; // initialize array's element type - subArrayObjectTypes = (BasicPluralType<?, ?>) mapping.getJdbcMapping(); + subArrayObjectTypes = (BasicPluralType<?, ?>) currentSelectableMapping.getJdbcMapping(); } @Override public void endArray() { assert (subArrayObjectList != null && subArrayObjectTypes != null) : "endArray called before startArray"; // flush array values - objectArrayResult[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, wrapperOptions ); + this.objectArrays.peek()[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, wrapperOptions ); // reset until we encounter next array element subArrayObjectList = null; subArrayObjectTypes = null; @@ -117,7 +127,7 @@ public void onObjectKey(String key) { currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); if ( currentSelectableIndexInResultArray >= 0 ) { // we may not have a selectable mapping for that key - mapping = embeddableMappingTypes.peek().getJdbcValueSelectable( currentSelectableIndexInResultArray ); + currentSelectableMapping = embeddableMappingTypes.peek().getJdbcValueSelectable( currentSelectableIndexInResultArray ); } else { throw new IllegalArgumentException( @@ -137,7 +147,7 @@ public void onNullValue() { subArrayObjectList.add( null ); } else { - objectArrayResult[currentSelectableIndexInResultArray] = null; + this.objectArrays.peek()[currentSelectableIndexInResultArray] = null; } } @@ -148,7 +158,7 @@ public void onBooleanValue(boolean value) { subArrayObjectList.add( value?Boolean.TRUE:Boolean.FALSE); } else { - objectArrayResult[currentSelectableIndexInResultArray] = value?Boolean.TRUE:Boolean.FALSE; + this.objectArrays.peek()[currentSelectableIndexInResultArray] = value?Boolean.TRUE:Boolean.FALSE; } } @@ -160,8 +170,8 @@ public void onStringValue(String value) { subArrayObjectTypes.getElementType().getJdbcJavaType().fromEncodedString( value ,0,value.length()) ); } else { - objectArrayResult[currentSelectableIndexInResultArray] = - mapping.getJdbcMapping().getJdbcJavaType().fromEncodedString( value,0,value.length()); + this.objectArrays.peek()[currentSelectableIndexInResultArray] = + currentSelectableMapping.getJdbcMapping().getJdbcJavaType().fromEncodedString( value,0,value.length()); } } @@ -181,9 +191,9 @@ public <T> void onOsonValue(T value) { subArrayObjectList.add( value ); } else { - objectArrayResult[currentSelectableIndexInResultArray] = - mapping.getJdbcMapping().convertToDomainValue( - mapping.getJdbcMapping().getJdbcJavaType() + this.objectArrays.peek()[currentSelectableIndexInResultArray] = + currentSelectableMapping.getJdbcMapping().convertToDomainValue( + currentSelectableMapping.getJdbcMapping().getJdbcJavaType() .wrap( value, wrapperOptions ) ); } } @@ -199,7 +209,7 @@ public void onOsonBinaryValue(byte[] bytes) { underlyingType = subArrayObjectTypes.getElementType().getJavaType(); } else { - underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); + underlyingType = (Class) currentSelectableMapping.getJdbcMapping().getJdbcJavaType().getJavaType(); } if (java.util.UUID.class.isAssignableFrom( underlyingType )) { @@ -214,7 +224,7 @@ public void onOsonBinaryValue(byte[] bytes) { subArrayObjectList.add( theOneToBeUsed ); } else { - objectArrayResult[currentSelectableIndexInResultArray] = theOneToBeUsed; + this.objectArrays.peek()[currentSelectableIndexInResultArray] = theOneToBeUsed; } } @@ -231,7 +241,7 @@ public void onOsonDateValue(LocalDateTime localDateTime) { underlyingType = subArrayObjectTypes.getElementType().getJavaType(); } else { - underlyingType = (Class) mapping.getJdbcMapping().getJdbcJavaType().getJavaType(); + underlyingType = (Class) currentSelectableMapping.getJdbcMapping().getJdbcJavaType().getJavaType(); } if (java.sql.Date.class.isAssignableFrom( underlyingType )) { theOneToBeUsed = Date.valueOf( localDateTime.toLocalDate()); @@ -261,7 +271,7 @@ else if ( java.util.Date.class.isAssignableFrom( underlyingType ) ) { subArrayObjectList.add( theOneToBeUsed ); } else { - objectArrayResult[currentSelectableIndexInResultArray] = theOneToBeUsed; + this.objectArrays.peek()[currentSelectableIndexInResultArray] = theOneToBeUsed; } } } From aad86becbdc5e1ff2eddaf2758aec3ad373056b3 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 8 Jan 2025 16:34:31 +0100 Subject: [PATCH 23/81] HHH-17404 : remove creation of ObjectMapper in the Oson type to rely on common ones --- .../OracleOsonJacksonArrayJdbcType.java | 49 ++++--------------- .../dialect/OracleOsonJacksonJdbcType.java | 48 +++++------------- .../jackson/JacksonJsonFormatMapper.java | 2 +- .../jackson/JacksonOsonFormatMapper.java | 38 +++++++------- .../NestedStructEmbeddableTest.java | 1 + .../NestedStructWithArrayEmbeddableTest.java | 1 + .../embeddable/StructEmbeddableArrayTest.java | 1 + .../embeddable/StructEmbeddableTest.java | 1 + 8 files changed, 46 insertions(+), 95 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index f75f6977f07a..7016818f61d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -4,8 +4,8 @@ */ package org.hibernate.dialect; +import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; import oracle.sql.json.OracleJsonDatum; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; @@ -20,8 +20,6 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -32,21 +30,20 @@ */ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { - private static Method jacksonOsonObjectMapperGetter = null; - + private static final Class osonFactoryKlass; static { try { - Class jacksonOsonConverter = OracleOsonJacksonJdbcType.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.JacksonOsonConverter" ); - jacksonOsonObjectMapperGetter = jacksonOsonConverter.getMethod( "getObjectMapper" ); + osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); } - catch (ClassNotFoundException | LinkageError | NoSuchMethodException e) { - // should not happen as OracleOsonJacksonJdbcType is loaded - // only when Oracle OSON JDBC extension is present + catch (ClassNotFoundException | LinkageError e) { + // should not happen as OracleOsonJacksonArrayJdbcType is loaded + // only when an Oracle OSON JDBC extension is present // see OracleDialect class. throw new ExceptionInInitializerError( "OracleOsonJacksonArrayJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); } } + public OracleOsonJacksonArrayJdbcType(JdbcType elementJdbcType) { super(elementJdbcType); } @@ -60,28 +57,14 @@ public String toString() { @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { - final ObjectMapper objectMapper; - try { - objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); - } - catch (IllegalAccessException | InvocationTargetException e) { - // should not happen - throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); - } - return new BasicBinder<>( javaType, this ) { private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // TODO : we should not have to do this. - // for now we have to inject the objectMapper. - // As this is not a validated architectural decision, we do not - // modify the interface yet. - ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, null ); ByteArrayOutputStream out = new ByteArrayOutputStream(); - - JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); + JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); + JsonGenerator osonGen = osonFactory.createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); osonGen.close(); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); @@ -114,24 +97,10 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions @Override public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { - final ObjectMapper objectMapper; - try { - objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); - } - catch (IllegalAccessException | InvocationTargetException e) { - // should not happen - throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); - } - return new BasicExtractor<>( javaType, this ) { private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // TODO : we should not have to do this. - // for now we have to inject the objectMapper. - // As this is not a validated architectural decision, we do not - // modify the interface yet. - ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, null ); return mapper.readFromSource( getJavaType(), osonBytes, options); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index b8f5e1cf33c3..fd23c4a9b51e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -4,8 +4,8 @@ */ package org.hibernate.dialect; +import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.ObjectMapper; import oracle.jdbc.OracleType; import oracle.sql.json.OracleJsonDatum; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -21,8 +21,6 @@ import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayOutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -33,20 +31,21 @@ */ public class OracleOsonJacksonJdbcType extends OracleJsonJdbcType { public static final OracleOsonJacksonJdbcType INSTANCE = new OracleOsonJacksonJdbcType( null ); - private static Method jacksonOsonObjectMapperGetter = null; + private static final Class osonFactoryKlass; static { try { - Class jacksonOsonConverter = OracleOsonJacksonJdbcType.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.JacksonOsonConverter" ); - jacksonOsonObjectMapperGetter = jacksonOsonConverter.getMethod( "getObjectMapper" ); + osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); } - catch (ClassNotFoundException | LinkageError | NoSuchMethodException e) { + catch (ClassNotFoundException | LinkageError e) { // should not happen as OracleOsonJacksonJdbcType is loaded // only when Oracle OSON JDBC extension is present // see OracleDialect class. throw new ExceptionInInitializerError( "OracleOsonJacksonJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); } } + + private OracleOsonJacksonJdbcType(EmbeddableMappingType embeddableMappingType) { super( embeddableMappingType ); } @@ -67,14 +66,7 @@ public AggregateJdbcType resolveAggregateJdbcType( @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { - final ObjectMapper objectMapper; - try { - objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); - } - catch (IllegalAccessException | InvocationTargetException e) { - // should not happen - throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); - } + return new BasicBinder<>( javaType, this ) { @@ -82,17 +74,11 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // TODO : we should not have to do this. - // for now we have to inject the objectMapper. - // As this is not a validated architectural decision, we do not - // modify the interface yet. - ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, getEmbeddableMappingType()); - - if(getEmbeddableMappingType()!= null) { - return ((JacksonOsonFormatMapper)mapper).toOson(value,javaType,options,getEmbeddableMappingType()); - } + ((JacksonOsonFormatMapper)mapper).setEmbeddableMappingType( getEmbeddableMappingType()); ByteArrayOutputStream out = new ByteArrayOutputStream(); - JsonGenerator osonGen = objectMapper.getFactory().createGenerator( out ); + JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); + JsonGenerator osonGen = osonFactory.createGenerator( out ); mapper.writeToTarget( value, javaType, osonGen, options ); osonGen.close(); // until now return out.toByteArray(); @@ -125,25 +111,13 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions @Override public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { - final ObjectMapper objectMapper; - try { - objectMapper = (ObjectMapper) jacksonOsonObjectMapperGetter.invoke( null ); - } - catch (IllegalAccessException | InvocationTargetException e) { - // should not happen - throw new RuntimeException("Can't retrieve ObjectMapper from OSON extension", e ); - } - return new BasicExtractor<>( javaType, this ) { private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); // TODO : we should not have to do this. - // for now we have to inject the objectMapper. - // As this is not a validated architectural decision, we do not - // modify the interface yet. - ((JacksonOsonFormatMapper)mapper).setJacksonObjectMapper(objectMapper, getEmbeddableMappingType() ); + ((JacksonOsonFormatMapper)mapper).setEmbeddableMappingType( getEmbeddableMappingType() ); if (getEmbeddableMappingType() != null && getJavaType().getJavaTypeClass() == Object[].class) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index 85f82149b196..ba936e9915a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -22,7 +22,7 @@ public class JacksonJsonFormatMapper extends AbstractJsonFormatMapper { public static final String SHORT_NAME = "jackson"; - private final ObjectMapper objectMapper; + protected final ObjectMapper objectMapper; public JacksonJsonFormatMapper() { this(new ObjectMapper().findAndRegisterModules()); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 4b460c9ef037..d1bd224847b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -54,26 +54,35 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { public static final String SHORT_NAME = "jackson"; - private ObjectMapper objectMapper; - private EmbeddableMappingType embeddableMappingType; - // fields/Methods to retrieve serializer data - - - /** - * Creates a new JacksonOsonFormatMapper - * @param objectMapper the Jackson object mapper - * same as JacksonOsonFormatMapper(objectMapper, null) - */ - public JacksonOsonFormatMapper(ObjectMapper objectMapper) { - super(objectMapper); + private static final Class osonModuleKlass; + static { + try { + osonModuleKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonModule" ); + } + catch (ClassNotFoundException | LinkageError e) { + // should not happen as JacksonOsonFormatMapper is loaded + // only when Oracle OSON JDBC extension is present + // see OracleDialect class. + throw new ExceptionInInitializerError( "JacksonOsonFormatMapper class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); + } } + // TODO : remove the use of this once the OSON writer has been refactor to Document handling + private EmbeddableMappingType embeddableMappingType = null; + /** * Creates a new JacksonOsonFormatMapper */ public JacksonOsonFormatMapper() { super(); + try { + objectMapper.registerModule( (Module) osonModuleKlass.getDeclaredConstructor().newInstance() ); + } + catch (Exception e) { + throw new RuntimeException( "Cannot instanciate " + osonModuleKlass.getCanonicalName(), e ); + } + objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } /** @@ -445,11 +454,6 @@ public boolean supportsTargetType(Class<?> targetType) { return JsonParser.class.isAssignableFrom( targetType ); } - public void setJacksonObjectMapper(ObjectMapper objectMapper, EmbeddableMappingType embeddableMappingType) { - this.objectMapper = objectMapper; - - this.embeddableMappingType = embeddableMappingType; - } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java index 32014e193fb6..3ea0197f0107 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java @@ -79,6 +79,7 @@ @RequiresDialect( PostgreSQLDialect.class ) @RequiresDialect( OracleDialect.class ) @RequiresDialect( DB2Dialect.class ) +@SkipForDialect(dialectClass = OracleDialect.class, reason = "Waiting for the fix of a bug that prevent creation of INTERVALDS from Duration") public class NestedStructEmbeddableTest implements AdditionalMappingContributor { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java index bd9b0bcdde3e..acef9d18de66 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java @@ -56,6 +56,7 @@ @RequiresDialect( PostgreSQLDialect.class ) @RequiresDialect( OracleDialect.class ) +@SkipForDialect(dialectClass = OracleDialect.class, reason = "Waiting for the fix of a bug that prevent creation of INTERVALDS from Duration") @BootstrapServiceRegistry( // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete integrators = SharedDriverManagerTypeCacheClearingIntegrator.class diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java index 28847fee8230..8eb9b5fb2abe 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java @@ -83,6 +83,7 @@ @SessionFactory @RequiresDialect( PostgreSQLDialect.class ) @RequiresDialect( OracleDialect.class ) +@SkipForDialect(dialectClass = OracleDialect.class, reason = "Waiting for the fix of a bug that prevent creation of INTERVALDS from Duration") public class StructEmbeddableArrayTest implements AdditionalMappingContributor { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java index b2caa216381e..2ff564c018eb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableTest.java @@ -76,6 +76,7 @@ @SessionFactory @RequiresDialect( PostgreSQLDialect.class ) @RequiresDialect( OracleDialect.class ) +@SkipForDialect(dialectClass = OracleDialect.class, reason = "Waiting for the fix of a bug that prevent creation of INTERVALDS from Duration") @RequiresDialect( DB2Dialect.class ) public class StructEmbeddableTest implements AdditionalMappingContributor { From 6b14f828f00e464a78f97d67e19c3ef5758b75a9 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 8 Jan 2025 19:50:23 +0100 Subject: [PATCH 24/81] HHH-17404 : fix OsonFactory for extaction --- .../org/hibernate/dialect/OracleOsonJacksonJdbcType.java | 7 ++++++- .../type/format/jackson/JacksonOsonFormatMapper.java | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index fd23c4a9b51e..22937d92bea1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; import oracle.sql.json.OracleJsonDatum; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -134,7 +135,11 @@ private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { // We are dealing with embeddable (@Embeddable) type = (JavaType<X>) getEmbeddableMappingType().getJavaType(); } - return mapper.readFromSource( type, osonBytes, options ); + + JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); + JsonParser osonParser = osonFactory.createParser( osonBytes ); + + return mapper.readFromSource( type, osonParser, options ); } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index d1bd224847b3..08887f72595c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -440,8 +440,8 @@ public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, Wrap @Override public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); - return objectMapper.readValue( osonParser, objectMapper.constructType( javaType.getJavaType()) ); + //JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); + return objectMapper.readValue( (JsonParser)source, objectMapper.constructType( javaType.getJavaType()) ); } @Override @@ -451,7 +451,7 @@ public boolean supportsSourceType(Class<?> sourceType) { @Override public boolean supportsTargetType(Class<?> targetType) { - return JsonParser.class.isAssignableFrom( targetType ); + return JsonGenerator.class.isAssignableFrom( targetType ); } From 3518b1b9e477542f127b24e6547248185fba787c Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Thu, 9 Jan 2025 08:41:22 +0100 Subject: [PATCH 25/81] HHH-17404 : fix OracleDurationJdbcType --- .../dialect/OracleDurationJdbcType.java | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java index b6a9323aaebb..f29bc723f4aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -4,14 +4,18 @@ */ package org.hibernate.dialect; +import oracle.jdbc.OracleType; +import org.hibernate.engine.jdbc.Size; import org.hibernate.type.SqlTypes; +import org.hibernate.type.Type; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; -import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.DurationJdbcType; +import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import java.sql.CallableStatement; import java.sql.PreparedStatement; @@ -19,11 +23,13 @@ import java.sql.SQLException; import java.time.Duration; -public class OracleDurationJdbcType implements JdbcType { +public class OracleDurationJdbcType extends DurationJdbcType { public static final OracleDurationJdbcType INSTANCE = new OracleDurationJdbcType(); - public OracleDurationJdbcType() {} + public OracleDurationJdbcType() { + super(); + } @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { @@ -80,13 +86,38 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o }; } - @Override + /** + * The {@linkplain SqlTypes JDBC type code} used when interacting with JDBC APIs. + * <p> + * For example, it's used when calling {@link java.sql.PreparedStatement#setNull(int, int)}. + * + * @return a JDBC type code + */ public int getJdbcTypeCode() { return SqlTypes.DURATION; } - - @Override + /** + * A {@linkplain SqlTypes JDBC type code} that identifies the SQL column type to + * be used for schema generation. + * <p> + * This value is passed to {@link DdlTypeRegistry#getTypeName(int, Size, Type)} + * to obtain the SQL column type. + * + * @return a JDBC type code + * @since 6.2 + */ + public int getDdlTypeCode() { + return OracleType.INTERVAL_DAY_TO_SECOND.getVendorTypeNumber(); + } + /** + * A {@linkplain SqlTypes JDBC type code} that identifies the SQL column type. + * <p> + * This value might be different from {@link #getDdlTypeCode()} if the actual type + * e.g. JSON is emulated through a type like CLOB. + * + * @return a JDBC type code + */ public int getDefaultSqlTypeCode() { - return SqlTypes.DURATION; + return OracleType.INTERVAL_DAY_TO_SECOND.getVendorTypeNumber(); } } From 6cac337f58673dbcfde7f22a9f579cac05a80f24 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Thu, 9 Jan 2025 22:29:27 +0530 Subject: [PATCH 26/81] HHH-17404 - Fix Nested Embeddable Tests --- .../java/org/hibernate/dialect/OracleDialect.java | 3 +++ .../hibernate/dialect/OracleDurationJdbcType.java | 5 ++--- .../dialect/OracleOsonJacksonJdbcType.java | 13 +++++++++++-- .../dialect/aggregate/OracleAggregateSupport.java | 13 +++++++++++++ .../format/jackson/JacksonOsonFormatMapper.java | 11 +++++------ 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 7861da828414..34d205cada78 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -826,6 +826,9 @@ protected String columnType(int sqlTypeCode) { case VARBINARY: return "raw($l)"; + case DURATION: + return "interval day to second"; + default: return super.columnType( sqlTypeCode ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java index f29bc723f4aa..6170d96dc184 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -4,7 +4,6 @@ */ package org.hibernate.dialect; -import oracle.jdbc.OracleType; import org.hibernate.engine.jdbc.Size; import org.hibernate.type.SqlTypes; import org.hibernate.type.Type; @@ -107,7 +106,7 @@ public int getJdbcTypeCode() { * @since 6.2 */ public int getDdlTypeCode() { - return OracleType.INTERVAL_DAY_TO_SECOND.getVendorTypeNumber(); + return SqlTypes.DURATION; } /** * A {@linkplain SqlTypes JDBC type code} that identifies the SQL column type. @@ -118,6 +117,6 @@ public int getDdlTypeCode() { * @return a JDBC type code */ public int getDefaultSqlTypeCode() { - return OracleType.INTERVAL_DAY_TO_SECOND.getVendorTypeNumber(); + return SqlTypes.DURATION; } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 22937d92bea1..c9f1de066d20 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -158,8 +158,17 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { // can I use rs.getBinaryStream( paramIndex); ? - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - return doExtraction(ojd,options); + try { + if(javaType.getJavaTypeClass().isAssignableFrom( String.class )) { + return fromString( rs.getString( 1 ),javaType,options ); + } + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction(ojd,options); + }catch (SQLException e) { + + throw new SQLException( e ); + } + } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index d8c7372cee4b..77eded5f0959 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -178,6 +178,19 @@ public String aggregateComponentCustomReadExpression( "json_value(" + parentPartExpression + columnExpression + "' returning timestamp)" ); } + case DURATION: + if (this.dateTypesStoreAsString) { + return template.replace( + placeholder, + "cast(json_value(" + parentPartExpression + columnExpression + "') as " + column.getColumnDefinition() + ')' + ); + } + else { + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "' returning interval day to second)" + ); + } case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_UTC: if (this.dateTypesStoreAsString) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 08887f72595c..5f1c4164ecc4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -6,8 +6,9 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializationFeature; import oracle.jdbc.driver.json.tree.OracleJsonDateImpl; import oracle.jdbc.driver.json.tree.OracleJsonTimestampImpl; import oracle.sql.DATE; @@ -242,7 +243,7 @@ else if (attributeMapping instanceof EmbeddedAttributeMapping) { generator, javaType, options, - embeddableMappingType ); + mappingType ); } else { // non flattened case @@ -253,7 +254,7 @@ else if (attributeMapping instanceof EmbeddedAttributeMapping) { generator, javaType, options, - embeddableMappingType); + mappingType); generator.writeEnd(); } @@ -432,15 +433,13 @@ private void _appendInt(int value, byte[] buffer, int offset) @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { - com.fasterxml.jackson.databind.JavaType jacksonJavaType = objectMapper.constructType( javaType.getJavaType() ); - ObjectWriter writer = objectMapper.writerFor( jacksonJavaType ); + ObjectWriter writer = objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ); writer.writeValue( (JsonGenerator) target, value); } @Override public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - //JsonParser osonParser = objectMapper.getFactory().createParser( (byte[]) source ); return objectMapper.readValue( (JsonParser)source, objectMapper.constructType( javaType.getJavaType()) ); } From 3a3f9c4554da662e75fca7e9ce15114ae8393d94 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Fri, 10 Jan 2025 13:21:45 +0530 Subject: [PATCH 27/81] HHH-17404- Fix Array Embeddables. --- .../hibernate/dialect/OracleOsonJacksonArrayJdbcType.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index 7016818f61d0..ea8fc7eb528e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; import oracle.sql.json.OracleJsonDatum; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; @@ -100,9 +101,11 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { - FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - return mapper.readFromSource( getJavaType(), osonBytes, options); + FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); + JsonParser osonParser = osonFactory.createParser( osonBytes ); + return mapper.readFromSource( getJavaType(), osonParser, options); } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { From 5ae78384130f7cc5a90ed27f209766019c67801d Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Fri, 10 Jan 2025 18:54:36 +0530 Subject: [PATCH 28/81] HHH-17404- Additional Fixes for Duration. --- .../java/org/hibernate/dialect/OracleDurationJdbcType.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java index 6170d96dc184..fe6d7c3bc738 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -4,6 +4,7 @@ */ package org.hibernate.dialect; +import oracle.jdbc.OracleTypes; import org.hibernate.engine.jdbc.Size; import org.hibernate.type.SqlTypes; import org.hibernate.type.Type; @@ -106,7 +107,7 @@ public int getJdbcTypeCode() { * @since 6.2 */ public int getDdlTypeCode() { - return SqlTypes.DURATION; + return OracleTypes.INTERVALDS; } /** * A {@linkplain SqlTypes JDBC type code} that identifies the SQL column type. @@ -117,6 +118,6 @@ public int getDdlTypeCode() { * @return a JDBC type code */ public int getDefaultSqlTypeCode() { - return SqlTypes.DURATION; + return OracleTypes.INTERVALDS; } } From 9dcf37de2bfb38e1c7429093ea7b409f4983c465 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Sat, 11 Jan 2025 23:35:39 +0530 Subject: [PATCH 29/81] HHH-17404 - Add JSON_ARRAY serializer --- .../OracleOsonJacksonArrayJdbcType.java | 36 +++++- .../jackson/JacksonOsonFormatMapper.java | 122 ++++++++++++++---- 2 files changed, 129 insertions(+), 29 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index ea8fc7eb528e..692918f1e298 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -5,16 +5,21 @@ package org.hibernate.dialect; import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import oracle.sql.json.OracleJsonDatum; +import oracle.sql.json.OracleJsonFactory; +import oracle.sql.json.OracleJsonGenerator; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; @@ -55,6 +60,26 @@ public String toString() { return "OracleOsonJacksonArrayJdbcType"; } + private <X> void toOson(FormatMapper mapper, + X value, + JavaType<X> javaType, + OracleJsonGenerator osonGen, + WrapperOptions options) { + final JdbcType elementJdbcType = getElementJdbcType(); + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { + final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); +// return JsonHelper.arrayToString( embeddableMappingType, domainObjects, options ); + ((JacksonOsonFormatMapper)mapper).arrayToOson(osonGen,embeddableMappingType,domainObjects,options); + } + else { + assert !( elementJdbcType instanceof AggregateJdbcType); + final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); +// return JsonHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options ); + ((JacksonOsonFormatMapper)mapper).arrayToOson(osonGen,elementJavaType,elementJdbcType,domainObjects,options); + } + } + @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { @@ -64,9 +89,12 @@ private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions opt FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); ByteArrayOutputStream out = new ByteArrayOutputStream(); - JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); - JsonGenerator osonGen = osonFactory.createGenerator( out ); - mapper.writeToTarget( value, javaType, osonGen, options ); +// JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); +// JsonGenerator osonGen = osonFactory.createGenerator( out ); + OracleJsonFactory oracleJsonFactory = new OracleJsonFactory(); + OracleJsonGenerator osonGen = oracleJsonFactory.createJsonBinaryGenerator( out ); + ((OracleOsonJacksonArrayJdbcType)getJdbcType()).toOson(mapper,value,javaType,osonGen,options); +// mapper.writeToTarget( value, javaType, osonGen, options ); osonGen.close(); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); return in; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 5f1c4164ecc4..714f0c80f9e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -19,6 +19,7 @@ import oracle.sql.json.OracleJsonParser; import oracle.sql.json.OracleJsonTimestamp; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; @@ -27,7 +28,7 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; - +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.format.JsonDocumentHandler; @@ -190,44 +191,115 @@ public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object s return (T)handler.getObjectArray(); } - public <X>byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options,EmbeddableMappingType embeddableMappingType) { + public <X>byte[] toOson(X value, WrapperOptions options,EmbeddableMappingType embeddableMappingType) { ByteArrayOutputStream out = new ByteArrayOutputStream(); OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); - serializetoOson( value,generator,javaType,options,embeddableMappingType); + toOson( value,generator,options,embeddableMappingType); generator.close(); return out.toByteArray(); } - private <X> void serializetoOson(X value, OracleJsonGenerator generator, JavaType<X> javaType, WrapperOptions options, EmbeddableMappingType embeddableMappingType) { + public void arrayToOson(OracleJsonGenerator osonGen, + EmbeddableMappingType elementMappingType, + Object[] values, + WrapperOptions options) { + if ( values.length == 0 ) { + osonGen.writeStartArray(); + osonGen.writeEnd(); + return; + } + osonGen.writeStartArray(); + for ( Object value : values ) { + toOson( elementMappingType, value, options, osonGen); + } + osonGen.writeEnd(); + } + + private <X>void toOson(MappingType mappedType, + Object value, WrapperOptions options, OracleJsonGenerator osonGen) { + if (value == null) { + osonGen.writeNull(); + } + else if ( mappedType instanceof EmbeddableMappingType ) { + toOson( (X) value, osonGen, options,(EmbeddableMappingType) mappedType ); + } + else if ( mappedType instanceof BasicType<?> ) { + //noinspection unchecked + final BasicType<Object> basicType = (BasicType<Object>) mappedType; + convertedBasicValueToOson(basicType.convertToRelationalValue( value ), + options, osonGen, basicType); + } + else { + throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + mappedType.getClass().getName() ); + } + } + + private void convertedBasicValueToOson(Object value, + WrapperOptions options, + OracleJsonGenerator osonGen, + BasicType<Object> basicType) { + serializeValue( + value, + (JavaType<Object>) basicType.getJdbcJavaType(), + basicType.getJdbcType(), + options, + osonGen + ); + } + + public void arrayToOson(OracleJsonGenerator osonGen, + JavaType<?> elementJavaType, + JdbcType elementJdbcType, + Object[] values, + WrapperOptions options) { + if ( values.length == 0 ) { + osonGen.writeStartArray(); + osonGen.writeEnd(); + } + + osonGen.writeStartArray(); + for ( Object value : values ) { + //noinspection unchecked + convertedValueToOson((JavaType<Object>) elementJavaType, elementJdbcType, value, options, osonGen); + } + osonGen.writeEnd(); + } + + private void convertedValueToOson(JavaType<Object> javaType, + JdbcType jdbcType, + Object value, + WrapperOptions options, + OracleJsonGenerator osonGen) { + if ( value == null ) { + osonGen.writeNull(); + } + else if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) { + toOson(value, osonGen, options, aggregateJdbcType.getEmbeddableMappingType()); + } + else { + serializeValue( value, javaType, jdbcType, options, osonGen ); + } + } + + private <X> void toOson(X value, OracleJsonGenerator generator, WrapperOptions options, EmbeddableMappingType embeddableMappingType) { generator.writeStartObject(); - serializetoOsonUtil( value, generator, javaType, options,embeddableMappingType ); + toOsonUtil( value, generator, options,embeddableMappingType ); generator.writeEnd(); } - private <X> void serializetoOsonUtil(X value, - OracleJsonGenerator generator, - JavaType<X> javaType, - WrapperOptions options, - EmbeddableMappingType embeddableMappingType) { + private <X> void toOsonUtil(X value, + OracleJsonGenerator generator, + WrapperOptions options, + EmbeddableMappingType embeddableMappingType) { final Object[] values = embeddableMappingType.getValues( value ); for ( int i = 0; i < values.length; i++ ) { final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); if ( attributeMapping instanceof SelectableMapping ) { final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); - final BasicType<Object> basicType = (BasicType<Object>) attributeMapping.getMappedType(); generator.writeKey( name ); - - if (values[i] == null) { - generator.writeNull(); - continue; - } - serializeValue( basicType.convertToRelationalValue( values[i] ), - (JavaType<Object>) basicType.getJdbcJavaType(), - basicType.getJdbcType(), - options, - generator); + toOson( attributeMapping.getMappedType(), values[i], options,generator ); } else if (attributeMapping instanceof EmbeddedAttributeMapping) { @@ -239,9 +311,8 @@ else if (attributeMapping instanceof EmbeddedAttributeMapping) { } if (aggregateMapping == null) { // flattened case - serializetoOsonUtil( (X) values[i], + toOsonUtil( (X) values[i], generator, - javaType, options, mappingType ); } @@ -250,9 +321,8 @@ else if (attributeMapping instanceof EmbeddedAttributeMapping) { final String name = aggregateMapping.getSelectableName(); generator.writeKey( name ); generator.writeStartObject(); - serializetoOsonUtil( (X) values[i], + toOsonUtil( (X) values[i], generator, - javaType, options, mappingType); generator.writeEnd(); @@ -455,4 +525,6 @@ public boolean supportsTargetType(Class<?> targetType) { + + } From 4a001e2518e7fbaf17d564e5665dec504e691fe1 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Wed, 15 Jan 2025 10:33:51 +0530 Subject: [PATCH 30/81] HHH-17404- Fix duration types --- .../org/hibernate/dialect/OracleDialect.java | 5 ++++- .../dialect/OracleDurationJdbcType.java | 22 +++++++++++++++++-- .../dialect/OracleOsonJacksonJdbcType.java | 6 +++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 34d205cada78..170d2fb426d0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -858,7 +858,7 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR } // We need the DDL type during runtime to produce the proper encoding in certain functions ddlTypeRegistry.addDescriptor( new DdlTypeImpl( BIT, "number(1,0)", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( DURATION, "interval day to second", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( oracle.jdbc.OracleTypes.INTERVALDS, "interval day to second", this ) ); javaTypeRegistry.addDescriptor( OracleDurationJavaType.INSTANCE ); } @@ -892,6 +892,8 @@ public JdbcType resolveSqlTypeDescriptor( switch ( jdbcTypeCode ) { case OracleTypes.JSON: return jdbcTypeRegistry.getDescriptor( JSON ); + case oracle.jdbc.OracleTypes.INTERVALDS: + return jdbcTypeRegistry.getDescriptor( DURATION ); case STRUCT: if ( "MDSYS.SDO_GEOMETRY".equals( columnTypeName ) ) { jdbcTypeCode = GEOMETRY; @@ -1070,6 +1072,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry typeContributions.contributeJdbcType( ObjectNullAsNullTypeJdbcType.INSTANCE ); // Oracle Stores the duration is ISO-8601 format. typeContributions.contributeJdbcType( OracleDurationJdbcType.INSTANCE ); + typeContributions.contributeJavaType( OracleDurationJavaType.INSTANCE ); // Until we remove StandardBasicTypes, we have to keep this typeContributions.contributeType( diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java index fe6d7c3bc738..3530b10c801d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -14,7 +14,8 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; -import org.hibernate.type.descriptor.jdbc.DurationJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import java.sql.CallableStatement; @@ -23,7 +24,7 @@ import java.sql.SQLException; import java.time.Duration; -public class OracleDurationJdbcType extends DurationJdbcType { +public class OracleDurationJdbcType implements JdbcType { public static final OracleDurationJdbcType INSTANCE = new OracleDurationJdbcType(); @@ -120,4 +121,21 @@ public int getDdlTypeCode() { public int getDefaultSqlTypeCode() { return OracleTypes.INTERVALDS; } + + @Override + public Class<?> getPreferredJavaTypeClass(WrapperOptions options) { + return Duration.class; + } + + @Override + public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) { + return (appender, value, dialect, wrapperOptions) -> dialect.appendIntervalLiteral( + appender, + javaType.unwrap( + value, + Duration.class, + wrapperOptions + ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index c9f1de066d20..23788df123ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -159,12 +159,14 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { // can I use rs.getBinaryStream( paramIndex); ? try { - if(javaType.getJavaTypeClass().isAssignableFrom( String.class )) { - return fromString( rs.getString( 1 ),javaType,options ); + if(javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class) { + return fromString( rs.getString( paramIndex ),javaType,options ); } + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); return doExtraction(ojd,options); }catch (SQLException e) { + // fallback to string if possible throw new SQLException( e ); } From 5824d23b3e99ae3c034fe57f3b16db5b19c4f4d1 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Tue, 14 Jan 2025 18:29:29 +0100 Subject: [PATCH 31/81] HHH-17404 : add JSON document handler for serialization --- .../internal/StrategySelectorBuilder.java | 24 +- .../OracleOsonJacksonArrayJdbcType.java | 50 +-- .../dialect/OracleOsonJacksonJdbcType.java | 29 +- .../descriptor/jdbc/JsonArrayJdbcType.java | 8 +- .../type/descriptor/jdbc/JsonHelper.java | 244 ++++++++---- .../type/descriptor/jdbc/JsonJdbcType.java | 13 +- .../type/format/JsonDocumentHandler.java | 12 +- .../type/format/JsonDocumentWriter.java | 83 ++++ .../ObjectArrayOsonDocumentHandler.java | 27 +- .../format/ObjectArrayOsonDocumentWriter.java | 241 ++++++++++++ .../type/format/StringJsonDocumentWriter.java | 358 ++++++++++++++++++ .../jackson/JacksonOsonFormatMapper.java | 247 +++--------- .../embeddable/NestedJsonEmbeddableTest.java | 1 + 13 files changed, 968 insertions(+), 369 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java rename hibernate-core/src/main/java/org/hibernate/type/format/{jackson => }/ObjectArrayOsonDocumentHandler.java (94%) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index 77150de87f70..480f746a5d61 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -45,6 +45,7 @@ import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.jackson.JacksonIntegration; import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import org.hibernate.type.format.jackson.JacksonXmlFormatMapper; @@ -302,21 +303,24 @@ private static void addCacheKeysFactories(StrategySelectorImpl strategySelector) } private static void addJsonFormatMappers(StrategySelectorImpl strategySelector) { - strategySelector.registerStrategyImplementor( - FormatMapper.class, - JacksonJsonFormatMapper.SHORT_NAME, - JacksonJsonFormatMapper.class - ); strategySelector.registerStrategyImplementor( FormatMapper.class, JsonBJsonFormatMapper.SHORT_NAME, JsonBJsonFormatMapper.class ); - strategySelector.registerStrategyImplementor( - FormatMapper.class, - JacksonOsonFormatMapper.SHORT_NAME, - JacksonOsonFormatMapper.class - ); + if ( JacksonIntegration.isOracleOsonExtensionAvailable() ) { + strategySelector.registerStrategyImplementor( + FormatMapper.class, + JacksonOsonFormatMapper.SHORT_NAME, + JacksonOsonFormatMapper.class + ); + } else { + strategySelector.registerStrategyImplementor( + FormatMapper.class, + JacksonJsonFormatMapper.SHORT_NAME, + JacksonJsonFormatMapper.class + ); + } } private static void addXmlFormatMappers(StrategySelectorImpl strategySelector) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index 692918f1e298..3af61871895a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -7,24 +7,17 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import oracle.sql.json.OracleJsonDatum; -import oracle.sql.json.OracleJsonFactory; -import oracle.sql.json.OracleJsonGenerator; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.sql.CallableStatement; import java.sql.PreparedStatement; @@ -33,10 +26,13 @@ /** * @author Emmanuel Jannetti + * @author Bidyadhar Mohanty */ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { + private static final Class osonFactoryKlass; + static { try { osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); @@ -45,7 +41,7 @@ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { // should not happen as OracleOsonJacksonArrayJdbcType is loaded // only when an Oracle OSON JDBC extension is present // see OracleDialect class. - throw new ExceptionInInitializerError( "OracleOsonJacksonArrayJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); + throw new ExceptionInInitializerError( "OracleOsonJacksonArrayJdbcType class loaded without OSON extension: " + e.getClass()+ " " + e.getMessage()); } } @@ -60,50 +56,21 @@ public String toString() { return "OracleOsonJacksonArrayJdbcType"; } - private <X> void toOson(FormatMapper mapper, - X value, - JavaType<X> javaType, - OracleJsonGenerator osonGen, - WrapperOptions options) { - final JdbcType elementJdbcType = getElementJdbcType(); - final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); - if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { - final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); -// return JsonHelper.arrayToString( embeddableMappingType, domainObjects, options ); - ((JacksonOsonFormatMapper)mapper).arrayToOson(osonGen,embeddableMappingType,domainObjects,options); - } - else { - assert !( elementJdbcType instanceof AggregateJdbcType); - final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); -// return JsonHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options ); - ((JacksonOsonFormatMapper)mapper).arrayToOson(osonGen,elementJavaType,elementJdbcType,domainObjects,options); - } - } @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { return new BasicBinder<>( javaType, this ) { - private <X> InputStream toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { + private <X> InputStream toOsonStream(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); -// JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); -// JsonGenerator osonGen = osonFactory.createGenerator( out ); - OracleJsonFactory oracleJsonFactory = new OracleJsonFactory(); - OracleJsonGenerator osonGen = oracleJsonFactory.createJsonBinaryGenerator( out ); - ((OracleOsonJacksonArrayJdbcType)getJdbcType()).toOson(mapper,value,javaType,osonGen,options); -// mapper.writeToTarget( value, javaType, osonGen, options ); - osonGen.close(); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - return in; + return new ByteArrayInputStream(((JacksonOsonFormatMapper)mapper).arrayToOson(value, javaType,getElementJdbcType(),options)); } @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { try { - st.setBinaryStream( index, toOson( value, getJavaType(), options ) ); + st.setBinaryStream( index, toOsonStream( value, getJavaType(), options ) ); } catch (Exception e) { throw new SQLException( e ); @@ -114,7 +81,7 @@ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions o protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { try { - st.setBinaryStream( name, toOson( value, getJavaType(), options ) ); + st.setBinaryStream( name, toOsonStream( value, getJavaType(), options ) ); } catch (Exception e) { throw new SQLException( e ); @@ -129,7 +96,6 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { - FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); JsonParser osonParser = osonFactory.createParser( osonBytes ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 23788df123ab..7acf03ab6e22 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -74,9 +74,11 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // TODO : we should not have to do this. - ((JacksonOsonFormatMapper)mapper).setEmbeddableMappingType( getEmbeddableMappingType()); + if (getEmbeddableMappingType()!= null) { + return ((JacksonOsonFormatMapper)mapper).fromObjectArray(value,javaType,options,getEmbeddableMappingType()); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); JsonGenerator osonGen = osonFactory.createGenerator( out ); @@ -112,13 +114,15 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions @Override public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { + if (javaType.getJavaTypeClass().isAssignableFrom( String.class )) { + return super.getExtractor(javaType); + } + return new BasicExtractor<>( javaType, this ) { private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - // TODO : we should not have to do this. - ((JacksonOsonFormatMapper)mapper).setEmbeddableMappingType( getEmbeddableMappingType() ); if (getEmbeddableMappingType() != null && getJavaType().getJavaTypeClass() == Object[].class) { @@ -157,25 +161,13 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - // can I use rs.getBinaryStream( paramIndex); ? - try { - if(javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class) { - return fromString( rs.getString( paramIndex ),javaType,options ); - } - - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - return doExtraction(ojd,options); - }catch (SQLException e) { - // fallback to string if possible - - throw new SQLException( e ); - } + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction(ojd,options); } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - // can I use rs.getBinaryStream( paramIndex); ? OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); return doExtraction(ojd,options); } @@ -183,7 +175,6 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - // can I use rs.getBinaryStream( paramIndex); ? OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); return doExtraction(ojd,options); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java index 1b64926ad1f0..6e2b3e640dab 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java @@ -16,6 +16,7 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.StringJsonDocumentWriter; /** * Specialized type mapping for {@code JSON_ARRAY} and the JSON ARRAY SQL data type. @@ -64,15 +65,18 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) { final JdbcType elementJdbcType = getElementJdbcType(); final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); - return JsonHelper.arrayToString( embeddableMappingType, domainObjects, options ); + JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer); } else { assert !( elementJdbcType instanceof AggregateJdbcType ); final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); - return JsonHelper.arrayToString( elementJavaType, elementJdbcType, domainObjects, options ); + JsonHelper.serializeArray( elementJavaType, elementJdbcType, domainObjects, options, writer ); } + return sb.toString(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 562249a97197..254b5fb04e91 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -5,6 +5,7 @@ package org.hibernate.type.descriptor.jdbc; +import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Array; import java.sql.SQLException; @@ -40,7 +41,10 @@ import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; - +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.format.JsonDocumentWriter; import static org.hibernate.type.descriptor.jdbc.StructHelper.getEmbeddedPart; import static org.hibernate.type.descriptor.jdbc.StructHelper.instantiate; @@ -59,41 +63,144 @@ public static String toString(EmbeddableMappingType embeddableMappingType, Objec return sb.toString(); } - public static String arrayToString(MappingType elementMappingType, Object[] values, WrapperOptions options) { + + public static void serializeArray(MappingType elementMappingType, Object[] values, WrapperOptions options, JsonDocumentWriter writer) { + writer.startArray(); if ( values.length == 0 ) { - return "[]"; + writer.endArray(); + return; } - final StringBuilder sb = new StringBuilder(); - final JsonAppender jsonAppender = new JsonAppender( sb ); - char separator = '['; for ( Object value : values ) { - sb.append( separator ); - toString( elementMappingType, value, options, jsonAppender ); - separator = ','; + try { + if (value == null) { + writer.nullValue(); + } + else { + JsonHelper.serialize( (EmbeddableMappingType) elementMappingType, value, options, writer ); + } + } + catch (IOException e) { + // TODO : do better than this + throw new RuntimeException( e ); + } } - sb.append( ']' ); - return sb.toString(); + writer.endArray(); } - public static String arrayToString( - JavaType<?> elementJavaType, - JdbcType elementJdbcType, - Object[] values, - WrapperOptions options) { + public static void serializeArray(JavaType<?> elementJavaType, JdbcType elementJdbcType, Object[] values, WrapperOptions options, JsonDocumentWriter writer) { + writer.startArray(); if ( values.length == 0 ) { - return "[]"; + writer.endArray(); + return; } - final StringBuilder sb = new StringBuilder(); - final JsonAppender jsonAppender = new JsonAppender( sb ); - char separator = '['; for ( Object value : values ) { - sb.append( separator ); - //noinspection unchecked - convertedValueToString( (JavaType<Object>) elementJavaType, elementJdbcType, value, options, jsonAppender ); - separator = ','; + if (value == null) { + writer.nullValue(); + } + else { + writer.serializeJsonValue( value ,(JavaType<Object>) elementJavaType,elementJdbcType,options); + } + } + writer.endArray(); + } + + /** + * Checks that a JDBCType is assignable to an array + * @param type the jdbc type + * @return <code>true</code> if types is of array kind <code>false</code> otherwise. + */ + private static boolean isArrayType(JdbcType type) { + return (type.getDefaultSqlTypeCode() == SqlTypes.ARRAY || + type.getDefaultSqlTypeCode() == SqlTypes.JSON_ARRAY); + } + + /** + * Serialized an Object value to a JSON document writer. + * + * @param embeddableMappingType the embeddable mapping definition of the given value. + * @param domainValue the value to be serialized. + * @param options wrapping options + * @param writer the document writer + * @throws IOException if the underlying writer failed to serialize a mpped value or failed to perform need I/O. + */ + public static void serialize(EmbeddableMappingType embeddableMappingType, + Object domainValue, WrapperOptions options, JsonDocumentWriter writer) throws IOException { + writer.startObject(); + serializeMapping(embeddableMappingType, domainValue, options, writer); + writer.endObject(); + } + + private static void serializeMapping(EmbeddableMappingType embeddableMappingType, + Object domainValue, WrapperOptions options, JsonDocumentWriter writer) throws IOException { + final Object[] values = embeddableMappingType.getValues( domainValue ); + for ( int i = 0; i < values.length; i++ ) { + final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); + if ( attributeMapping instanceof SelectableMapping ) { + final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); + writer.objectKey( name ); + if (values[i] == null) { + writer.nullValue(); + } + else if (attributeMapping.getMappedType() instanceof BasicType<?>) { + final BasicType<Object> basicType = (BasicType<Object>) attributeMapping.getMappedType(); + if ( isArrayType(basicType.getJdbcType())) { + final int length = Array.getLength( values[i] ); + writer.startArray(); + if ( length != 0 ) { + //noinspection unchecked + final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) basicType.getJdbcJavaType() ).getElementJavaType(); + final JdbcType elementJdbcType = ( (ArrayJdbcType) basicType.getJdbcType() ).getElementJdbcType(); + final Object domainArray = basicType.convertToRelationalValue( values[i] ); + for ( int j = 0; j < length; j++ ) { + writer.serializeJsonValue(Array.get(domainArray,j), elementJavaType, elementJdbcType, options); + } + } + writer.endArray(); + } + else { + writer.serializeJsonValue(basicType.convertToRelationalValue( values[i]), + (JavaType<Object>)basicType.getJdbcJavaType(),basicType.getJdbcType(), options); + } + } + else if ( attributeMapping.getMappedType() instanceof EmbeddableMappingType ) { + writer.startObject(); + serializeMapping( (EmbeddableMappingType)attributeMapping.getMappedType(), values[i], options,writer); + writer.endObject(); + } + + } + else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { + if ( values[i] == null ) { + //writer.nullValue(); + continue; + } + final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); + final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); + if (aggregateMapping == null) { + serializeMapping( + mappingType, + values[i], + options, + writer ); + } + else { + final String name = aggregateMapping.getSelectableName(); + writer.objectKey( name ); + writer.startObject(); + serializeMapping( + mappingType, + values[i], + options, + writer); + writer.endObject(); + + } + } + else { + throw new UnsupportedOperationException( "Support for attribute mapping type not yet implemented: " + attributeMapping.getClass().getName() ); + } + } - sb.append( ']' ); - return sb.toString(); } private static void toString(EmbeddableMappingType embeddableMappingType, Object value, WrapperOptions options, JsonAppender appender) { @@ -118,31 +225,29 @@ private static void toString( appender.append( "\":" ); toString( attributeMapping.getMappedType(), values[i], options, appender ); } - else if ( attributeMapping instanceof EmbeddedAttributeMapping embeddedAttributeMapping ) { + else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { if ( values[i] == null ) { - // Skipping the update of the separator on purpose + // Skipping the update of the separator is on purpose continue; } + final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); + final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); + if ( mappingType.shouldSelectAggregateMapping()) { + final String name = aggregateMapping.getSelectableName(); + appender.append( separator ); + appender.append( '"' ); + appender.append( name ); + appender.append( "\":" ); + toString( mappingType, values[i], options, appender ); + } else { - final EmbeddableMappingType mappingType = embeddedAttributeMapping.getMappedType(); - final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); - if ( aggregateMapping == null ) { - toString( - mappingType, - options, - appender, - values[i], - separator - ); - } - else { - final String name = aggregateMapping.getSelectableName(); - appender.append( separator ); - appender.append( '"' ); - appender.append( name ); - appender.append( "\":" ); - toString( mappingType, values[i], options, appender ); - } + toString( + mappingType, + options, + appender, + values[i], + separator + ); } } else { @@ -159,7 +264,9 @@ private static void toString(MappingType mappedType, Object value, WrapperOption else if ( mappedType instanceof EmbeddableMappingType embeddableMappingType ) { toString( embeddableMappingType, value, options, appender ); } - else if ( mappedType instanceof BasicType<?> basicType ) { + else if ( mappedType instanceof BasicType<?> ) { + //noinspection unchecked + final BasicType<Object> basicType = (BasicType<Object>) mappedType; convertedBasicValueToString( basicType.convertToRelationalValue( value ), options, appender, basicType ); } else { @@ -167,8 +274,8 @@ else if ( mappedType instanceof BasicType<?> basicType ) { } } - private static <T> void convertedValueToString( - JavaType<T> javaType, + private static void convertedValueToString( + JavaType<Object> javaType, JdbcType jdbcType, Object value, WrapperOptions options, @@ -180,41 +287,31 @@ else if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) { toString( aggregateJdbcType.getEmbeddableMappingType(), value, options, appender ); } else { - convertedCastBasicValueToString( value, options, appender, javaType, jdbcType ); + convertedBasicValueToString( value, options, appender, javaType, jdbcType ); } } - private static <T> void convertedBasicValueToString( + private static void convertedBasicValueToString( Object value, WrapperOptions options, JsonAppender appender, - BasicType<T> basicType) { - convertedCastBasicValueToString( + BasicType<Object> basicType) { + //noinspection unchecked + convertedBasicValueToString( value, options, appender, - basicType.getJdbcJavaType(), + (JavaType<Object>) basicType.getJdbcJavaType(), basicType.getJdbcType() ); } - private static <T> void convertedCastBasicValueToString( + private static void convertedBasicValueToString( Object value, WrapperOptions options, JsonAppender appender, - JavaType<T> javaType, - JdbcType jdbcType) { - assert javaType.isInstance( value ); - //noinspection unchecked - convertedBasicValueToString( (T) value, options, appender, javaType, jdbcType ); - } - - private static <T> void convertedBasicValueToString( - T value, - WrapperOptions options, - JsonAppender appender, - JavaType<T> javaType, + JavaType<Object> javaType, JdbcType jdbcType) { switch ( jdbcType.getDefaultSqlTypeCode() ) { case SqlTypes.TINYINT: @@ -326,12 +423,13 @@ private static <T> void convertedBasicValueToString( final int length = Array.getLength( value ); appender.append( '[' ); if ( length != 0 ) { - final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); + //noinspection unchecked + final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) javaType ).getElementJavaType(); final JdbcType elementJdbcType = ( (ArrayJdbcType) jdbcType ).getElementJdbcType(); - final Object firstArrayElement = Array.get( value, 0 ); - convertedValueToString( elementJavaType, elementJdbcType, firstArrayElement, options, appender ); + Object arrayElement = Array.get( value, 0 ); + convertedValueToString( elementJavaType, elementJdbcType, arrayElement, options, appender ); for ( int i = 1; i < length; i++ ) { - final Object arrayElement = Array.get( value, i ); + arrayElement = Array.get( value, i ); appender.append( ',' ); convertedValueToString( elementJavaType, elementJdbcType, arrayElement, options, appender ); } @@ -1376,7 +1474,7 @@ String expectedChars() { } } - private static class JsonAppender extends OutputStream implements SqlAppender { + public static class JsonAppender extends OutputStream implements SqlAppender { private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java index f0631db28929..015280b910b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java @@ -4,6 +4,7 @@ */ package org.hibernate.type.descriptor.jdbc; +import java.io.IOException; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -16,6 +17,7 @@ import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.StringJsonDocumentWriter; /** * Specialized type mapping for {@code JSON} and the JSON SQL data type. @@ -97,7 +99,16 @@ public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) t protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) { if ( embeddableMappingType != null ) { - return JsonHelper.toString( embeddableMappingType, value, options ); + // used to be JsonHelper.toString( embeddableMappingType, value, options ); + try { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + JsonHelper.serialize( embeddableMappingType, value, options, writer); + return sb.toString(); + } + catch (IOException e) { + throw new RuntimeException("Failed to serialize JSON mapping", e ); + } } return options.getJsonFormatMapper().toString( value, javaType, options ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java index fd88db2fbd35..139e4b3683b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java @@ -3,10 +3,6 @@ * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ /** @@ -21,22 +17,22 @@ public interface JsonDocumentHandler { /** * Callback to be called when the start of an JSON object is encountered. */ - void startObject(); + void onStartObject(); /** * Callback to be called when the end of an JSON object is encountered. */ - void endObject(); + void onEndObject(); /** * Callback to be called when the start of an array is encountered. */ - void startArray(); + void onStartArray(); /** * Callback to be called when the end of an array is encountered. */ - void endArray(); + void onEndArray(); /** * Callback to be called when the key of JSON attribute is encountered. diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java new file mode 100644 index 000000000000..6a9923b1a9ed --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java @@ -0,0 +1,83 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import java.io.IOException; + +/** + * JSON document producer. + * Used to parse JSON documents. Implementors of this will define + * proper callback implementations. + * + * @author Emmanuel Jannetti + */ + +public interface JsonDocumentWriter { + /** + * Callback to be called when the start of an JSON object is encountered. + */ + void startObject() throws IOException; + + /** + * Callback to be called when the end of an JSON object is encountered. + */ + void endObject() throws IOException; + + /** + * Callback to be called when the start of an array is encountered. + */ + void startArray(); + + /** + * Callback to be called when the end of an array is encountered. + */ + void endArray(); + + /** + * Callback to be called when the key of JSON attribute is encountered. + * @param key the attribute name + */ + void objectKey(String key); + + /** + * Callback to be called when null value is encountered. + */ + void nullValue(); + + /** + * Callback to be called when boolean value is encountered. + * @param value the boolean value + */ + void booleanValue(boolean value); + + /** + * Callback to be called when string value is encountered. + * @param value the String value + */ + void stringValue(String value); + + /** + * Callback to be called when Number value is encountered. + * @param value the String value. + */ + void numberValue(Number value); + + /** + * Serialize a JSON value to the document + * @param value the value to be serialized + * @param javaType the Java type of the value + * @param jdbcType the JDBC type for the value to be serialized + * @param options the wrapping options + */ + void serializeJsonValue(Object value, + JavaType<Object> javaType, + JdbcType jdbcType, + WrapperOptions options); +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java similarity index 94% rename from hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java rename to hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java index b8eb3a7c1542..0833fd8707ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/ObjectArrayOsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java @@ -2,7 +2,7 @@ * SPDX-License-Identifier: LGPL-2.1-or-later * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.type.format.jackson; +package org.hibernate.type.format; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.SelectableMapping; @@ -10,7 +10,6 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.UUIDJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; -import org.hibernate.type.format.JsonDocumentHandler; import java.sql.Date; import java.sql.Time; @@ -31,8 +30,8 @@ */ public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { - // final result of mapped obejct array - private Object [] objectArrayResult; + // final result of a mapped object array + private final Object [] objectArrayResult; // current mapping to be used SelectableMapping currentSelectableMapping = null; String currentKeyName = null; @@ -42,7 +41,7 @@ public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { // mapping definitions are in a tree // Each mapping definition may contain sub mappings (sub embeddable mapping) // This stack is used to keep a pointer on the current mapping to be used to assign correct types. - // see startObject()/endObject() methods + // see onStartObject()/onEndObject() methods Stack<EmbeddableMappingType> embeddableMappingTypes = new Stack<>(); // As for mapping definitions, when "sub embeddable" is encountered, the array // that needs to be filled with Objects is the one we allocate in the final result array slot. @@ -58,7 +57,7 @@ public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { public ObjectArrayOsonDocumentHandler(EmbeddableMappingType embeddableMappingType, WrapperOptions wrapperOptions) { this.embeddableMappingTypes.push(embeddableMappingType); this.wrapperOptions = wrapperOptions; - this.objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()]; + this.objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()+ ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; this.objectArrays.push( this.objectArrayResult ); } @@ -71,7 +70,7 @@ public ObjectArrayOsonDocumentHandler(EmbeddableMappingType embeddableMappingTyp } @Override - public void startObject() { + public void onStartObject() { if (currentKeyName != null) { // We are dealing with a sub-object, allocate space for it then, // otherwise, we have nothing to do. @@ -92,15 +91,15 @@ public void startObject() { } @Override - public void endObject() { + public void onEndObject() { // go back in the mapping definition tree this.embeddableMappingTypes.pop(); this.objectArrays.pop(); } @Override - public void startArray() { - assert (subArrayObjectList == null && subArrayObjectTypes == null) : "startArray called twice ?"; + public void onStartArray() { + assert (subArrayObjectList == null && subArrayObjectTypes == null) : "onStartArray called twice ?"; // initialize an array to gather values subArrayObjectList = new ArrayList<>(); @@ -111,8 +110,8 @@ public void startArray() { } @Override - public void endArray() { - assert (subArrayObjectList != null && subArrayObjectTypes != null) : "endArray called before startArray"; + public void onEndArray() { + assert (subArrayObjectList != null && subArrayObjectTypes != null) : "onEndArray called before onStartArray"; // flush array values this.objectArrays.peek()[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, wrapperOptions ); // reset until we encounter next array element @@ -203,7 +202,7 @@ public <T> void onOsonValue(T value) { * @param bytes the OSON byters */ public void onOsonBinaryValue(byte[] bytes) { - Class underlyingType = null; + Class underlyingType; Object theOneToBeUsed; if(subArrayObjectTypes!=null) { underlyingType = subArrayObjectTypes.getElementType().getJavaType(); @@ -234,7 +233,7 @@ public void onOsonBinaryValue(byte[] bytes) { */ public void onOsonDateValue(LocalDateTime localDateTime) { - Class underlyingType = null; + Class underlyingType; Object theOneToBeUsed = localDateTime; if(subArrayObjectTypes!=null) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java new file mode 100644 index 000000000000..3a5300e11ad3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java @@ -0,0 +1,241 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import oracle.jdbc.driver.json.tree.OracleJsonDateImpl; +import oracle.jdbc.driver.json.tree.OracleJsonTimestampImpl; +import oracle.sql.DATE; +import oracle.sql.TIMESTAMP; +import oracle.sql.json.OracleJsonDate; +import oracle.sql.json.OracleJsonGenerator; +import oracle.sql.json.OracleJsonTimestamp; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.UUIDJavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.UUID; + +/** + * Implementation of <code>JsonDocumentWriter</code> for OSON document. + * This implementation will produce an Object Array based on + * embeddable mapping + * Once All JSON document is handle the mapped Object array can be retrieved using the + * <code>getObjectArray()</code> method. + * + * @author Emmanuel Jannetti + */ +public class ObjectArrayOsonDocumentWriter implements JsonDocumentWriter { + + + private final OracleJsonGenerator generator; + + public ObjectArrayOsonDocumentWriter(OracleJsonGenerator generator) { + this.generator = generator; + } + + + @Override + public void startObject() throws IOException { + this.generator.writeStartObject(); + } + + + @Override + public void endObject() throws IOException { + this.generator.writeEnd(); + } + + + @Override + public void startArray() { + generator.writeStartArray(); + } + + + @Override + public void endArray() { + generator.writeEnd(); + } + + + @Override + public void objectKey(String key) { + this.generator.writeKey( key ); + } + + + @Override + public void nullValue() { + this.generator.writeNull(); + } + + + @Override + public void booleanValue(boolean value) { + this.generator.write(value); + } + + + @Override + public void stringValue(String value) { + this.generator.write(value); + } + + + @Override + public void numberValue(Number value) { + this.generator.write((BigDecimal) value ); + } + + @Override + public void serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { + serializeValue(value, javaType, jdbcType, options); + } + + + private void serializeValue(Object value, + JavaType<Object> javaType, + JdbcType jdbcType, + WrapperOptions options) { + switch ( jdbcType.getDefaultSqlTypeCode() ) { + case SqlTypes.TINYINT: + case SqlTypes.SMALLINT: + case SqlTypes.INTEGER: + if ( value instanceof Boolean ) { + // BooleanJavaType has this as an implicit conversion + int i = ((Boolean) value) ? 1 : 0; + generator.write( i ); + break; + } + if ( value instanceof Enum ) { + generator.write( ((Enum<?>) value ).ordinal() ); + break; + } + generator.write( javaType.unwrap( value,Integer.class,options ) ); + break; + case SqlTypes.BOOLEAN: + generator.write( javaType.unwrap( value,Boolean.class,options ) ); + break; + case SqlTypes.BIT: + generator.write( javaType.unwrap( value,Integer.class,options ) ); + break; + case SqlTypes.BIGINT: + generator.write( javaType.unwrap( value, BigInteger.class,options ) ); + break; + case SqlTypes.FLOAT: + generator.write( javaType.unwrap( value,Float.class,options ) ); + break; + case SqlTypes.REAL: + case SqlTypes.DOUBLE: + generator.write( javaType.unwrap( value,Double.class,options ) ); + break; + case SqlTypes.CHAR: + case SqlTypes.NCHAR: + case SqlTypes.VARCHAR: + case SqlTypes.NVARCHAR: + if ( value instanceof Boolean ) { + String c = ((Boolean) value) ? "Y" : "N"; + generator.write( c ); + break; + } + case SqlTypes.LONGVARCHAR: + case SqlTypes.LONGNVARCHAR: + case SqlTypes.LONG32VARCHAR: + case SqlTypes.LONG32NVARCHAR: + case SqlTypes.CLOB: + case SqlTypes.MATERIALIZED_CLOB: + case SqlTypes.NCLOB: + case SqlTypes.MATERIALIZED_NCLOB: + case SqlTypes.ENUM: + case SqlTypes.NAMED_ENUM: + // correct? + generator.write( javaType.unwrap( value,String.class,options ) ); + break; + case SqlTypes.DATE: + DATE dd = new DATE(javaType.unwrap( value,java.sql.Date.class,options )); + OracleJsonDate jsonDate = new OracleJsonDateImpl(dd.shareBytes()); + generator.write(jsonDate); + break; + case SqlTypes.TIME: + case SqlTypes.TIME_WITH_TIMEZONE: + case SqlTypes.TIME_UTC: + Time time = javaType.unwrap( value, Time.class,options ); + generator.write( time.toString() ); + break; + case SqlTypes.TIMESTAMP: + TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( value, Timestamp.class, options )); + OracleJsonTimestamp writeTimeStamp = new OracleJsonTimestampImpl(TS.shareBytes()); + generator.write(writeTimeStamp); + break; + case SqlTypes.TIMESTAMP_WITH_TIMEZONE: + try { + OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options ); + generator.write( dateTime ); + } + catch (Exception e) { + Timestamp tswtz = javaType.unwrap( value, Timestamp.class, options ); + TIMESTAMP TSWTZ = new TIMESTAMP(tswtz); + OracleJsonTimestamp writeTimeStampWTZ = new OracleJsonTimestampImpl(TSWTZ.shareBytes()); + generator.write(writeTimeStampWTZ); + } + break; + case SqlTypes.TIMESTAMP_UTC: + if( value instanceof OffsetDateTime ) { + OffsetDateTime odt = javaType.unwrap( value, OffsetDateTime.class, options ); + generator.write( odt ); + break; + } + else if (value instanceof Instant ) { + Instant instant = javaType.unwrap( value, Instant.class, options ); + generator.write(instant.atOffset( ZoneOffset.UTC ) ); + break; + } + generator.write( javaType.unwrap( value,String.class,options ) ); + break; + case SqlTypes.NUMERIC: + case SqlTypes.DECIMAL: + BigDecimal bd = javaType.unwrap( value, BigDecimal.class, options ); + generator.write( bd ); + break; + + case SqlTypes.DURATION: + Duration duration = javaType.unwrap( value, Duration.class, options ); + generator.write( duration ); + break; + case SqlTypes.UUID: + generator.write( UUIDJavaType.INSTANCE.unwrap( (UUID)value, byte[].class, options ) ); + break; + case SqlTypes.BINARY: + case SqlTypes.VARBINARY: + case SqlTypes.LONGVARBINARY: + case SqlTypes.LONG32VARBINARY: + case SqlTypes.BLOB: + case SqlTypes.MATERIALIZED_BLOB: + // how to handle + byte[] bytes = javaType.unwrap( value, byte[].class, options ); + generator.write( bytes ); + break; + case SqlTypes.ARRAY: + case SqlTypes.JSON_ARRAY: + assert false:"array case should be treated at upper level"; + break; + default: + throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + jdbcType ); + } + + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java new file mode 100644 index 000000000000..6bac99e8bc1a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -0,0 +1,358 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import org.hibernate.dialect.JsonHelper; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BooleanJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.JdbcDateJavaType; +import org.hibernate.type.descriptor.java.JdbcTimeJavaType; +import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Stack; + +/** + * JsonDocument String writer implementation + * @author Emmanuel Jannetti + */ +public class StringJsonDocumentWriter implements JsonDocumentWriter{ + + private static final char ARRAY_END_MARKER = ']'; + private static final char ARRAY_START_MARKER = '['; + private static final char OBJECT_END_MARKER = '}'; + private static final char OBJECT_START_MARKER = '{'; + private static final char SEPARATOR_MARKER = ','; + private static final char TOKEN_QUOTE = '"'; + + private JsonHelper.JsonAppender appender; + + /** + * Processing states. This can be (nested)Object or Arrays. + * When processing objects, values are stored as [,]"key":"value"[,]. we add separator when adding new key + * When processing arrays, values are stored as [,]"value"[,]. we add separator when adding new value + */ + private enum PROCESSING_STATE { + NONE, + STARTING_OBJECT, // object started but no value added + OBJECT, // object started, and we've started adding key/value pairs + ENDING_OBJECT, // we are ending an object + STARTING_ARRAY, // array started but no value added + ENDING_ARRAY, // we are ending an array + ARRAY // we are piling array values + } + private Stack<PROCESSING_STATE> processingStates = new Stack<>(); + + + public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { + this.processingStates.push( PROCESSING_STATE.NONE ); + this.appender = appender; + } + + /** + * Callback to be called when the start of an JSON object is encountered. + */ + @Override + public void startObject() throws IOException { + // Note: startArray and startObject must not call moveProcessingStateMachine() + if (this.processingStates.peek() == PROCESSING_STATE.STARTING_ARRAY) { + // are we building an array of objects? + // i.e, [{},...] + // move to PROCESSING_STATE.ARRAY first + this.processingStates.push( PROCESSING_STATE.ARRAY); + } + else if (this.processingStates.peek() == PROCESSING_STATE.ARRAY) { + // That means that we ae building an array of object ([{},...]) + // JSON object hee are treat as array item. + // -> add the marker first + this.appender.append(SEPARATOR_MARKER); + } + this.appender.append( OBJECT_START_MARKER); + this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); + } + + /** + * Callback to be called when the end of an JSON object is encountered. + */ + @Override + public void endObject() throws IOException { + this.appender.append( OBJECT_END_MARKER ); + this.processingStates.push( PROCESSING_STATE.ENDING_OBJECT); + moveProcessingStateMachine(); + } + + /** + * Callback to be called when the start of an array is encountered. + */ + @Override + public void startArray() { + this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); + // Note: startArray and startObject do not call moveProcessingStateMachine() + this.appender.append( ARRAY_START_MARKER ); + + } + + /** + * Callback to be called when the end of an array is encountered. + */ + @Override + public void endArray() { + this.appender.append( ARRAY_END_MARKER ); + this.processingStates.push( PROCESSING_STATE.ENDING_ARRAY); + moveProcessingStateMachine(); + } + + + @Override + public void objectKey(String key) { + if (this.processingStates.peek().equals( PROCESSING_STATE.OBJECT )) { + // we have started an object, and we are adding an item key: we do add a separator. + this.appender.append( SEPARATOR_MARKER ); + } + this.appender.append( TOKEN_QUOTE ); + this.appender.append( key ); + this.appender.append( "\":" ); + moveProcessingStateMachine(); + } + + /** + * Adds a separator if needed. + * The logic here is know if we have to prepend a separator + * as such, it must be called at the beginning of all methods + * Separator is to separate array items or key/value pairs in an object. + */ + private void addItemsSeparator() { + if (this.processingStates.peek().equals( PROCESSING_STATE.ARRAY )) { + // We started to serialize an array and already added item to it:add a separator anytime. + this.appender.append( SEPARATOR_MARKER ); + } + } + + /** + * Changes the current processing state. + * we are called after an item (array item or object item) has been added, + * do whatever it takes to move away from the current state by picking up the next logical one. + * <p> + * We have to deal with two kinds of (possibly empty) structure + * <ul> + * <li>array of objects and values [{},null,{},"foo", ...]</li> + * <li>objects than have array as attribute value {k1:v1, k2:[v21,v22,..], k3:v3, k4:null, ...}</li> + * </ul> + * <pre> + * NONE -> SA -> (A,...) --> SO -> O -> EO -> A + * --> EA -> NONE + * -> EA -> NONE + * + * -> SO -> (O,...) ------------------> SA -> A -> EA -> O + * --> EO -> NONE -> EA -> O + * -> EO -> NONE + * + * </pre> + * + */ + private void moveProcessingStateMachine() { + switch (this.processingStates.peek()) { + case STARTING_OBJECT: + //after starting an object, we start adding key/value pairs + this.processingStates.push( PROCESSING_STATE.OBJECT ); + break; + case STARTING_ARRAY: + //after starting an object, we start adding value to it + this.processingStates.push( PROCESSING_STATE.ARRAY ); + break; + case ENDING_ARRAY: + // when ending an array, we have one or two states. + // ARRAY (unless this is an empty array) + // STARTING_ARRAY + // first pop ENDING_ARRAY + this.processingStates.pop(); + if (this.processingStates.peek().equals( PROCESSING_STATE.ARRAY )) + this.processingStates.pop(); + assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_ARRAY ); + break; + case ENDING_OBJECT: + // when ending an object, we have one or two states. + // OBJECT (unless this is an empty object) + // STARTING_OBJECT + // first pop ENDING_OBJECT + this.processingStates.pop(); + if (this.processingStates.peek().equals( PROCESSING_STATE.OBJECT )) + this.processingStates.pop(); + assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_OBJECT ); + break; + default: + //nothing to do for the other ones. + } + } + + @Override + public void nullValue() { + addItemsSeparator(); + this.appender.append( "null" ); + moveProcessingStateMachine(); + } + + @Override + public void booleanValue(boolean value) { + addItemsSeparator(); + BooleanJavaType.INSTANCE.appendEncodedString( this.appender, value); + moveProcessingStateMachine(); + } + + @Override + public void stringValue(String value) { + addItemsSeparator(); + + appender.append( TOKEN_QUOTE); + appender.startEscaping(); + appender.append( value ); + appender.endEscaping(); + appender.append(TOKEN_QUOTE ); + + moveProcessingStateMachine(); + + } + + @Override + public void numberValue(Number value) { + addItemsSeparator(); + this.appender.append( value.toString() ); + moveProcessingStateMachine(); + } + + + @Override + public void serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { + addItemsSeparator(); + convertedBasicValueToString(value, options,this.appender,javaType,jdbcType); + moveProcessingStateMachine(); + } + + private void convertedBasicValueToString( + Object value, + WrapperOptions options, + JsonHelper.JsonAppender appender, + JavaType<Object> javaType, + JdbcType jdbcType) { + switch ( jdbcType.getDefaultSqlTypeCode() ) { + case SqlTypes.TINYINT: + case SqlTypes.SMALLINT: + case SqlTypes.INTEGER: + if ( value instanceof Boolean ) { + // BooleanJavaType has this as an implicit conversion + appender.append( (Boolean) value ? '1' : '0' ); + break; + } + if ( value instanceof Enum ) { + appender.appendSql( ((Enum<?>) value ).ordinal() ); + break; + } + case SqlTypes.BOOLEAN: + case SqlTypes.BIT: + case SqlTypes.BIGINT: + case SqlTypes.FLOAT: + case SqlTypes.REAL: + case SqlTypes.DOUBLE: + // These types fit into the native representation of JSON, so let's use that + javaType.appendEncodedString( appender, value ); + break; + case SqlTypes.CHAR: + case SqlTypes.NCHAR: + case SqlTypes.VARCHAR: + case SqlTypes.NVARCHAR: + if ( value instanceof Boolean ) { + // BooleanJavaType has this as an implicit conversion + appender.append( TOKEN_QUOTE ); + appender.append( (Boolean) value ? 'Y' : 'N' ); + appender.append( TOKEN_QUOTE); + break; + } + case SqlTypes.LONGVARCHAR: + case SqlTypes.LONGNVARCHAR: + case SqlTypes.LONG32VARCHAR: + case SqlTypes.LONG32NVARCHAR: + case SqlTypes.CLOB: + case SqlTypes.MATERIALIZED_CLOB: + case SqlTypes.NCLOB: + case SqlTypes.MATERIALIZED_NCLOB: + case SqlTypes.ENUM: + case SqlTypes.NAMED_ENUM: + // These literals can contain the '"' character, so we need to escape it + appender.append( TOKEN_QUOTE ); + appender.startEscaping(); + javaType.appendEncodedString( appender, value ); + appender.endEscaping(); + appender.append( TOKEN_QUOTE ); + break; + case SqlTypes.DATE: + appender.append( TOKEN_QUOTE ); + JdbcDateJavaType.INSTANCE.appendEncodedString( + appender, + javaType.unwrap( value, java.sql.Date.class, options ) + ); + appender.append( TOKEN_QUOTE ); + break; + case SqlTypes.TIME: + case SqlTypes.TIME_WITH_TIMEZONE: + case SqlTypes.TIME_UTC: + appender.append( TOKEN_QUOTE ); + JdbcTimeJavaType.INSTANCE.appendEncodedString( + appender, + javaType.unwrap( value, java.sql.Time.class, options ) + ); + appender.append( TOKEN_QUOTE ); + break; + case SqlTypes.TIMESTAMP: + appender.append( TOKEN_QUOTE ); + JdbcTimestampJavaType.INSTANCE.appendEncodedString( + appender, + javaType.unwrap( value, java.sql.Timestamp.class, options ) + ); + appender.append( TOKEN_QUOTE ); + break; + case SqlTypes.TIMESTAMP_WITH_TIMEZONE: + case SqlTypes.TIMESTAMP_UTC: + appender.append( TOKEN_QUOTE ); + DateTimeFormatter.ISO_OFFSET_DATE_TIME.formatTo( + javaType.unwrap( value, OffsetDateTime.class, options ), + appender + ); + appender.append( TOKEN_QUOTE ); + break; + case SqlTypes.DECIMAL: + case SqlTypes.NUMERIC: + case SqlTypes.DURATION: + case SqlTypes.UUID: + // These types need to be serialized as JSON string, but don't have a need for escaping + appender.append( TOKEN_QUOTE ); + javaType.appendEncodedString( appender, value ); + appender.append( TOKEN_QUOTE ); + break; + case SqlTypes.BINARY: + case SqlTypes.VARBINARY: + case SqlTypes.LONGVARBINARY: + case SqlTypes.LONG32VARBINARY: + case SqlTypes.BLOB: + case SqlTypes.MATERIALIZED_BLOB: + // These types need to be serialized as JSON string, and for efficiency uses appendString directly + appender.append( TOKEN_QUOTE ); + appender.write( javaType.unwrap( value, byte[].class, options ) ); + appender.append( TOKEN_QUOTE ); + break; + case SqlTypes.ARRAY: + case SqlTypes.JSON_ARRAY: + // Caller handles this. We should never end up here actually. + break; + default: + throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + jdbcType ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 714f0c80f9e3..2ffdf09982a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -9,43 +9,29 @@ import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; -import oracle.jdbc.driver.json.tree.OracleJsonDateImpl; -import oracle.jdbc.driver.json.tree.OracleJsonTimestampImpl; -import oracle.sql.DATE; -import oracle.sql.TIMESTAMP; -import oracle.sql.json.OracleJsonDate; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; import oracle.sql.json.OracleJsonParser; -import oracle.sql.json.OracleJsonTimestamp; +import org.hibernate.dialect.JsonHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.type.BasicType; -import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; -import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.JsonDocumentHandler; +import org.hibernate.type.format.ObjectArrayOsonDocumentHandler; +import org.hibernate.type.format.ObjectArrayOsonDocumentWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.lang.reflect.Array; -import java.math.BigDecimal; -import java.math.BigInteger; import java.nio.ByteBuffer; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.Duration; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.util.UUID; /** @@ -70,9 +56,6 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { } } - // TODO : remove the use of this once the OSON writer has been refactor to Document handling - private EmbeddableMappingType embeddableMappingType = null; - /** * Creates a new JacksonOsonFormatMapper */ @@ -104,10 +87,10 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve handler.onObjectKey( osonParser.getString() ); break; case OracleJsonParser.Event.START_ARRAY: - handler.startArray(); + handler.onStartArray(); break; case OracleJsonParser.Event.END_ARRAY: - handler.endArray(); + handler.onEndArray(); break; case OracleJsonParser.Event.VALUE_DATE: case OracleJsonParser.Event.VALUE_TIMESTAMP: @@ -155,10 +138,10 @@ private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Eve osonParser.getBytes()); break; case OracleJsonParser.Event.START_OBJECT: - handler.startObject(); + handler.onStartObject(); break; case OracleJsonParser.Event.END_OBJECT: - handler.endObject(); + handler.onEndObject(); break; default: throw new IOException( "Unknown OSON event " + event ); @@ -191,24 +174,53 @@ public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object s return (T)handler.getObjectArray(); } - public <X>byte[] toOson(X value, WrapperOptions options,EmbeddableMappingType embeddableMappingType) { + public <X>byte[] fromObjectArray(X value, JavaType<X> javaType, WrapperOptions options,EmbeddableMappingType embeddableMappingType) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); + ObjectArrayOsonDocumentWriter writer = new ObjectArrayOsonDocumentWriter(generator); + JsonHelper.serialize( embeddableMappingType, value,options,writer); + generator.close(); + return out.toByteArray(); + } + + public <X>byte[] arrayToOson(X value, + JavaType<X> javaType, + JdbcType elementJdbcType, + WrapperOptions options) { + + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + ByteArrayOutputStream out = new ByteArrayOutputStream(); OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); - toOson( value,generator,options,embeddableMappingType); + ObjectArrayOsonDocumentWriter writer = new ObjectArrayOsonDocumentWriter(generator); + + if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { + final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); + JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer); + } + else { + assert !( elementJdbcType instanceof AggregateJdbcType); + final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); + JsonHelper.serializeArray( elementJavaType, elementJdbcType, domainObjects, options, writer ); + } + generator.close(); return out.toByteArray(); } - public void arrayToOson(OracleJsonGenerator osonGen, + public void _arrayToOson(OracleJsonGenerator osonGen, EmbeddableMappingType elementMappingType, Object[] values, WrapperOptions options) { + + osonGen.writeStartArray(); + if ( values.length == 0 ) { - osonGen.writeStartArray(); osonGen.writeEnd(); return; } - osonGen.writeStartArray(); + for ( Object value : values ) { toOson( elementMappingType, value, options, osonGen); } @@ -247,7 +259,11 @@ private void convertedBasicValueToOson(Object value, ); } - public void arrayToOson(OracleJsonGenerator osonGen, + private void serializeValue(Object value, JavaType<Object> jdbcJavaType, JdbcType jdbcType, WrapperOptions options, OracleJsonGenerator osonGen) { + //TODO: remove me. + } + + public void __arrayToOson(OracleJsonGenerator osonGen, JavaType<?> elementJavaType, JdbcType elementJdbcType, Object[] values, @@ -333,172 +349,6 @@ else if (attributeMapping instanceof EmbeddedAttributeMapping) { } } - private void serializeValue(Object value, - JavaType<Object> javaType, - JdbcType jdbcType, - WrapperOptions options, - OracleJsonGenerator generator) { - switch ( jdbcType.getDefaultSqlTypeCode() ) { - case SqlTypes.TINYINT: - case SqlTypes.SMALLINT: - case SqlTypes.INTEGER: - if ( value instanceof Boolean ) { - // BooleanJavaType has this as an implicit conversion - int i = ((Boolean) value) ? 1 : 0; - generator.write( i ); - break; - } - if ( value instanceof Enum ) { - generator.write( ((Enum<?>) value ).ordinal() ); - break; - } - generator.write( javaType.unwrap( value,Integer.class,options ) ); - break; - case SqlTypes.BOOLEAN: - generator.write( javaType.unwrap( value,Boolean.class,options ) ); - break; - case SqlTypes.BIT: - generator.write( javaType.unwrap( value,Integer.class,options ) ); - break; - case SqlTypes.BIGINT: - generator.write( javaType.unwrap( value,BigInteger.class,options ) ); - break; - case SqlTypes.FLOAT: - generator.write( javaType.unwrap( value,Float.class,options ) ); - break; - case SqlTypes.REAL: - case SqlTypes.DOUBLE: - generator.write( javaType.unwrap( value,Double.class,options ) ); - break; - case SqlTypes.CHAR: - case SqlTypes.NCHAR: - case SqlTypes.VARCHAR: - case SqlTypes.NVARCHAR: - if ( value instanceof Boolean ) { - String c = ((Boolean) value) ? "Y" : "N"; - generator.write( c ); - break; - } - case SqlTypes.LONGVARCHAR: - case SqlTypes.LONGNVARCHAR: - case SqlTypes.LONG32VARCHAR: - case SqlTypes.LONG32NVARCHAR: - case SqlTypes.CLOB: - case SqlTypes.MATERIALIZED_CLOB: - case SqlTypes.NCLOB: - case SqlTypes.MATERIALIZED_NCLOB: - case SqlTypes.ENUM: - case SqlTypes.NAMED_ENUM: - // correct? - generator.write( javaType.unwrap( value,String.class,options ) ); - break; - case SqlTypes.DATE: - DATE dd = new DATE(javaType.unwrap( value,java.sql.Date.class,options )); - OracleJsonDate jsonDate = new OracleJsonDateImpl(dd.shareBytes()); - generator.write(jsonDate); - break; - case SqlTypes.TIME: - case SqlTypes.TIME_WITH_TIMEZONE: - case SqlTypes.TIME_UTC: - Time time = javaType.unwrap( value, Time.class,options ); - generator.write( time.toString() ); - break; - case SqlTypes.TIMESTAMP: - TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( value, Timestamp.class, options )); - OracleJsonTimestamp writeTimeStamp = new OracleJsonTimestampImpl(TS.shareBytes()); - generator.write(writeTimeStamp); - break; - case SqlTypes.TIMESTAMP_WITH_TIMEZONE: - try { - OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options ); - generator.write( dateTime ); - } - catch (Exception e) { - Timestamp tswtz = javaType.unwrap( value, Timestamp.class, options ); - TIMESTAMP TSWTZ = new TIMESTAMP(tswtz); - OracleJsonTimestamp writeTimeStampWTZ = new OracleJsonTimestampImpl(TSWTZ.shareBytes()); - generator.write(writeTimeStampWTZ); - } - break; - case SqlTypes.TIMESTAMP_UTC: - if( value instanceof OffsetDateTime ) { - OffsetDateTime odt = javaType.unwrap( value, OffsetDateTime.class, options ); - generator.write( odt ); - break; - } - else if (value instanceof Instant ) { - Instant instant = javaType.unwrap( value, Instant.class, options ); - generator.write(instant.atOffset( ZoneOffset.UTC ) ); - break; - } - generator.write( javaType.unwrap( value,String.class,options ) ); - break; - case SqlTypes.NUMERIC: - case SqlTypes.DECIMAL: - BigDecimal bd = javaType.unwrap( value, BigDecimal.class, options ); - generator.write( bd ); - break; - - case SqlTypes.DURATION: - Duration duration = javaType.unwrap( value, Duration.class, options ); - generator.write( duration ); - break; - case SqlTypes.UUID: - UUID uuid = javaType.unwrap( value, UUID.class, options ); - byte[] uuidBytes = _asBytes( uuid ); - generator.write( uuidBytes ); - break; - case SqlTypes.BINARY: - case SqlTypes.VARBINARY: - case SqlTypes.LONGVARBINARY: - case SqlTypes.LONG32VARBINARY: - case SqlTypes.BLOB: - case SqlTypes.MATERIALIZED_BLOB: - // how to handle - byte[] bytes = javaType.unwrap( value, byte[].class, options ); - generator.write( bytes ); - break; - case SqlTypes.ARRAY: - case SqlTypes.JSON_ARRAY: - final int length = Array.getLength( value ); - generator.writeStartArray(); - if ( length != 0 ) { - //noinspection unchecked - final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) javaType ).getElementJavaType(); - final JdbcType elementJdbcType = ( (ArrayJdbcType) jdbcType ).getElementJdbcType(); - - for ( int i = 0; i < length; i++ ) { - Object arrayElement = Array.get( value, i ); - serializeValue( arrayElement,elementJavaType, elementJdbcType, options, generator ); - } - } - generator.writeEnd(); - break; - default: - throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + jdbcType ); - } - - } - private byte[] _asBytes(UUID uuid) - { - byte[] buffer = new byte[16]; - long hi = uuid.getMostSignificantBits(); - long lo = uuid.getLeastSignificantBits(); - _appendInt((int) (hi >> 32), buffer, 0); - _appendInt((int) hi, buffer, 4); - _appendInt((int) (lo >> 32), buffer, 8); - _appendInt((int) lo, buffer, 12); - return buffer; - } - - private void _appendInt(int value, byte[] buffer, int offset) - { - buffer[offset] = (byte) (value >> 24); - buffer[++offset] = (byte) (value >> 16); - buffer[++offset] = (byte) (value >> 8); - buffer[++offset] = (byte) value; - } - @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) @@ -524,7 +374,4 @@ public boolean supportsTargetType(Class<?> targetType) { } - - - } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java index a1f25a98d226..054c2a860eda 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java @@ -88,6 +88,7 @@ public void testUpdate() { @Test public void testFetch() { + sessionFactoryScope().inSession( entityManager -> { List<JsonHolder> jsonHolders = entityManager.createQuery( "from JsonHolder b where b.id = 1", JsonHolder.class ).getResultList(); From bb606ec9d44865deb6b10e832677246e26866193 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Wed, 15 Jan 2025 11:04:43 +0530 Subject: [PATCH 32/81] HHH-17404 - Fix extractor --- .../java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 7acf03ab6e22..870efd4f0aef 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -114,8 +114,8 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions @Override public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { - if (javaType.getJavaTypeClass().isAssignableFrom( String.class )) { - return super.getExtractor(javaType); + if(javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class) { + return super.getExtractor( javaType ); } return new BasicExtractor<>( javaType, this ) { From ee559bc084bc34b5f75edb31c39b3e630cd62522 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Wed, 15 Jan 2025 16:16:14 +0530 Subject: [PATCH 33/81] HHH-17404- UUID fix --- .../main/java/org/hibernate/dialect/OracleDialect.java | 3 --- .../org/hibernate/dialect/OracleDurationJdbcType.java | 4 ++-- .../dialect/aggregate/OracleAggregateSupport.java | 8 +++++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 170d2fb426d0..3a2b37689e74 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -92,7 +92,6 @@ import org.hibernate.type.NullType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; -import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.NullJdbcType; @@ -838,7 +837,6 @@ protected String columnType(int sqlTypeCode) { protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.registerColumnTypes( typeContributions, serviceRegistry ); final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); - final JavaTypeRegistry javaTypeRegistry = typeContributions.getTypeConfiguration().getJavaTypeRegistry(); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "SYS.XMLTYPE", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "MDSYS.SDO_GEOMETRY", this ) ); @@ -859,7 +857,6 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR // We need the DDL type during runtime to produce the proper encoding in certain functions ddlTypeRegistry.addDescriptor( new DdlTypeImpl( BIT, "number(1,0)", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( oracle.jdbc.OracleTypes.INTERVALDS, "interval day to second", this ) ); - javaTypeRegistry.addDescriptor( OracleDurationJavaType.INSTANCE ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java index 3530b10c801d..dd85678f4261 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -14,8 +14,8 @@ import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.DurationJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; -import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import java.sql.CallableStatement; @@ -24,7 +24,7 @@ import java.sql.SQLException; import java.time.Duration; -public class OracleDurationJdbcType implements JdbcType { +public class OracleDurationJdbcType extends DurationJdbcType { public static final OracleDurationJdbcType INSTANCE = new OracleDurationJdbcType(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index 77eded5f0959..4e7acca28883 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -206,10 +206,16 @@ public String aggregateComponentCustomReadExpression( "json_value(" + parentPartExpression + columnExpression + "')" ); } + case UUID: + if (this.dateTypesStoreAsString) { + return template.replace( + placeholder, + "hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))" + ); + } case BINARY: case VARBINARY: case LONG32VARBINARY: - case UUID: // We encode binary data as hex, so we have to decode here if ( determineLength( column ) * 2 < 4000L ) { return template.replace( From 3dfbbcce392db3335f9739c75aff62c517886925 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 15 Jan 2025 09:50:10 +0100 Subject: [PATCH 34/81] HHH-17404 : remove unwanted committed settings --- hibernate-testing/hibernate-testing.gradle | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hibernate-testing/hibernate-testing.gradle b/hibernate-testing/hibernate-testing.gradle index 0c13cdefd35c..7a4322acf911 100644 --- a/hibernate-testing/hibernate-testing.gradle +++ b/hibernate-testing/hibernate-testing.gradle @@ -48,11 +48,6 @@ dependencies { annotationProcessor project( ':hibernate-processor' ) - - runtimeOnly ('com.oracle.database.jdbc:ojdbc-provider-jackson-oson:1.0.2') { - exclude group: 'com.oracle.database.jdbc', module: 'ojdbc8' - } - } From 7ee81baee2d24ed5eaeadb0fef4fa40e1e2c73e1 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 15 Jan 2025 10:41:29 +0100 Subject: [PATCH 35/81] HHH-17404 : remove unused methods --- .../jackson/JacksonOsonFormatMapper.java | 146 ------------------ 1 file changed, 146 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 2ffdf09982a9..fdd2eb7b9216 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -14,11 +14,6 @@ import oracle.sql.json.OracleJsonParser; import org.hibernate.dialect.JsonHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; -import org.hibernate.metamodel.mapping.MappingType; -import org.hibernate.metamodel.mapping.SelectableMapping; -import org.hibernate.metamodel.mapping.ValuedModelPart; -import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; @@ -209,147 +204,6 @@ public <X>byte[] arrayToOson(X value, return out.toByteArray(); } - public void _arrayToOson(OracleJsonGenerator osonGen, - EmbeddableMappingType elementMappingType, - Object[] values, - WrapperOptions options) { - - osonGen.writeStartArray(); - - if ( values.length == 0 ) { - osonGen.writeEnd(); - return; - } - - for ( Object value : values ) { - toOson( elementMappingType, value, options, osonGen); - } - osonGen.writeEnd(); - } - - private <X>void toOson(MappingType mappedType, - Object value, WrapperOptions options, OracleJsonGenerator osonGen) { - if (value == null) { - osonGen.writeNull(); - } - else if ( mappedType instanceof EmbeddableMappingType ) { - toOson( (X) value, osonGen, options,(EmbeddableMappingType) mappedType ); - } - else if ( mappedType instanceof BasicType<?> ) { - //noinspection unchecked - final BasicType<Object> basicType = (BasicType<Object>) mappedType; - convertedBasicValueToOson(basicType.convertToRelationalValue( value ), - options, osonGen, basicType); - } - else { - throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + mappedType.getClass().getName() ); - } - } - - private void convertedBasicValueToOson(Object value, - WrapperOptions options, - OracleJsonGenerator osonGen, - BasicType<Object> basicType) { - serializeValue( - value, - (JavaType<Object>) basicType.getJdbcJavaType(), - basicType.getJdbcType(), - options, - osonGen - ); - } - - private void serializeValue(Object value, JavaType<Object> jdbcJavaType, JdbcType jdbcType, WrapperOptions options, OracleJsonGenerator osonGen) { - //TODO: remove me. - } - - public void __arrayToOson(OracleJsonGenerator osonGen, - JavaType<?> elementJavaType, - JdbcType elementJdbcType, - Object[] values, - WrapperOptions options) { - if ( values.length == 0 ) { - osonGen.writeStartArray(); - osonGen.writeEnd(); - } - - osonGen.writeStartArray(); - for ( Object value : values ) { - //noinspection unchecked - convertedValueToOson((JavaType<Object>) elementJavaType, elementJdbcType, value, options, osonGen); - } - osonGen.writeEnd(); - } - - private void convertedValueToOson(JavaType<Object> javaType, - JdbcType jdbcType, - Object value, - WrapperOptions options, - OracleJsonGenerator osonGen) { - if ( value == null ) { - osonGen.writeNull(); - } - else if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) { - toOson(value, osonGen, options, aggregateJdbcType.getEmbeddableMappingType()); - } - else { - serializeValue( value, javaType, jdbcType, options, osonGen ); - } - } - - private <X> void toOson(X value, OracleJsonGenerator generator, WrapperOptions options, EmbeddableMappingType embeddableMappingType) { - generator.writeStartObject(); - toOsonUtil( value, generator, options,embeddableMappingType ); - generator.writeEnd(); - } - - private <X> void toOsonUtil(X value, - OracleJsonGenerator generator, - WrapperOptions options, - EmbeddableMappingType embeddableMappingType) { - - final Object[] values = embeddableMappingType.getValues( value ); - for ( int i = 0; i < values.length; i++ ) { - final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); - if ( attributeMapping instanceof SelectableMapping ) { - final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); - - generator.writeKey( name ); - toOson( attributeMapping.getMappedType(), values[i], options,generator ); - - } - else if (attributeMapping instanceof EmbeddedAttributeMapping) { - final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); - final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); - if ( values[i] == null ) { - // Skipping the update of the separator is on purpose - continue; - } - if (aggregateMapping == null) { - // flattened case - toOsonUtil( (X) values[i], - generator, - options, - mappingType ); - } - else { - // non flattened case - final String name = aggregateMapping.getSelectableName(); - generator.writeKey( name ); - generator.writeStartObject(); - toOsonUtil( (X) values[i], - generator, - options, - mappingType); - generator.writeEnd(); - - } - - } - } - } - - @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { From 3dc18c0b8bd1d55fad27d14baf623f2db8eca501 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Wed, 15 Jan 2025 17:35:22 +0530 Subject: [PATCH 36/81] HHH-17404 : Rollback Test Entity changes and JsonMapping tests fix. --- .../dialect/OracleOsonJacksonJdbcType.java | 4 +- .../embeddable/EmbeddableAggregate.java | 8 +-- .../embeddable/NestedJsonEmbeddableTest.java | 65 ------------------- 3 files changed, 7 insertions(+), 70 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 870efd4f0aef..64d35831526b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -67,7 +67,9 @@ public AggregateJdbcType resolveAggregateJdbcType( @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { - + if(javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class) { + return super.getBinder( javaType ); + } return new BasicBinder<>( javaType, this ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java index 01d63dcb166a..069ed1ac1559 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java @@ -60,7 +60,7 @@ public class EmbeddableAggregate { private byte[] theBinary; private Date theDate; private Date theTime; - private Timestamp theTimestamp; + private Date theTimestamp; private Instant theInstant; private UUID theUuid; private EntityOfBasics.Gender gender; @@ -183,12 +183,12 @@ public void setTheTime(Date theTime) { this.theTime = theTime; } - //@Temporal( TemporalType.TIMESTAMP ) - public Timestamp getTheTimestamp() { + @Temporal( TemporalType.TIMESTAMP ) + public Date getTheTimestamp() { return theTimestamp; } - public void setTheTimestamp(Timestamp theTimestamp) { + public void setTheTimestamp(Date theTimestamp) { this.theTimestamp = theTimestamp; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java index 054c2a860eda..2f5d880bcad5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedJsonEmbeddableTest.java @@ -88,7 +88,6 @@ public void testUpdate() { @Test public void testFetch() { - sessionFactoryScope().inSession( entityManager -> { List<JsonHolder> jsonHolders = entityManager.createQuery( "from JsonHolder b where b.id = 1", JsonHolder.class ).getResultList(); @@ -454,30 +453,6 @@ public static class TheJson { public TheJson() { } - public String getStringField() { - return stringField; - } - - public void setStringField(String stringField) { - this.stringField = stringField; - } - - public SimpleEmbeddable getSimpleEmbeddable() { - return simpleEmbeddable; - } - - public void setSimpleEmbeddable(SimpleEmbeddable simpleEmbeddable) { - this.simpleEmbeddable = simpleEmbeddable; - } - - public EmbeddableAggregate getNested() { - return nested; - } - - public void setNested(EmbeddableAggregate nested) { - this.nested = nested; - } - public TheJson(String stringField, Integer integerField, String leaf, EmbeddableAggregate nested) { this.stringField = stringField; this.simpleEmbeddable = new SimpleEmbeddable( integerField, leaf ); @@ -497,22 +472,6 @@ public static class SimpleEmbeddable { @JdbcTypeCode(SqlTypes.JSON) private DoubleNested doubleNested; - public Integer getIntegerField() { - return integerField; - } - - public void setIntegerField(Integer integerField) { - this.integerField = integerField; - } - - public DoubleNested getDoubleNested() { - return doubleNested; - } - - public void setDoubleNested(DoubleNested doubleNested) { - this.doubleNested = doubleNested; - } - public SimpleEmbeddable() { } @@ -558,14 +517,6 @@ public DoubleNested(String leaf) { this.theNested = new Nested( leaf ); } - public Nested getTheNested() { - return theNested; - } - - public void setTheNested(Nested theNested) { - this.theNested = theNested; - } - @Override public boolean equals(Object o) { if ( this == o ) { @@ -598,14 +549,6 @@ public Nested(String stringField) { this.theLeaf = new Leaf( stringField ); } - public Leaf getTheLeaf() { - return theLeaf; - } - - public void setTheLeaf(Leaf theLeaf) { - this.theLeaf = theLeaf; - } - @Override public boolean equals(Object o) { if ( this == o ) { @@ -637,14 +580,6 @@ public Leaf(String stringField) { this.stringField = stringField; } - public String getStringField() { - return stringField; - } - - public void setStringField(String stringField) { - this.stringField = stringField; - } - @Override public boolean equals(Object o) { if ( this == o ) { From 51cbd5d013c98b99304a2b839a53c47a5936a007 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Thu, 16 Jan 2025 18:24:14 +0100 Subject: [PATCH 37/81] HHH-17404 : general cleanup before PR --- .../SessionFactoryOptionsBuilder.java | 5 --- .../java/org/hibernate/dialect/Dialect.java | 13 ------ .../org/hibernate/dialect/OracleDialect.java | 11 +---- .../dialect/OracleDurationJavaType.java | 10 +++++ .../dialect/OracleDurationJdbcType.java | 41 ++++++------------- .../OracleOsonJacksonArrayJdbcType.java | 5 +++ .../dialect/OracleOsonJacksonJdbcType.java | 17 +++++--- .../aggregate/OracleAggregateSupport.java | 2 + .../type/descriptor/jdbc/JsonHelper.java | 13 +++++- .../type/format/JsonDocumentWriter.java | 41 ++++++++++--------- .../format/ObjectArrayOsonDocumentWriter.java | 19 +++++++-- .../type/format/StringJsonDocumentWriter.java | 24 ++++++++--- .../jackson/JacksonOsonFormatMapper.java | 6 ++- hibernate-testing/hibernate-testing.gradle | 3 -- 14 files changed, 114 insertions(+), 96 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 50b4aefc85b1..75d07ae139f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -795,11 +795,6 @@ private static FormatMapper determineJsonFormatMapper(Object setting, StrategySe setting, (Callable<FormatMapper>) () -> { FormatMapper jsonJacksonFormatMapper = null; - configurationService.getSetting( - SESSION_FACTORY_NAME_IS_JNDI, - BOOLEAN, - true - ); if (JacksonIntegration.isOracleOsonExtensionAvailable()) { jsonJacksonFormatMapper = getOsonJacksonFormatMapperOrNull(); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 3988cfa22505..f566d03caf27 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -5473,19 +5473,6 @@ public Boolean supportsRefCursors() { return null; } - - /** - * Whether this Dialect supports jakarta Temporal annotation while - * serialising or deserialising JSON - * - * @return {@code true} indicates it does; {@code false} indicates it does not - * - * @see org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData#supportsRefCursors - */ - public Boolean getSupportsJakartaTemporalAnnotationInEmbeddable() { - return Boolean.TRUE; - } - /** * Pluggable strategy for determining the {@link Size} to use for * columns of a given SQL type. diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 3a2b37689e74..5dffa323d45f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1095,19 +1095,10 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry jdbcTypeRegistry.addDescriptor( OracleOrdinalEnumJdbcType.INSTANCE ); } } - @Override - public Boolean getSupportsJakartaTemporalAnnotationInEmbeddable() { - return OracleOsonExtensionUsed == false; - } @Override public AggregateSupport getAggregateSupport() { - if (OracleOsonExtensionUsed) { - return OracleAggregateSupport.valueOf( this, false ); - } - else { - return OracleAggregateSupport.valueOf( this, true ); - } + return OracleAggregateSupport.valueOf( this ,false); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java index 7bff7256d666..ea64ae7353c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java @@ -10,6 +10,16 @@ import java.time.Duration; +/** + * Oracle sub-implementation of {@link DurationJavaType} + * which is a descriptor for {@link Duration} + * This implementation brings the support of <code>oracle.sql.INTERVALDS</code> + * as source type. + * @see #wrap(Object, WrapperOptions) + * + * @author ejannett + * @author Bidyadhar Mohanty + */ public class OracleDurationJavaType extends DurationJavaType { public static final OracleDurationJavaType INSTANCE = new OracleDurationJavaType(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java index dd85678f4261..69eb2686ffb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java @@ -5,9 +5,7 @@ package org.hibernate.dialect; import oracle.jdbc.OracleTypes; -import org.hibernate.engine.jdbc.Size; import org.hibernate.type.SqlTypes; -import org.hibernate.type.Type; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; @@ -16,7 +14,6 @@ import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.DurationJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; -import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import java.sql.CallableStatement; import java.sql.PreparedStatement; @@ -24,6 +21,14 @@ import java.sql.SQLException; import java.time.Duration; +/** + * Oracle sub-implementation of {@link DurationJdbcType} + * which is a descriptor for {@link java.time.Duration}. + * In Oracle databse Duration is stored as {@link OracleTypes.INTERVALDS} + * + * @author ejannett + * @author Bidyadhar Mohanty + */ public class OracleDurationJdbcType extends DurationJdbcType { public static final OracleDurationJdbcType INSTANCE = new OracleDurationJdbcType(); @@ -87,37 +92,17 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o }; } - /** - * The {@linkplain SqlTypes JDBC type code} used when interacting with JDBC APIs. - * <p> - * For example, it's used when calling {@link java.sql.PreparedStatement#setNull(int, int)}. - * - * @return a JDBC type code - */ + @Override public int getJdbcTypeCode() { return SqlTypes.DURATION; } - /** - * A {@linkplain SqlTypes JDBC type code} that identifies the SQL column type to - * be used for schema generation. - * <p> - * This value is passed to {@link DdlTypeRegistry#getTypeName(int, Size, Type)} - * to obtain the SQL column type. - * - * @return a JDBC type code - * @since 6.2 - */ + + @Override public int getDdlTypeCode() { return OracleTypes.INTERVALDS; } - /** - * A {@linkplain SqlTypes JDBC type code} that identifies the SQL column type. - * <p> - * This value might be different from {@link #getDdlTypeCode()} if the actual type - * e.g. JSON is emulated through a type like CLOB. - * - * @return a JDBC type code - */ + + @Override public int getDefaultSqlTypeCode() { return OracleTypes.INTERVALDS; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index 3af61871895a..9174046e0c40 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -25,6 +25,11 @@ import java.sql.SQLException; /** + * + * Type mapping of (JSON) array of JSON SQL data type for Oracle database. + * This implementation is used when Jackson mapper is used and that the JDBC OSON extension + * is available. + * * @author Emmanuel Jannetti * @author Bidyadhar Mohanty */ diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 64d35831526b..325b310eb9ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -28,6 +28,11 @@ import java.sql.SQLException; /** + * + * Type mapping JSON SQL data type for Oracle database. + * This implementation is used when Jackson mapper is used and that the JDBC OSON extension + * is available. + * * @author Emmanuel Jannetti */ public class OracleOsonJacksonJdbcType extends OracleJsonJdbcType { @@ -83,9 +88,9 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) ByteArrayOutputStream out = new ByteArrayOutputStream(); JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); - JsonGenerator osonGen = osonFactory.createGenerator( out ); - mapper.writeToTarget( value, javaType, osonGen, options ); - osonGen.close(); // until now + try (JsonGenerator osonGen = osonFactory.createGenerator( out )) { + mapper.writeToTarget( value, javaType, osonGen, options ); + } return out.toByteArray(); } @@ -143,9 +148,9 @@ private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { } JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); - JsonParser osonParser = osonFactory.createParser( osonBytes ); - - return mapper.readFromSource( type, osonParser, options ); + try (JsonParser osonParser = osonFactory.createParser( osonBytes )) { + return mapper.readFromSource( type, osonParser, options ); + } } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index 4e7acca28883..82e89624a3a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -75,6 +75,8 @@ public class OracleAggregateSupport extends AggregateSupportImpl { OracleAggregateSupport(boolean checkConstraintSupport, JsonSupport jsonSupport, boolean dateTypesStoreAsString) { this.checkConstraintSupport = checkConstraintSupport; this.jsonSupport = jsonSupport; + // this flag tell us if data is serialized/de-serialized as String. As opposed to using OSON + // In other words, this flag tells us if the Oracle OSON JDBC extension is used or not. this.dateTypesStoreAsString = dateTypesStoreAsString; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 254b5fb04e91..2335cd105dff 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -105,7 +105,7 @@ public static void serializeArray(JavaType<?> elementJavaType, JdbcType elementJ } /** - * Checks that a JDBCType is assignable to an array + * Checks that a <code>JDBCType</code> is assignable to an array * @param type the jdbc type * @return <code>true</code> if types is of array kind <code>false</code> otherwise. */ @@ -115,7 +115,7 @@ private static boolean isArrayType(JdbcType type) { } /** - * Serialized an Object value to a JSON document writer. + * Serialized an Object value to JSON object using a document writer. * * @param embeddableMappingType the embeddable mapping definition of the given value. * @param domainValue the value to be serialized. @@ -130,6 +130,15 @@ public static void serialize(EmbeddableMappingType embeddableMappingType, writer.endObject(); } + /** + * JSON object attirbute serialization + * @see #serialize(EmbeddableMappingType, Object, WrapperOptions, JsonDocumentWriter) + * @param embeddableMappingType the embeddable mapping definition of the given value. + * @param domainValue the value to be serialized. + * @param options wrapping options + * @param writer the document writer + * @throws IOException + */ private static void serializeMapping(EmbeddableMappingType embeddableMappingType, Object domainValue, WrapperOptions options, JsonDocumentWriter writer) throws IOException { final Object[] values = embeddableMappingType.getValues( domainValue ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java index 6a9923b1a9ed..2a0f8df1a687 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java @@ -13,64 +13,67 @@ /** * JSON document producer. - * Used to parse JSON documents. Implementors of this will define - * proper callback implementations. - * + * Implementation of this inteface will used to build a JSON document. + * Implementation example is {@link StringJsonDocumentWriter } * @author Emmanuel Jannetti */ public interface JsonDocumentWriter { /** - * Callback to be called when the start of an JSON object is encountered. + * Starts a new JSON Objects. */ - void startObject() throws IOException; + void startObject(); /** - * Callback to be called when the end of an JSON object is encountered. + * Ends a new JSON Objects */ - void endObject() throws IOException; + void endObject(); /** - * Callback to be called when the start of an array is encountered. + * Starts a new JSON array. + * @throws IOException an I/O error roccured while starting the object. */ void startArray(); /** - * Callback to be called when the end of an array is encountered. + * Ends a new JSON array. + * @throws IOException an I/O error roccured while starting the object. */ void endArray(); /** - * Callback to be called when the key of JSON attribute is encountered. - * @param key the attribute name + * Adds a new JSON element name. + * @param key the element name. + * @throws IOException an I/O error roccured while starting the object. */ void objectKey(String key); /** - * Callback to be called when null value is encountered. + * Adds a new JSON element null value. + * @throws IOException an I/O error roccured while starting the object. */ void nullValue(); /** - * Callback to be called when boolean value is encountered. - * @param value the boolean value + * Adds a new JSON element boolean value. + * @param value the element boolean name. */ void booleanValue(boolean value); /** - * Callback to be called when string value is encountered. - * @param value the String value + * Adds a new JSON element string value. + * @param value the element string name. */ void stringValue(String value); /** - * Callback to be called when Number value is encountered. - * @param value the String value. + * Adds a new JSON element Number value. + * @param value the element Number name. */ void numberValue(Number value); /** - * Serialize a JSON value to the document + * Adds a JSON value to the document * @param value the value to be serialized * @param javaType the Java type of the value * @param jdbcType the JDBC type for the value to be serialized diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java index 3a5300e11ad3..dfcf537bb191 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java @@ -17,7 +17,6 @@ import org.hibernate.type.descriptor.java.UUIDJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; -import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Time; @@ -42,19 +41,23 @@ public class ObjectArrayOsonDocumentWriter implements JsonDocumentWriter { private final OracleJsonGenerator generator; + /** + * Creates a new OSON document writer + * @param generator the JSON generator. + */ public ObjectArrayOsonDocumentWriter(OracleJsonGenerator generator) { this.generator = generator; } @Override - public void startObject() throws IOException { + public void startObject() { this.generator.writeStartObject(); } @Override - public void endObject() throws IOException { + public void endObject() { this.generator.writeEnd(); } @@ -105,7 +108,15 @@ public void serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType serializeValue(value, javaType, jdbcType, options); } - + /** + * Serializes a value according to its mapping type. + * This method serializes the value and writes it into the underlying generator + * + * @param value the value + * @param javaType the Java type of the value + * @param jdbcType the JDBC SQL type of the value + * @param options the wapping options. + */ private void serializeValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java index 6bac99e8bc1a..a0d867bf4763 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -14,13 +14,13 @@ import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; -import java.io.IOException; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.Stack; /** - * JsonDocument String writer implementation + * Implementation of <code>JsonDocumentWriter</code> for String based OSON document. + * This implementation will receive a {@link JsonHelper.JsonAppender } to a serialze JSON object to it * @author Emmanuel Jannetti */ public class StringJsonDocumentWriter implements JsonDocumentWriter{ @@ -50,7 +50,10 @@ private enum PROCESSING_STATE { } private Stack<PROCESSING_STATE> processingStates = new Stack<>(); - + /** + * Creates a new StringJsonDocumentWriter. + * @param appender the appender to receive the serialze JSON object + */ public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { this.processingStates.push( PROCESSING_STATE.NONE ); this.appender = appender; @@ -60,7 +63,7 @@ public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { * Callback to be called when the start of an JSON object is encountered. */ @Override - public void startObject() throws IOException { + public void startObject() { // Note: startArray and startObject must not call moveProcessingStateMachine() if (this.processingStates.peek() == PROCESSING_STATE.STARTING_ARRAY) { // are we building an array of objects? @@ -82,7 +85,7 @@ else if (this.processingStates.peek() == PROCESSING_STATE.ARRAY) { * Callback to be called when the end of an JSON object is encountered. */ @Override - public void endObject() throws IOException { + public void endObject() { this.appender.append( OBJECT_END_MARKER ); this.processingStates.push( PROCESSING_STATE.ENDING_OBJECT); moveProcessingStateMachine(); @@ -173,6 +176,7 @@ private void moveProcessingStateMachine() { // STARTING_ARRAY // first pop ENDING_ARRAY this.processingStates.pop(); + // if we have ARRAY, so that's not an empty array. pop that state if (this.processingStates.peek().equals( PROCESSING_STATE.ARRAY )) this.processingStates.pop(); assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_ARRAY ); @@ -183,6 +187,7 @@ private void moveProcessingStateMachine() { // STARTING_OBJECT // first pop ENDING_OBJECT this.processingStates.pop(); + // if we have OBJECT, so that's not an empty object. pop that state if (this.processingStates.peek().equals( PROCESSING_STATE.OBJECT )) this.processingStates.pop(); assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_OBJECT ); @@ -235,6 +240,15 @@ public void serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType moveProcessingStateMachine(); } + /** + * Converts a value to String according to its mapping type. + * This method serializes the value and writes it into the underlying appender + * + * @param value the value + * @param javaType the Java type of the value + * @param jdbcType the JDBC SQL type of the value + * @param options the wapping options. + */ private void convertedBasicValueToString( Object value, WrapperOptions options, diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index fdd2eb7b9216..f7c4b1900e74 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -30,6 +30,8 @@ /** + * Implementation of FormatMapper for Orale OSON support + * * @author Emmanuel Jannetti * @author Bidyadhar Mohanty */ @@ -66,7 +68,9 @@ public JacksonOsonFormatMapper() { } /** - * Process OSON parser tokens + * Process OSON parser tokens. + * This method consume one by one event coming from an OSON parser and use the given JsonDocumentHandler + * to populate values into Object array * @param osonParser the OSON parser * @param currentEvent the current of the parser * @throws IOException error while reading from underlying parser diff --git a/hibernate-testing/hibernate-testing.gradle b/hibernate-testing/hibernate-testing.gradle index 7a4322acf911..f64f1e44418c 100644 --- a/hibernate-testing/hibernate-testing.gradle +++ b/hibernate-testing/hibernate-testing.gradle @@ -46,8 +46,5 @@ dependencies { implementation testLibs.junit5Launcher annotationProcessor project( ':hibernate-processor' ) - - } - From 9a3b85c615541853f2f6141f82d2fe09fd0bb862 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Fri, 17 Jan 2025 13:16:13 +0530 Subject: [PATCH 38/81] HHH-17404- Rollback OracleUserDefinedTypeExporter changes --- .../hibernate/dialect/type/OracleUserDefinedTypeExporter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java index 4c37624d4344..4808017f80ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleUserDefinedTypeExporter.java @@ -337,7 +337,7 @@ private String determineValueExpression(String expression, int elementSqlTypeCod case DATE: return "to_date(" + expression + ",'YYYY-MM-DD')"; case TIME: - return "to_timestamp(CONCAT('1970-01-01 ', \" + expression + \"),'YYYY-MM-DD hh24:mi:ss')"; + return "to_timestamp(" + expression + ",'hh24:mi:ss')"; case TIMESTAMP: return "to_timestamp(" + expression + ",'YYYY-MM-DD\"T\"hh24:mi:ss.FF9')"; case TIMESTAMP_WITH_TIMEZONE: From 0548022ed6c09bd7720d6021952e0c8d6ffbff0c Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Fri, 17 Jan 2025 14:06:58 +0530 Subject: [PATCH 39/81] HHH-17404- Aggregate instance creation fix. --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 5dffa323d45f..58e9df7ebcf3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1098,7 +1098,8 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this ,false); + return OracleAggregateSupport.valueOf( this , + !JacksonIntegration.isOracleOsonExtensionAvailable()); } @Override From 5181de9dd7e813b18c7f3865034db3a09041967c Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Fri, 17 Jan 2025 09:38:31 +0100 Subject: [PATCH 40/81] HHH-17404 : fix aggregatesupport for "no-extension" case --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 58e9df7ebcf3..9e5d71c618ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1098,8 +1098,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this , - !JacksonIntegration.isOracleOsonExtensionAvailable()); + return OracleAggregateSupport.valueOf( this ,OracleOsonExtensionUsed); } @Override From eeec7c30609b8c072aa5eba23f99a7e76772415e Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Fri, 17 Jan 2025 10:38:27 +0100 Subject: [PATCH 41/81] HHH-17404 : fix condition on aggregate support load --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 9e5d71c618ac..b20b7ed47533 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1098,7 +1098,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this ,OracleOsonExtensionUsed); + return OracleAggregateSupport.valueOf( this ,!OracleOsonExtensionUsed); } @Override From e3205caed62cacf2059f14fc2b78f933512e970a Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Sat, 18 Jan 2025 17:43:47 +0100 Subject: [PATCH 42/81] HHH-17404 : refactor OSON formatMapper to align on JSON one. --- .../dialect/OracleOsonJacksonJdbcType.java | 23 +++- .../org/hibernate/dialect/OsonHelper.java | 104 +++++++++++++++ .../jackson/JacksonOsonFormatMapper.java | 123 +----------------- 3 files changed, 125 insertions(+), 125 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 325b310eb9ea..dd7364105a97 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -9,6 +9,9 @@ import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; import oracle.sql.json.OracleJsonDatum; +import oracle.sql.json.OracleJsonFactory; +import oracle.sql.json.OracleJsonGenerator; +import oracle.sql.json.OracleJsonParser; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.type.descriptor.ValueBinder; @@ -19,9 +22,12 @@ import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.ObjectArrayOsonDocumentHandler; +import org.hibernate.type.format.ObjectArrayOsonDocumentWriter; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -83,7 +89,12 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); if (getEmbeddableMappingType()!= null) { - return ((JacksonOsonFormatMapper)mapper).fromObjectArray(value,javaType,options,getEmbeddableMappingType()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); + ObjectArrayOsonDocumentWriter writer = new ObjectArrayOsonDocumentWriter(generator); + JsonHelper.serialize( getEmbeddableMappingType(), value,options,writer); + generator.close(); + return out.toByteArray(); } ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -137,8 +148,14 @@ private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { // an array of objects. We use JsonParser to fetch values // and build the array.(as opposed to let Jackson do it as we do not // have a proper object definition at that stage). - return ((JacksonOsonFormatMapper)mapper).toObjectArray( - getEmbeddableMappingType(), osonBytes, options ); + OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( ByteBuffer.wrap( osonBytes ) ); + + ObjectArrayOsonDocumentHandler handler = new ObjectArrayOsonDocumentHandler( getEmbeddableMappingType(), + options); + + OsonHelper.consumeOsonTokens(osonParser, osonParser.next(), handler); + + return (X) handler.getObjectArray(); } JavaType <X> type = getJavaType(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java new file mode 100644 index 000000000000..09c57aa9c2c0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java @@ -0,0 +1,104 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect; + +import oracle.sql.json.OracleJsonParser; +import org.hibernate.Internal; +import org.hibernate.type.format.JsonDocumentHandler; +import org.hibernate.type.format.ObjectArrayOsonDocumentHandler; + +import java.io.IOException; + +/** + * A Helper for handling OSON events + */ +@Internal +public class OsonHelper { + + /** + * Process OSON parser tokens. + * This method consumes one by one event coming from an OSON parser and uses the given JsonDocumentHandler + * to populate values into Object array + * @param osonParser the OSON parser + * @param currentEvent the current of the parser + * @throws IOException error while reading from underlying parser + */ + public static void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Event currentEvent, JsonDocumentHandler handler) + throws IOException { + + OracleJsonParser.Event event = currentEvent; + + while ( event != null ) { + switch ( event ) { + case OracleJsonParser.Event.KEY_NAME: + handler.onObjectKey( osonParser.getString() ); + break; + case OracleJsonParser.Event.START_ARRAY: + handler.onStartArray(); + break; + case OracleJsonParser.Event.END_ARRAY: + handler.onEndArray(); + break; + case OracleJsonParser.Event.VALUE_DATE: + case OracleJsonParser.Event.VALUE_TIMESTAMP: + ((ObjectArrayOsonDocumentHandler)handler).onOsonDateValue( + osonParser.getLocalDateTime()); + break; + case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getOffsetDateTime()); + break; + case OracleJsonParser.Event.VALUE_INTERVALDS: + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getDuration()); + break; + case OracleJsonParser.Event.VALUE_INTERVALYM: + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getPeriod()); + break; + case OracleJsonParser.Event.VALUE_STRING: + handler.onStringValue( osonParser.getString() ); + break; + case OracleJsonParser.Event.VALUE_TRUE: + handler.onBooleanValue( true ); + break; + case OracleJsonParser.Event.VALUE_FALSE: + handler.onBooleanValue( false ); + break; + case OracleJsonParser.Event.VALUE_NULL: + handler.onNullValue(); + break; + case OracleJsonParser.Event.VALUE_DECIMAL: + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getBigDecimal()); + break; + case OracleJsonParser.Event.VALUE_DOUBLE: + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getDouble()); + break; + case OracleJsonParser.Event.VALUE_FLOAT: + ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( + osonParser.getFloat()); + break; + case OracleJsonParser.Event.VALUE_BINARY: + ((ObjectArrayOsonDocumentHandler)handler).onOsonBinaryValue( + osonParser.getBytes()); + break; + case OracleJsonParser.Event.START_OBJECT: + handler.onStartObject(); + break; + case OracleJsonParser.Event.END_OBJECT: + handler.onEndObject(); + break; + default: + throw new IOException( "Unknown OSON event " + event ); + + } + event = osonParser.hasNext() ? osonParser.next() : null; + } + + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index f7c4b1900e74..34368a05b58d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -7,11 +7,9 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.Module; -import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.SerializationFeature; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; -import oracle.sql.json.OracleJsonParser; import org.hibernate.dialect.JsonHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.descriptor.WrapperOptions; @@ -20,13 +18,10 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JsonJdbcType; -import org.hibernate.type.format.JsonDocumentHandler; -import org.hibernate.type.format.ObjectArrayOsonDocumentHandler; import org.hibernate.type.format.ObjectArrayOsonDocumentWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.ByteBuffer; /** @@ -67,121 +62,6 @@ public JacksonOsonFormatMapper() { objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } - /** - * Process OSON parser tokens. - * This method consume one by one event coming from an OSON parser and use the given JsonDocumentHandler - * to populate values into Object array - * @param osonParser the OSON parser - * @param currentEvent the current of the parser - * @throws IOException error while reading from underlying parser - */ - private void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Event currentEvent, JsonDocumentHandler handler) - throws IOException { - - OracleJsonParser.Event event = currentEvent; - - while ( event != null ) { - switch ( event ) { - case OracleJsonParser.Event.KEY_NAME: - handler.onObjectKey( osonParser.getString() ); - break; - case OracleJsonParser.Event.START_ARRAY: - handler.onStartArray(); - break; - case OracleJsonParser.Event.END_ARRAY: - handler.onEndArray(); - break; - case OracleJsonParser.Event.VALUE_DATE: - case OracleJsonParser.Event.VALUE_TIMESTAMP: - ((ObjectArrayOsonDocumentHandler)handler).onOsonDateValue( - osonParser.getLocalDateTime()); - break; - case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getOffsetDateTime()); - break; - case OracleJsonParser.Event.VALUE_INTERVALDS: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getDuration()); - break; - case OracleJsonParser.Event.VALUE_INTERVALYM: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getPeriod()); - break; - case OracleJsonParser.Event.VALUE_STRING: - handler.onStringValue( osonParser.getString() ); - break; - case OracleJsonParser.Event.VALUE_TRUE: - handler.onBooleanValue( true ); - break; - case OracleJsonParser.Event.VALUE_FALSE: - handler.onBooleanValue( false ); - break; - case OracleJsonParser.Event.VALUE_NULL: - handler.onNullValue(); - break; - case OracleJsonParser.Event.VALUE_DECIMAL: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getBigDecimal()); - break; - case OracleJsonParser.Event.VALUE_DOUBLE: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getDouble()); - break; - case OracleJsonParser.Event.VALUE_FLOAT: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getFloat()); - break; - case OracleJsonParser.Event.VALUE_BINARY: - ((ObjectArrayOsonDocumentHandler)handler).onOsonBinaryValue( - osonParser.getBytes()); - break; - case OracleJsonParser.Event.START_OBJECT: - handler.onStartObject(); - break; - case OracleJsonParser.Event.END_OBJECT: - handler.onEndObject(); - break; - default: - throw new IOException( "Unknown OSON event " + event ); - - } - event = osonParser.hasNext() ? osonParser.next() : null; - } - - } - - - /** - * Consumes OSON bytes and populate an Object array as described in the embeddable mapping definitions. - * @param embeddableMappingType the embeddable mapping definitions - * @param source the OSON bytes as <code>byte[]</code> - * @param options the wrapping options - * @return the Object array - * @param <T> return type i.e., object array - * @throws IOException OSON parsing has failed - */ - public <T> T toObjectArray(EmbeddableMappingType embeddableMappingType, Object source, WrapperOptions options) throws IOException { - - OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( ByteBuffer.wrap( (byte[])source ) ); - - ObjectArrayOsonDocumentHandler handler = new ObjectArrayOsonDocumentHandler( embeddableMappingType, - options); - - consumeOsonTokens(osonParser, osonParser.next(), handler); - - return (T)handler.getObjectArray(); - } - - public <X>byte[] fromObjectArray(X value, JavaType<X> javaType, WrapperOptions options,EmbeddableMappingType embeddableMappingType) - throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); - ObjectArrayOsonDocumentWriter writer = new ObjectArrayOsonDocumentWriter(generator); - JsonHelper.serialize( embeddableMappingType, value,options,writer); - generator.close(); - return out.toByteArray(); - } public <X>byte[] arrayToOson(X value, JavaType<X> javaType, @@ -211,8 +91,7 @@ public <X>byte[] arrayToOson(X value, @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { - ObjectWriter writer = objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ); - writer.writeValue( (JsonGenerator) target, value); + objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ).writeValue( (JsonGenerator) target, value); } From 29ad797ffbaf5bb4f4818ce1566c5edf8c61d1b2 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Tue, 21 Jan 2025 11:44:13 +0100 Subject: [PATCH 43/81] HHH-17404 : applies some review comments --- .../OracleOsonJacksonArrayJdbcType.java | 45 ++++++++++++++++--- .../dialect/OracleOsonJacksonJdbcType.java | 13 +++--- .../type/descriptor/jdbc/JsonHelper.java | 18 ++++---- .../ObjectArrayOsonDocumentHandler.java | 37 ++++++++------- ...entWriter.java => OsonDocumentWriter.java} | 4 +- .../type/format/StringJsonDocumentWriter.java | 19 ++++---- .../jackson/JacksonOsonFormatMapper.java | 36 --------------- 7 files changed, 86 insertions(+), 86 deletions(-) rename hibernate-core/src/main/java/org/hibernate/type/format/{ObjectArrayOsonDocumentWriter.java => OsonDocumentWriter.java} (97%) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index 9174046e0c40..5026ef539e57 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -6,19 +6,27 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; +import oracle.jdbc.OracleType; import oracle.sql.json.OracleJsonDatum; +import oracle.sql.json.OracleJsonFactory; +import oracle.sql.json.OracleJsonGenerator; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.OsonDocumentWriter; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; -import java.io.ByteArrayInputStream; -import java.io.InputStream; +import java.io.ByteArrayOutputStream; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -67,15 +75,38 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { return new BasicBinder<>( javaType, this ) { - private <X> InputStream toOsonStream(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { - FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - return new ByteArrayInputStream(((JacksonOsonFormatMapper)mapper).arrayToOson(value, javaType,getElementJdbcType(),options)); + private <X> byte[] toOsonStream(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out )) { + OsonDocumentWriter writer = new OsonDocumentWriter( generator ); + + if ( getElementJdbcType() instanceof JsonJdbcType jsonElementJdbcType ) { + final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); + JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer ); + } + else { + assert !( getElementJdbcType() instanceof AggregateJdbcType ); + final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); + JsonHelper.serializeArray( + elementJavaType, + getElementJdbcType(), + domainObjects, + options, + writer + ); + } + generator.close(); + return out.toByteArray(); + } + } @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { try { - st.setBinaryStream( index, toOsonStream( value, getJavaType(), options ) ); + st.setObject( index, toOsonStream( value, getJavaType(), options ), OracleType.JSON ); } catch (Exception e) { throw new SQLException( e ); @@ -86,7 +117,7 @@ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions o protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { try { - st.setBinaryStream( name, toOsonStream( value, getJavaType(), options ) ); + st.setObject( name, toOsonStream( value, getJavaType(), options ) , OracleType.JSON); } catch (Exception e) { throw new SQLException( e ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index dd7364105a97..6ab8a284a4dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -23,7 +23,7 @@ import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.ObjectArrayOsonDocumentHandler; -import org.hibernate.type.format.ObjectArrayOsonDocumentWriter; +import org.hibernate.type.format.OsonDocumentWriter; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayOutputStream; @@ -88,12 +88,13 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); - if (getEmbeddableMappingType()!= null) { + if (getEmbeddableMappingType() != null) { ByteArrayOutputStream out = new ByteArrayOutputStream(); - OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); - ObjectArrayOsonDocumentWriter writer = new ObjectArrayOsonDocumentWriter(generator); - JsonHelper.serialize( getEmbeddableMappingType(), value,options,writer); - generator.close(); + // OracleJsonFactory is used and not OracleOsonFactory as Jackson is not involved here + try (OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out )) { + OsonDocumentWriter writer = new OsonDocumentWriter( generator); + JsonHelper.serialize( getEmbeddableMappingType(), value,options,writer); + } return out.toByteArray(); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 2335cd105dff..3c57a42129ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -241,15 +241,7 @@ else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { } final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); - if ( mappingType.shouldSelectAggregateMapping()) { - final String name = aggregateMapping.getSelectableName(); - appender.append( separator ); - appender.append( '"' ); - appender.append( name ); - appender.append( "\":" ); - toString( mappingType, values[i], options, appender ); - } - else { + if ( aggregateMapping == null) { toString( mappingType, options, @@ -258,6 +250,14 @@ else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { separator ); } + else { + final String name = aggregateMapping.getSelectableName(); + appender.append( separator ); + appender.append( '"' ); + appender.append( name ); + appender.append( "\":" ); + toString( mappingType, values[i], options, appender ); + } } else { throw new UnsupportedOperationException( "Support for attribute mapping type not yet implemented: " + attributeMapping.getClass().getName() ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java index 0833fd8707ed..608467bcc43f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java @@ -4,6 +4,7 @@ */ package org.hibernate.type.format; +import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.type.BasicPluralType; @@ -18,7 +19,7 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.List; -import java.util.Stack; + /** * Implementation of <code>JsonDocumentHandler</code> for OSON document. @@ -42,11 +43,11 @@ public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { // Each mapping definition may contain sub mappings (sub embeddable mapping) // This stack is used to keep a pointer on the current mapping to be used to assign correct types. // see onStartObject()/onEndObject() methods - Stack<EmbeddableMappingType> embeddableMappingTypes = new Stack<>(); + StandardStack<EmbeddableMappingType> embeddableMappingTypes = new StandardStack<>(); // As for mapping definitions, when "sub embeddable" is encountered, the array // that needs to be filled with Objects is the one we allocate in the final result array slot. // We use a stack to keep track of array ref - Stack<Object[]> objectArrays = new Stack<>(); + StandardStack<Object[]> objectArrays = new StandardStack<>(); WrapperOptions wrapperOptions; @@ -75,18 +76,20 @@ public void onStartObject() { // We are dealing with a sub-object, allocate space for it then, // otherwise, we have nothing to do. // Push the new (sub)mapping definition. - this.currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); + assert embeddableMappingTypes.getCurrent() != null; + this.currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); assert currentSelectableIndexInResultArray != -1: "Cannot get index of " + currentKeyName; - final SelectableMapping selectable = embeddableMappingTypes.peek().getJdbcValueSelectable( + final SelectableMapping selectable = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( currentSelectableIndexInResultArray ); final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() .getJdbcType(); final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); - this.objectArrays.peek()[currentSelectableIndexInResultArray] = + assert this.objectArrays.getCurrent() != null; + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = new Object[subMappingType.getJdbcValueCount()]; this.embeddableMappingTypes.push( subMappingType ); - this.objectArrays.push( (Object[]) this.objectArrays.peek()[currentSelectableIndexInResultArray] ); + this.objectArrays.push( (Object[]) this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] ); } } @@ -113,7 +116,7 @@ public void onStartArray() { public void onEndArray() { assert (subArrayObjectList != null && subArrayObjectTypes != null) : "onEndArray called before onStartArray"; // flush array values - this.objectArrays.peek()[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, wrapperOptions ); + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, wrapperOptions ); // reset until we encounter next array element subArrayObjectList = null; subArrayObjectTypes = null; @@ -123,17 +126,17 @@ public void onEndArray() { public void onObjectKey(String key) { this.currentKeyName = key; - currentSelectableIndexInResultArray = embeddableMappingTypes.peek().getSelectableIndex( currentKeyName ); + currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); if ( currentSelectableIndexInResultArray >= 0 ) { // we may not have a selectable mapping for that key - currentSelectableMapping = embeddableMappingTypes.peek().getJdbcValueSelectable( currentSelectableIndexInResultArray ); + currentSelectableMapping = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( currentSelectableIndexInResultArray ); } else { throw new IllegalArgumentException( String.format( "Could not find selectable [%s] in embeddable type [%s] for JSON processing.", currentKeyName, - embeddableMappingTypes.peek().getMappedJavaType().getJavaTypeClass().getName() + embeddableMappingTypes.getCurrent().getMappedJavaType().getJavaTypeClass().getName() ) ); } @@ -146,7 +149,7 @@ public void onNullValue() { subArrayObjectList.add( null ); } else { - this.objectArrays.peek()[currentSelectableIndexInResultArray] = null; + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = null; } } @@ -157,7 +160,7 @@ public void onBooleanValue(boolean value) { subArrayObjectList.add( value?Boolean.TRUE:Boolean.FALSE); } else { - this.objectArrays.peek()[currentSelectableIndexInResultArray] = value?Boolean.TRUE:Boolean.FALSE; + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = value?Boolean.TRUE:Boolean.FALSE; } } @@ -169,7 +172,7 @@ public void onStringValue(String value) { subArrayObjectTypes.getElementType().getJdbcJavaType().fromEncodedString( value ,0,value.length()) ); } else { - this.objectArrays.peek()[currentSelectableIndexInResultArray] = + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = currentSelectableMapping.getJdbcMapping().getJdbcJavaType().fromEncodedString( value,0,value.length()); } } @@ -190,7 +193,7 @@ public <T> void onOsonValue(T value) { subArrayObjectList.add( value ); } else { - this.objectArrays.peek()[currentSelectableIndexInResultArray] = + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = currentSelectableMapping.getJdbcMapping().convertToDomainValue( currentSelectableMapping.getJdbcMapping().getJdbcJavaType() .wrap( value, wrapperOptions ) ); @@ -223,7 +226,7 @@ public void onOsonBinaryValue(byte[] bytes) { subArrayObjectList.add( theOneToBeUsed ); } else { - this.objectArrays.peek()[currentSelectableIndexInResultArray] = theOneToBeUsed; + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = theOneToBeUsed; } } @@ -270,7 +273,7 @@ else if ( java.util.Date.class.isAssignableFrom( underlyingType ) ) { subArrayObjectList.add( theOneToBeUsed ); } else { - this.objectArrays.peek()[currentSelectableIndexInResultArray] = theOneToBeUsed; + this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = theOneToBeUsed; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java similarity index 97% rename from hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java rename to hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index dfcf537bb191..7a4588750664 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -36,7 +36,7 @@ * * @author Emmanuel Jannetti */ -public class ObjectArrayOsonDocumentWriter implements JsonDocumentWriter { +public class OsonDocumentWriter implements JsonDocumentWriter { private final OracleJsonGenerator generator; @@ -45,7 +45,7 @@ public class ObjectArrayOsonDocumentWriter implements JsonDocumentWriter { * Creates a new OSON document writer * @param generator the JSON generator. */ - public ObjectArrayOsonDocumentWriter(OracleJsonGenerator generator) { + public OsonDocumentWriter(OracleJsonGenerator generator) { this.generator = generator; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java index a0d867bf4763..07c15071a438 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -5,6 +5,7 @@ package org.hibernate.type.format; import org.hibernate.dialect.JsonHelper; +import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BooleanJavaType; @@ -16,7 +17,7 @@ import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; -import java.util.Stack; + /** * Implementation of <code>JsonDocumentWriter</code> for String based OSON document. @@ -48,7 +49,7 @@ private enum PROCESSING_STATE { ENDING_ARRAY, // we are ending an array ARRAY // we are piling array values } - private Stack<PROCESSING_STATE> processingStates = new Stack<>(); + private StandardStack<PROCESSING_STATE> processingStates = new StandardStack<>(); /** * Creates a new StringJsonDocumentWriter. @@ -65,13 +66,13 @@ public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { @Override public void startObject() { // Note: startArray and startObject must not call moveProcessingStateMachine() - if (this.processingStates.peek() == PROCESSING_STATE.STARTING_ARRAY) { + if (this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_ARRAY) { // are we building an array of objects? // i.e, [{},...] // move to PROCESSING_STATE.ARRAY first this.processingStates.push( PROCESSING_STATE.ARRAY); } - else if (this.processingStates.peek() == PROCESSING_STATE.ARRAY) { + else if (this.processingStates.getCurrent() == PROCESSING_STATE.ARRAY) { // That means that we ae building an array of object ([{},...]) // JSON object hee are treat as array item. // -> add the marker first @@ -115,7 +116,7 @@ public void endArray() { @Override public void objectKey(String key) { - if (this.processingStates.peek().equals( PROCESSING_STATE.OBJECT )) { + if (this.processingStates.getCurrent().equals( PROCESSING_STATE.OBJECT )) { // we have started an object, and we are adding an item key: we do add a separator. this.appender.append( SEPARATOR_MARKER ); } @@ -132,7 +133,7 @@ public void objectKey(String key) { * Separator is to separate array items or key/value pairs in an object. */ private void addItemsSeparator() { - if (this.processingStates.peek().equals( PROCESSING_STATE.ARRAY )) { + if (this.processingStates.getCurrent().equals( PROCESSING_STATE.ARRAY )) { // We started to serialize an array and already added item to it:add a separator anytime. this.appender.append( SEPARATOR_MARKER ); } @@ -161,7 +162,7 @@ private void addItemsSeparator() { * */ private void moveProcessingStateMachine() { - switch (this.processingStates.peek()) { + switch (this.processingStates.getCurrent()) { case STARTING_OBJECT: //after starting an object, we start adding key/value pairs this.processingStates.push( PROCESSING_STATE.OBJECT ); @@ -177,7 +178,7 @@ private void moveProcessingStateMachine() { // first pop ENDING_ARRAY this.processingStates.pop(); // if we have ARRAY, so that's not an empty array. pop that state - if (this.processingStates.peek().equals( PROCESSING_STATE.ARRAY )) + if (this.processingStates.getCurrent().equals( PROCESSING_STATE.ARRAY )) this.processingStates.pop(); assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_ARRAY ); break; @@ -188,7 +189,7 @@ private void moveProcessingStateMachine() { // first pop ENDING_OBJECT this.processingStates.pop(); // if we have OBJECT, so that's not an empty object. pop that state - if (this.processingStates.peek().equals( PROCESSING_STATE.OBJECT )) + if (this.processingStates.getCurrent().equals( PROCESSING_STATE.OBJECT )) this.processingStates.pop(); assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_OBJECT ); break; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 34368a05b58d..883179173648 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -8,19 +8,9 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.SerializationFeature; -import oracle.sql.json.OracleJsonFactory; -import oracle.sql.json.OracleJsonGenerator; -import org.hibernate.dialect.JsonHelper; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; -import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.JsonJdbcType; -import org.hibernate.type.format.ObjectArrayOsonDocumentWriter; -import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -62,32 +52,6 @@ public JacksonOsonFormatMapper() { objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } - - public <X>byte[] arrayToOson(X value, - JavaType<X> javaType, - JdbcType elementJdbcType, - WrapperOptions options) { - - final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out ); - ObjectArrayOsonDocumentWriter writer = new ObjectArrayOsonDocumentWriter(generator); - - if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { - final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); - JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer); - } - else { - assert !( elementJdbcType instanceof AggregateJdbcType); - final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); - JsonHelper.serializeArray( elementJavaType, elementJdbcType, domainObjects, options, writer ); - } - - generator.close(); - return out.toByteArray(); - } - @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { From c1f149ad79f09d7f91fabc020f091fbe9138a745 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 29 Jan 2025 17:55:22 +0100 Subject: [PATCH 44/81] HHH-17404 : implement JSON document readers and writers for OSON extension and for ususla String-based JSON support --- .../internal/StrategySelectorBuilder.java | 3 +- .../org/hibernate/dialect/OracleDialect.java | 1 + .../OracleOsonJacksonArrayJdbcType.java | 5 +- .../dialect/OracleOsonJacksonJdbcType.java | 32 +- .../internal/util/CharSequenceHelper.java | 4 + .../java/BooleanPrimitiveArrayJavaType.java | 2 +- .../java/DoublePrimitiveArrayJavaType.java | 2 +- .../java/FloatPrimitiveArrayJavaType.java | 2 +- .../java/IntegerPrimitiveArrayJavaType.java | 2 +- .../type/descriptor/java/JavaType.java | 4 + .../java/LongPrimitiveArrayJavaType.java | 2 +- .../java/ShortPrimitiveArrayJavaType.java | 2 +- .../type/descriptor/jdbc/JsonHelper.java | 1448 +++-------------- .../type/descriptor/jdbc/JsonJdbcType.java | 18 +- .../type/format/JsonDocumentItem.java | 87 + .../type/format/JsonDocumentReader.java | 148 ++ .../format/JsonDocumentReaderFactory.java | 33 + .../type/format/JsonDocumentWriter.java | 33 +- .../type/format/JsonValueJDBCTypeAdapter.java | 47 + .../JsonValueJDBCTypeAdapterFactory.java | 31 + .../type/format/OsonDocumentReader.java | 215 +++ .../type/format/OsonDocumentWriter.java | 30 +- .../type/format/OsonValueJDBCTypeAdapter.java | 83 + .../type/format/StringJsonDocument.java | 34 + .../type/format/StringJsonDocumentMarker.java | 51 + .../type/format/StringJsonDocumentReader.java | 505 ++++++ .../type/format/StringJsonDocumentWriter.java | 126 +- .../StringJsonValueJDBCTypeAdapter.java | 149 ++ .../jackson/JacksonJsonFormatMapper.java | 1 - .../util/StringJsonDocumentReaderTest.java | 384 +++++ .../util/StringJsonDocumentWriterTest.java | 183 +++ 31 files changed, 2321 insertions(+), 1346 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index 480f746a5d61..bf64c2676f7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -314,7 +314,8 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector) JacksonOsonFormatMapper.SHORT_NAME, JacksonOsonFormatMapper.class ); - } else { + } + else { strategySelector.registerStrategyImplementor( FormatMapper.class, JacksonJsonFormatMapper.SHORT_NAME, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index b20b7ed47533..a91990e0a856 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1026,6 +1026,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry StandardConverters.STRING,JACKSON_MAPPER_NAME); if ( getVersion().isSameOrAfter( 21 ) ) { + if ( JacksonIntegration.isOracleOsonExtensionAvailable() && JACKSON_MAPPER_NAME.equalsIgnoreCase( mapperName )) { // We must check that that extension is available and actually used. typeContributions.contributeJdbcType( OracleOsonJacksonJdbcType.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index 5026ef539e57..282798f062aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -27,6 +27,7 @@ import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -131,7 +132,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { - private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { + private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); JsonParser osonParser = osonFactory.createParser( osonBytes ); @@ -142,7 +143,7 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ if ( datum == null ) { return null; } - byte[] osonBytes = datum.shareBytes(); + InputStream osonBytes = datum.getStream(); try { return fromOson( osonBytes ,options); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 6ab8a284a4dc..4413058bfb06 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -22,12 +22,11 @@ import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.format.FormatMapper; -import org.hibernate.type.format.ObjectArrayOsonDocumentHandler; import org.hibernate.type.format.OsonDocumentWriter; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; +import java.io.InputStream; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -139,7 +138,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { - private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { + private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); @@ -149,14 +148,23 @@ private X fromOson(byte[] osonBytes, WrapperOptions options) throws Exception { // an array of objects. We use JsonParser to fetch values // and build the array.(as opposed to let Jackson do it as we do not // have a proper object definition at that stage). - OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( ByteBuffer.wrap( osonBytes ) ); - - ObjectArrayOsonDocumentHandler handler = new ObjectArrayOsonDocumentHandler( getEmbeddableMappingType(), - options); - - OsonHelper.consumeOsonTokens(osonParser, osonParser.next(), handler); - - return (X) handler.getObjectArray(); + OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( osonBytes ); + + + //ObjectArrayOsonDocumentHandler handler = new ObjectArrayOsonDocumentHandler( getEmbeddableMappingType(), + // options); + //OsonHelper.consumeOsonTokens(osonParser, osonParser.next(), handler); + + //osonBytes.reset(); + //OracleJsonParser osonParser2 = new OracleJsonFactory().createJsonBinaryParser( osonBytes); + Object[] objects = JsonHelper.deserialize( + getEmbeddableMappingType(), + osonParser, + javaType.getJavaTypeClass() != Object[].class, + options + ); + //Object[] objects2 = (Object[]) handler.getObjectArray(); + return (X) objects; } JavaType <X> type = getJavaType(); @@ -175,7 +183,7 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ if ( datum == null ) { return null; } - byte[] osonBytes = datum.shareBytes(); + InputStream osonBytes = datum.getStream(); try { return fromOson( osonBytes ,options); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/CharSequenceHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/CharSequenceHelper.java index 041013f5ad68..6dafe79e662a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/CharSequenceHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/CharSequenceHelper.java @@ -24,6 +24,10 @@ else if ( sequence instanceof SubSequence ) { } } + public static CharSequence subSequence(CharSequence sequence) { + return subSequence(sequence, 0, sequence.length()); + } + public static boolean isEmpty(CharSequence string) { return string == null || string.length() == 0; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java index c955f68a49cc..182ca0db7c6d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/BooleanPrimitiveArrayJavaType.java @@ -81,7 +81,7 @@ public boolean[] fromString(CharSequence charSequence) { final char lastChar = charSequence.charAt( charSequence.length() - 1 ); final char firstChar = charSequence.charAt( 0 ); if ( firstChar != '{' || lastChar != '}' ) { - throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" ); + throw new IllegalArgumentException( "Cannot parse given string into array of Booleans. First and last character must be { and }" ); } final int len = charSequence.length(); int elementStart = 1; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java index 8317b4caf391..0ed6cc8f997f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/DoublePrimitiveArrayJavaType.java @@ -81,7 +81,7 @@ public double[] fromString(CharSequence charSequence) { final char lastChar = charSequence.charAt( charSequence.length() - 1 ); final char firstChar = charSequence.charAt( 0 ); if ( firstChar != '{' || lastChar != '}' ) { - throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" ); + throw new IllegalArgumentException( "Cannot parse given string into array of Doubles. First and last character must be { and }" ); } final int len = charSequence.length(); int elementStart = 1; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java index ad795cea243f..25f431b6a114 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/FloatPrimitiveArrayJavaType.java @@ -81,7 +81,7 @@ public float[] fromString(CharSequence charSequence) { final char lastChar = charSequence.charAt( charSequence.length() - 1 ); final char firstChar = charSequence.charAt( 0 ); if ( firstChar != '{' || lastChar != '}' ) { - throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" ); + throw new IllegalArgumentException( "Cannot parse given string into array of Floats. First and last character must be { and }" ); } final int len = charSequence.length(); int elementStart = 1; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java index 693a60a5e0f4..703e12cf1fbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/IntegerPrimitiveArrayJavaType.java @@ -81,7 +81,7 @@ public int[] fromString(CharSequence charSequence) { final char lastChar = charSequence.charAt( charSequence.length() - 1 ); final char firstChar = charSequence.charAt( 0 ); if ( firstChar != '{' || lastChar != '}' ) { - throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" ); + throw new IllegalArgumentException( "Cannot parse given string into array of integers. First and last character must be { and }" ); } final int len = charSequence.length(); int elementStart = 1; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java index 4a1df2c08b79..be7453a46d61 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/JavaType.java @@ -268,6 +268,10 @@ default T fromEncodedString(CharSequence charSequence, int start, int end) { return fromString( CharSequenceHelper.subSequence( charSequence, start, end ) ); } + default T fromEncodedString(CharSequence charSequence) { + return fromEncodedString( charSequence, 0, charSequence.length() ); + } + /** * Unwrap an instance of our handled Java type into the requested type. * <p> diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongPrimitiveArrayJavaType.java index 5331dc3f59b1..9ba2fb465034 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/LongPrimitiveArrayJavaType.java @@ -81,7 +81,7 @@ public long[] fromString(CharSequence charSequence) { final char lastChar = charSequence.charAt( charSequence.length() - 1 ); final char firstChar = charSequence.charAt( 0 ); if ( firstChar != '{' || lastChar != '}' ) { - throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" ); + throw new IllegalArgumentException( "Cannot parse given string into array of Long. First and last character must be { and }" ); } final int len = charSequence.length(); int elementStart = 1; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortPrimitiveArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortPrimitiveArrayJavaType.java index 05175ddba8de..37b7cf758aac 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortPrimitiveArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ShortPrimitiveArrayJavaType.java @@ -81,7 +81,7 @@ public short[] fromString(CharSequence charSequence) { final char lastChar = charSequence.charAt( charSequence.length() - 1 ); final char firstChar = charSequence.charAt( 0 ); if ( firstChar != '{' || lastChar != '}' ) { - throw new IllegalArgumentException( "Cannot parse given string into array of strings. First and last character must be { and }" ); + throw new IllegalArgumentException( "Cannot parse given string into array of Shorts. First and last character must be { and }" ); } final int len = charSequence.length(); int elementStart = 1; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 3c57a42129ef..14e06bdfe668 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -9,21 +9,19 @@ import java.io.OutputStream; import java.lang.reflect.Array; import java.sql.SQLException; -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; import java.util.AbstractCollection; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import org.hibernate.Internal; -import org.hibernate.internal.build.AllowReflection; -import org.hibernate.internal.util.CharSequenceHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.mapping.EmbeddableMappingType; -import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; @@ -34,36 +32,35 @@ import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; -import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.JdbcDateJavaType; -import org.hibernate.type.descriptor.java.JdbcTimeJavaType; -import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; -import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType; -import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.format.JsonDocumentItem; +import org.hibernate.type.format.JsonDocumentReader; +import org.hibernate.type.format.JsonDocumentReaderFactory; import org.hibernate.type.format.JsonDocumentWriter; import static org.hibernate.type.descriptor.jdbc.StructHelper.getEmbeddedPart; import static org.hibernate.type.descriptor.jdbc.StructHelper.instantiate; +import org.hibernate.type.format.JsonValueJDBCTypeAdapter; +import org.hibernate.type.format.JsonValueJDBCTypeAdapterFactory; /** * A Helper for serializing and deserializing JSON, based on an {@link org.hibernate.metamodel.mapping.EmbeddableMappingType}. + * + * @author Christian Beikov + * @author Emmanuel Jannetti */ @Internal public class JsonHelper { - public static String toString(EmbeddableMappingType embeddableMappingType, Object value, WrapperOptions options) { - if ( value == null ) { - return null; - } - final StringBuilder sb = new StringBuilder(); - toString( embeddableMappingType, value, options, new JsonAppender( sb ) ); - return sb.toString(); - } - - + /** + * Serializes an array of values into JSON object/array + * @param elementMappingType the type definitions + * @param values the values to be serialized + * @param options wrapping options + * @param writer the document writer used for serialization + */ public static void serializeArray(MappingType elementMappingType, Object[] values, WrapperOptions options, JsonDocumentWriter writer) { writer.startArray(); if ( values.length == 0 ) { @@ -87,6 +84,14 @@ public static void serializeArray(MappingType elementMappingType, Object[] value writer.endArray(); } + /** + * Serializes an array of values into JSON object/array + * @param elementJavaType the array element type + * @param elementJdbcType the JDBC type + * @param values values to be serialized + * @param options wrapping options + * @param writer the document writer used for serialization + */ public static void serializeArray(JavaType<?> elementJavaType, JdbcType elementJdbcType, Object[] values, WrapperOptions options, JsonDocumentWriter writer) { writer.startArray(); if ( values.length == 0 ) { @@ -137,7 +142,7 @@ public static void serialize(EmbeddableMappingType embeddableMappingType, * @param domainValue the value to be serialized. * @param options wrapping options * @param writer the document writer - * @throws IOException + * @throws IOException if an error occurred while writing to an underlying writer */ private static void serializeMapping(EmbeddableMappingType embeddableMappingType, Object domainValue, WrapperOptions options, JsonDocumentWriter writer) throws IOException { @@ -212,257 +217,180 @@ else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { } } - private static void toString(EmbeddableMappingType embeddableMappingType, Object value, WrapperOptions options, JsonAppender appender) { - toString( embeddableMappingType, options, appender, value, '{' ); - appender.append( '}' ); - } - - private static void toString( - EmbeddableMappingType embeddableMappingType, - WrapperOptions options, - JsonAppender appender, - Object domainValue, - char separator) { - final Object[] values = embeddableMappingType.getValues( domainValue ); - for ( int i = 0; i < values.length; i++ ) { - final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i ); - if ( attributeMapping instanceof SelectableMapping selectableMapping ) { - final String name = selectableMapping.getSelectableName(); - appender.append( separator ); - appender.append( '"' ); - appender.append( name ); - appender.append( "\":" ); - toString( attributeMapping.getMappedType(), values[i], options, appender ); - } - else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { - if ( values[i] == null ) { - // Skipping the update of the separator is on purpose - continue; - } - final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); - final SelectableMapping aggregateMapping = mappingType.getAggregateMapping(); - if ( aggregateMapping == null) { - toString( - mappingType, - options, - appender, - values[i], - separator - ); - } - else { - final String name = aggregateMapping.getSelectableName(); - appender.append( separator ); - appender.append( '"' ); - appender.append( name ); - appender.append( "\":" ); - toString( mappingType, values[i], options, appender ); - } - } - else { - throw new UnsupportedOperationException( "Support for attribute mapping type not yet implemented: " + attributeMapping.getClass().getName() ); - } - separator = ','; - } - } - - private static void toString(MappingType mappedType, Object value, WrapperOptions options, JsonAppender appender) { - if ( value == null ) { - appender.append( "null" ); - } - else if ( mappedType instanceof EmbeddableMappingType embeddableMappingType ) { - toString( embeddableMappingType, value, options, appender ); - } - else if ( mappedType instanceof BasicType<?> ) { - //noinspection unchecked - final BasicType<Object> basicType = (BasicType<Object>) mappedType; - convertedBasicValueToString( basicType.convertToRelationalValue( value ), options, appender, basicType ); - } - else { - throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + mappedType.getClass().getName() ); - } - } - - private static void convertedValueToString( - JavaType<Object> javaType, - JdbcType jdbcType, - Object value, - WrapperOptions options, - JsonAppender appender) { - if ( value == null ) { - appender.append( "null" ); - } - else if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) { - toString( aggregateJdbcType.getEmbeddableMappingType(), value, options, appender ); - } - else { - convertedBasicValueToString( value, options, appender, javaType, jdbcType ); - } - } - - - private static void convertedBasicValueToString( - Object value, - WrapperOptions options, - JsonAppender appender, - BasicType<Object> basicType) { - //noinspection unchecked - convertedBasicValueToString( - value, - options, - appender, - (JavaType<Object>) basicType.getJdbcJavaType(), - basicType.getJdbcType() - ); - } - - private static void convertedBasicValueToString( - Object value, - WrapperOptions options, - JsonAppender appender, - JavaType<Object> javaType, - JdbcType jdbcType) { - switch ( jdbcType.getDefaultSqlTypeCode() ) { - case SqlTypes.TINYINT: - case SqlTypes.SMALLINT: - case SqlTypes.INTEGER: - if ( value instanceof Boolean booleanValue ) { - // BooleanJavaType has this as an implicit conversion - appender.append( booleanValue ? '1' : '0' ); + /** + * Consumes Json document items from a document reader and return the serialized Objects + * @param reader the document reader + * @param embeddableMappingType the type definitions + * @param returnEmbeddable do we return an Embeddable object or array of Objects ? + * @param options wrapping options + * @return serialized values + * @param <X> + * @throws SQLException if error occured during mapping of types + */ + private static <X> X consumeJsonDocumentItems(JsonDocumentReader reader, EmbeddableMappingType embeddableMappingType, boolean returnEmbeddable, WrapperOptions options) + throws SQLException { + // final result of a mapped object array + Object [] objectArrayResult; + // current mapping to be used + SelectableMapping currentSelectableMapping = null; + String currentKeyName = null; + List<Object> subArrayObjectList = null; + BasicPluralType<?, ?> subArrayObjectTypes = null; + + // mapping definitions are in a tree + // Each mapping definition may contain sub mappings (sub embeddable mapping) + // This stack is used to keep a pointer on the current mapping to be used to assign correct types. + // see onStartObject()/onEndObject() methods + StandardStack<EmbeddableMappingType> embeddableMappingTypes = new StandardStack<>(); + // As for mapping definitions, when "sub embeddable" is encountered, the array + // that needs to be filled with Objects is the one we allocate in the final result array slot. + // We use a stack to keep track of array ref + StandardStack<Object[]> objectArrays = new StandardStack<>(); + + // index within objectArrayResult + int currentSelectableIndexInResultArray = -1; + + JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,returnEmbeddable); + + embeddableMappingTypes.push(embeddableMappingType); + objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()+ ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; + objectArrays.push( objectArrayResult ); + + while(reader.hasNext()) { + JsonDocumentItem.JsonDocumentItemType type = reader.next(); + switch (type) { + case VALUE_KEY: + currentKeyName = reader.getObjectKeyName(); + + currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); + if ( currentSelectableIndexInResultArray >= 0 ) { + // we may not have a selectable mapping for that key + currentSelectableMapping = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( currentSelectableIndexInResultArray ); + } + else { + throw new IllegalArgumentException( + String.format( + "Could not find selectable [%s] in embeddable type [%s] for JSON processing.", + currentKeyName, + embeddableMappingTypes.getCurrent().getMappedJavaType().getJavaTypeClass().getName() + ) + ); + } break; - } - if ( value instanceof Enum<?> enumValue ) { - appender.appendSql( enumValue.ordinal() ); + case ARRAY_START: + assert (subArrayObjectList == null && subArrayObjectTypes == null) : "ARRAY_START item received twice in a row"; + + // initialize an array to gather values + subArrayObjectList = new ArrayList<>(); + assert (currentSelectableMapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) + : "Array event received for non plural type"; + // initialize array's element type + subArrayObjectTypes = (BasicPluralType<?, ?>) currentSelectableMapping.getJdbcMapping(); break; - } - case SqlTypes.BOOLEAN: - case SqlTypes.BIT: - case SqlTypes.BIGINT: - case SqlTypes.FLOAT: - case SqlTypes.REAL: - case SqlTypes.DOUBLE: - // These types fit into the native representation of JSON, so let's use that - javaType.appendEncodedString( appender, value ); - break; - case SqlTypes.CHAR: - case SqlTypes.NCHAR: - case SqlTypes.VARCHAR: - case SqlTypes.NVARCHAR: - if ( value instanceof Boolean booleanValue ) { - // BooleanJavaType has this as an implicit conversion - appender.append( '"' ); - appender.append( booleanValue ? 'Y' : 'N' ); - appender.append( '"' ); + case ARRAY_END: + assert (subArrayObjectList != null && subArrayObjectTypes != null) : "ARRAY_END item received twice in a row"; + // flush array values + objectArrays.getCurrent()[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, options ); + // reset until we encounter next array element + subArrayObjectList = null; + subArrayObjectTypes = null; break; - } - case SqlTypes.LONGVARCHAR: - case SqlTypes.LONGNVARCHAR: - case SqlTypes.LONG32VARCHAR: - case SqlTypes.LONG32NVARCHAR: - case SqlTypes.CLOB: - case SqlTypes.MATERIALIZED_CLOB: - case SqlTypes.NCLOB: - case SqlTypes.MATERIALIZED_NCLOB: - case SqlTypes.ENUM: - case SqlTypes.NAMED_ENUM: - // These literals can contain the '"' character, so we need to escape it - appender.append( '"' ); - appender.startEscaping(); - javaType.appendEncodedString( appender, value ); - appender.endEscaping(); - appender.append( '"' ); - break; - case SqlTypes.DATE: - appender.append( '"' ); - JdbcDateJavaType.INSTANCE.appendEncodedString( - appender, - javaType.unwrap( value, java.sql.Date.class, options ) - ); - appender.append( '"' ); - break; - case SqlTypes.TIME: - case SqlTypes.TIME_WITH_TIMEZONE: - case SqlTypes.TIME_UTC: - appender.append( '"' ); - JdbcTimeJavaType.INSTANCE.appendEncodedString( - appender, - javaType.unwrap( value, java.sql.Time.class, options ) - ); - appender.append( '"' ); - break; - case SqlTypes.TIMESTAMP: - appender.append( '"' ); - JdbcTimestampJavaType.INSTANCE.appendEncodedString( - appender, - javaType.unwrap( value, java.sql.Timestamp.class, options ) - ); - appender.append( '"' ); - break; - case SqlTypes.TIMESTAMP_WITH_TIMEZONE: - case SqlTypes.TIMESTAMP_UTC: - appender.append( '"' ); - DateTimeFormatter.ISO_OFFSET_DATE_TIME.formatTo( - javaType.unwrap( value, OffsetDateTime.class, options ), - appender - ); - appender.append( '"' ); - break; - case SqlTypes.DECIMAL: - case SqlTypes.NUMERIC: - case SqlTypes.DURATION: - case SqlTypes.UUID: - // These types need to be serialized as JSON string, but don't have a need for escaping - appender.append( '"' ); - javaType.appendEncodedString( appender, value ); - appender.append( '"' ); - break; - case SqlTypes.BINARY: - case SqlTypes.VARBINARY: - case SqlTypes.LONGVARBINARY: - case SqlTypes.LONG32VARBINARY: - case SqlTypes.BLOB: - case SqlTypes.MATERIALIZED_BLOB: - // These types need to be serialized as JSON string, and for efficiency uses appendString directly - appender.append( '"' ); - appender.write( javaType.unwrap( value, byte[].class, options ) ); - appender.append( '"' ); - break; - case SqlTypes.ARRAY: - case SqlTypes.JSON_ARRAY: - final int length = Array.getLength( value ); - appender.append( '[' ); - if ( length != 0 ) { - //noinspection unchecked - final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) javaType ).getElementJavaType(); - final JdbcType elementJdbcType = ( (ArrayJdbcType) jdbcType ).getElementJdbcType(); - Object arrayElement = Array.get( value, 0 ); - convertedValueToString( elementJavaType, elementJdbcType, arrayElement, options, appender ); - for ( int i = 1; i < length; i++ ) { - arrayElement = Array.get( value, i ); - appender.append( ',' ); - convertedValueToString( elementJavaType, elementJdbcType, arrayElement, options, appender ); + case OBJECT_START: + if (currentKeyName != null) { + // We are dealing with a sub-object, allocate space for it then, + // otherwise, we have nothing to do. + // Push the new (sub)mapping definition. + assert embeddableMappingTypes.getCurrent() != null; + currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); + assert currentSelectableIndexInResultArray != -1: "Cannot get index of " + currentKeyName; + + final SelectableMapping selectable = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( + currentSelectableIndexInResultArray ); + final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() + .getJdbcType(); + final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); + assert objectArrays.getCurrent() != null; + objectArrays.getCurrent()[currentSelectableIndexInResultArray] = + new Object[subMappingType.getJdbcValueCount()]; + embeddableMappingTypes.push( subMappingType ); + objectArrays.push( (Object[]) objectArrays.getCurrent()[currentSelectableIndexInResultArray] ); } - } - appender.append( ']' ); - break; - default: - throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + jdbcType ); + break; + case OBJECT_END: + // go back in the mapping definition tree + embeddableMappingTypes.pop(); + objectArrays.pop(); + break; + case NULL_VALUE: + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( null ); + } + else { + objectArrays.getCurrent()[currentSelectableIndexInResultArray] = null; + } + break; + case NUMERIC_VALUE: + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( adapter.fromNumericValue( subArrayObjectTypes.getElementType().getJdbcJavaType(), + subArrayObjectTypes.getElementType().getJdbcType(),reader,options)); + } + else { + objectArrays.getCurrent()[currentSelectableIndexInResultArray] = adapter.fromNumericValue( currentSelectableMapping.getJdbcMapping().getJdbcJavaType(), + currentSelectableMapping.getJdbcMapping().getJdbcType(),reader,options); + } + break; + case BOOLEAN_VALUE: + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add( reader.getBooleanValue()?Boolean.TRUE:Boolean.FALSE); + } + else { + objectArrays.getCurrent()[currentSelectableIndexInResultArray] = reader.getBooleanValue()?Boolean.TRUE:Boolean.FALSE; + } + break; + case VALUE: + if ( subArrayObjectList != null ) { + // dealing with arrays + subArrayObjectList.add(adapter.fromValue( subArrayObjectTypes.getElementType().getJdbcJavaType(), + subArrayObjectTypes.getElementType().getJdbcType(),reader,options)); + } + else { + objectArrays.getCurrent()[currentSelectableIndexInResultArray] = adapter.fromValue( currentSelectableMapping.getJdbcMapping().getJdbcJavaType(), + currentSelectableMapping.getJdbcMapping().getJdbcType(),reader,options); + } + + break; + default: + assert false: "Unexpected type " + type; + } } + return (X) objectArrayResult; } - public static <X> X fromString( + /** + * Deserialize a JSON value to Java Object + * @param embeddableMappingType the mapping type + * @param source the JSON value + * @param returnEmbeddable do we return an Embeddable object or array of Objects + * @param options wrappping options + * @return the deserialized value + * @param <X> + * @throws SQLException + */ + public static <X> X deserialize( EmbeddableMappingType embeddableMappingType, - String string, + Object source, boolean returnEmbeddable, WrapperOptions options) throws SQLException { - if ( string == null ) { + + if ( source == null ) { return null; } + JsonDocumentReader reader = JsonDocumentReaderFactory.getJsonDocumentReader(source); - final int jdbcValueCount = embeddableMappingType.getJdbcValueCount(); - final Object[] values = new Object[jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; - final int end = fromString( embeddableMappingType, string, 0, string.length(), values, returnEmbeddable, options ); - assert string.substring( end ).isBlank(); + final Object[] values = consumeJsonDocumentItems(reader, embeddableMappingType, returnEmbeddable, options); if ( returnEmbeddable ) { final StructAttributeValues attributeValues = StructHelper.getAttributeValues( embeddableMappingType, @@ -482,9 +410,12 @@ public static <X> X arrayFromString( JdbcType elementJdbcType, String string, WrapperOptions options) throws SQLException { + if ( string == null ) { return null; } + + final CustomArrayList arrayList = new CustomArrayList(); final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) javaType).getElementJavaType(); final Class<?> preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( options ); final JavaType<?> jdbcJavaType; @@ -494,994 +425,41 @@ public static <X> X arrayFromString( else { jdbcJavaType = options.getTypeConfiguration().getJavaTypeRegistry().resolveDescriptor( preferredJavaTypeClass ); } - final CustomArrayList arrayList = new CustomArrayList(); - final int i = fromArrayString( - string, - false, - options, - 0, - arrayList, - elementJavaType, - jdbcJavaType, - elementJdbcType - ); - assert string.charAt( i - 1 ) == ']'; - return javaType.wrap( arrayList, options ); - } - - private static int fromString( - EmbeddableMappingType embeddableMappingType, - String string, - int begin, - int end, - Object[] values, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - boolean hasEscape = false; - assert string.charAt( begin ) == '{'; - int start = begin + 1; - State s = State.KEY_START; - int selectableIndex = -1; - // The following parsing logic assumes JSON is well-formed, - // but for the sake of the Java compiler's flow analysis - // and hopefully also for a better understanding, contains throws for some syntax errors - for ( int i = start; i < string.length(); i++ ) { - final char c = string.charAt( i ); - switch ( c ) { - case '\\': - assert s == State.KEY_QUOTE || s == State.VALUE_QUOTE; - hasEscape = true; - i++; - break; - case '"': - switch ( s ) { - case KEY_START: - s = State.KEY_QUOTE; - selectableIndex = -1; - start = i + 1; - hasEscape = false; - break; - case KEY_QUOTE: - s = State.KEY_END; - selectableIndex = getSelectableMapping( - embeddableMappingType, - string, - start, - i, - hasEscape - ); - start = -1; - hasEscape = false; - break; - case VALUE_START: - s = State.VALUE_QUOTE; - start = i + 1; - hasEscape = false; - break; - case VALUE_QUOTE: - s = State.VALUE_END; - values[selectableIndex] = fromString( - embeddableMappingType.getJdbcValueSelectable( selectableIndex ).getJdbcMapping(), - string, - start, - i, - hasEscape, - returnEmbeddable, - options - ); - selectableIndex = -1; - start = -1; - hasEscape = false; - break; - default: - throw syntaxError( string, s, i ); - } - break; - case ':': - switch ( s ) { - case KEY_QUOTE: - // I guess it's ok to have a ':' in the key.. - case VALUE_QUOTE: - // In the value it's fine - break; - case KEY_END: - s = State.VALUE_START; - break; - default: - throw syntaxError( string, s, i ); - } - break; - case ',': - switch ( s ) { - case KEY_QUOTE: - // I guess it's ok to have a ',' in the key.. - case VALUE_QUOTE: - // In the value it's fine - break; - case VALUE_END: - s = State.KEY_START; - break; - default: - throw syntaxError( string, s, i ); - } - break; - case '{': - switch ( s ) { - case KEY_QUOTE: - // I guess it's ok to have a '{' in the key.. - case VALUE_QUOTE: - // In the value it's fine - break; - case VALUE_START: - final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable( - selectableIndex - ); - if ( !( selectable.getJdbcMapping().getJdbcType() - instanceof AggregateJdbcType aggregateJdbcType) ) { - throw new IllegalArgumentException( - String.format( - "JSON starts sub-object for a non-aggregate type at index %d. Selectable [%s] is of type [%s]", - i, - selectable.getSelectableName(), - selectable.getJdbcMapping().getJdbcType().getClass().getName() - ) - ); - } - final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); - // This encoding is only possible if the JDBC type is JSON again - assert aggregateJdbcType.getJdbcTypeCode() == SqlTypes.JSON - || aggregateJdbcType.getDefaultSqlTypeCode() == SqlTypes.JSON; - final Object[] subValues = new Object[subMappingType.getJdbcValueCount()]; - i = fromString( subMappingType, string, i, end, subValues, returnEmbeddable, options ) - 1; - assert string.charAt( i ) == '}'; - if ( returnEmbeddable ) { - final StructAttributeValues attributeValues = StructHelper.getAttributeValues( - subMappingType, - subValues, - options - ); - values[selectableIndex] = instantiate( embeddableMappingType, attributeValues ); - } - else { - values[selectableIndex] = subValues; - } - s = State.VALUE_END; - selectableIndex = -1; - break; - default: - throw syntaxError( string, s, i ); - } - break; - case '[': - switch ( s ) { - case KEY_QUOTE: - // I guess it's ok to have a '[' in the key.. - case VALUE_QUOTE: - // In the value it's fine - break; - case VALUE_START: - final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable( - selectableIndex - ); - final JdbcMapping jdbcMapping = selectable.getJdbcMapping(); - if ( !(jdbcMapping instanceof BasicPluralType<?, ?> pluralType) ) { - throw new IllegalArgumentException( - String.format( - "JSON starts array for a non-plural type at index %d. Selectable [%s] is of type [%s]", - i, - selectable.getSelectableName(), - jdbcMapping.getJdbcType().getClass().getName() - ) - ); - } - final BasicType<?> elementType = pluralType.getElementType(); - final CustomArrayList arrayList = new CustomArrayList(); - i = fromArrayString( string, returnEmbeddable, options, i, arrayList, elementType ) - 1; - assert string.charAt( i ) == ']'; - values[selectableIndex] = pluralType.getJdbcJavaType().wrap( arrayList, options ); - s = State.VALUE_END; - selectableIndex = -1; - break; - default: - throw syntaxError( string, s, i ); - } - break; - case '}': - switch ( s ) { - case KEY_QUOTE: - // I guess it's ok to have a '}' in the key.. - case VALUE_QUOTE: - // In the value it's fine - break; - case VALUE_END: - // At this point, we are done - return i + 1; - default: - throw syntaxError( string, s, i ); - } - break; - default: - switch ( s ) { - case KEY_QUOTE: - case VALUE_QUOTE: - // In keys and values, all chars are fine - break; - case VALUE_START: - // Skip whitespace - if ( Character.isWhitespace( c ) ) { - break; - } - // Here we also allow certain literals - final int endIdx = consumeLiteral( - string, - i, - values, - embeddableMappingType.getJdbcValueSelectable( selectableIndex ).getJdbcMapping(), - selectableIndex, - returnEmbeddable, - options - ); - if ( endIdx != -1 ) { - i = endIdx; - s = State.VALUE_END; - selectableIndex = -1; - start = -1; - break; - } - throw syntaxError( string, s, i ); - case KEY_START: - case KEY_END: - case VALUE_END: - // Only whitespace is allowed here - if ( Character.isWhitespace( c ) ) { - break; - } - default: - throw syntaxError( string, s, i ); - } - break; - } - } - - throw new IllegalArgumentException( "JSON not properly formed: " + string.subSequence( start, end ) ); - } - - private static int fromArrayString( - String string, - boolean returnEmbeddable, - WrapperOptions options, - int begin, - CustomArrayList arrayList, - BasicType<?> elementType) throws SQLException { - return fromArrayString( - string, - returnEmbeddable, - options, - begin, - arrayList, - elementType.getMappedJavaType(), - elementType.getJdbcJavaType(), - elementType.getJdbcType() - ); - } - - private static int fromArrayString( - String string, - boolean returnEmbeddable, - WrapperOptions options, - int begin, - CustomArrayList arrayList, - JavaType<?> javaType, - JavaType<?> jdbcJavaType, - JdbcType jdbcType) throws SQLException { - if ( string.length() == begin + 2 ) { - return begin + 2; - } - boolean hasEscape = false; - assert string.charAt( begin ) == '['; - int start = begin + 1; - State s = State.VALUE_START; - // The following parsing logic assumes JSON is well-formed, - // but for the sake of the Java compiler's flow analysis - // and hopefully also for a better understanding, contains throws for some syntax errors - for ( int i = start; i < string.length(); i++ ) { - final char c = string.charAt( i ); - switch ( c ) { - case '\\': - assert s == State.VALUE_QUOTE; - hasEscape = true; - i++; + JsonDocumentReader reader = JsonDocumentReaderFactory.getJsonDocumentReader(string); + JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,false); + + assert reader.hasNext():"Invalid array string"; + assert reader.next() == JsonDocumentItem.JsonDocumentItemType.ARRAY_START:"Invalid start of array"; + boolean endArrayFound = false; + while(reader.hasNext()) { + JsonDocumentItem.JsonDocumentItemType type = reader.next(); + switch ( type ) { + case JsonDocumentItem.JsonDocumentItemType.ARRAY_END: + endArrayFound=true; break; - case '"': - switch ( s ) { - case VALUE_START: - s = State.VALUE_QUOTE; - start = i + 1; - hasEscape = false; - break; - case VALUE_QUOTE: - s = State.VALUE_END; - arrayList.add( - fromString( - javaType, - jdbcJavaType, - jdbcType, - string, - start, - i, - hasEscape, - returnEmbeddable, - options - ) - ); - start = -1; - hasEscape = false; - break; - default: - throw syntaxError( string, s, i ); - } + case NULL_VALUE: + arrayList.add( null ); break; - case ',': - switch ( s ) { - case VALUE_QUOTE: - // In the value it's fine - break; - case VALUE_END: - s = State.VALUE_START; - break; - default: - throw syntaxError( string, s, i ); - } + case NUMERIC_VALUE: + arrayList.add( adapter.fromNumericValue(jdbcJavaType, elementJdbcType ,reader, options) ); break; - case '{': - switch ( s ) { - case VALUE_QUOTE: - // In the value it's fine - break; -// case VALUE_START: -// final SelectableMapping selectable = embeddableMappingType.getJdbcValueSelectable( -// selectableIndex -// ); -// if ( !( selectable.getJdbcMapping().getJdbcType() instanceof AggregateJdbcType ) ) { -// throw new IllegalArgumentException( -// String.format( -// "JSON starts sub-object for a non-aggregate type at index %d. Selectable [%s] is of type [%s]", -// i, -// selectable.getSelectableName(), -// selectable.getJdbcMapping().getJdbcType().getClass().getName() -// ) -// ); -// } -// final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping().getJdbcType(); -// final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); -// // This encoding is only possible if the JDBC type is JSON again -// assert aggregateJdbcType.getJdbcTypeCode() == SqlTypes.JSON -// || aggregateJdbcType.getDefaultSqlTypeCode() == SqlTypes.JSON; -// final Object[] subValues = new Object[subMappingType.getJdbcValueCount()]; -// i = fromString( subMappingType, string, i, end, subValues, returnEmbeddable, options ) - 1; -// assert string.charAt( i ) == '}'; -// if ( returnEmbeddable ) { -// final Object[] attributeValues = StructHelper.getAttributeValues( -// subMappingType, -// subValues, -// options -// ); -// values[selectableIndex] = embeddableMappingType.getRepresentationStrategy() -// .getInstantiator() -// .instantiate( -// () -> attributeValues, -// options.getSessionFactory() -// ); -// } -// else { -// values[selectableIndex] = subValues; -// } -// s = State.VALUE_END; -// selectableIndex = -1; -// break; - default: - throw syntaxError( string, s, i ); - } - break; - case ']': - switch ( s ) { - case VALUE_QUOTE: - // In the value it's fine - break; - case VALUE_END: - // At this point, we are done - return i + 1; - default: - throw syntaxError( string, s, i ); - } - break; - default: - switch ( s ) { - case VALUE_QUOTE: - // In keys and values, all chars are fine - break; - case VALUE_START: - // Skip whitespace - if ( Character.isWhitespace( c ) ) { - break; - } - final int elementIndex = arrayList.size(); - arrayList.add( null ); - // Here we also allow certain literals - final int endIdx = consumeLiteral( - string, - i, - arrayList.getUnderlyingArray(), - javaType, - jdbcJavaType, - jdbcType, - elementIndex, - returnEmbeddable, - options - ); - if ( endIdx != -1 ) { - i = endIdx; - s = State.VALUE_END; - start = -1; - break; - } - throw syntaxError( string, s, i ); - case VALUE_END: - // Only whitespace is allowed here - if ( Character.isWhitespace( c ) ) { - break; - } - default: - throw syntaxError( string, s, i ); - } - break; - } - } - - throw new IllegalArgumentException( "JSON not properly formed: " + string.subSequence( start, string.length() ) ); - } - - private static int consumeLiteral( - String string, - int start, - Object[] values, - JdbcMapping jdbcMapping, - int selectableIndex, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - return consumeLiteral( - string, - start, - values, - jdbcMapping.getMappedJavaType(), - jdbcMapping.getJdbcJavaType(), - jdbcMapping.getJdbcType(), - selectableIndex, - returnEmbeddable, - options - ); - } - - private static int consumeLiteral( - String string, - int start, - Object[] values, - JavaType<?> javaType, - JavaType<?> jdbcJavaType, - JdbcType jdbcType, - int selectableIndex, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - final char c = string.charAt( start ); - switch ( c ) { - case 'n': - // only null is possible - values[selectableIndex] = null; - return consume(string, start, "null"); - case 'f': - // only false is possible - values[selectableIndex] = false; - return consume(string, start, "false"); - case 't': - // only false is possible - values[selectableIndex] = true; - return consume(string, start, "true"); - case '0': - switch ( string.charAt( start + 1 ) ) { - case '.': - return consumeFractional( - string, - start, - start + 1, - values, - javaType, - jdbcJavaType, - jdbcType, - selectableIndex, - returnEmbeddable, - options - ); - case 'E': - case 'e': - return consumeExponential( - string, - start, - start + 1, - values, - javaType, - jdbcJavaType, - jdbcType, - selectableIndex, - returnEmbeddable, - options - ); - } - values[selectableIndex] = fromString( - javaType, - jdbcJavaType, - jdbcType, - string, - start, - start + 1, - returnEmbeddable, - options - ); - return start; - case '-': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - // number = [ minus ] int [ frac ] [ exp ] - // decimal-point = %x2E ; . - // digit1-9 = %x31-39 ; 1-9 - // e = %x65 / %x45 ; e E - // exp = e [ minus / plus ] 1*DIGIT - // frac = decimal-point 1*DIGIT - // int = zero / ( digit1-9 *DIGIT ) - // minus = %x2D ; - - // plus = %x2B ; + - // zero = %x30 ; 0 - for (int i = start + 1; i < string.length(); i++) { - final char digit = string.charAt( i ); - switch ( digit ) { - case '.': - return consumeFractional( - string, - start, - i, - values, - javaType, - jdbcJavaType, - jdbcType, - selectableIndex, - returnEmbeddable, - options - ); - case 'E': - case 'e': - return consumeExponential( - string, - start, - i, - values, - javaType, - jdbcJavaType, - jdbcType, - selectableIndex, - returnEmbeddable, - options - ); - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - break; - default: - values[selectableIndex] = fromString( - javaType, - jdbcJavaType, - jdbcType, - string, - start, - i, - returnEmbeddable, - options - ); - return i - 1; - } - } - } - - return -1; - } - - private static int consumeFractional( - String string, - int start, - int dotIndex, - Object[] values, - JavaType<?> javaType, - JavaType<?> jdbcJavaType, - JdbcType jdbcType, - int selectableIndex, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - for (int i = dotIndex + 1; i < string.length(); i++) { - final char digit = string.charAt( i ); - switch ( digit ) { - case 'E': - case 'e': - return consumeExponential( - string, - start, - i, - values, - javaType, - jdbcJavaType, - jdbcType, - selectableIndex, - returnEmbeddable, - options - ); - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + case BOOLEAN_VALUE: + arrayList.add( reader.getBooleanValue() ? Boolean.TRUE : Boolean.FALSE ); break; - default: - values[selectableIndex] = fromString( - javaType, - jdbcJavaType, - jdbcType, - string, - start, - i, - returnEmbeddable, - options - ); - return i - 1; - } - } - return start; - } - - private static int consumeExponential( - String string, - int start, - int eIndex, - Object[] values, - JavaType<?> javaType, - JavaType<?> jdbcJavaType, - JdbcType jdbcType, - int selectableIndex, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - int i = eIndex + 1; - switch ( string.charAt( i ) ) { - case '-': - case '+': - i++; - break; - } - for (; i < string.length(); i++) { - final char digit = string.charAt( i ); - switch ( digit ) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': + case VALUE: + arrayList.add( adapter.fromValue(jdbcJavaType, elementJdbcType ,reader, options) ); break; default: - values[selectableIndex] = fromString( - javaType, - jdbcJavaType, - jdbcType, - string, - start, - i, - returnEmbeddable, - options - ); - return i - 1; + assert false : "Unexpected type " + type; } } - return start; - } - - private static int consume(String string, int start, String text) { - if ( !string.regionMatches( start + 1, text, 1, text.length() - 1 ) ) { - throw new IllegalArgumentException( - String.format( - "Syntax error at position %d. Unexpected char [%s]. Expecting [%s]", - start + 1, - string.charAt( start + 1 ), - text - ) - ); - } - return start + text.length() - 1; - } - private static IllegalArgumentException syntaxError(String string, State s, int charIndex) { - return new IllegalArgumentException( - String.format( - "Syntax error at position %d. Unexpected char [%s]. Expecting one of [%s]", - charIndex, - string.charAt( charIndex ), - s.expectedChars() - ) - ); - } - private static int getSelectableMapping( - EmbeddableMappingType embeddableMappingType, - String string, - int start, - int end, - boolean hasEscape) { - final String name = hasEscape - ? unescape( string, start, end ) - : string.substring( start, end ); - final int selectableIndex = embeddableMappingType.getSelectableIndex( name ); - if ( selectableIndex == -1 ) { - throw new IllegalArgumentException( - String.format( - "Could not find selectable [%s] in embeddable type [%s] for JSON processing.", - name, - embeddableMappingType.getMappedJavaType().getJavaTypeClass().getName() - ) - ); - } - return selectableIndex; - } - - private static Object fromString( - JdbcMapping jdbcMapping, - String string, - int start, - int end, - boolean hasEscape, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - return fromString( - jdbcMapping.getMappedJavaType(), - jdbcMapping.getJdbcJavaType(), - jdbcMapping.getJdbcType(), - string, - start, - end, - hasEscape, - returnEmbeddable, - options - ); - } - - private static Object fromString( - JavaType<?> javaType, - JavaType<?> jdbcJavaType, - JdbcType jdbcType, - String string, - int start, - int end, - boolean hasEscape, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - if ( hasEscape ) { - final String unescaped = unescape( string, start, end ); - return fromString( - javaType, - jdbcJavaType, - jdbcType, - unescaped, - 0, - unescaped.length(), - returnEmbeddable, - options - ); - } - return fromString( - javaType, - jdbcJavaType, - jdbcType, - string, - start, - end, - returnEmbeddable, - options - ); - } - - private static Object fromString( - JavaType<?> javaType, - JavaType<?> jdbcJavaType, - JdbcType jdbcType, - String string, - int start, - int end, - boolean returnEmbeddable, - WrapperOptions options) throws SQLException { - switch ( jdbcType.getDefaultSqlTypeCode() ) { - case SqlTypes.BINARY: - case SqlTypes.VARBINARY: - case SqlTypes.LONGVARBINARY: - case SqlTypes.LONG32VARBINARY: - return jdbcJavaType.wrap( - PrimitiveByteArrayJavaType.INSTANCE.fromEncodedString( - string, - start, - end - ), - options - ); - case SqlTypes.UUID: - return jdbcJavaType.wrap( - PrimitiveByteArrayJavaType.INSTANCE.fromString( - string.substring( start, end ).replace( "-", "" ) - ), - options - ); - case SqlTypes.DATE: - return jdbcJavaType.wrap( - JdbcDateJavaType.INSTANCE.fromEncodedString( - string, - start, - end - ), - options - ); - case SqlTypes.TIME: - case SqlTypes.TIME_WITH_TIMEZONE: - case SqlTypes.TIME_UTC: - return jdbcJavaType.wrap( - JdbcTimeJavaType.INSTANCE.fromEncodedString( - string, - start, - end - ), - options - ); - case SqlTypes.TIMESTAMP: - return jdbcJavaType.wrap( - JdbcTimestampJavaType.INSTANCE.fromEncodedString( - string, - start, - end - ), - options - ); - case SqlTypes.TIMESTAMP_WITH_TIMEZONE: - case SqlTypes.TIMESTAMP_UTC: - return jdbcJavaType.wrap( - OffsetDateTimeJavaType.INSTANCE.fromEncodedString( - string, - start, - end - ), - options - ); - case SqlTypes.TINYINT: - case SqlTypes.SMALLINT: - case SqlTypes.INTEGER: - if ( jdbcJavaType.getJavaTypeClass() == Boolean.class ) { - return jdbcJavaType.wrap( Integer.parseInt( string, start, end, 10 ), options ); - } - else if ( jdbcJavaType instanceof EnumJavaType<?> ) { - return jdbcJavaType.wrap( Integer.parseInt( string, start, end, 10 ), options ); - } - case SqlTypes.CHAR: - case SqlTypes.NCHAR: - case SqlTypes.VARCHAR: - case SqlTypes.NVARCHAR: - if ( jdbcJavaType.getJavaTypeClass() == Boolean.class && end == start + 1 ) { - return jdbcJavaType.wrap( string.charAt( start ), options ); - } - default: - if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) { - final Object[] subValues = aggregateJdbcType.extractJdbcValues( - CharSequenceHelper.subSequence( - string, - start, - end - ), - options - ); - if ( returnEmbeddable ) { - final StructAttributeValues subAttributeValues = StructHelper.getAttributeValues( - aggregateJdbcType.getEmbeddableMappingType(), - subValues, - options - ); - return instantiate( aggregateJdbcType.getEmbeddableMappingType(), subAttributeValues ) ; - } - return subValues; - } - - return jdbcJavaType.fromEncodedString( string, start, end ); - } - } - - private static String unescape(String string, int start, int end) { - final StringBuilder sb = new StringBuilder( end - start ); - for ( int i = start; i < end; i++ ) { - final char c = string.charAt( i ); - if ( c == '\\' ) { - i++; - final char cNext = string.charAt( i ); - switch ( cNext ) { - case '\\': - case '"': - case '/': - sb.append( cNext ); - break; - case 'b': - sb.append( '\b' ); - break; - case 'f': - sb.append( '\f' ); - break; - case 'n': - sb.append( '\n' ); - break; - case 'r': - sb.append( '\r' ); - break; - case 't': - sb.append( '\t' ); - break; - case 'u': - sb.append( (char) Integer.parseInt( string, i + 1, i + 5, 16 ) ); - i += 4; - break; - } - continue; - } - sb.append( c ); - } - return sb.toString(); + assert endArrayFound:"Invalid end of array"; + return javaType.wrap( arrayList, options ); } - enum State { - KEY_START( "\"\\s" ), - KEY_QUOTE( "" ), - KEY_END( ":\\s" ), - VALUE_START( "\"\\s" ), - VALUE_QUOTE( "" ), - VALUE_END( ",}\\s" ); - final String expectedChars; - - State(String expectedChars) { - this.expectedChars = expectedChars; - } - - String expectedChars() { - return expectedChars; - } - } public static class JsonAppender extends OutputStream implements SqlAppender { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java index 015280b910b3..ebc6c3b1b3e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java @@ -23,6 +23,7 @@ * Specialized type mapping for {@code JSON} and the JSON SQL data type. * * @author Christian Beikov + * @author Emmanuel Jannetti */ public class JsonJdbcType implements AggregateJdbcType { /** @@ -75,7 +76,7 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o return null; } if ( embeddableMappingType != null ) { - return JsonHelper.fromString( + return (X) JsonHelper.deserialize( embeddableMappingType, string, javaType.getJavaTypeClass() != Object[].class, @@ -88,23 +89,30 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o @Override public Object createJdbcValue(Object domainValue, WrapperOptions options) throws SQLException { assert embeddableMappingType != null; - return JsonHelper.toString( embeddableMappingType, domainValue, options ); + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + try { + JsonHelper.serialize( embeddableMappingType ,domainValue,options, writer ); + return writer.toString(); + } + catch (IOException e) { + throw new SQLException( e ); + } } @Override public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) throws SQLException { assert embeddableMappingType != null; - return JsonHelper.fromString( embeddableMappingType, (String) rawJdbcValue, false, options ); + return JsonHelper.deserialize( embeddableMappingType, (String) rawJdbcValue, false, options ); } protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) { if ( embeddableMappingType != null ) { - // used to be JsonHelper.toString( embeddableMappingType, value, options ); try { StringBuilder sb = new StringBuilder(); StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); JsonHelper.serialize( embeddableMappingType, value, options, writer); - return sb.toString(); + return writer.toString(); } catch (IOException e) { throw new RuntimeException("Failed to serialize JSON mapping", e ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java new file mode 100644 index 000000000000..693e36d64967 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +public class JsonDocumentItem { + public static final JsonDocumentItem OBJECT_START_ITEM = new JsonDocumentItem(JsonDocumentItemType.OBJECT_START, + Boolean.TRUE ); + public static final JsonDocumentItem OBJECT_END_ITEM = new JsonDocumentItem(JsonDocumentItemType.OBJECT_END, + Boolean.TRUE ); + public static final JsonDocumentItem ARRAY_START_ITEM = new JsonDocumentItem(JsonDocumentItemType.ARRAY_START, + Boolean.TRUE ); + public static final JsonDocumentItem ARRAY_END_ITEM = new JsonDocumentItem(JsonDocumentItemType.ARRAY_END, + Boolean.TRUE ); + public static final JsonDocumentItem KEY_NAME_ITEM = new JsonDocumentItem(JsonDocumentItemType.VALUE_KEY, + Boolean.TRUE ); + + public static final JsonDocumentItem TRUE_VALUE_ITEM = new JsonDocumentItem(JsonDocumentItemType.BOOLEAN_VALUE,Boolean.TRUE); + public static final JsonDocumentItem FALSE_VALUE_ITEM = new JsonDocumentItem(JsonDocumentItemType.BOOLEAN_VALUE,Boolean.FALSE); + public static final JsonDocumentItem NULL_VALUE_ITEM = new JsonDocumentItem(JsonDocumentItemType.NULL_VALUE,null); + + + + private JsonDocumentItemType type; + private Object value; + + + public Object getValue() { + return value; + } + + public JsonDocumentItem(JsonDocumentItemType type, Object value) { + this(type); + this.value = value; + } + public JsonDocumentItem(JsonDocumentItemType type) { + this.type = type; + this.value = null; + } + + public JsonDocumentItemType getType() { + return type; + } + + /** + * Json item types + */ + public enum JsonDocumentItemType { + /** + * Start of a Json Object '{' + */ + OBJECT_START, + /** + * end of a Json Object '}' + */ + OBJECT_END, + /** + * Start of a Json array '[' + */ + ARRAY_START, + /** + * End of a Json array ']' + */ + ARRAY_END, + /** + * key of Json attribute + */ + VALUE_KEY, + /** + * boolean value within Json object or array + */ + BOOLEAN_VALUE, + /** + * null value within Json object or array + */ + NULL_VALUE, + /** + * numeric value within Json object or array + */ + NUMERIC_VALUE, + /** + * String (quoted) value within Json object or array + */ + VALUE + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java new file mode 100644 index 000000000000..e8eb6ab92399 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java @@ -0,0 +1,148 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Iterator; + +/** + * JSON document reader. + * Reads a JSON document (i.e., String or OSON bytes) and produce Json item type event. + * Calling #next() will return one of a JsonDocumentItem.JsonDocumentItemType. + * The sequence of return types follows JSON specification. + * <p> + * When {@link JsonDocumentItemType.VALUE_KEY} is returned #getObjectKeyName() should be called to get the key name. + * <p> + * When {@link JsonDocumentItemType.VALUE}, {@link JsonDocumentItemType.BOOLEAN_VALUE}, {@link JsonDocumentItemType.NULL_VALUE} or {@link JsonDocumentItemType.NUMERIC_VALUE} is returned one of the getxxxValue() should be called to get the value. + * <p> + * example : + * <pre> + * { + * "key1": "value1", + * "key2": ["x","y","z"], + * "key3": { + * "key4" : ["a"], + * "key5" : {} + * }, + * "key6":12, + * "key7":null + * } + * </pre> + * This Json object could be read as follows + * <pre> + * while (reader.hasNext()) {} + * JsonDocumentItemType type = reader.next(); + * switch(type) { + * case VALUE_KEY: + * String keyName = reader.getObjectKeyName(); + * break; + * case VALUE: + * String value = reader.getStringValue() + * break + * //... + * } + * } + * </pre> + * This Json object above would trigger this sequence of events + * <pre> + * JsonDocumentItemType.OBJECT_START + * JsonDocumentItemType.VALUE_KEY // "key1" + * JsonDocumentItemType.VALUE // "value1" + * JsonDocumentItemType.VALUE_KEY // "key2" + * JsonDocumentItemType.ARRAY_START + * JsonDocumentItemType.VALUE // "x" + * JsonDocumentItemType.VALUE // "y" + * JsonDocumentItemType.VALUE // "z" + * JsonDocumentItemType.ARRAY_END + * JsonDocumentItemType.VALUE_KEY // "key3" + * JsonDocumentItemType.OBJECT_START + * JsonDocumentItemType.VALUE_KEY // "key4" + * JsonDocumentItemType.ARRAY_START + * JsonDocumentItemType.VALUE // "a" + * JsonDocumentItemType.ARRAY_END + * JsonDocumentItemType.VALUE_KEY // "key5" + * JsonDocumentItemType.OBJECT_START + * JsonDocumentItemType.OBJECT_END + * JsonDocumentItemType.VALUE_KEY // "key6" + * JsonDocumentItemType.NUMERIC_VALUE + * JsonDocumentItemType.VALUE_KEY // "key7" + * JsonDocumentItemType.NULL_VALUE + * JsonDocumentItemType.OBJECT_END + * JsonDocumentItemType.OBJECT_END + * </pre> + * + * @author Emmanuel Jannetti + */ +public interface JsonDocumentReader extends Iterator<JsonDocumentItem.JsonDocumentItemType> { + default void forEachRemaining() { + throw new UnsupportedOperationException("forEachRemaining"); + } + + /** + * Gets the key name once JsonDocumentItemType.VALUE_KEY has been received + * @return the name + */ + String getObjectKeyName(); + /** + * Gets value as String + * @return the value. + */ + String getStringValue(); + /** + * Gets value as BigDecimal + * @return the value. + */ + BigDecimal getBigDecimalValue(); + /** + * Gets value as BigInteger + * @return the value. + */ + BigInteger getBigIntegerValue(); + /** + * Gets value as double + * @return the value. + */ + double getDoubleValue(); + /** + * Gets value as float + * @return the value. + */ + float getFloatValue(); + /** + * Gets value as long + * @return the value. + */ + long getLongValue(); + /** + * Gets value as int + * @return the value. + */ + int getIntegerValue(); + /** + * Gets value as short + * @return the value. + */ + short getShortValue(); + /** + * Gets value as byte + * @return the value. + */ + byte getByteValue(); + /** + * Gets value as boolean + * @return the value. + */ + boolean getBooleanValue(); + + /** + * Gets value as JavaType + * @return the value. + */ + <T> T getValue(JavaType<T> javaType, WrapperOptions options); +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java new file mode 100644 index 000000000000..3dcbed17f1b8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import oracle.sql.json.OracleJsonParser; + +/** + * Factory class to get proper <code>JsonDocumentReader</code>. + * + * @author Emmanuel Jannetti + */ +public class JsonDocumentReaderFactory { + /** + * Gets a <code>JsonDocumentReader</code> appropriate to a given source. + * Source can be a <code>String</code> or a <code>OracleJsonParser</code> instance + * @param jsonSource the document source + * @return the reader + */ + public static JsonDocumentReader getJsonDocumentReader(Object jsonSource) { + assert jsonSource != null : "jsonSource is null"; + + if (jsonSource instanceof String) { + return new StringJsonDocumentReader( (String)jsonSource ); + } + if (jsonSource instanceof OracleJsonParser ) { + return new OsonDocumentReader( (OracleJsonParser)jsonSource ); + } + + throw new IllegalArgumentException("Unsupported type of JSON source " + jsonSource.getClass()); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java index 2a0f8df1a687..5d92084ab01a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java @@ -21,56 +21,66 @@ public interface JsonDocumentWriter { /** * Starts a new JSON Objects. + * @return this instance */ - void startObject(); + JsonDocumentWriter startObject(); /** * Ends a new JSON Objects + * @return this instance */ - void endObject(); + JsonDocumentWriter endObject(); /** * Starts a new JSON array. + * @return this instance * @throws IOException an I/O error roccured while starting the object. */ - void startArray(); + JsonDocumentWriter startArray(); /** * Ends a new JSON array. + * @return this instance * @throws IOException an I/O error roccured while starting the object. */ - void endArray(); + JsonDocumentWriter endArray(); /** * Adds a new JSON element name. * @param key the element name. - * @throws IOException an I/O error roccured while starting the object. + * @return this instance + * @throws IllegalArgumentException key name does not follow JSON specification. + * @throws IOException an I/O error occurred while starting the object. */ - void objectKey(String key); + JsonDocumentWriter objectKey(String key); /** * Adds a new JSON element null value. + * @return this instance * @throws IOException an I/O error roccured while starting the object. */ - void nullValue(); + JsonDocumentWriter nullValue(); /** * Adds a new JSON element boolean value. + * @return this instance * @param value the element boolean name. */ - void booleanValue(boolean value); + JsonDocumentWriter booleanValue(boolean value); /** * Adds a new JSON element string value. + * @return this instance * @param value the element string name. */ - void stringValue(String value); + JsonDocumentWriter stringValue(String value); /** * Adds a new JSON element Number value. + * @return this instance * @param value the element Number name. */ - void numberValue(Number value); + JsonDocumentWriter numberValue(Number value); /** * Adds a JSON value to the document @@ -78,8 +88,9 @@ public interface JsonDocumentWriter { * @param javaType the Java type of the value * @param jdbcType the JDBC type for the value to be serialized * @param options the wrapping options + * @return this instance */ - void serializeJsonValue(Object value, + JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java new file mode 100644 index 000000000000..d1c85b0bc500 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import java.sql.SQLException; + +/** + * Adapter for JSON value on given JDBC types. + */ +public interface JsonValueJDBCTypeAdapter { + /** + * Gets an Object out of a JSON document reader according to a given types. + * @param jdbcJavaType the desired JavaType for the return Object. + * @param jdbcType the desired JdbcType for the return Object. + * @param source the JSON document reader from which to get the value to be translated. + * @param options the wrapping option + * @return the translated value. + * @throws SQLException if translation failed. + */ + Object fromValue( + JavaType<?> jdbcJavaType, + JdbcType jdbcType, + JsonDocumentReader source, + WrapperOptions options) throws SQLException; + + /** + * Gets an Object out of a JSON document reader according to a given types. + * This method is called when the current available value in the reader is a numeric one. + * @param jdbcJavaType the desired JavaType for the return Object. + * @param jdbcType the desired JdbcType for the return Object. + * @param source the JSON document reader from which to get the value to be translated. + * @param options the wrapping option + * @return the translated value. + * @throws SQLException if translation failed. + */ + Object fromNumericValue(JavaType<?> jdbcJavaType, + JdbcType jdbcType, + JsonDocumentReader source, + WrapperOptions options) throws SQLException; + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java new file mode 100644 index 000000000000..d1336a84cd7c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +/** + * Factory class to get proper <code>JsonValueJDBCTypeAdapter</code>. + * + * @author Emmanuel Jannetti + */ +public class JsonValueJDBCTypeAdapterFactory { + /** + * Gets a type adapter for a given reader + * @param reader the JSON document reader from which the adapter gets its value from. + * @param returnEmbeddable + * @return the adapter + */ + public static JsonValueJDBCTypeAdapter getAdapter(JsonDocumentReader reader , boolean returnEmbeddable) { + assert reader != null : "reader is null"; + + if (reader instanceof StringJsonDocumentReader) { + return new StringJsonValueJDBCTypeAdapter( returnEmbeddable ); + } + if (reader instanceof OsonDocumentReader ) { + return new OsonValueJDBCTypeAdapter( ); + } + + throw new IllegalArgumentException("Unsupported type of document reader " + reader.getClass()); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java new file mode 100644 index 000000000000..36bea03a26cf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java @@ -0,0 +1,215 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import oracle.sql.json.OracleJsonParser; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BooleanJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.NoSuchElementException; + +/** + * OSON-based implementation of <code>JsonDocumentReader</code> + */ +public class OsonDocumentReader implements JsonDocumentReader { + + final private OracleJsonParser parser; + private String currentKeyName; + private Object currentValue; + private boolean currentValueIsAString; // avoid later introspection + /** + * Creates a new <code>OsonDocumentReader</code> on top of a <code>OracleJsonParser</code> + * @param parser the parser + */ + public OsonDocumentReader(OracleJsonParser parser) { + this.parser = parser; + } + + @Override + public boolean hasNext() { + return this.parser.hasNext(); + } + + @Override + public JsonDocumentItem.JsonDocumentItemType next() { + if (!this.parser.hasNext()) + throw new NoSuchElementException("No more item in JSON document"); + OracleJsonParser.Event evt = this.parser.next(); + currentKeyName = null; + currentValue = null; + currentValueIsAString = false; + switch (evt) { + case OracleJsonParser.Event.START_OBJECT: + return JsonDocumentItem.JsonDocumentItemType.OBJECT_START; + case OracleJsonParser.Event.END_OBJECT: + return JsonDocumentItem.JsonDocumentItemType.OBJECT_END; + case OracleJsonParser.Event.START_ARRAY: + return JsonDocumentItem.JsonDocumentItemType.ARRAY_START; + case OracleJsonParser.Event.END_ARRAY: + return JsonDocumentItem.JsonDocumentItemType.ARRAY_END; + case OracleJsonParser.Event.KEY_NAME: + currentKeyName = this.parser.getString(); + return JsonDocumentItem.JsonDocumentItemType.VALUE_KEY; + case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: + currentValue = this.parser.getOffsetDateTime(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_DATE: + case OracleJsonParser.Event.VALUE_TIMESTAMP: + currentValue = this.parser.getLocalDateTime(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_INTERVALDS: + currentValue = this.parser.getDuration(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_INTERVALYM: + currentValue = this.parser.getPeriod(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_STRING: + currentValue = this.parser.getString(); + currentValueIsAString = true; + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_TRUE: + currentValue = Boolean.TRUE; + return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + case OracleJsonParser.Event.VALUE_FALSE: + currentValue = Boolean.FALSE; + return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + case OracleJsonParser.Event.VALUE_NULL: + currentValue = null; + return JsonDocumentItem.JsonDocumentItemType.NULL_VALUE; + case OracleJsonParser.Event.VALUE_DECIMAL: + currentValue = this.parser.getBigDecimal(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_DOUBLE: + currentValue = this.parser.getDouble(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_FLOAT: + currentValue = this.parser.getFloat(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case OracleJsonParser.Event.VALUE_BINARY: + currentValue = this.parser.getBytes(); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + default : + assert false:"Unknown OSON event"; + } + return null; + } + + @Override + public String getObjectKeyName() { + if (currentKeyName == null) + throw new IllegalStateException("no object key available"); + return currentKeyName; + } + + @Override + public String getStringValue() { + return (String)currentValue; + } + + @Override + public BigDecimal getBigDecimalValue() { + return (BigDecimal)currentValue; + } + + @Override + public BigInteger getBigIntegerValue() { + return ((BigDecimal)currentValue).toBigInteger(); + } + + @Override + public double getDoubleValue() { + if (currentValueIsAString) return Double.parseDouble( (String)currentValue ); + return ((Double)currentValue).doubleValue(); + } + + @Override + public float getFloatValue() { + if (currentValueIsAString) return Float.parseFloat( (String)currentValue ); + return ((Float)currentValue).floatValue(); + } + + @Override + public long getLongValue() { + if (currentValueIsAString) return Long.parseLong( (String)currentValue ); + return ((BigDecimal)currentValue).longValue(); + } + + @Override + public int getIntegerValue() { + if (currentValueIsAString) return Integer.parseInt( (String)currentValue ); + return ((BigDecimal)currentValue).intValue(); + } + + @Override + public short getShortValue() { + if (currentValueIsAString) return Short.parseShort( (String)currentValue ); + return ((BigDecimal)currentValue).shortValue(); + } + + @Override + public byte getByteValue() { + if (currentValueIsAString) return Byte.parseByte( (String)currentValue ); + return ((Byte)currentValue).byteValue(); + } + + @Override + public boolean getBooleanValue() { + if (currentValueIsAString) return BooleanJavaType.INSTANCE.fromEncodedString((String)currentValue); + return ((Boolean)currentValue).booleanValue(); + } + + + + @Override + public <T> T getValue(JavaType<T> javaType, WrapperOptions options) { + if ( currentValueIsAString ) { + if (javaType.equals(PrimitiveByteArrayJavaType.INSTANCE)) { + // be sure that we have only allowed characters. + // that may happen for string representation of UUID (i.e 53886a8a-7082-4879-b430-25cb94415be8) for instance + return javaType.fromEncodedString( (((String) currentValue).replaceAll( "-","" )) ); + } + return javaType.fromEncodedString( (String) currentValue ); + } + + Object theOneToBeUsed = currentValue; + // handle special cases for Date things + if ( currentValue.getClass() == LocalDateTime.class ) { + if ( java.sql.Date.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { + theOneToBeUsed = Date.valueOf( ((LocalDateTime)currentValue).toLocalDate() ); + } + else if ( java.time.LocalDate.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { + theOneToBeUsed = ((LocalDateTime)currentValue).toLocalDate(); + } + else if ( java.time.LocalTime.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { + theOneToBeUsed = ((LocalDateTime)currentValue).toLocalTime(); + } + else if ( java.sql.Time.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { + theOneToBeUsed = Time.valueOf( ((LocalDateTime)currentValue).toLocalTime() ); + } + else if ( java.sql.Timestamp.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { + theOneToBeUsed = Timestamp.valueOf( ((LocalDateTime)currentValue) ); + } + else if ( java.time.LocalTime.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { + theOneToBeUsed = ((LocalDateTime)currentValue).toLocalTime(); + } + else if ( java.util.Date.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { + // better way? + theOneToBeUsed = java.util.Date.from( ((LocalDateTime)currentValue).atZone( ZoneId.of( "UTC" ) ).toInstant() ); + } + } + + return javaType.wrap( theOneToBeUsed ,options ); + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index 7a4588750664..e3be47512647 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -51,61 +51,71 @@ public OsonDocumentWriter(OracleJsonGenerator generator) { @Override - public void startObject() { + public JsonDocumentWriter startObject() { this.generator.writeStartObject(); + return this; } @Override - public void endObject() { + public JsonDocumentWriter endObject() { this.generator.writeEnd(); + return this; } @Override - public void startArray() { + public JsonDocumentWriter startArray() { generator.writeStartArray(); + return this; } @Override - public void endArray() { + public JsonDocumentWriter endArray() { generator.writeEnd(); + return this; } @Override - public void objectKey(String key) { + public JsonDocumentWriter objectKey(String key) { this.generator.writeKey( key ); + return this; } @Override - public void nullValue() { + public JsonDocumentWriter nullValue() { this.generator.writeNull(); + return this; } @Override - public void booleanValue(boolean value) { + public JsonDocumentWriter booleanValue(boolean value) { this.generator.write(value); + return this; } @Override - public void stringValue(String value) { + public JsonDocumentWriter stringValue(String value) { this.generator.write(value); + return this; } @Override - public void numberValue(Number value) { + public JsonDocumentWriter numberValue(Number value) { this.generator.write((BigDecimal) value ); + return this; } @Override - public void serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { + public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { serializeValue(value, javaType, jdbcType, options); + return this; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java new file mode 100644 index 000000000000..5d8a38831fd0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java @@ -0,0 +1,83 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.EnumJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.JdbcDateJavaType; +import org.hibernate.type.descriptor.java.JdbcTimeJavaType; +import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; +import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType; +import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import java.sql.SQLException; + + +/** + * JDBC type adapter for OSON-based JSON document reader. + */ +public class OsonValueJDBCTypeAdapter implements JsonValueJDBCTypeAdapter { + @Override + public Object fromValue(JavaType<?> jdbcJavaType, JdbcType jdbcType, JsonDocumentReader source, WrapperOptions options) + throws SQLException { + Object valueToBeWrapped = null; + switch ( jdbcType.getDefaultSqlTypeCode() ) { + case SqlTypes.BINARY: + case SqlTypes.VARBINARY: + case SqlTypes.LONGVARBINARY: + case SqlTypes.LONG32VARBINARY: + case SqlTypes.UUID: + valueToBeWrapped = source.getValue( PrimitiveByteArrayJavaType.INSTANCE, options ); + break; + case SqlTypes.DATE: + valueToBeWrapped = source.getValue( JdbcDateJavaType.INSTANCE , options); + break; + case SqlTypes.TIME: + case SqlTypes.TIME_WITH_TIMEZONE: + case SqlTypes.TIME_UTC: + valueToBeWrapped = source.getValue( JdbcTimeJavaType.INSTANCE , options); + break; + case SqlTypes.TIMESTAMP: + valueToBeWrapped = source.getValue( JdbcTimestampJavaType.INSTANCE , options); + break; + case SqlTypes.TIMESTAMP_WITH_TIMEZONE: + case SqlTypes.TIMESTAMP_UTC: + valueToBeWrapped = source.getValue( OffsetDateTimeJavaType.INSTANCE , options); + break; + case SqlTypes.TINYINT: + case SqlTypes.SMALLINT: + case SqlTypes.INTEGER: + if ( jdbcJavaType.getJavaTypeClass() == Boolean.class ) { + valueToBeWrapped = source.getIntegerValue(); + break; + } + else if ( jdbcJavaType instanceof EnumJavaType<?> ) { + valueToBeWrapped = source.getIntegerValue(); + break; + } + case SqlTypes.CHAR: + case SqlTypes.NCHAR: + case SqlTypes.VARCHAR: + case SqlTypes.NVARCHAR: + if ( jdbcJavaType.getJavaTypeClass() == Boolean.class ) { + valueToBeWrapped = source.getBooleanValue(); + break; + } + } + if (valueToBeWrapped == null) { + valueToBeWrapped = source.getValue( jdbcJavaType , options); + } + return jdbcJavaType.wrap(valueToBeWrapped, options); + } + + @Override + public Object fromNumericValue(JavaType<?> jdbcJavaType, JdbcType jdbcType, JsonDocumentReader source, WrapperOptions options) + throws SQLException { + return fromValue( jdbcJavaType, jdbcType, source, options ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java new file mode 100644 index 000000000000..fe9059644220 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import org.hibernate.internal.util.collections.StandardStack; + +/** + * base class for JSON document String reader + * @author Emmanuel Jannetti + */ +public abstract class StringJsonDocument { + /** + * Processing states. This can be (nested)Object or Arrays. + * When processing objects, values are stored as [,]"key":"value"[,]. we add separator when adding new key + * When processing arrays, values are stored as [,]"value"[,]. we add separator when adding new value + */ + enum PROCESSING_STATE { + NONE, + STARTING_OBJECT, // object started but no value added + OBJECT_KEY_NAME, // We are processing an object key name + OBJECT, // object started, and we've started adding key/value pairs + ENDING_OBJECT, // we are ending an object + STARTING_ARRAY, // array started but no value added + ENDING_ARRAY, // we are ending an array + ARRAY // we are piling array values + } + // Stack of current processing states + protected StandardStack<PROCESSING_STATE> processingStates = new StandardStack<>(); + + + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java new file mode 100644 index 000000000000..d017529f8439 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +/** + * Enum class for JSON object markers. + */ +public enum StringJsonDocumentMarker { + ARRAY_END(']'), + ARRAY_START('['), + OBJECT_END('}'), + OBJECT_START('{'), + SEPARATOR(','), + QUOTE('"'), + KEY_VALUE_SEPARATOR(':'), + OTHER(); + + private final char val; + StringJsonDocumentMarker(char val) { + this.val = val; + } + StringJsonDocumentMarker() { + this.val = 0; + } + public char getMarkerCharacter() { + return this.val; + } + + public static StringJsonDocumentMarker markerOf(char ch) { + switch (ch) { + case ']': + return ARRAY_END; + case '[': + return ARRAY_START; + case '}': + return OBJECT_END; + case '{': + return OBJECT_START; + case ',': + return SEPARATOR; + case '"': + return QUOTE; + case ':': + return KEY_VALUE_SEPARATOR; + default: + return OTHER; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java new file mode 100644 index 000000000000..c12407960f8a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -0,0 +1,505 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.CharBuffer; +import java.util.NoSuchElementException; + +/** + * Implementation of <code>JsonDocumentReader</code> for String representation of JSON objects. + */ +public class StringJsonDocumentReader extends StringJsonDocument implements JsonDocumentReader { + + private final CharBuffer json; + private final CharBuffer jsonValueWindow; + + /** + * Creates a new <code>StringJsonDocumentReader</code> + * @param json the JSON String. of the object to be parsed. + */ + public StringJsonDocumentReader(String json) { + if (json == null) { + throw new IllegalArgumentException( "json cannot be null" ); + } + this.json = CharBuffer.wrap( json.toCharArray() ).asReadOnlyBuffer(); + this.jsonValueWindow = this.json.slice(); + } + + @Override + public boolean hasNext() { + // enough for now. + return this.json.hasRemaining(); + } + + private void skipWhiteSpace() { + for (int i = this.json.position(); i < this.json.limit(); i++ ) { + if (!Character.isWhitespace( this.json.get(i))) { + this.json.position(i); + return; + } + } + } + + private void resetValueWindow() { + this.jsonValueWindow.position(0); + this.jsonValueWindow.limit( 0); + } + + /** + * Moves the state machine according to the current state and the given marker + * + * @param marker the marker we just read + */ + private void moveStateMachine(StringJsonDocumentMarker marker) { + StringJsonDocument.PROCESSING_STATE currentState = this.processingStates.getCurrent(); + switch (marker) { + case OBJECT_START: + if (currentState == PROCESSING_STATE.STARTING_ARRAY ) { + // move the state machine to ARRAY as we are adding something to it + this.processingStates.push(PROCESSING_STATE.ARRAY); + } + this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); + break; + case OBJECT_END: + assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT || + this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_OBJECT; + if (this.processingStates.pop() == PROCESSING_STATE.OBJECT) { + assert this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_OBJECT; + this.processingStates.pop(); + } + break; + case ARRAY_START: + this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); + break; + case ARRAY_END: + assert this.processingStates.getCurrent() == PROCESSING_STATE.ARRAY || + this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_ARRAY; + if (this.processingStates.pop() == PROCESSING_STATE.ARRAY) { + assert this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_ARRAY; + this.processingStates.pop(); + } + break; + case SEPARATOR: + // While processing an object, following SEPARATOR that will a key + if (currentState == PROCESSING_STATE.OBJECT) { + this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); + } + break; + case KEY_VALUE_SEPARATOR: + // that's the start of an attribute value + assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT_KEY_NAME; + // flush the OBJECT_KEY_NAME + this.processingStates.pop(); + assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT; + break; + case QUOTE: + switch ( currentState ) { + case PROCESSING_STATE.STARTING_ARRAY: + this.processingStates.push( PROCESSING_STATE.ARRAY ); + break; + case PROCESSING_STATE.STARTING_OBJECT: + this.processingStates.push( PROCESSING_STATE.OBJECT ); + this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); + break; + } + break; + case OTHER: + if (currentState == PROCESSING_STATE.STARTING_ARRAY) { + this.processingStates.push( PROCESSING_STATE.ARRAY ); + } + break; + } + } + + /** + * Returns the next item. + * @return the item + * @throws NoSuchElementException no more item available + * @throws IllegalStateException not a well-formed JSON string. + */ + @Override + public JsonDocumentItem.JsonDocumentItemType next() { + + if ( !hasNext()) throw new NoSuchElementException("no more elements"); + + while (hasNext()) { + skipWhiteSpace(); + StringJsonDocumentMarker marker = StringJsonDocumentMarker.markerOf( this.json.get() ); + moveStateMachine( marker ); + switch ( marker) { + case OBJECT_START: + //this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); + resetValueWindow(); + return JsonDocumentItem.JsonDocumentItemType.OBJECT_START; + case OBJECT_END: + resetValueWindow(); + //this.processingStates.pop(); // closing an object or a nested one. + return JsonDocumentItem.JsonDocumentItemType.OBJECT_END; + case ARRAY_START: + resetValueWindow(); + //this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); + return JsonDocumentItem.JsonDocumentItemType.ARRAY_START; + case ARRAY_END: + resetValueWindow(); + //this.processingStates.pop(); + return JsonDocumentItem.JsonDocumentItemType.ARRAY_END; + case QUOTE: // that's the start of an attribute key or a quoted value + // put back the quote + moveBufferPosition(-1); + consumeQuottedString(); + // That's a quote: + // - if we are at the beginning of an array that's a quoted value + // - if we are in the middle of an array, that's a quoted value + // - if we are at the beginning of an object that's a quoted key + // - if we are in the middle of an object : + // - if we just hit ':' that's a quoted value + // - if we just hit ',' that's a quoted key + switch ( this.processingStates.getCurrent() ) { + case PROCESSING_STATE.STARTING_ARRAY: + //this.processingStates.push( PROCESSING_STATE.ARRAY ); + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case PROCESSING_STATE.ARRAY: + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case PROCESSING_STATE.STARTING_OBJECT: + //this.processingStates.push( PROCESSING_STATE.OBJECT ); + //this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); + return JsonDocumentItem.JsonDocumentItemType.VALUE_KEY; + case PROCESSING_STATE.OBJECT: // we are processing object attribute value elements + return JsonDocumentItem.JsonDocumentItemType.VALUE; + case PROCESSING_STATE.OBJECT_KEY_NAME: // we are processing object elements key + return JsonDocumentItem.JsonDocumentItemType.VALUE_KEY; + default: + throw new IllegalStateException( "unexpected quote read in current processing state " + + this.processingStates.getCurrent() ); + } + case KEY_VALUE_SEPARATOR: // that's the start of an attribute value + //assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT_KEY_NAME; + // flush the OBJECT_KEY_NAME + //this.processingStates.pop(); + break; + case SEPARATOR: + // unless we are processing an array, following SEPARATOR that will a key +// if (this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT) { +// this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); +// } + break; + case OTHER: + // here we are in front of a boolean, a null or a numeric value. + // if none of these cases we going to raise IllegalStateException + // put back what we've read + moveBufferPosition(-1); + final int valueSize = consumeNonStringValue(); + if (valueSize == -1) { + throw new IllegalStateException( "Unrecognized marker: " + StringJsonDocumentMarker.markerOf( + json.get( this.json.position() ))); + } + switch ( this.processingStates.getCurrent() ) { + case PROCESSING_STATE.ARRAY: + case PROCESSING_STATE.OBJECT: + return getUnquotedValueType(this.jsonValueWindow); + default: + throw new IllegalStateException( "unexpected read ["+ + this.jsonValueWindow.toString()+ + "] in current processing state " + + this.processingStates.getCurrent() ); + } + } + } + // no way we get here. + return null; + } + + /** + * Gets the type of unquoted value. + * We assume that the String value follows JSON specification. I.e unquoted value that starts with 't' can't be anything else + * than <code>true</code> + * @param jsonValueWindow the value + * @return the type of the value + */ + private JsonDocumentItem.JsonDocumentItemType getUnquotedValueType(CharBuffer jsonValueWindow) { + final int size = jsonValueWindow.remaining(); + switch(jsonValueWindow.charAt( 0 )) { + case 't': { + //true + return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + } + case 'f': { + //false + return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + } + case 'n' : { + // null + return JsonDocumentItem.JsonDocumentItemType.NULL_VALUE; + } + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + return JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE; + } + default : + return JsonDocumentItem.JsonDocumentItemType.VALUE; + } + } + + private void moveBufferPosition(int shift) { + this.json.position(this.json.position() + shift); + } + + /** + * Moves the current position to a given character. + * @param character the character we should stop at. + * @throws IllegalStateException if we encounter an unexpected character other than white spaces before the desired one. + */ + + private void moveTo(char character) throws IllegalStateException { + this.json.mark(); + while ( this.json.hasRemaining()) { + char c = this.json.get(); + if ( c == character) { + this.json.reset(); + return; + } + if (!Character.isWhitespace(c)) { + // we did find an unexpected character + // let the exception raise + this.json.reset(); + break; + } + } + throw new IllegalStateException("Can't find character: " + character); + } + + private int locateCharacter(char character, char escape) { + assert character != escape; + this.json.mark(); + int found = -1; + boolean escapeIsOn = false; + while ( this.json.hasRemaining()) { + final char c = this.json.get(); + if (c == escape) { + escapeIsOn = true; + } + else { + if ( c == character ) { + if (escapeIsOn) { + escapeIsOn = false; + } + else { + found = this.json.position() - 1; + break; + } + } + } + } + this.json.reset(); + return found; + } + + /** + * Consume a non-quotted value + * @return the length of this value. can be 0, -1 in case of error + */ + private int consumeNonStringValue() { + int newViewLimit = 0; + boolean allGood = false; + for (int i = this.json.position(); i < this.json.limit(); i++ ) { + char c = this.json.get(i); + if ((StringJsonDocumentMarker.markerOf( c ) != StringJsonDocumentMarker.OTHER) || + Character.isWhitespace( c )) { + // hit a JSON marker or a space. + allGood = true; + // give that character back to the buffer + newViewLimit = i; + break; + } + } + + if (allGood) { + this.jsonValueWindow.limit(newViewLimit); + this.jsonValueWindow.position( this.json.position() ); + this.json.position(newViewLimit); + } + return allGood?(this.jsonValueWindow.remaining()):-1; + } + private void consumeQuottedString() { + + // be sure we are at a meaningful place + // key name are unquoted + moveTo( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); + + // skip the quote we are positioned on. + this.json.get(); + + //locate ending quote + int endingQuote = locateCharacter( StringJsonDocumentMarker.QUOTE.getMarkerCharacter(), '\\'); + if (endingQuote == -1) { + throw new IllegalStateException("Can't find ending quote of key name"); + } + + this.jsonValueWindow.limit( endingQuote ); + this.jsonValueWindow.position(this.json.position()); + this.json.position( endingQuote + 1); + + } + + private void ensureValueState() throws IllegalStateException { + if ((this.processingStates.getCurrent() != PROCESSING_STATE.OBJECT ) && + this.processingStates.getCurrent() != PROCESSING_STATE.ARRAY) { + throw new IllegalStateException( "unexpected processing state: " + this.processingStates.getCurrent() ); + } + } + private void ensureAvailableValue() throws IllegalStateException { + if (this.jsonValueWindow.limit() == 0 ) { + throw new IllegalStateException( "No available value"); + } + } + + @Override + public String getObjectKeyName() { + if (this.processingStates.getCurrent() != PROCESSING_STATE.OBJECT_KEY_NAME ) { + throw new IllegalStateException( "unexpected processing state: " + this.processingStates.getCurrent() ); + } + ensureAvailableValue(); + return this.jsonValueWindow.toString(); + } + @Override + public String getStringValue() { + ensureValueState(); + ensureAvailableValue(); + if (hasEscape(this.jsonValueWindow)) { + return unescape(this.jsonValueWindow); + } + return this.jsonValueWindow.toString(); + } + + + @Override + public BigDecimal getBigDecimalValue() { + ensureValueState(); + ensureAvailableValue(); + return BigDecimal.valueOf( Long.valueOf(this.jsonValueWindow.toString()) ); + } + + @Override + public BigInteger getBigIntegerValue() { + ensureValueState(); + ensureAvailableValue(); + return BigInteger.valueOf( Long.valueOf(this.jsonValueWindow.toString()) ); + } + + @Override + public double getDoubleValue() { + ensureValueState(); + ensureAvailableValue(); + return Double.valueOf(this.jsonValueWindow.toString()).doubleValue(); + } + + @Override + public float getFloatValue() { + ensureValueState(); + ensureAvailableValue(); + return Float.valueOf(this.jsonValueWindow.toString()).floatValue(); + } + + @Override + public long getLongValue() { + ensureValueState(); + ensureAvailableValue(); + return Long.valueOf(this.jsonValueWindow.toString()).longValue(); + } + + @Override + public int getIntegerValue() { + ensureValueState(); + ensureAvailableValue(); + return Integer.valueOf(this.jsonValueWindow.toString()).intValue(); + } + + @Override + public short getShortValue() { + ensureValueState(); + ensureAvailableValue(); + return Short.valueOf(this.jsonValueWindow.toString()).shortValue(); + } + + @Override + public byte getByteValue() { + ensureValueState(); + ensureAvailableValue(); + return Byte.valueOf(this.jsonValueWindow.toString()).byteValue(); + } + + @Override + public boolean getBooleanValue() { + ensureValueState(); + ensureAvailableValue(); + return Boolean.parseBoolean( this.jsonValueWindow.toString() ); + } + + @Override + public <T> T getValue(JavaType<T> javaType, WrapperOptions options) { + return javaType.fromEncodedString( this.jsonValueWindow.toString() ); + } + + private boolean hasEscape(CharBuffer jsonValueWindow) { + for (int i = 0;i<jsonValueWindow.remaining();i++) { + if (jsonValueWindow.charAt( i ) == '\\') return true; + } + return false; + } + private String unescape(CharBuffer string) { + final StringBuilder sb = new StringBuilder( string.remaining() ); + for ( int i = 0; i < string.length(); i++ ) { + final char c = string.charAt( i ); + if ( c == '\\' ) { + i++; + final char cNext = string.charAt( i ); + switch ( cNext ) { + case '\\': + case '"': + case '/': + sb.append( cNext ); + break; + case 'b': + sb.append( '\b' ); + break; + case 'f': + sb.append( '\f' ); + break; + case 'n': + sb.append( '\n' ); + break; + case 'r': + sb.append( '\r' ); + break; + case 't': + sb.append( '\t' ); + break; + case 'u': + sb.append( (char) Integer.parseInt( string, i + 1, i + 5, 16 ) ); + i += 4; + break; + } + continue; + } + sb.append( c ); + } + return sb.toString(); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java index 07c15071a438..0bcbc44a71c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -5,7 +5,6 @@ package org.hibernate.type.format; import org.hibernate.dialect.JsonHelper; -import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BooleanJavaType; @@ -20,36 +19,17 @@ /** - * Implementation of <code>JsonDocumentWriter</code> for String based OSON document. + * Implementation of <code>JsonDocumentWriter</code> for String-based OSON document. * This implementation will receive a {@link JsonHelper.JsonAppender } to a serialze JSON object to it * @author Emmanuel Jannetti */ -public class StringJsonDocumentWriter implements JsonDocumentWriter{ +public class StringJsonDocumentWriter extends StringJsonDocument implements JsonDocumentWriter { + - private static final char ARRAY_END_MARKER = ']'; - private static final char ARRAY_START_MARKER = '['; - private static final char OBJECT_END_MARKER = '}'; - private static final char OBJECT_START_MARKER = '{'; - private static final char SEPARATOR_MARKER = ','; - private static final char TOKEN_QUOTE = '"'; private JsonHelper.JsonAppender appender; - /** - * Processing states. This can be (nested)Object or Arrays. - * When processing objects, values are stored as [,]"key":"value"[,]. we add separator when adding new key - * When processing arrays, values are stored as [,]"value"[,]. we add separator when adding new value - */ - private enum PROCESSING_STATE { - NONE, - STARTING_OBJECT, // object started but no value added - OBJECT, // object started, and we've started adding key/value pairs - ENDING_OBJECT, // we are ending an object - STARTING_ARRAY, // array started but no value added - ENDING_ARRAY, // we are ending an array - ARRAY // we are piling array values - } - private StandardStack<PROCESSING_STATE> processingStates = new StandardStack<>(); + /** * Creates a new StringJsonDocumentWriter. @@ -64,7 +44,7 @@ public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { * Callback to be called when the start of an JSON object is encountered. */ @Override - public void startObject() { + public JsonDocumentWriter startObject() { // Note: startArray and startObject must not call moveProcessingStateMachine() if (this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_ARRAY) { // are we building an array of objects? @@ -76,54 +56,63 @@ else if (this.processingStates.getCurrent() == PROCESSING_STATE.ARRAY) { // That means that we ae building an array of object ([{},...]) // JSON object hee are treat as array item. // -> add the marker first - this.appender.append(SEPARATOR_MARKER); + this.appender.append(StringJsonDocumentMarker.SEPARATOR.getMarkerCharacter()); } - this.appender.append( OBJECT_START_MARKER); + this.appender.append( StringJsonDocumentMarker.OBJECT_START.getMarkerCharacter()); this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); + return this; } /** * Callback to be called when the end of an JSON object is encountered. */ @Override - public void endObject() { - this.appender.append( OBJECT_END_MARKER ); + public JsonDocumentWriter endObject() { + this.appender.append( StringJsonDocumentMarker.OBJECT_END.getMarkerCharacter() ); this.processingStates.push( PROCESSING_STATE.ENDING_OBJECT); moveProcessingStateMachine(); + return this; } /** * Callback to be called when the start of an array is encountered. */ @Override - public void startArray() { + public JsonDocumentWriter startArray() { this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); // Note: startArray and startObject do not call moveProcessingStateMachine() - this.appender.append( ARRAY_START_MARKER ); - + this.appender.append( StringJsonDocumentMarker.ARRAY_START.getMarkerCharacter() ); + return this; } /** * Callback to be called when the end of an array is encountered. */ @Override - public void endArray() { - this.appender.append( ARRAY_END_MARKER ); + public JsonDocumentWriter endArray() { + this.appender.append( StringJsonDocumentMarker.ARRAY_END.getMarkerCharacter() ); this.processingStates.push( PROCESSING_STATE.ENDING_ARRAY); moveProcessingStateMachine(); + return this; } @Override - public void objectKey(String key) { + public JsonDocumentWriter objectKey(String key) { + + if (key == null || key.length() == 0) { + throw new IllegalArgumentException( "key cannot be null or empty" ); + } + if (this.processingStates.getCurrent().equals( PROCESSING_STATE.OBJECT )) { // we have started an object, and we are adding an item key: we do add a separator. - this.appender.append( SEPARATOR_MARKER ); + this.appender.append( StringJsonDocumentMarker.SEPARATOR.getMarkerCharacter() ); } - this.appender.append( TOKEN_QUOTE ); + this.appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); this.appender.append( key ); this.appender.append( "\":" ); moveProcessingStateMachine(); + return this; } /** @@ -135,7 +124,7 @@ public void objectKey(String key) { private void addItemsSeparator() { if (this.processingStates.getCurrent().equals( PROCESSING_STATE.ARRAY )) { // We started to serialize an array and already added item to it:add a separator anytime. - this.appender.append( SEPARATOR_MARKER ); + this.appender.append( StringJsonDocumentMarker.SEPARATOR.getMarkerCharacter() ); } } @@ -168,7 +157,7 @@ private void moveProcessingStateMachine() { this.processingStates.push( PROCESSING_STATE.OBJECT ); break; case STARTING_ARRAY: - //after starting an object, we start adding value to it + //after starting an array, we start adding value to it this.processingStates.push( PROCESSING_STATE.ARRAY ); break; case ENDING_ARRAY: @@ -199,46 +188,53 @@ private void moveProcessingStateMachine() { } @Override - public void nullValue() { + public JsonDocumentWriter nullValue() { addItemsSeparator(); this.appender.append( "null" ); moveProcessingStateMachine(); + return this; } @Override - public void booleanValue(boolean value) { + public JsonDocumentWriter booleanValue(boolean value) { addItemsSeparator(); BooleanJavaType.INSTANCE.appendEncodedString( this.appender, value); moveProcessingStateMachine(); + return this; } @Override - public void stringValue(String value) { + public JsonDocumentWriter stringValue(String value) { addItemsSeparator(); - appender.append( TOKEN_QUOTE); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter()); appender.startEscaping(); appender.append( value ); appender.endEscaping(); - appender.append(TOKEN_QUOTE ); + appender.append(StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); moveProcessingStateMachine(); - + return this; } @Override - public void numberValue(Number value) { + public JsonDocumentWriter numberValue(Number value) { + if (value == null ) { + throw new IllegalArgumentException( "value cannot be null" ); + } addItemsSeparator(); this.appender.append( value.toString() ); moveProcessingStateMachine(); + return this; } @Override - public void serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { + public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { addItemsSeparator(); convertedBasicValueToString(value, options,this.appender,javaType,jdbcType); moveProcessingStateMachine(); + return this; } /** @@ -284,9 +280,9 @@ private void convertedBasicValueToString( case SqlTypes.NVARCHAR: if ( value instanceof Boolean ) { // BooleanJavaType has this as an implicit conversion - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); appender.append( (Boolean) value ? 'Y' : 'N' ); - appender.append( TOKEN_QUOTE); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter()); break; } case SqlTypes.LONGVARCHAR: @@ -300,55 +296,55 @@ private void convertedBasicValueToString( case SqlTypes.ENUM: case SqlTypes.NAMED_ENUM: // These literals can contain the '"' character, so we need to escape it - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); appender.startEscaping(); javaType.appendEncodedString( appender, value ); appender.endEscaping(); - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.DATE: - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); JdbcDateJavaType.INSTANCE.appendEncodedString( appender, javaType.unwrap( value, java.sql.Date.class, options ) ); - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.TIME: case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_UTC: - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); JdbcTimeJavaType.INSTANCE.appendEncodedString( appender, javaType.unwrap( value, java.sql.Time.class, options ) ); - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.TIMESTAMP: - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); JdbcTimestampJavaType.INSTANCE.appendEncodedString( appender, javaType.unwrap( value, java.sql.Timestamp.class, options ) ); - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.TIMESTAMP_WITH_TIMEZONE: case SqlTypes.TIMESTAMP_UTC: - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); DateTimeFormatter.ISO_OFFSET_DATE_TIME.formatTo( javaType.unwrap( value, OffsetDateTime.class, options ), appender ); - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.DECIMAL: case SqlTypes.NUMERIC: case SqlTypes.DURATION: case SqlTypes.UUID: // These types need to be serialized as JSON string, but don't have a need for escaping - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); javaType.appendEncodedString( appender, value ); - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.BINARY: case SqlTypes.VARBINARY: @@ -357,9 +353,9 @@ private void convertedBasicValueToString( case SqlTypes.BLOB: case SqlTypes.MATERIALIZED_BLOB: // These types need to be serialized as JSON string, and for efficiency uses appendString directly - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); appender.write( javaType.unwrap( value, byte[].class, options ) ); - appender.append( TOKEN_QUOTE ); + appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.ARRAY: case SqlTypes.JSON_ARRAY: @@ -370,4 +366,8 @@ private void convertedBasicValueToString( } } + @Override + public String toString() { + return appender.toString(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java new file mode 100644 index 000000000000..7f6f614869fd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java @@ -0,0 +1,149 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +import org.hibernate.dialect.StructAttributeValues; +import org.hibernate.dialect.StructHelper; +import org.hibernate.internal.util.CharSequenceHelper; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.EnumJavaType; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.JdbcDateJavaType; +import org.hibernate.type.descriptor.java.JdbcTimeJavaType; +import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; +import org.hibernate.type.descriptor.java.OffsetDateTimeJavaType; +import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import java.sql.SQLException; + +import static org.hibernate.dialect.StructHelper.instantiate; + +/** + * JDBC type adapter for String-based JSON document reader. + */ +public class StringJsonValueJDBCTypeAdapter implements JsonValueJDBCTypeAdapter { + + private boolean returnEmbeddable; + public StringJsonValueJDBCTypeAdapter(boolean returnEmbeddable) { + this.returnEmbeddable = returnEmbeddable; + } + + @Override + public Object fromValue(JavaType<?> jdbcJavaType, JdbcType jdbcType, JsonDocumentReader source, WrapperOptions options) + throws SQLException { + return fromAnyValue (jdbcJavaType, jdbcType, source, options); + } + + private Object fromAnyValue(JavaType<?> jdbcJavaType, JdbcType jdbcType, JsonDocumentReader source, WrapperOptions options) + throws SQLException { + + String string = source.getStringValue(); + + switch ( jdbcType.getDefaultSqlTypeCode() ) { + case SqlTypes.BINARY: + case SqlTypes.VARBINARY: + case SqlTypes.LONGVARBINARY: + case SqlTypes.LONG32VARBINARY: + return jdbcJavaType.wrap( + PrimitiveByteArrayJavaType.INSTANCE.fromEncodedString( + string, + 0, string.length()), + options + ); + case SqlTypes.UUID: + return jdbcJavaType.wrap( + PrimitiveByteArrayJavaType.INSTANCE.fromString( + string.substring( 0, string.length() ).replace( "-", "" ) + ), + options + ); + case SqlTypes.DATE: + return jdbcJavaType.wrap( + JdbcDateJavaType.INSTANCE.fromEncodedString( + string, + 0, + string.length() + ), + options + ); + case SqlTypes.TIME: + case SqlTypes.TIME_WITH_TIMEZONE: + case SqlTypes.TIME_UTC: + return jdbcJavaType.wrap( + JdbcTimeJavaType.INSTANCE.fromEncodedString( + string, + 0, + string.length() + ), + options + ); + case SqlTypes.TIMESTAMP: + return jdbcJavaType.wrap( + JdbcTimestampJavaType.INSTANCE.fromEncodedString( + string, + 0, + string.length() + ), + options + ); + case SqlTypes.TIMESTAMP_WITH_TIMEZONE: + case SqlTypes.TIMESTAMP_UTC: + return jdbcJavaType.wrap( + OffsetDateTimeJavaType.INSTANCE.fromEncodedString( + string, + 0, + string.length()), + options + ); + case SqlTypes.TINYINT: + case SqlTypes.SMALLINT: + case SqlTypes.INTEGER: + if ( jdbcJavaType.getJavaTypeClass() == Boolean.class ) { + return jdbcJavaType.wrap( Integer.parseInt( string, 0, string.length(), 10 ), options ); + } + else if ( jdbcJavaType instanceof EnumJavaType<?> ) { + return jdbcJavaType.wrap( Integer.parseInt( string, 0, string.length(), 10 ), options ); + } + case SqlTypes.CHAR: + case SqlTypes.NCHAR: + case SqlTypes.VARCHAR: + case SqlTypes.NVARCHAR: + if ( jdbcJavaType.getJavaTypeClass() == Boolean.class && (string.length() == 1 ) ) { + return jdbcJavaType.wrap( string.charAt( 0 ), options ); + } + default: + if ( jdbcType instanceof AggregateJdbcType aggregateJdbcType ) { + final Object[] subValues = aggregateJdbcType.extractJdbcValues( + CharSequenceHelper.subSequence( + string, + 0, + string.length()), + options + ); + if ( returnEmbeddable ) { + final StructAttributeValues subAttributeValues = StructHelper.getAttributeValues( + aggregateJdbcType.getEmbeddableMappingType(), + subValues, + options + ); + final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); + return instantiate( embeddableMappingType, subAttributeValues, options.getSessionFactory() ) ; + } + return subValues; + } + + return jdbcJavaType.fromEncodedString(string); + } + } + + @Override + public Object fromNumericValue(JavaType<?> jdbcJavaType, JdbcType jdbcType, JsonDocumentReader source, WrapperOptions options) throws SQLException { + return fromAnyValue (jdbcJavaType, jdbcType, source, options); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index ba936e9915a8..5e9f0f232afd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -68,7 +68,6 @@ public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, Wrap @Override public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - try { return objectMapper.readValue( ((CharSequence)source).toString(), objectMapper.constructType( javaType.getJavaType() ) ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java new file mode 100644 index 000000000000..11770525dc07 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java @@ -0,0 +1,384 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.util; + +import org.hibernate.type.format.JsonDocumentItem; +import org.hibernate.type.format.StringJsonDocumentReader; +import org.junit.jupiter.api.Test; + +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Emmanuel Jannetti + */ +public class StringJsonDocumentReaderTest { + @Test + public void testNullDocument() { + assertThrows( IllegalArgumentException.class, () -> {new StringJsonDocumentReader( null );} ); + } + @Test + public void testEmptyDocument() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "" ); + assertFalse(reader.hasNext(), "Should not have anymore element"); + } + @Test + public void testEmptyJsonObject() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{}" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + } + @Test + public void testEmptyJsonArray() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "[]" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.ARRAY_START, reader.next()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.ARRAY_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + } + @Test + public void testWrongNext() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{}" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + @Test + public void testSimpleDocument() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"key1\" :\"value1\" }" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals("key1", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE, reader.next()); + assertEquals("value1", reader.getStringValue()); + + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + @Test + public void testSimpleDoubleValueDocument() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"key1\":\"\",\"key2\" : \" x value2 x \" }" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "key1", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( "", reader.getStringValue()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "key2", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertTrue( reader.getStringValue().equals(" x value2 x ")); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + @Test + public void testNonStringValueDocument() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"aNull\":null, \"aNumber\" : 12 , \"aBoolean\" : true}" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aNull", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NULL_VALUE,reader.next()); + assertEquals( "null", reader.getStringValue()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aNumber", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( 12, reader.getIntegerValue()); + assertEquals( "12", reader.getStringValue()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aBoolean", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE,reader.next()); + assertEquals( true, reader.getBooleanValue()); + assertEquals( "true", reader.getStringValue()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + + @Test + public void testNonAvailableValueDocument() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{}" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertThrows( IllegalStateException.class, () -> {reader.getStringValue();} ); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + + @Test + public void testBooleanValueDocument() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"aBoolean\" : true}" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aBoolean", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE,reader.next()); + assertTrue( reader.getBooleanValue() ); + assertEquals( "true",reader.getStringValue()); + assertTrue(reader.getBooleanValue()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + + @Test + public void testNumericValueDocument() { + final StringJsonDocumentReader reader = + new StringJsonDocumentReader( "{ \"aInteger\" : 12, \"aDouble\" : 123.456 , \"aLong\" : 123456, \"aShort\" : 1}" ); + assertTrue(reader.hasNext(), "should have more element"); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aInteger", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( (int)12 , reader.getIntegerValue()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aDouble", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( (double)123.456 ,reader.getDoubleValue() ); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aLong", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( (long)123456 , reader.getLongValue() ); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "aShort", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( (short)1, reader.getLongValue() ); + + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + + @Test + public void testArrayValueDocument() { + final StringJsonDocumentReader reader = + new StringJsonDocumentReader( "{ \"anEmptyArray\" : [], \"anArray\" : [1,2,3] }" ); + assertTrue(reader.hasNext()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "anEmptyArray", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "anArray", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals(1, reader.getIntegerValue()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals(2, reader.getIntegerValue()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals(3, reader.getIntegerValue()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + @Test + public void testObjectArrayMultipleValueDocument() { + final StringJsonDocumentReader reader = + new StringJsonDocumentReader( "{ \"anArray\" : [1, null, \"2\" , {\"foo\":\"bar\"} ] \n" + + " }" ); + assertTrue(reader.hasNext()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "anArray", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals("1", reader.getStringValue()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NULL_VALUE,reader.next()); + assertEquals("null", reader.getStringValue()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals(2, reader.getIntegerValue()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "foo", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals("bar", reader.getStringValue()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + @Test + public void testEscapeStringDocument() { + final StringJsonDocumentReader reader = + new StringJsonDocumentReader( "{ \"str1\" : \"abc\" , \"str2\" : \"\\\"abc\\\"\" , \"str3\" : \"a\\\"b\\\"c\" }" ); + assertTrue(reader.hasNext()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "str1", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals("abc", reader.getStringValue()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "str2", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals("\"abc\"", reader.getStringValue()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "str3", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals("a\"b\"c", reader.getStringValue()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + + assertFalse(reader.hasNext(), "Should not have anymore element"); + assertThrows( NoSuchElementException.class, () -> {reader.next();} ); + } + + @Test + public void testNestedDocument() { + final StringJsonDocumentReader reader = + new StringJsonDocumentReader( """ + { + "nested": { + "converted_gender": "M", + "theInteger": -1 + }, + "doubleNested": { + "theNested": { + "theLeaf": { + "stringField": "String \\"<abc>A&B</abc>\\"" + } + } + }, + "integerField": 10 + } + """); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "nested", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "converted_gender", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals("M", reader.getStringValue()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "theInteger", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals(-1, reader.getIntegerValue()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "doubleNested", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "theNested", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "theLeaf", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "stringField", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals("String \"<abc>A&B</abc>\"", reader.getStringValue()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "integerField", reader.getObjectKeyName()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals("10", reader.getStringValue()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + } + + @Test + public void testNestedArrayDocument() { + final StringJsonDocumentReader reader = + new StringJsonDocumentReader( """ + { + "nested": [ + { + "anArray": [1] + } + ] + } + """); + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "nested", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( "anArray", reader.getObjectKeyName()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals(1L, reader.getLongValue()); + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_END,reader.next()); + + assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + + assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java new file mode 100644 index 000000000000..1e7a3207fc99 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java @@ -0,0 +1,183 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.util; + +import org.hibernate.dialect.JsonHelper; +import org.hibernate.type.format.StringJsonDocumentWriter; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +/** + * @author Emmanuel Jannetti + */ +public class StringJsonDocumentWriterTest { + + private static void assertEqualsIgnoreSpace(String expected, String actual) { + assertEquals(expected.replaceAll("\\s", ""), actual.replaceAll("\\s", "")); + } + + @Test + public void testEmptyDocument() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + writer.startObject(); + writer.endObject(); + assertEquals( "{}" , writer.toString()); + } + + @Test + public void testEmptyArray() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + writer.startArray(); + writer.endArray(); + assertEquals( "[]" , writer.toString() ); + } + @Test + public void testArray() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + writer.startArray(); + writer.numberValue( Integer.valueOf( 1 ) ); + writer.numberValue( Integer.valueOf( 2 ) ); + writer.numberValue( Integer.valueOf( 3 ) ); + writer.endArray(); + assertEquals( "[1,2,3]" , writer.toString() ); + } + + @Test + public void testMixedArrayDocument() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + writer.startArray(); + writer.numberValue( Integer.valueOf( 1 ) ); + writer.nullValue(); + writer.booleanValue( false ); + writer.stringValue( "foo" ); + writer.endArray(); + assertEqualsIgnoreSpace( """ + [1,null,false,"foo"] + """ , writer.toString() ); + } + @Test + public void testSimpleDocument() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + writer.startObject(); + writer.objectKey( "key1" ); + writer.stringValue( "value1" ); + writer.endObject(); + + assertEqualsIgnoreSpace( + """ + { + "key1":"value1" + } + """, writer.toString()); + + } + + @Test + public void testNonStringValueDocument() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + writer.startObject(); + writer.objectKey( "aNull" ); + writer.nullValue(); + writer.objectKey( "aNumber" ); + writer.numberValue( Integer.valueOf( 12 ) ); + writer.objectKey( "aBoolean" ); + writer.booleanValue( true ); + writer.endObject(); + + assertEqualsIgnoreSpace( """ + { + "aNull":null, + "aNumber" : 12 , + "aBoolean" : true + } + """ , writer.toString() ); + + } + + + @Test + public void testArrayValueDocument() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); + writer.startObject(); + writer.objectKey( "anEmptyArray" ); + writer.startArray(); + writer.endArray(); + writer.objectKey( "anArray" ); + writer.startArray(); + writer.numberValue( Integer.valueOf( 1 ) ); + writer.numberValue( Integer.valueOf( 2 ) ); + writer.numberValue( Integer.valueOf( 3 ) ); + writer.endArray(); + writer.endObject(); + + assertEqualsIgnoreSpace( """ + { + "anEmptyArray" : [], + "anArray" : [1,2,3] + } + """, writer.toString() ); + } + @Test + public void testObjectArrayMultipleValueDocument() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); + writer.startObject(); + writer.objectKey( "anArray" ).startArray().numberValue( Integer.valueOf( 1 ) ).nullValue().stringValue( "2" ).startObject() + .objectKey( "foo" ).stringValue( "bar" ).endObject().endArray().endObject(); + + assertEqualsIgnoreSpace( """ + { + "anArray" : [1, null, "2" , {\"foo\":\"bar\"} ] + } + """ , sb.toString() ); + + } + + @Test + public void testNestedDocument() { + StringBuilder sb = new StringBuilder(); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); + writer.startObject().objectKey( "nested" ).startObject() + .objectKey( "converted_gender" ).stringValue( "M" ).objectKey( "theInteger" ).numberValue( Integer.valueOf( -1 ) ).endObject() + .objectKey( "doubleNested" ).startObject() + .objectKey( "theNested" ).startObject() + .objectKey( "theLeaf" ) + .startObject().objectKey( "stringField" ).stringValue( "String \"<abc>A&B</abc>\"" ).endObject() + .endObject() + .endObject() + .objectKey( "integerField" ).numberValue( Integer.valueOf( 10 ) ) + .endObject(); + + assertEqualsIgnoreSpace( """ + { + "nested": { + "converted_gender": "M", + "theInteger": -1 + }, + "doubleNested": { + "theNested": { + "theLeaf": { + "stringField": "String \\"<abc>A&B</abc>\\"" + } + } + }, + "integerField": 10 + } + """,writer.toString()); + + + } + + +} From 77ade6a5af9cc3bc44fdab0e73ed470d6e7a90ba Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Wed, 29 Jan 2025 18:54:15 +0100 Subject: [PATCH 45/81] HHH-17404 : removed dead code --- .../dialect/OracleOsonJacksonJdbcType.java | 9 - .../org/hibernate/dialect/OsonHelper.java | 104 ------- .../type/descriptor/jdbc/JsonHelper.java | 10 +- .../type/format/JsonDocumentHandler.java | 62 ---- .../type/format/JsonDocumentItem.java | 87 ------ .../type/format/JsonDocumentItemType.java | 47 +++ .../type/format/JsonDocumentReader.java | 2 +- .../ObjectArrayOsonDocumentHandler.java | 279 ------------------ .../type/format/OsonDocumentReader.java | 36 +-- .../type/format/StringJsonDocumentReader.java | 32 +- .../util/StringJsonDocumentReaderTest.java | 210 ++++++------- 11 files changed, 192 insertions(+), 686 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 4413058bfb06..eabb8cf716c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -149,21 +149,12 @@ private X fromOson(InputStream osonBytes, WrapperOptions options) throws Excepti // and build the array.(as opposed to let Jackson do it as we do not // have a proper object definition at that stage). OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( osonBytes ); - - - //ObjectArrayOsonDocumentHandler handler = new ObjectArrayOsonDocumentHandler( getEmbeddableMappingType(), - // options); - //OsonHelper.consumeOsonTokens(osonParser, osonParser.next(), handler); - - //osonBytes.reset(); - //OracleJsonParser osonParser2 = new OracleJsonFactory().createJsonBinaryParser( osonBytes); Object[] objects = JsonHelper.deserialize( getEmbeddableMappingType(), osonParser, javaType.getJavaTypeClass() != Object[].class, options ); - //Object[] objects2 = (Object[]) handler.getObjectArray(); return (X) objects; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java deleted file mode 100644 index 09c57aa9c2c0..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OsonHelper.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.dialect; - -import oracle.sql.json.OracleJsonParser; -import org.hibernate.Internal; -import org.hibernate.type.format.JsonDocumentHandler; -import org.hibernate.type.format.ObjectArrayOsonDocumentHandler; - -import java.io.IOException; - -/** - * A Helper for handling OSON events - */ -@Internal -public class OsonHelper { - - /** - * Process OSON parser tokens. - * This method consumes one by one event coming from an OSON parser and uses the given JsonDocumentHandler - * to populate values into Object array - * @param osonParser the OSON parser - * @param currentEvent the current of the parser - * @throws IOException error while reading from underlying parser - */ - public static void consumeOsonTokens(OracleJsonParser osonParser, OracleJsonParser.Event currentEvent, JsonDocumentHandler handler) - throws IOException { - - OracleJsonParser.Event event = currentEvent; - - while ( event != null ) { - switch ( event ) { - case OracleJsonParser.Event.KEY_NAME: - handler.onObjectKey( osonParser.getString() ); - break; - case OracleJsonParser.Event.START_ARRAY: - handler.onStartArray(); - break; - case OracleJsonParser.Event.END_ARRAY: - handler.onEndArray(); - break; - case OracleJsonParser.Event.VALUE_DATE: - case OracleJsonParser.Event.VALUE_TIMESTAMP: - ((ObjectArrayOsonDocumentHandler)handler).onOsonDateValue( - osonParser.getLocalDateTime()); - break; - case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getOffsetDateTime()); - break; - case OracleJsonParser.Event.VALUE_INTERVALDS: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getDuration()); - break; - case OracleJsonParser.Event.VALUE_INTERVALYM: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getPeriod()); - break; - case OracleJsonParser.Event.VALUE_STRING: - handler.onStringValue( osonParser.getString() ); - break; - case OracleJsonParser.Event.VALUE_TRUE: - handler.onBooleanValue( true ); - break; - case OracleJsonParser.Event.VALUE_FALSE: - handler.onBooleanValue( false ); - break; - case OracleJsonParser.Event.VALUE_NULL: - handler.onNullValue(); - break; - case OracleJsonParser.Event.VALUE_DECIMAL: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getBigDecimal()); - break; - case OracleJsonParser.Event.VALUE_DOUBLE: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getDouble()); - break; - case OracleJsonParser.Event.VALUE_FLOAT: - ((ObjectArrayOsonDocumentHandler)handler).onOsonValue( - osonParser.getFloat()); - break; - case OracleJsonParser.Event.VALUE_BINARY: - ((ObjectArrayOsonDocumentHandler)handler).onOsonBinaryValue( - osonParser.getBytes()); - break; - case OracleJsonParser.Event.START_OBJECT: - handler.onStartObject(); - break; - case OracleJsonParser.Event.END_OBJECT: - handler.onEndObject(); - break; - default: - throw new IOException( "Unknown OSON event " + event ); - - } - event = osonParser.hasNext() ? osonParser.next() : null; - } - - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 14e06bdfe668..a98536091697 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -36,7 +36,7 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.format.JsonDocumentItem; +import org.hibernate.type.format.JsonDocumentItemType; import org.hibernate.type.format.JsonDocumentReader; import org.hibernate.type.format.JsonDocumentReaderFactory; import org.hibernate.type.format.JsonDocumentWriter; @@ -257,7 +257,7 @@ private static <X> X consumeJsonDocumentItems(JsonDocumentReader reader, Embedda objectArrays.push( objectArrayResult ); while(reader.hasNext()) { - JsonDocumentItem.JsonDocumentItemType type = reader.next(); + JsonDocumentItemType type = reader.next(); switch (type) { case VALUE_KEY: currentKeyName = reader.getObjectKeyName(); @@ -429,12 +429,12 @@ public static <X> X arrayFromString( JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,false); assert reader.hasNext():"Invalid array string"; - assert reader.next() == JsonDocumentItem.JsonDocumentItemType.ARRAY_START:"Invalid start of array"; + assert reader.next() == JsonDocumentItemType.ARRAY_START:"Invalid start of array"; boolean endArrayFound = false; while(reader.hasNext()) { - JsonDocumentItem.JsonDocumentItemType type = reader.next(); + JsonDocumentItemType type = reader.next(); switch ( type ) { - case JsonDocumentItem.JsonDocumentItemType.ARRAY_END: + case JsonDocumentItemType.ARRAY_END: endArrayFound=true; break; case NULL_VALUE: diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java deleted file mode 100644 index 139e4b3683b8..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.type.format; - - -/** - * JSON document handler. - * Used to parse JSON documents. Implementors of this will define - * proper callback implementations. - * - * @author Emmanuel Jannetti - */ - -public interface JsonDocumentHandler { - /** - * Callback to be called when the start of an JSON object is encountered. - */ - void onStartObject(); - - /** - * Callback to be called when the end of an JSON object is encountered. - */ - void onEndObject(); - - /** - * Callback to be called when the start of an array is encountered. - */ - void onStartArray(); - - /** - * Callback to be called when the end of an array is encountered. - */ - void onEndArray(); - - /** - * Callback to be called when the key of JSON attribute is encountered. - */ - void onObjectKey(String key); - - /** - * Callback to be called when null value is encountered. - */ - void onNullValue(); - - /** - * Callback to be called when boolean value is encountered. - */ - void onBooleanValue(boolean value); - - /** - * Callback to be called when string value is encountered. - */ - void onStringValue(String value); - - /** - * Callback to be called when Number value is encountered. - */ - void onNumberValue(Number value); - -} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java deleted file mode 100644 index 693e36d64967..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItem.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.type.format; - -public class JsonDocumentItem { - public static final JsonDocumentItem OBJECT_START_ITEM = new JsonDocumentItem(JsonDocumentItemType.OBJECT_START, - Boolean.TRUE ); - public static final JsonDocumentItem OBJECT_END_ITEM = new JsonDocumentItem(JsonDocumentItemType.OBJECT_END, - Boolean.TRUE ); - public static final JsonDocumentItem ARRAY_START_ITEM = new JsonDocumentItem(JsonDocumentItemType.ARRAY_START, - Boolean.TRUE ); - public static final JsonDocumentItem ARRAY_END_ITEM = new JsonDocumentItem(JsonDocumentItemType.ARRAY_END, - Boolean.TRUE ); - public static final JsonDocumentItem KEY_NAME_ITEM = new JsonDocumentItem(JsonDocumentItemType.VALUE_KEY, - Boolean.TRUE ); - - public static final JsonDocumentItem TRUE_VALUE_ITEM = new JsonDocumentItem(JsonDocumentItemType.BOOLEAN_VALUE,Boolean.TRUE); - public static final JsonDocumentItem FALSE_VALUE_ITEM = new JsonDocumentItem(JsonDocumentItemType.BOOLEAN_VALUE,Boolean.FALSE); - public static final JsonDocumentItem NULL_VALUE_ITEM = new JsonDocumentItem(JsonDocumentItemType.NULL_VALUE,null); - - - - private JsonDocumentItemType type; - private Object value; - - - public Object getValue() { - return value; - } - - public JsonDocumentItem(JsonDocumentItemType type, Object value) { - this(type); - this.value = value; - } - public JsonDocumentItem(JsonDocumentItemType type) { - this.type = type; - this.value = null; - } - - public JsonDocumentItemType getType() { - return type; - } - - /** - * Json item types - */ - public enum JsonDocumentItemType { - /** - * Start of a Json Object '{' - */ - OBJECT_START, - /** - * end of a Json Object '}' - */ - OBJECT_END, - /** - * Start of a Json array '[' - */ - ARRAY_START, - /** - * End of a Json array ']' - */ - ARRAY_END, - /** - * key of Json attribute - */ - VALUE_KEY, - /** - * boolean value within Json object or array - */ - BOOLEAN_VALUE, - /** - * null value within Json object or array - */ - NULL_VALUE, - /** - * numeric value within Json object or array - */ - NUMERIC_VALUE, - /** - * String (quoted) value within Json object or array - */ - VALUE - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java new file mode 100644 index 000000000000..a2256446f95d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.type.format; + +/** + * Json item types + */ +public enum JsonDocumentItemType { + /** + * Start of a Json Object '{' + */ + OBJECT_START, + /** + * end of a Json Object '}' + */ + OBJECT_END, + /** + * Start of a Json array '[' + */ + ARRAY_START, + /** + * End of a Json array ']' + */ + ARRAY_END, + /** + * key of Json attribute + */ + VALUE_KEY, + /** + * boolean value within Json object or array + */ + BOOLEAN_VALUE, + /** + * null value within Json object or array + */ + NULL_VALUE, + /** + * numeric value within Json object or array + */ + NUMERIC_VALUE, + /** + * String (quoted) value within Json object or array + */ + VALUE +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java index e8eb6ab92399..f97b51e3f474 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java @@ -79,7 +79,7 @@ * * @author Emmanuel Jannetti */ -public interface JsonDocumentReader extends Iterator<JsonDocumentItem.JsonDocumentItemType> { +public interface JsonDocumentReader extends Iterator<JsonDocumentItemType> { default void forEachRemaining() { throw new UnsupportedOperationException("forEachRemaining"); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java b/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java deleted file mode 100644 index 608467bcc43f..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/type/format/ObjectArrayOsonDocumentHandler.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.type.format; - -import org.hibernate.internal.util.collections.StandardStack; -import org.hibernate.metamodel.mapping.EmbeddableMappingType; -import org.hibernate.metamodel.mapping.SelectableMapping; -import org.hibernate.type.BasicPluralType; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.UUIDJavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; - -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.List; - - -/** - * Implementation of <code>JsonDocumentHandler</code> for OSON document. - * This implementation will produce an Object Array based on - * embeddable mapping - * Once All JSON document is handle the mapped Object array can be retrieved using the - * <code>getObjectArray()</code> method. - * - */ -public class ObjectArrayOsonDocumentHandler implements JsonDocumentHandler { - - // final result of a mapped object array - private final Object [] objectArrayResult; - // current mapping to be used - SelectableMapping currentSelectableMapping = null; - String currentKeyName = null; - List<Object> subArrayObjectList = null; - BasicPluralType<?, ?> subArrayObjectTypes = null; - - // mapping definitions are in a tree - // Each mapping definition may contain sub mappings (sub embeddable mapping) - // This stack is used to keep a pointer on the current mapping to be used to assign correct types. - // see onStartObject()/onEndObject() methods - StandardStack<EmbeddableMappingType> embeddableMappingTypes = new StandardStack<>(); - // As for mapping definitions, when "sub embeddable" is encountered, the array - // that needs to be filled with Objects is the one we allocate in the final result array slot. - // We use a stack to keep track of array ref - StandardStack<Object[]> objectArrays = new StandardStack<>(); - - - WrapperOptions wrapperOptions; - - // index within objectArrayResult - int currentSelectableIndexInResultArray = -1; - - public ObjectArrayOsonDocumentHandler(EmbeddableMappingType embeddableMappingType, WrapperOptions wrapperOptions) { - this.embeddableMappingTypes.push(embeddableMappingType); - this.wrapperOptions = wrapperOptions; - this.objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()+ ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; - this.objectArrays.push( this.objectArrayResult ); - } - - /** - * Gets the Object array built from document handling - * @return the array of objects - */ - public Object [] getObjectArray() { - return this.objectArrayResult; - } - - @Override - public void onStartObject() { - if (currentKeyName != null) { - // We are dealing with a sub-object, allocate space for it then, - // otherwise, we have nothing to do. - // Push the new (sub)mapping definition. - assert embeddableMappingTypes.getCurrent() != null; - this.currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); - assert currentSelectableIndexInResultArray != -1: "Cannot get index of " + currentKeyName; - - final SelectableMapping selectable = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( - currentSelectableIndexInResultArray ); - final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() - .getJdbcType(); - final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); - assert this.objectArrays.getCurrent() != null; - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = - new Object[subMappingType.getJdbcValueCount()]; - this.embeddableMappingTypes.push( subMappingType ); - this.objectArrays.push( (Object[]) this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] ); - } - } - - @Override - public void onEndObject() { - // go back in the mapping definition tree - this.embeddableMappingTypes.pop(); - this.objectArrays.pop(); - } - - @Override - public void onStartArray() { - assert (subArrayObjectList == null && subArrayObjectTypes == null) : "onStartArray called twice ?"; - - // initialize an array to gather values - subArrayObjectList = new ArrayList<>(); - assert (currentSelectableMapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) - : "Array event received for non plural type"; - // initialize array's element type - subArrayObjectTypes = (BasicPluralType<?, ?>) currentSelectableMapping.getJdbcMapping(); - } - - @Override - public void onEndArray() { - assert (subArrayObjectList != null && subArrayObjectTypes != null) : "onEndArray called before onStartArray"; - // flush array values - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, wrapperOptions ); - // reset until we encounter next array element - subArrayObjectList = null; - subArrayObjectTypes = null; - } - - @Override - public void onObjectKey(String key) { - this.currentKeyName = key; - - currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); - if ( currentSelectableIndexInResultArray >= 0 ) { - // we may not have a selectable mapping for that key - currentSelectableMapping = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( currentSelectableIndexInResultArray ); - } - else { - throw new IllegalArgumentException( - String.format( - "Could not find selectable [%s] in embeddable type [%s] for JSON processing.", - currentKeyName, - embeddableMappingTypes.getCurrent().getMappedJavaType().getJavaTypeClass().getName() - ) - ); - } - } - - @Override - public void onNullValue() { - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( null ); - } - else { - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = null; - } - } - - @Override - public void onBooleanValue(boolean value) { - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( value?Boolean.TRUE:Boolean.FALSE); - } - else { - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = value?Boolean.TRUE:Boolean.FALSE; - } - } - - @Override - public void onStringValue(String value) { - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( - subArrayObjectTypes.getElementType().getJdbcJavaType().fromEncodedString( value ,0,value.length()) ); - } - else { - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = - currentSelectableMapping.getJdbcMapping().getJdbcJavaType().fromEncodedString( value,0,value.length()); - } - } - - @Override - public void onNumberValue(Number value) { - onOsonValue(value); - } - - /** - * Callback for OSON values - * @param value the OSON value - * @param <T> the type of the value as returned by OracleJsonParser - */ - public <T> void onOsonValue(T value) { - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( value ); - } - else { - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = - currentSelectableMapping.getJdbcMapping().convertToDomainValue( - currentSelectableMapping.getJdbcMapping().getJdbcJavaType() - .wrap( value, wrapperOptions ) ); - } - } - - /** - * Callback for OSON binary value - * @param bytes the OSON byters - */ - public void onOsonBinaryValue(byte[] bytes) { - Class underlyingType; - Object theOneToBeUsed; - if(subArrayObjectTypes!=null) { - underlyingType = subArrayObjectTypes.getElementType().getJavaType(); - } - else { - underlyingType = (Class) currentSelectableMapping.getJdbcMapping().getJdbcJavaType().getJavaType(); - } - - if (java.util.UUID.class.isAssignableFrom( underlyingType )) { - theOneToBeUsed = UUIDJavaType.INSTANCE.wrap( bytes, wrapperOptions ); - } - else { - theOneToBeUsed = bytes; - } - - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( theOneToBeUsed ); - } - else { - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = theOneToBeUsed; - } - } - - /** - * Callback for OracleJsonParser.Event.VALUE_DATE and OracleJsonParser.Event.VALUE_TIMESTAMP: - * @param localDateTime the time - */ - public void onOsonDateValue(LocalDateTime localDateTime) { - - Class underlyingType; - Object theOneToBeUsed = localDateTime; - - if(subArrayObjectTypes!=null) { - underlyingType = subArrayObjectTypes.getElementType().getJavaType(); - } - else { - underlyingType = (Class) currentSelectableMapping.getJdbcMapping().getJdbcJavaType().getJavaType(); - } - if (java.sql.Date.class.isAssignableFrom( underlyingType )) { - theOneToBeUsed = Date.valueOf( localDateTime.toLocalDate()); - } - else if (java.time.LocalDate.class.isAssignableFrom( underlyingType )) { - theOneToBeUsed = localDateTime.toLocalDate(); - } - else if (java.time.LocalTime.class.isAssignableFrom( underlyingType )) { - theOneToBeUsed = localDateTime.toLocalTime(); - } - else if (java.sql.Time.class.isAssignableFrom( underlyingType )) { - theOneToBeUsed = Time.valueOf( localDateTime.toLocalTime() ); - } - else if (java.sql.Timestamp.class.isAssignableFrom( underlyingType )) { - theOneToBeUsed = Timestamp.valueOf( localDateTime ); - } - else if(java.time.LocalTime.class.isAssignableFrom( underlyingType )) { - theOneToBeUsed = localDateTime.toLocalTime(); - } - else if ( java.util.Date.class.isAssignableFrom( underlyingType ) ) { - // better way? - theOneToBeUsed = java.util.Date.from( localDateTime.atZone( ZoneId.of( "UTC" ) ).toInstant()); - } - - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( theOneToBeUsed ); - } - else { - this.objectArrays.getCurrent()[currentSelectableIndexInResultArray] = theOneToBeUsed; - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java index 36bea03a26cf..b14bdc7f3702 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java @@ -42,7 +42,7 @@ public boolean hasNext() { } @Override - public JsonDocumentItem.JsonDocumentItemType next() { + public JsonDocumentItemType next() { if (!this.parser.hasNext()) throw new NoSuchElementException("No more item in JSON document"); OracleJsonParser.Event evt = this.parser.next(); @@ -51,54 +51,54 @@ public JsonDocumentItem.JsonDocumentItemType next() { currentValueIsAString = false; switch (evt) { case OracleJsonParser.Event.START_OBJECT: - return JsonDocumentItem.JsonDocumentItemType.OBJECT_START; + return JsonDocumentItemType.OBJECT_START; case OracleJsonParser.Event.END_OBJECT: - return JsonDocumentItem.JsonDocumentItemType.OBJECT_END; + return JsonDocumentItemType.OBJECT_END; case OracleJsonParser.Event.START_ARRAY: - return JsonDocumentItem.JsonDocumentItemType.ARRAY_START; + return JsonDocumentItemType.ARRAY_START; case OracleJsonParser.Event.END_ARRAY: - return JsonDocumentItem.JsonDocumentItemType.ARRAY_END; + return JsonDocumentItemType.ARRAY_END; case OracleJsonParser.Event.KEY_NAME: currentKeyName = this.parser.getString(); - return JsonDocumentItem.JsonDocumentItemType.VALUE_KEY; + return JsonDocumentItemType.VALUE_KEY; case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: currentValue = this.parser.getOffsetDateTime(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_DATE: case OracleJsonParser.Event.VALUE_TIMESTAMP: currentValue = this.parser.getLocalDateTime(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_INTERVALDS: currentValue = this.parser.getDuration(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_INTERVALYM: currentValue = this.parser.getPeriod(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_STRING: currentValue = this.parser.getString(); currentValueIsAString = true; - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_TRUE: currentValue = Boolean.TRUE; - return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + return JsonDocumentItemType.BOOLEAN_VALUE; case OracleJsonParser.Event.VALUE_FALSE: currentValue = Boolean.FALSE; - return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + return JsonDocumentItemType.BOOLEAN_VALUE; case OracleJsonParser.Event.VALUE_NULL: currentValue = null; - return JsonDocumentItem.JsonDocumentItemType.NULL_VALUE; + return JsonDocumentItemType.NULL_VALUE; case OracleJsonParser.Event.VALUE_DECIMAL: currentValue = this.parser.getBigDecimal(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_DOUBLE: currentValue = this.parser.getDouble(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_FLOAT: currentValue = this.parser.getFloat(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_BINARY: currentValue = this.parser.getBytes(); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; default : assert false:"Unknown OSON event"; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index c12407960f8a..68aa98df47d8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -125,7 +125,7 @@ private void moveStateMachine(StringJsonDocumentMarker marker) { * @throws IllegalStateException not a well-formed JSON string. */ @Override - public JsonDocumentItem.JsonDocumentItemType next() { + public JsonDocumentItemType next() { if ( !hasNext()) throw new NoSuchElementException("no more elements"); @@ -137,19 +137,19 @@ public JsonDocumentItem.JsonDocumentItemType next() { case OBJECT_START: //this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); resetValueWindow(); - return JsonDocumentItem.JsonDocumentItemType.OBJECT_START; + return JsonDocumentItemType.OBJECT_START; case OBJECT_END: resetValueWindow(); //this.processingStates.pop(); // closing an object or a nested one. - return JsonDocumentItem.JsonDocumentItemType.OBJECT_END; + return JsonDocumentItemType.OBJECT_END; case ARRAY_START: resetValueWindow(); //this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); - return JsonDocumentItem.JsonDocumentItemType.ARRAY_START; + return JsonDocumentItemType.ARRAY_START; case ARRAY_END: resetValueWindow(); //this.processingStates.pop(); - return JsonDocumentItem.JsonDocumentItemType.ARRAY_END; + return JsonDocumentItemType.ARRAY_END; case QUOTE: // that's the start of an attribute key or a quoted value // put back the quote moveBufferPosition(-1); @@ -164,17 +164,17 @@ public JsonDocumentItem.JsonDocumentItemType next() { switch ( this.processingStates.getCurrent() ) { case PROCESSING_STATE.STARTING_ARRAY: //this.processingStates.push( PROCESSING_STATE.ARRAY ); - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case PROCESSING_STATE.ARRAY: - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case PROCESSING_STATE.STARTING_OBJECT: //this.processingStates.push( PROCESSING_STATE.OBJECT ); //this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); - return JsonDocumentItem.JsonDocumentItemType.VALUE_KEY; + return JsonDocumentItemType.VALUE_KEY; case PROCESSING_STATE.OBJECT: // we are processing object attribute value elements - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; case PROCESSING_STATE.OBJECT_KEY_NAME: // we are processing object elements key - return JsonDocumentItem.JsonDocumentItemType.VALUE_KEY; + return JsonDocumentItemType.VALUE_KEY; default: throw new IllegalStateException( "unexpected quote read in current processing state " + this.processingStates.getCurrent() ); @@ -223,20 +223,20 @@ public JsonDocumentItem.JsonDocumentItemType next() { * @param jsonValueWindow the value * @return the type of the value */ - private JsonDocumentItem.JsonDocumentItemType getUnquotedValueType(CharBuffer jsonValueWindow) { + private JsonDocumentItemType getUnquotedValueType(CharBuffer jsonValueWindow) { final int size = jsonValueWindow.remaining(); switch(jsonValueWindow.charAt( 0 )) { case 't': { //true - return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + return JsonDocumentItemType.BOOLEAN_VALUE; } case 'f': { //false - return JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE; + return JsonDocumentItemType.BOOLEAN_VALUE; } case 'n' : { // null - return JsonDocumentItem.JsonDocumentItemType.NULL_VALUE; + return JsonDocumentItemType.NULL_VALUE; } case '-': case '0': @@ -249,10 +249,10 @@ private JsonDocumentItem.JsonDocumentItemType getUnquotedValueType(CharBuffer js case '7': case '8': case '9': { - return JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE; + return JsonDocumentItemType.NUMERIC_VALUE; } default : - return JsonDocumentItem.JsonDocumentItemType.VALUE; + return JsonDocumentItemType.VALUE; } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java index 11770525dc07..a614b63efb29 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java @@ -4,7 +4,7 @@ */ package org.hibernate.orm.test.util; -import org.hibernate.type.format.JsonDocumentItem; +import org.hibernate.type.format.JsonDocumentItemType; import org.hibernate.type.format.StringJsonDocumentReader; import org.junit.jupiter.api.Test; @@ -32,24 +32,24 @@ public void testEmptyDocument() { public void testEmptyJsonObject() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{}" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); } @Test public void testEmptyJsonArray() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "[]" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.ARRAY_START, reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.ARRAY_END, reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_START, reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); } @Test public void testWrongNext() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{}" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -57,15 +57,15 @@ public void testWrongNext() { public void testSimpleDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"key1\" :\"value1\" }" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals("key1", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE, reader.next()); + assertEquals( JsonDocumentItemType.VALUE, reader.next()); assertEquals("value1", reader.getStringValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -73,19 +73,19 @@ public void testSimpleDocument() { public void testSimpleDoubleValueDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"key1\":\"\",\"key2\" : \" x value2 x \" }" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "key1", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals( "", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "key2", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertTrue( reader.getStringValue().equals(" x value2 x ")); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -93,27 +93,27 @@ public void testSimpleDoubleValueDocument() { public void testNonStringValueDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"aNull\":null, \"aNumber\" : 12 , \"aBoolean\" : true}" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aNull", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NULL_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NULL_VALUE,reader.next()); assertEquals( "null", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aNumber", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals( 12, reader.getIntegerValue()); assertEquals( "12", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aBoolean", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.BOOLEAN_VALUE,reader.next()); assertEquals( true, reader.getBooleanValue()); assertEquals( "true", reader.getStringValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -122,11 +122,11 @@ public void testNonStringValueDocument() { public void testNonAvailableValueDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{}" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); assertThrows( IllegalStateException.class, () -> {reader.getStringValue();} ); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -135,16 +135,16 @@ public void testNonAvailableValueDocument() { public void testBooleanValueDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"aBoolean\" : true}" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aBoolean", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.BOOLEAN_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.BOOLEAN_VALUE,reader.next()); assertTrue( reader.getBooleanValue() ); assertEquals( "true",reader.getStringValue()); assertTrue(reader.getBooleanValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -154,31 +154,31 @@ public void testNumericValueDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"aInteger\" : 12, \"aDouble\" : 123.456 , \"aLong\" : 123456, \"aShort\" : 1}" ); assertTrue(reader.hasNext(), "should have more element"); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aInteger", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals( (int)12 , reader.getIntegerValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aDouble", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals( (double)123.456 ,reader.getDoubleValue() ); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aLong", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals( (long)123456 , reader.getLongValue() ); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "aShort", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals( (short)1, reader.getLongValue() ); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -188,25 +188,25 @@ public void testArrayValueDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"anEmptyArray\" : [], \"anArray\" : [1,2,3] }" ); assertTrue(reader.hasNext()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "anEmptyArray", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_END,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "anArray", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals(1, reader.getIntegerValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals(2, reader.getIntegerValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals(3, reader.getIntegerValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_END,reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); } @@ -216,30 +216,30 @@ public void testObjectArrayMultipleValueDocument() { new StringJsonDocumentReader( "{ \"anArray\" : [1, null, \"2\" , {\"foo\":\"bar\"} ] \n" + " }" ); assertTrue(reader.hasNext()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "anArray", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals("1", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NULL_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NULL_VALUE,reader.next()); assertEquals("null", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals(2, reader.getIntegerValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "foo", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals("bar", reader.getStringValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_END,reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); @@ -249,24 +249,24 @@ public void testEscapeStringDocument() { final StringJsonDocumentReader reader = new StringJsonDocumentReader( "{ \"str1\" : \"abc\" , \"str2\" : \"\\\"abc\\\"\" , \"str3\" : \"a\\\"b\\\"c\" }" ); assertTrue(reader.hasNext()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "str1", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals("abc", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "str2", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals("\"abc\"", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "str3", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals("a\"b\"c", reader.getStringValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); assertFalse(reader.hasNext(), "Should not have anymore element"); assertThrows( NoSuchElementException.class, () -> {reader.next();} ); @@ -292,57 +292,57 @@ public void testNestedDocument() { } """); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "nested", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "converted_gender", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals("M", reader.getStringValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "theInteger", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals(-1, reader.getIntegerValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "doubleNested", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "theNested", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "theLeaf", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_START,reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "stringField", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE,reader.next()); + assertEquals( JsonDocumentItemType.VALUE,reader.next()); assertEquals("String \"<abc>A&B</abc>\"", reader.getStringValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "integerField", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals("10", reader.getStringValue()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); } @Test @@ -357,28 +357,28 @@ public void testNestedArrayDocument() { ] } """); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "nested", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_START,reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_START, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); assertEquals( "anArray", reader.getObjectKeyName()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_START,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.NUMERIC_VALUE,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_START,reader.next()); + assertEquals( JsonDocumentItemType.NUMERIC_VALUE,reader.next()); assertEquals(1L, reader.getLongValue()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_END,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.OBJECT_END,reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END,reader.next()); - assertEquals( JsonDocumentItem.JsonDocumentItemType.ARRAY_END,reader.next()); + assertEquals( JsonDocumentItemType.ARRAY_END,reader.next()); - assertEquals(JsonDocumentItem.JsonDocumentItemType.OBJECT_END, reader.next()); + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); } } From 4592c417eba1ab4f46a36b4ed56a63102cc7400c Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Tue, 11 Feb 2025 15:55:52 +0530 Subject: [PATCH 46/81] HHH-17404- Remove DURATION mapping to INTERVALDS --- .../org/hibernate/dialect/OracleDialect.java | 3 - .../dialect/OracleDurationJavaType.java | 43 ------ .../dialect/OracleDurationJdbcType.java | 126 ------------------ 3 files changed, 172 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index a91990e0a856..1e893633a285 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1068,9 +1068,6 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry // Oracle requires a custom binder for binding untyped nulls with the NULL type typeContributions.contributeJdbcType( NullJdbcType.INSTANCE ); typeContributions.contributeJdbcType( ObjectNullAsNullTypeJdbcType.INSTANCE ); - // Oracle Stores the duration is ISO-8601 format. - typeContributions.contributeJdbcType( OracleDurationJdbcType.INSTANCE ); - typeContributions.contributeJavaType( OracleDurationJavaType.INSTANCE ); // Until we remove StandardBasicTypes, we have to keep this typeContributions.contributeType( diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java deleted file mode 100644 index ea64ae7353c8..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJavaType.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.dialect; - -import oracle.sql.INTERVALDS; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.DurationJavaType; - -import java.time.Duration; - -/** - * Oracle sub-implementation of {@link DurationJavaType} - * which is a descriptor for {@link Duration} - * This implementation brings the support of <code>oracle.sql.INTERVALDS</code> - * as source type. - * @see #wrap(Object, WrapperOptions) - * - * @author ejannett - * @author Bidyadhar Mohanty - */ -public class OracleDurationJavaType extends DurationJavaType { - - public static final OracleDurationJavaType INSTANCE = new OracleDurationJavaType(); - - public OracleDurationJavaType() { - super(); - } - - @Override - public <X> Duration wrap(X value, WrapperOptions options) { - if(value == null) { - return null; - } - - if ( value instanceof INTERVALDS ) { - return INTERVALDS.toDuration( ((INTERVALDS) value).toBytes() ); - } - - return super.wrap( value, options ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java deleted file mode 100644 index 69eb2686ffb8..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDurationJdbcType.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SPDX-License-Identifier: LGPL-2.1-or-later - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.dialect; - -import oracle.jdbc.OracleTypes; -import org.hibernate.type.SqlTypes; -import org.hibernate.type.descriptor.ValueBinder; -import org.hibernate.type.descriptor.ValueExtractor; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.BasicBinder; -import org.hibernate.type.descriptor.jdbc.BasicExtractor; -import org.hibernate.type.descriptor.jdbc.DurationJdbcType; -import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.Duration; - -/** - * Oracle sub-implementation of {@link DurationJdbcType} - * which is a descriptor for {@link java.time.Duration}. - * In Oracle databse Duration is stored as {@link OracleTypes.INTERVALDS} - * - * @author ejannett - * @author Bidyadhar Mohanty - */ -public class OracleDurationJdbcType extends DurationJdbcType { - - public static final OracleDurationJdbcType INSTANCE = new OracleDurationJdbcType(); - - public OracleDurationJdbcType() { - super(); - } - - @Override - public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { - return new BasicBinder<>( javaType, this ) { - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) - throws SQLException { - st.setObject( index, javaType.unwrap( value, Duration.class, options ) ); - } - - @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { - st.setObject( name, javaType.unwrap( value, Duration.class, options ) ); - } - - - }; - } - - @Override - public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { - return new BasicExtractor<>( javaType, this ) { - @Override - protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - // Handle the fact that a duration could also come as number of nanoseconds - final Object nativeValue = rs.getObject( paramIndex ); - if ( nativeValue instanceof Number ) { - return javaType.wrap( nativeValue, options ); - } - return javaType.wrap( rs.getObject( paramIndex, Duration.class ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - // Handle the fact that a duration could also come as number of nanoseconds - final Object nativeValue = statement.getObject( index ); - if ( nativeValue instanceof Number ) { - return javaType.wrap( nativeValue, options ); - } - return javaType.wrap( statement.getObject( index, Duration.class ), options ); - } - - @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) - throws SQLException { - // Handle the fact that a duration could also come as number of nanoseconds - final Object nativeValue = statement.getObject( name ); - if ( nativeValue instanceof Number ) { - return javaType.wrap( nativeValue, options ); - } - return javaType.wrap( statement.getObject( name, Duration.class ), options ); - } - }; - } - - @Override - public int getJdbcTypeCode() { - return SqlTypes.DURATION; - } - - @Override - public int getDdlTypeCode() { - return OracleTypes.INTERVALDS; - } - - @Override - public int getDefaultSqlTypeCode() { - return OracleTypes.INTERVALDS; - } - - @Override - public Class<?> getPreferredJavaTypeClass(WrapperOptions options) { - return Duration.class; - } - - @Override - public <T> JdbcLiteralFormatter<T> getJdbcLiteralFormatter(JavaType<T> javaType) { - return (appender, value, dialect, wrapperOptions) -> dialect.appendIntervalLiteral( - appender, - javaType.unwrap( - value, - Duration.class, - wrapperOptions - ) - ); - } -} From e938fda07f9543d3adea8913427158dfd78ebd0e Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Tue, 11 Feb 2025 17:52:35 +0530 Subject: [PATCH 47/81] HHH-17404- Remove DURATION mapping to INTERVALDS (2) --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 1e893633a285..00d379ebcad6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -856,7 +856,6 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR } // We need the DDL type during runtime to produce the proper encoding in certain functions ddlTypeRegistry.addDescriptor( new DdlTypeImpl( BIT, "number(1,0)", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( oracle.jdbc.OracleTypes.INTERVALDS, "interval day to second", this ) ); } @@ -889,8 +888,6 @@ public JdbcType resolveSqlTypeDescriptor( switch ( jdbcTypeCode ) { case OracleTypes.JSON: return jdbcTypeRegistry.getDescriptor( JSON ); - case oracle.jdbc.OracleTypes.INTERVALDS: - return jdbcTypeRegistry.getDescriptor( DURATION ); case STRUCT: if ( "MDSYS.SDO_GEOMETRY".equals( columnTypeName ) ) { jdbcTypeCode = GEOMETRY; From 0182f5d1d83ae9a2c3cf338cb6f4be6b716bca34 Mon Sep 17 00:00:00 2001 From: Bidyadhar Mohanty <bidyadhar.mohanty@oracle.com> Date: Tue, 11 Feb 2025 20:41:59 +0530 Subject: [PATCH 48/81] HHH-17404: Undo Test Skips --- .../org/hibernate/orm/test/mapping/embeddable/Aggregate.java | 4 ---- .../test/mapping/embeddable/NestedStructEmbeddableTest.java | 1 - .../embeddable/NestedStructWithArrayEmbeddableTest.java | 1 - .../test/mapping/embeddable/StructEmbeddableArrayTest.java | 1 - 4 files changed, 7 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java index c0d229411aa8..c761c25904af 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/Aggregate.java @@ -149,10 +149,6 @@ public void setConvertedGender(EntityOfBasics.Gender convertedGender) { this.convertedGender = convertedGender; } - public Boolean getTheBoolean() { - return theBoolean; - } - @Column(name = "ordinal_gender") public EntityOfBasics.Gender getOrdinalGender() { return ordinalGender; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java index 3ea0197f0107..32014e193fb6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructEmbeddableTest.java @@ -79,7 +79,6 @@ @RequiresDialect( PostgreSQLDialect.class ) @RequiresDialect( OracleDialect.class ) @RequiresDialect( DB2Dialect.class ) -@SkipForDialect(dialectClass = OracleDialect.class, reason = "Waiting for the fix of a bug that prevent creation of INTERVALDS from Duration") public class NestedStructEmbeddableTest implements AdditionalMappingContributor { @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java index acef9d18de66..bd9b0bcdde3e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/NestedStructWithArrayEmbeddableTest.java @@ -56,7 +56,6 @@ @RequiresDialect( PostgreSQLDialect.class ) @RequiresDialect( OracleDialect.class ) -@SkipForDialect(dialectClass = OracleDialect.class, reason = "Waiting for the fix of a bug that prevent creation of INTERVALDS from Duration") @BootstrapServiceRegistry( // Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete integrators = SharedDriverManagerTypeCacheClearingIntegrator.class diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java index 8eb9b5fb2abe..28847fee8230 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/StructEmbeddableArrayTest.java @@ -83,7 +83,6 @@ @SessionFactory @RequiresDialect( PostgreSQLDialect.class ) @RequiresDialect( OracleDialect.class ) -@SkipForDialect(dialectClass = OracleDialect.class, reason = "Waiting for the fix of a bug that prevent creation of INTERVALDS from Duration") public class StructEmbeddableArrayTest implements AdditionalMappingContributor { @Override From 1f947db186c3509addd1c9bfbf2da13d3b8609c6 Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Tue, 18 Feb 2025 10:22:35 +0100 Subject: [PATCH 49/81] HHH-17404 : applied eview comment --- .../type/descriptor/jdbc/JsonHelper.java | 25 ++++++++++++++++- .../type/format/OsonDocumentReader.java | 3 +-- .../type/format/OsonDocumentWriter.java | 27 ++++++++----------- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index a98536091697..64e3e3b6167b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -72,8 +72,31 @@ public static void serializeArray(MappingType elementMappingType, Object[] value if (value == null) { writer.nullValue(); } - else { + else if ( elementMappingType instanceof EmbeddableMappingType ) { JsonHelper.serialize( (EmbeddableMappingType) elementMappingType, value, options, writer ); + } else if ( elementMappingType instanceof BasicType<?> ) { + //noinspection unchecked + final BasicType<Object> basicType = (BasicType<Object>) elementMappingType; + + if ( isArrayType(basicType.getJdbcType())) { + final int length = Array.getLength( value ); + if ( length != 0 ) { + //noinspection unchecked + final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) basicType.getJdbcJavaType() ).getElementJavaType(); + final JdbcType elementJdbcType = ( (ArrayJdbcType) basicType.getJdbcType() ).getElementJdbcType(); + final Object domainArray = basicType.convertToRelationalValue( value ); + for ( int j = 0; j < length; j++ ) { + writer.serializeJsonValue(Array.get(domainArray,j), elementJavaType, elementJdbcType, options); + } + } + } + else { + writer.serializeJsonValue(basicType.convertToRelationalValue( value), + (JavaType<Object>)basicType.getJdbcJavaType(),basicType.getJdbcType(), options); + } + } + else { + throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + elementMappingType.getClass().getName() ); } } catch (IOException e) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java index b14bdc7f3702..750a3b884f27 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java @@ -100,9 +100,8 @@ public JsonDocumentItemType next() { currentValue = this.parser.getBytes(); return JsonDocumentItemType.VALUE; default : - assert false:"Unknown OSON event"; + throw new IllegalStateException( "Unknown OSON event: " + evt ); } - return null; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index e3be47512647..89a7a3da8867 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -22,9 +22,7 @@ import java.sql.Time; import java.sql.Timestamp; import java.time.Duration; -import java.time.Instant; import java.time.OffsetDateTime; -import java.time.ZoneOffset; import java.util.UUID; /** @@ -108,7 +106,14 @@ public JsonDocumentWriter stringValue(String value) { @Override public JsonDocumentWriter numberValue(Number value) { - this.generator.write((BigDecimal) value ); + if (value instanceof BigDecimal) { + this.generator.write((BigDecimal) value ); + } else if (value instanceof BigInteger) { + this.generator.write((BigInteger) value ); + } else { + //fallback. + this.generator.write( value.longValue() ); + } return this; } @@ -214,17 +219,8 @@ private void serializeValue(Object value, } break; case SqlTypes.TIMESTAMP_UTC: - if( value instanceof OffsetDateTime ) { - OffsetDateTime odt = javaType.unwrap( value, OffsetDateTime.class, options ); - generator.write( odt ); - break; - } - else if (value instanceof Instant ) { - Instant instant = javaType.unwrap( value, Instant.class, options ); - generator.write(instant.atOffset( ZoneOffset.UTC ) ); - break; - } - generator.write( javaType.unwrap( value,String.class,options ) ); + OffsetDateTime odt = javaType.unwrap( value, OffsetDateTime.class, options ); + generator.write( odt ); break; case SqlTypes.NUMERIC: case SqlTypes.DECIMAL: @@ -251,8 +247,7 @@ else if (value instanceof Instant ) { break; case SqlTypes.ARRAY: case SqlTypes.JSON_ARRAY: - assert false:"array case should be treated at upper level"; - break; + throw new IllegalStateException( "array case should be treated at upper level" ); default: throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + jdbcType ); } From fd04e1cdb89f859334bf3cb34bd2d6567306c17b Mon Sep 17 00:00:00 2001 From: Emmanuel Jannetti <emmanuel.jannetti@oracle.com> Date: Thu, 13 Mar 2025 19:35:29 +0100 Subject: [PATCH 50/81] HHH-17404 : applied review comments. removed use of valueIsAString --- .../type/format/OsonDocumentReader.java | 23 +++++++++---------- .../type/format/OsonDocumentWriter.java | 4 +--- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java index 750a3b884f27..6a7a2a7e2841 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java @@ -27,7 +27,7 @@ public class OsonDocumentReader implements JsonDocumentReader { final private OracleJsonParser parser; private String currentKeyName; private Object currentValue; - private boolean currentValueIsAString; // avoid later introspection + /** * Creates a new <code>OsonDocumentReader</code> on top of a <code>OracleJsonParser</code> * @param parser the parser @@ -48,7 +48,6 @@ public JsonDocumentItemType next() { OracleJsonParser.Event evt = this.parser.next(); currentKeyName = null; currentValue = null; - currentValueIsAString = false; switch (evt) { case OracleJsonParser.Event.START_OBJECT: return JsonDocumentItemType.OBJECT_START; @@ -76,7 +75,6 @@ public JsonDocumentItemType next() { return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_STRING: currentValue = this.parser.getString(); - currentValueIsAString = true; return JsonDocumentItemType.VALUE; case OracleJsonParser.Event.VALUE_TRUE: currentValue = Boolean.TRUE; @@ -128,43 +126,44 @@ public BigInteger getBigIntegerValue() { @Override public double getDoubleValue() { - if (currentValueIsAString) return Double.parseDouble( (String)currentValue ); + if (currentValue instanceof String) + return Double.parseDouble( (String)currentValue ); return ((Double)currentValue).doubleValue(); } @Override public float getFloatValue() { - if (currentValueIsAString) return Float.parseFloat( (String)currentValue ); + if (currentValue instanceof String) return Float.parseFloat( (String)currentValue ); return ((Float)currentValue).floatValue(); } @Override public long getLongValue() { - if (currentValueIsAString) return Long.parseLong( (String)currentValue ); + if (currentValue instanceof String) return Long.parseLong( (String)currentValue ); return ((BigDecimal)currentValue).longValue(); } @Override public int getIntegerValue() { - if (currentValueIsAString) return Integer.parseInt( (String)currentValue ); + if (currentValue instanceof String) return Integer.parseInt( (String)currentValue ); return ((BigDecimal)currentValue).intValue(); } @Override public short getShortValue() { - if (currentValueIsAString) return Short.parseShort( (String)currentValue ); + if (currentValue instanceof String) return Short.parseShort( (String)currentValue ); return ((BigDecimal)currentValue).shortValue(); } @Override public byte getByteValue() { - if (currentValueIsAString) return Byte.parseByte( (String)currentValue ); + if (currentValue instanceof String) return Byte.parseByte( (String)currentValue ); return ((Byte)currentValue).byteValue(); } @Override public boolean getBooleanValue() { - if (currentValueIsAString) return BooleanJavaType.INSTANCE.fromEncodedString((String)currentValue); + if (currentValue instanceof String) return BooleanJavaType.INSTANCE.fromEncodedString((String)currentValue); return ((Boolean)currentValue).booleanValue(); } @@ -172,7 +171,7 @@ public boolean getBooleanValue() { @Override public <T> T getValue(JavaType<T> javaType, WrapperOptions options) { - if ( currentValueIsAString ) { + if ( currentValue instanceof String ) { if (javaType.equals(PrimitiveByteArrayJavaType.INSTANCE)) { // be sure that we have only allowed characters. // that may happen for string representation of UUID (i.e 53886a8a-7082-4879-b430-25cb94415be8) for instance @@ -183,7 +182,7 @@ public <T> T getValue(JavaType<T> javaType, WrapperOptions options) { Object theOneToBeUsed = currentValue; // handle special cases for Date things - if ( currentValue.getClass() == LocalDateTime.class ) { + if ( currentValue instanceof LocalDateTime ) { if ( java.sql.Date.class.isAssignableFrom( javaType.getJavaTypeClass() ) ) { theOneToBeUsed = Date.valueOf( ((LocalDateTime)currentValue).toLocalDate() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index 89a7a3da8867..37ba46e26f15 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -14,7 +14,6 @@ import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.UUIDJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; import java.math.BigDecimal; @@ -23,7 +22,6 @@ import java.sql.Timestamp; import java.time.Duration; import java.time.OffsetDateTime; -import java.util.UUID; /** * Implementation of <code>JsonDocumentWriter</code> for OSON document. @@ -233,7 +231,7 @@ private void serializeValue(Object value, generator.write( duration ); break; case SqlTypes.UUID: - generator.write( UUIDJavaType.INSTANCE.unwrap( (UUID)value, byte[].class, options ) ); + generator.write( javaType.unwrap( value, String.class, options ) ); break; case SqlTypes.BINARY: case SqlTypes.VARBINARY: From 9598bd2fb76014cd8b8422e9a3a436b1c51958b0 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Wed, 26 Mar 2025 08:31:01 +0100 Subject: [PATCH 51/81] HHH-17404 : fix build after merge --- .../boot/internal/SessionFactoryOptionsBuilder.java | 2 +- .../java/org/hibernate/dialect/OracleDialect.java | 2 +- .../dialect/OracleOsonArrayJdbcTypeConstructor.java | 2 +- .../dialect/OracleOsonJacksonArrayJdbcType.java | 4 ++-- .../hibernate/dialect/OracleOsonJacksonJdbcType.java | 12 ++++++------ .../dialect/aggregate/OracleAggregateSupport.java | 2 +- .../hibernate/type/descriptor/jdbc/JsonHelper.java | 2 +- .../hibernate/type/format/JsonDocumentItemType.java | 2 +- .../hibernate/type/format/JsonDocumentReader.java | 2 +- .../type/format/JsonDocumentReaderFactory.java | 2 +- .../hibernate/type/format/JsonDocumentWriter.java | 2 +- .../type/format/JsonValueJDBCTypeAdapter.java | 2 +- .../type/format/JsonValueJDBCTypeAdapterFactory.java | 2 +- .../hibernate/type/format/OsonDocumentReader.java | 2 +- .../hibernate/type/format/OsonDocumentWriter.java | 2 +- .../type/format/OsonValueJDBCTypeAdapter.java | 2 +- .../hibernate/type/format/StringJsonDocument.java | 2 +- .../type/format/StringJsonDocumentMarker.java | 2 +- .../type/format/StringJsonDocumentReader.java | 2 +- .../type/format/StringJsonDocumentWriter.java | 2 +- .../type/format/StringJsonValueJDBCTypeAdapter.java | 4 ++-- .../type/format/jackson/JacksonOsonFormatMapper.java | 2 +- .../orm/test/util/StringJsonDocumentReaderTest.java | 2 +- .../orm/test/util/StringJsonDocumentWriterTest.java | 2 +- 24 files changed, 31 insertions(+), 31 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 75d07ae139f4..2684a90f168d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -789,7 +789,7 @@ private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( .getDefaultConnectionHandlingMode(); } - private static FormatMapper determineJsonFormatMapper(Object setting, StrategySelector strategySelector, ConfigurationService configurationService) { + private static FormatMapper determineJsonFormatMapper(Object setting, StrategySelector strategySelector) { return strategySelector.resolveDefaultableStrategy( FormatMapper.class, setting, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 00d379ebcad6..054e9ccb4e0d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -92,7 +92,6 @@ import org.hibernate.type.NullType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; -import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.NullJdbcType; import org.hibernate.type.descriptor.jdbc.ObjectNullAsNullTypeJdbcType; @@ -1019,6 +1018,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ); } + final String mapperName = configurationService.getSetting( "hibernate.type.json_format_mapper", StandardConverters.STRING,JACKSON_MAPPER_NAME); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java index b91c6a86f931..806d9f98d8a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.dialect; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index 282798f062aa..b123fc26988f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.dialect; @@ -133,7 +133,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { - FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + FormatMapper mapper = options.getJsonFormatMapper(); JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); JsonParser osonParser = osonFactory.createParser( osonBytes ); return mapper.readFromSource( getJavaType(), osonParser, options); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index eabb8cf716c5..7922a665c83f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.dialect; @@ -85,7 +85,7 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { - FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + FormatMapper mapper = options.getJsonFormatMapper(); if (getEmbeddableMappingType() != null) { ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -96,7 +96,7 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) } return out.toByteArray(); } - + ByteArrayOutputStream out = new ByteArrayOutputStream(); JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); try (JsonGenerator osonGen = osonFactory.createGenerator( out )) { @@ -140,7 +140,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { - FormatMapper mapper = options.getSession().getSessionFactory().getFastSessionServices().getJsonFormatMapper(); + FormatMapper mapper = options.getJsonFormatMapper(); if (getEmbeddableMappingType() != null && getJavaType().getJavaTypeClass() == Object[].class) { @@ -185,8 +185,8 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - return doExtraction(ojd,options); + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction(ojd,options); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index 82e89624a3a9..a36edcf8f1b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -90,7 +90,7 @@ public static AggregateSupport valueOf(Dialect dialect, boolean useDateStoredAsS default -> version.isSameOrAfter( 23 ) ? useDateStoredAsString?OracleAggregateSupport.V23_INSTANCE: OracleAggregateSupport.V23_OSON_EXT_INSTANCE - : OracleAggregateSupport.LEGACY_INSTANCE; + : OracleAggregateSupport.LEGACY_INSTANCE; }; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 64e3e3b6167b..173acab3d86b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -17,8 +17,8 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; - import org.hibernate.Internal; +import org.hibernate.internal.build.AllowReflection; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.mapping.EmbeddableMappingType; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java index a2256446f95d..1c57718619e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentItemType.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java index f97b51e3f474..f276e8d69aad 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReader.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java index 3dcbed17f1b8..92bf2350b236 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java index 5d92084ab01a..71b66cf9c9e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java index d1c85b0bc500..dbee32e4ed2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java index d1336a84cd7c..92cf62eb5697 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapterFactory.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java index 6a7a2a7e2841..dd7405d8fb0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index 37ba46e26f15..cfa9841bcb0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java index 5d8a38831fd0..d94af51b883d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java index fe9059644220..6eda9b3ce7e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java index d017529f8439..4aefcbb3f6dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentMarker.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index 68aa98df47d8..2f749a08fa7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java index 0bcbc44a71c3..b537c05c7cc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java index 7f6f614869fd..90c41e3d58eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format; @@ -133,7 +133,7 @@ else if ( jdbcJavaType instanceof EnumJavaType<?> ) { options ); final EmbeddableMappingType embeddableMappingType = aggregateJdbcType.getEmbeddableMappingType(); - return instantiate( embeddableMappingType, subAttributeValues, options.getSessionFactory() ) ; + return instantiate( embeddableMappingType, subAttributeValues ) ; } return subValues; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 883179173648..f6cc9fbe62c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.type.format.jackson; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java index a614b63efb29..2371a39ffab3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.orm.test.util; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java index 1e7a3207fc99..0c4981b610eb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java @@ -1,5 +1,5 @@ /* - * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ package org.hibernate.orm.test.util; From 941c8e7835f94a2f9d3bbfa40aba4b0ace22670c Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Thu, 27 Mar 2025 00:17:42 +0100 Subject: [PATCH 52/81] HHH-17404 : refactor StringJsonDocumentReader to remove usage of CharBuffer --- .../cfg/DialectSpecificSettings.java | 15 + .../org/hibernate/dialect/OracleDialect.java | 10 +- .../OracleOsonJacksonArrayJdbcType.java | 17 +- .../dialect/OracleOsonJacksonJdbcType.java | 81 +++++- .../dialect/OracleServerConfiguration.java | 17 +- .../hibernate/internal/CoreMessageLogger.java | 7 + .../type/descriptor/jdbc/JsonHelper.java | 16 +- .../type/descriptor/jdbc/JsonJdbcType.java | 5 +- .../type/format/AbstractJsonFormatMapper.java | 20 -- .../hibernate/type/format/FormatMapper.java | 16 +- .../format/JsonDocumentReaderFactory.java | 33 --- .../type/format/JsonDocumentWriter.java | 7 - .../type/format/JsonValueJDBCTypeAdapter.java | 1 + .../type/format/OsonDocumentReader.java | 3 +- .../type/format/OsonDocumentWriter.java | 14 - .../type/format/StringJsonDocument.java | 4 +- .../type/format/StringJsonDocumentReader.java | 265 ++++++++++-------- .../type/format/StringJsonDocumentWriter.java | 48 ++-- .../jackson/JacksonXmlFormatMapper.java | 21 -- .../util/StringJsonDocumentWriterTest.java | 38 ++- 20 files changed, 333 insertions(+), 305 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java index d7dca4889247..8e9db591048c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java @@ -52,6 +52,21 @@ public interface DialectSpecificSettings { */ String ORACLE_USE_BINARY_FLOATS = "hibernate.dialect.oracle.use_binary_floats"; + /** + * Specifies whether usage of the Oracle JSON binary format (aka OSON) should be disabled. + * <p> + * Starting to 21c, if the ojdbc-provider-jackson-oson extension is available. JSON data in an oracle + * database are stored using the OSON binary format. This setting can be used to fallback to the old implementation + * based on String serialization. + * + * @settingDefault {@code false} + * @since 7.0 + * + * @see <a href="https://docs.oracle.com/en/database/oracle/oracle-database/23/adjsn/json-oracle-database.html">Orace OSON format</a> + * @see <a href="https://github.com/oracle/ojdbc-extensions/blob/main/ojdbc-provider-jackson-oson/README.md">Jackson OSON provider</a> + */ + String ORACLE_OSON_DISABLED = "hibernate.dialect.oracle.oson_format_disabled"; + /** * Specifies whether the {@code ansinull} setting is enabled on Sybase. * <p> diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 054e9ccb4e0d..54ece231e171 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -221,6 +221,10 @@ protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggr // Is the database accessed using a database service protected by Application Continuity. protected final boolean applicationContinuity; + + // Is the database OSON format should be disabled. + protected final boolean isOracleOsonDisabled; + protected final int driverMajorVersion; protected final int driverMinorVersion; private boolean useBinaryFloat; @@ -234,6 +238,7 @@ public OracleDialect(DatabaseVersion version) { autonomous = false; extended = false; applicationContinuity = false; + isOracleOsonDisabled = false; driverMajorVersion = 19; driverMinorVersion = 0; } @@ -247,6 +252,7 @@ public OracleDialect(DialectResolutionInfo info, OracleServerConfiguration confi autonomous = configuration.isAutonomous(); extended = configuration.isExtended(); applicationContinuity = configuration.isApplicationContinuity(); + isOracleOsonDisabled = configuration.isOSONEnabled(); driverMinorVersion = configuration.getDriverMinorVersion(); driverMajorVersion = configuration.getDriverMajorVersion(); } @@ -263,6 +269,8 @@ public boolean isApplicationContinuity() { return applicationContinuity; } + public boolean isOracleOsonDisabled() {return isOracleOsonDisabled;} + @Override protected DatabaseVersion getMinimumSupportedVersion() { return MINIMUM_VERSION; @@ -1024,7 +1032,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry if ( getVersion().isSameOrAfter( 21 ) ) { - if ( JacksonIntegration.isOracleOsonExtensionAvailable() && JACKSON_MAPPER_NAME.equalsIgnoreCase( mapperName )) { + if ( !isOracleOsonDisabled() && JacksonIntegration.isOracleOsonExtensionAvailable() && JACKSON_MAPPER_NAME.equalsIgnoreCase( mapperName )) { // We must check that that extension is available and actually used. typeContributions.contributeJdbcType( OracleOsonJacksonJdbcType.INSTANCE ); typeContributions.contributeJdbcTypeConstructor( OracleOsonArrayJdbcTypeConstructor.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index b123fc26988f..be465fab4378 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -45,17 +45,17 @@ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { - private static final Class osonFactoryKlass; - + private static final Object osonFactory; static { try { - osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); + Class osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); + osonFactory = osonFactoryKlass.getDeclaredConstructor().newInstance(); } - catch (ClassNotFoundException | LinkageError e) { - // should not happen as OracleOsonJacksonArrayJdbcType is loaded - // only when an Oracle OSON JDBC extension is present + catch (Exception | LinkageError e) { + // should not happen as OracleOsonJacksonJdbcType is loaded + // only when Oracle OSON JDBC extension is present // see OracleDialect class. - throw new ExceptionInInitializerError( "OracleOsonJacksonArrayJdbcType class loaded without OSON extension: " + e.getClass()+ " " + e.getMessage()); + throw new ExceptionInInitializerError( "OracleOsonJacksonJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); } } @@ -134,8 +134,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getJsonFormatMapper(); - JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); - JsonParser osonParser = osonFactory.createParser( osonBytes ); + JsonParser osonParser = ((JsonFactory)osonFactory).createParser( osonBytes ); return mapper.readFromSource( getJavaType(), osonParser, options); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 7922a665c83f..55f6dfd66f5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -8,10 +8,13 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; +import oracle.jdbc.driver.DatabaseError; import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; import oracle.sql.json.OracleJsonParser; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.type.descriptor.ValueBinder; @@ -22,9 +25,11 @@ import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.OsonDocumentReader; import org.hibernate.type.format.OsonDocumentWriter; import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.sql.CallableStatement; @@ -43,12 +48,15 @@ public class OracleOsonJacksonJdbcType extends OracleJsonJdbcType { public static final OracleOsonJacksonJdbcType INSTANCE = new OracleOsonJacksonJdbcType( null ); - private static final Class osonFactoryKlass; + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJacksonJdbcType.class ); + + private static final Object osonFactory; static { try { - osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); + Class osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); + osonFactory = osonFactoryKlass.getDeclaredConstructor().newInstance(); } - catch (ClassNotFoundException | LinkageError e) { + catch (Exception | LinkageError e) { // should not happen as OracleOsonJacksonJdbcType is loaded // only when Oracle OSON JDBC extension is present // see OracleDialect class. @@ -98,8 +106,7 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) } ByteArrayOutputStream out = new ByteArrayOutputStream(); - JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); - try (JsonGenerator osonGen = osonFactory.createGenerator( out )) { + try (JsonGenerator osonGen = ((JsonFactory)osonFactory).createGenerator( out )) { mapper.writeToTarget( value, javaType, osonGen, options ); } return out.toByteArray(); @@ -151,7 +158,7 @@ private X fromOson(InputStream osonBytes, WrapperOptions options) throws Excepti OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( osonBytes ); Object[] objects = JsonHelper.deserialize( getEmbeddableMappingType(), - osonParser, + new OsonDocumentReader(osonParser), javaType.getJavaTypeClass() != Object[].class, options ); @@ -164,12 +171,23 @@ private X fromOson(InputStream osonBytes, WrapperOptions options) throws Excepti type = (JavaType<X>) getEmbeddableMappingType().getJavaType(); } - JsonFactory osonFactory = (JsonFactory) osonFactoryKlass.getDeclaredConstructor().newInstance(); - try (JsonParser osonParser = osonFactory.createParser( osonBytes )) { + try (JsonParser osonParser = ((JsonFactory)osonFactory).createParser( osonBytes )) { return mapper.readFromSource( type, osonParser, options ); } } + private X doExtraction(byte[] bytes, WrapperOptions options) throws SQLException { + if ( bytes == null ) { + return null; + } + + try { + return fromOson( new ByteArrayInputStream(bytes) ,options); + } + catch (Exception e) { + throw new SQLException( e ); + } + } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { if ( datum == null ) { return null; @@ -185,22 +203,55 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - return doExtraction(ojd,options); - + try { + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction(ojd,options); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // this may happen if we are fetching data from an existing schema + // that use CBLOB for JSON column + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return doExtraction(rs.getBytes( paramIndex ), options); + } else { + throw exc; + } + } } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); - return doExtraction(ojd,options); + try { + OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); + return doExtraction(ojd,options); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // this may happen if we are fetching data from an existing schema + // that use CBLOB for JSON column + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return doExtraction(statement.getBytes( index ), options); + } else { + throw exc; + } + } } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); - return doExtraction(ojd,options); + try { + OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); + return doExtraction(ojd,options); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // this may happen if we are fetching data from an existing schema + // that use CBLOB for JSON column + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return doExtraction(statement.getBytes( name ), options); + } else { + throw exc; + } + } + } }; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java index e0f748e8add5..c2f41088fe2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java @@ -20,6 +20,7 @@ import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_APPLICATION_CONTINUITY; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE; +import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; /** @@ -33,6 +34,7 @@ public class OracleServerConfiguration { private final boolean autonomous; private final boolean extended; private final boolean applicationContinuity; + private final boolean osonDisabled; private final int driverMajorVersion; private final int driverMinorVersion; @@ -48,6 +50,10 @@ public boolean isApplicationContinuity() { return applicationContinuity; } + public boolean isOSONEnabled() { + return osonDisabled; + } + public int getDriverMajorVersion() { return driverMajorVersion; } @@ -57,7 +63,7 @@ public int getDriverMinorVersion() { } public OracleServerConfiguration(boolean autonomous, boolean extended) { - this( autonomous, extended, false, 19, 0 ); + this( autonomous, extended, false, false, 19, 0 ); } public OracleServerConfiguration( @@ -65,18 +71,20 @@ public OracleServerConfiguration( boolean extended, int driverMajorVersion, int driverMinorVersion) { - this( autonomous, extended, false, driverMajorVersion, driverMinorVersion ); + this( autonomous, extended, false, false, driverMajorVersion, driverMinorVersion ); } public OracleServerConfiguration( boolean autonomous, boolean extended, boolean applicationContinuity, + boolean osonDisabled, int driverMajorVersion, int driverMinorVersion) { this.autonomous = autonomous; this.extended = extended; this.applicationContinuity = applicationContinuity; + this.osonDisabled = osonDisabled; this.driverMajorVersion = driverMajorVersion; this.driverMinorVersion = driverMinorVersion; } @@ -88,10 +96,13 @@ public static OracleServerConfiguration fromDialectResolutionInfo(DialectResolut final boolean defaultExtended = getBoolean( ORACLE_EXTENDED_STRING_SIZE, configuration, false ); final boolean defaultAutonomous = getBoolean( ORACLE_AUTONOMOUS_DATABASE, configuration, false ); final boolean defaultContinuity = getBoolean( ORACLE_APPLICATION_CONTINUITY, configuration, false ); + final boolean defaultOsonDisabled = getBoolean( ORACLE_OSON_DISABLED , configuration, false ); boolean extended; boolean autonomous; boolean applicationContinuity; + boolean osonDisabled = defaultOsonDisabled; + int majorVersion; int minorVersion; final DatabaseMetaData databaseMetaData = info.getDatabaseMetadata(); @@ -127,7 +138,7 @@ public static OracleServerConfiguration fromDialectResolutionInfo(DialectResolut } } - return new OracleServerConfiguration( autonomous, extended, applicationContinuity, majorVersion, minorVersion ); + return new OracleServerConfiguration( autonomous, extended, applicationContinuity, osonDisabled,majorVersion, minorVersion ); } private static boolean isExtended(Statement statement) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index f4e02ec60ee9..e4c891cec5c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -760,6 +760,13 @@ void unableToLocateStaticMetamodelField( ) void duplicatedPersistenceUnitName(String name); + @LogMessage(level = WARN) + @Message( + id = 15019, + value = "Invalid JSON column type [%s], was expecting [%s]; for efficiency schema should be migrate to JSON DDL type" + ) + void invalidJSONColumnType(String actual, String expected); + @LogMessage(level = DEBUG) @Message( id = 455, diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 173acab3d86b..98c6a7b3b3e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -38,12 +38,12 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.format.JsonDocumentItemType; import org.hibernate.type.format.JsonDocumentReader; -import org.hibernate.type.format.JsonDocumentReaderFactory; import org.hibernate.type.format.JsonDocumentWriter; import static org.hibernate.type.descriptor.jdbc.StructHelper.getEmbeddedPart; import static org.hibernate.type.descriptor.jdbc.StructHelper.instantiate; import org.hibernate.type.format.JsonValueJDBCTypeAdapter; import org.hibernate.type.format.JsonValueJDBCTypeAdapterFactory; +import org.hibernate.type.format.StringJsonDocumentReader; /** * A Helper for serializing and deserializing JSON, based on an {@link org.hibernate.metamodel.mapping.EmbeddableMappingType}. @@ -395,7 +395,7 @@ private static <X> X consumeJsonDocumentItems(JsonDocumentReader reader, Embedda /** * Deserialize a JSON value to Java Object * @param embeddableMappingType the mapping type - * @param source the JSON value + * @param reader the JSON reader * @param returnEmbeddable do we return an Embeddable object or array of Objects * @param options wrappping options * @return the deserialized value @@ -404,14 +404,10 @@ private static <X> X consumeJsonDocumentItems(JsonDocumentReader reader, Embedda */ public static <X> X deserialize( EmbeddableMappingType embeddableMappingType, - Object source, + JsonDocumentReader reader, boolean returnEmbeddable, WrapperOptions options) throws SQLException { - if ( source == null ) { - return null; - } - JsonDocumentReader reader = JsonDocumentReaderFactory.getJsonDocumentReader(source); final Object[] values = consumeJsonDocumentItems(reader, embeddableMappingType, returnEmbeddable, options); if ( returnEmbeddable ) { @@ -448,7 +444,7 @@ public static <X> X arrayFromString( else { jdbcJavaType = options.getTypeConfiguration().getJavaTypeRegistry().resolveDescriptor( preferredJavaTypeClass ); } - JsonDocumentReader reader = JsonDocumentReaderFactory.getJsonDocumentReader(string); + JsonDocumentReader reader = new StringJsonDocumentReader(string); JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,false); assert reader.hasNext():"Invalid array string"; @@ -457,7 +453,7 @@ public static <X> X arrayFromString( while(reader.hasNext()) { JsonDocumentItemType type = reader.next(); switch ( type ) { - case JsonDocumentItemType.ARRAY_END: + case ARRAY_END: endArrayFound=true; break; case NULL_VALUE: @@ -473,7 +469,7 @@ public static <X> X arrayFromString( arrayList.add( adapter.fromValue(jdbcJavaType, elementJdbcType ,reader, options) ); break; default: - assert false : "Unexpected type " + type; + throw new UnsupportedOperationException( "Unexpected JSON type " + type ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java index ebc6c3b1b3e0..c927c262f38a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java @@ -17,6 +17,7 @@ import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.StringJsonDocumentReader; import org.hibernate.type.format.StringJsonDocumentWriter; /** @@ -78,7 +79,7 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o if ( embeddableMappingType != null ) { return (X) JsonHelper.deserialize( embeddableMappingType, - string, + new StringJsonDocumentReader(string), javaType.getJavaTypeClass() != Object[].class, options ); @@ -103,7 +104,7 @@ public Object createJdbcValue(Object domainValue, WrapperOptions options) throws @Override public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) throws SQLException { assert embeddableMappingType != null; - return JsonHelper.deserialize( embeddableMappingType, (String) rawJdbcValue, false, options ); + return JsonHelper.deserialize( embeddableMappingType, new StringJsonDocumentReader( (String)rawJdbcValue ), false, options ); } protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java index c6cd1a0b5819..eceee2682a1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/AbstractJsonFormatMapper.java @@ -7,7 +7,6 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; -import java.io.IOException; import java.lang.reflect.Type; /** @@ -38,23 +37,4 @@ public final <T> String toString(T value, JavaType<T> javaType, WrapperOptions w protected abstract <T> String toString(T value, Type type); - @Override - public boolean supportsSourceType(Class<?> sourceType) { - return CharSequence.class.isAssignableFrom(sourceType); - } - - @Override - public boolean supportsTargetType(Class<?> targetType) { - return String.class.isAssignableFrom( targetType ); - } - - @Override - public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) - throws IOException { - } - - @Override - public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - return null; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java index bff9726cb0b5..e2f324b6719d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/FormatMapper.java @@ -49,16 +49,24 @@ public interface FormatMapper { * @param sourceType the source type * @return <code>true</code> if the type is supported, false otherwise. */ - boolean supportsSourceType(Class<?> sourceType); + default boolean supportsSourceType(Class<?> sourceType) { + return false; + }; /** * Checks that this mapper supports a type as a target type. * @param targetType the target type * @return <code>true</code> if the type is supported, false otherwise. */ - boolean supportsTargetType(Class<?> targetType); + default boolean supportsTargetType(Class<?> targetType) { + return false; + } - <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException; + default <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { + throw new UnsupportedOperationException( "Unsupportd target type " + target.getClass() ); + }; - <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException; + default <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { + throw new UnsupportedOperationException( "Unsupportd source type " + source.getClass() ); + }; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java deleted file mode 100644 index 92bf2350b236..000000000000 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentReaderFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.type.format; - -import oracle.sql.json.OracleJsonParser; - -/** - * Factory class to get proper <code>JsonDocumentReader</code>. - * - * @author Emmanuel Jannetti - */ -public class JsonDocumentReaderFactory { - /** - * Gets a <code>JsonDocumentReader</code> appropriate to a given source. - * Source can be a <code>String</code> or a <code>OracleJsonParser</code> instance - * @param jsonSource the document source - * @return the reader - */ - public static JsonDocumentReader getJsonDocumentReader(Object jsonSource) { - assert jsonSource != null : "jsonSource is null"; - - if (jsonSource instanceof String) { - return new StringJsonDocumentReader( (String)jsonSource ); - } - if (jsonSource instanceof OracleJsonParser ) { - return new OsonDocumentReader( (OracleJsonParser)jsonSource ); - } - - throw new IllegalArgumentException("Unsupported type of JSON source " + jsonSource.getClass()); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java index 71b66cf9c9e3..93cef54dac51 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java @@ -75,13 +75,6 @@ public interface JsonDocumentWriter { */ JsonDocumentWriter stringValue(String value); - /** - * Adds a new JSON element Number value. - * @return this instance - * @param value the element Number name. - */ - JsonDocumentWriter numberValue(Number value); - /** * Adds a JSON value to the document * @param value the value to be serialized diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java index dbee32e4ed2f..7f292bc0e16c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonValueJDBCTypeAdapter.java @@ -12,6 +12,7 @@ /** * Adapter for JSON value on given JDBC types. + * @author emmanuel Jannetti */ public interface JsonValueJDBCTypeAdapter { /** diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java index dd7405d8fb0e..bf01bfd1fd55 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java @@ -21,6 +21,7 @@ /** * OSON-based implementation of <code>JsonDocumentReader</code> + * @author Emmanuel Jannetti */ public class OsonDocumentReader implements JsonDocumentReader { @@ -172,7 +173,7 @@ public boolean getBooleanValue() { @Override public <T> T getValue(JavaType<T> javaType, WrapperOptions options) { if ( currentValue instanceof String ) { - if (javaType.equals(PrimitiveByteArrayJavaType.INSTANCE)) { + if (((String)currentValue).length() == 36 && javaType == PrimitiveByteArrayJavaType.INSTANCE) { // be sure that we have only allowed characters. // that may happen for string representation of UUID (i.e 53886a8a-7082-4879-b430-25cb94415be8) for instance return javaType.fromEncodedString( (((String) currentValue).replaceAll( "-","" )) ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index cfa9841bcb0a..804fd0c48f0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -101,20 +101,6 @@ public JsonDocumentWriter stringValue(String value) { return this; } - - @Override - public JsonDocumentWriter numberValue(Number value) { - if (value instanceof BigDecimal) { - this.generator.write((BigDecimal) value ); - } else if (value instanceof BigInteger) { - this.generator.write((BigInteger) value ); - } else { - //fallback. - this.generator.write( value.longValue() ); - } - return this; - } - @Override public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { serializeValue(value, javaType, jdbcType, options); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java index 6eda9b3ce7e6..74efb38c60d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocument.java @@ -16,7 +16,7 @@ public abstract class StringJsonDocument { * When processing objects, values are stored as [,]"key":"value"[,]. we add separator when adding new key * When processing arrays, values are stored as [,]"value"[,]. we add separator when adding new value */ - enum PROCESSING_STATE { + enum JsonProcessingState { NONE, STARTING_OBJECT, // object started but no value added OBJECT_KEY_NAME, // We are processing an object key name @@ -27,7 +27,7 @@ enum PROCESSING_STATE { ARRAY // we are piling array values } // Stack of current processing states - protected StandardStack<PROCESSING_STATE> processingStates = new StandardStack<>(); + protected final StandardStack<JsonProcessingState> processingStates = new StandardStack<>(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index 2f749a08fa7f..f9fdc85d5d74 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -5,20 +5,31 @@ package org.hibernate.type.format; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BigDecimalJavaType; +import org.hibernate.type.descriptor.java.BigIntegerJavaType; +import org.hibernate.type.descriptor.java.BooleanJavaType; +import org.hibernate.type.descriptor.java.ByteJavaType; +import org.hibernate.type.descriptor.java.DoubleJavaType; +import org.hibernate.type.descriptor.java.FloatJavaType; +import org.hibernate.type.descriptor.java.IntegerJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.LongJavaType; +import org.hibernate.type.descriptor.java.ShortJavaType; import java.math.BigDecimal; import java.math.BigInteger; -import java.nio.CharBuffer; import java.util.NoSuchElementException; /** * Implementation of <code>JsonDocumentReader</code> for String representation of JSON objects. */ -public class StringJsonDocumentReader extends StringJsonDocument implements JsonDocumentReader { +public class StringJsonDocumentReader extends StringJsonDocument implements JsonDocumentReader { - private final CharBuffer json; - private final CharBuffer jsonValueWindow; + private final String jsonString; + private final int limit; + private int position; + private int jsonValueStart; + private int jsonValueEnd; /** * Creates a new <code>StringJsonDocumentReader</code> @@ -28,28 +39,29 @@ public StringJsonDocumentReader(String json) { if (json == null) { throw new IllegalArgumentException( "json cannot be null" ); } - this.json = CharBuffer.wrap( json.toCharArray() ).asReadOnlyBuffer(); - this.jsonValueWindow = this.json.slice(); + this.jsonString = json; + this.position = 0; + this.limit = jsonString.length(); + this.jsonValueStart = 0; + this.jsonValueEnd = 0; } @Override public boolean hasNext() { - // enough for now. - return this.json.hasRemaining(); + return this.position < this.limit; } private void skipWhiteSpace() { - for (int i = this.json.position(); i < this.json.limit(); i++ ) { - if (!Character.isWhitespace( this.json.get(i))) { - this.json.position(i); + for (;this.position < this.limit; this.position++ ) { + if (!Character.isWhitespace( this.jsonString.charAt(this.position))) { return; } } } private void resetValueWindow() { - this.jsonValueWindow.position(0); - this.jsonValueWindow.limit( 0); + this.jsonValueStart = 0; + this.jsonValueEnd = 0; } /** @@ -58,61 +70,61 @@ private void resetValueWindow() { * @param marker the marker we just read */ private void moveStateMachine(StringJsonDocumentMarker marker) { - StringJsonDocument.PROCESSING_STATE currentState = this.processingStates.getCurrent(); + JsonProcessingState currentState = this.processingStates.getCurrent(); switch (marker) { case OBJECT_START: - if (currentState == PROCESSING_STATE.STARTING_ARRAY ) { + if ( currentState == JsonProcessingState.STARTING_ARRAY ) { // move the state machine to ARRAY as we are adding something to it - this.processingStates.push(PROCESSING_STATE.ARRAY); + this.processingStates.push( JsonProcessingState.ARRAY); } - this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); + this.processingStates.push( JsonProcessingState.STARTING_OBJECT ); break; case OBJECT_END: - assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT || - this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_OBJECT; - if (this.processingStates.pop() == PROCESSING_STATE.OBJECT) { - assert this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_OBJECT; + assert this.processingStates.getCurrent() == JsonProcessingState.OBJECT || + this.processingStates.getCurrent() == JsonProcessingState.STARTING_OBJECT; + if ( this.processingStates.pop() == JsonProcessingState.OBJECT) { + assert this.processingStates.getCurrent() == JsonProcessingState.STARTING_OBJECT; this.processingStates.pop(); } break; case ARRAY_START: - this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); + this.processingStates.push( JsonProcessingState.STARTING_ARRAY ); break; case ARRAY_END: - assert this.processingStates.getCurrent() == PROCESSING_STATE.ARRAY || - this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_ARRAY; - if (this.processingStates.pop() == PROCESSING_STATE.ARRAY) { - assert this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_ARRAY; + assert this.processingStates.getCurrent() == JsonProcessingState.ARRAY || + this.processingStates.getCurrent() == JsonProcessingState.STARTING_ARRAY; + if ( this.processingStates.pop() == JsonProcessingState.ARRAY) { + assert this.processingStates.getCurrent() == JsonProcessingState.STARTING_ARRAY; this.processingStates.pop(); } break; case SEPARATOR: // While processing an object, following SEPARATOR that will a key - if (currentState == PROCESSING_STATE.OBJECT) { - this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); + if ( currentState == JsonProcessingState.OBJECT) { + this.processingStates.push( JsonProcessingState.OBJECT_KEY_NAME ); } break; case KEY_VALUE_SEPARATOR: // that's the start of an attribute value - assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT_KEY_NAME; + assert this.processingStates.getCurrent() == JsonProcessingState.OBJECT_KEY_NAME; // flush the OBJECT_KEY_NAME this.processingStates.pop(); - assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT; + assert this.processingStates.getCurrent() == JsonProcessingState.OBJECT; break; case QUOTE: switch ( currentState ) { - case PROCESSING_STATE.STARTING_ARRAY: - this.processingStates.push( PROCESSING_STATE.ARRAY ); + case JsonProcessingState.STARTING_ARRAY: + this.processingStates.push( JsonProcessingState.ARRAY ); break; - case PROCESSING_STATE.STARTING_OBJECT: - this.processingStates.push( PROCESSING_STATE.OBJECT ); - this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); + case JsonProcessingState.STARTING_OBJECT: + this.processingStates.push( JsonProcessingState.OBJECT ); + this.processingStates.push( JsonProcessingState.OBJECT_KEY_NAME ); break; } break; case OTHER: - if (currentState == PROCESSING_STATE.STARTING_ARRAY) { - this.processingStates.push( PROCESSING_STATE.ARRAY ); + if ( currentState == JsonProcessingState.STARTING_ARRAY) { + this.processingStates.push( JsonProcessingState.ARRAY ); } break; } @@ -131,11 +143,10 @@ public JsonDocumentItemType next() { while (hasNext()) { skipWhiteSpace(); - StringJsonDocumentMarker marker = StringJsonDocumentMarker.markerOf( this.json.get() ); + StringJsonDocumentMarker marker = StringJsonDocumentMarker.markerOf( this.jsonString.charAt( this.position++ ) ); moveStateMachine( marker ); switch ( marker) { case OBJECT_START: - //this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); resetValueWindow(); return JsonDocumentItemType.OBJECT_START; case OBJECT_END: @@ -144,7 +155,7 @@ public JsonDocumentItemType next() { return JsonDocumentItemType.OBJECT_END; case ARRAY_START: resetValueWindow(); - //this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); + //this.processingStates.push( JsonProcessingState.STARTING_ARRAY ); return JsonDocumentItemType.ARRAY_START; case ARRAY_END: resetValueWindow(); @@ -162,33 +173,30 @@ public JsonDocumentItemType next() { // - if we just hit ':' that's a quoted value // - if we just hit ',' that's a quoted key switch ( this.processingStates.getCurrent() ) { - case PROCESSING_STATE.STARTING_ARRAY: - //this.processingStates.push( PROCESSING_STATE.ARRAY ); + case JsonProcessingState.STARTING_ARRAY: + //this.processingStates.push( JsonProcessingState.ARRAY ); return JsonDocumentItemType.VALUE; - case PROCESSING_STATE.ARRAY: + case JsonProcessingState.ARRAY: return JsonDocumentItemType.VALUE; - case PROCESSING_STATE.STARTING_OBJECT: - //this.processingStates.push( PROCESSING_STATE.OBJECT ); - //this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); + case JsonProcessingState.STARTING_OBJECT: + //this.processingStates.push( JsonProcessingState.OBJECT ); + //this.processingStates.push( JsonProcessingState.OBJECT_KEY_NAME ); return JsonDocumentItemType.VALUE_KEY; - case PROCESSING_STATE.OBJECT: // we are processing object attribute value elements + case JsonProcessingState.OBJECT: // we are processing object attribute value elements return JsonDocumentItemType.VALUE; - case PROCESSING_STATE.OBJECT_KEY_NAME: // we are processing object elements key + case JsonProcessingState.OBJECT_KEY_NAME: // we are processing object elements key return JsonDocumentItemType.VALUE_KEY; default: throw new IllegalStateException( "unexpected quote read in current processing state " + this.processingStates.getCurrent() ); } case KEY_VALUE_SEPARATOR: // that's the start of an attribute value - //assert this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT_KEY_NAME; + //assert this.processingStates.getCurrent() == JsonProcessingState.OBJECT_KEY_NAME; // flush the OBJECT_KEY_NAME //this.processingStates.pop(); break; case SEPARATOR: // unless we are processing an array, following SEPARATOR that will a key -// if (this.processingStates.getCurrent() == PROCESSING_STATE.OBJECT) { -// this.processingStates.push( PROCESSING_STATE.OBJECT_KEY_NAME ); -// } break; case OTHER: // here we are in front of a boolean, a null or a numeric value. @@ -198,15 +206,15 @@ public JsonDocumentItemType next() { final int valueSize = consumeNonStringValue(); if (valueSize == -1) { throw new IllegalStateException( "Unrecognized marker: " + StringJsonDocumentMarker.markerOf( - json.get( this.json.position() ))); + this.jsonString.charAt( this.position ))); } switch ( this.processingStates.getCurrent() ) { - case PROCESSING_STATE.ARRAY: - case PROCESSING_STATE.OBJECT: - return getUnquotedValueType(this.jsonValueWindow); + case JsonProcessingState.ARRAY: + case JsonProcessingState.OBJECT: + return getUnquotedValueType(this.jsonString.charAt( this.jsonValueStart)); default: throw new IllegalStateException( "unexpected read ["+ - this.jsonValueWindow.toString()+ + this.jsonString.substring( this.jsonValueStart,this.jsonValueEnd )+ "] in current processing state " + this.processingStates.getCurrent() ); } @@ -220,12 +228,11 @@ public JsonDocumentItemType next() { * Gets the type of unquoted value. * We assume that the String value follows JSON specification. I.e unquoted value that starts with 't' can't be anything else * than <code>true</code> - * @param jsonValueWindow the value + * @param jsonValueChar the value * @return the type of the value */ - private JsonDocumentItemType getUnquotedValueType(CharBuffer jsonValueWindow) { - final int size = jsonValueWindow.remaining(); - switch(jsonValueWindow.charAt( 0 )) { + private JsonDocumentItemType getUnquotedValueType(char jsonValueChar) { + switch(jsonValueChar) { case 't': { //true return JsonDocumentItemType.BOOLEAN_VALUE; @@ -257,40 +264,48 @@ private JsonDocumentItemType getUnquotedValueType(CharBuffer jsonValueWindow) { } private void moveBufferPosition(int shift) { - this.json.position(this.json.position() + shift); + this.position += shift; } /** - * Moves the current position to a given character. + * Moves the current position to a given character, skipping any whitespace + * We expect the character to be the next non-blank character in the sequence * @param character the character we should stop at. * @throws IllegalStateException if we encounter an unexpected character other than white spaces before the desired one. */ private void moveTo(char character) throws IllegalStateException { - this.json.mark(); - while ( this.json.hasRemaining()) { - char c = this.json.get(); + int pointer = this.position; + while ( pointer < this.limit) { + char c = this.jsonString.charAt( pointer ); if ( c == character) { - this.json.reset(); + this.position = pointer == this.position?this.position:pointer - 1; return; } if (!Character.isWhitespace(c)) { // we did find an unexpected character // let the exception raise - this.json.reset(); + //this.json.reset(); break; } + pointer++; } - throw new IllegalStateException("Can't find character: " + character); + throw new IllegalStateException("character [" + character + "] is not the next non-blank character"); } + /** + * Goes through the json string to locate a character. + * @param character character to be found + * @param escape character to be found + * @return the position of the character or -1 if not found. + */ private int locateCharacter(char character, char escape) { assert character != escape; - this.json.mark(); - int found = -1; + int pointer = this.position; + boolean escapeIsOn = false; - while ( this.json.hasRemaining()) { - final char c = this.json.get(); + while ( pointer< this.limit) { + final char c = this.jsonString.charAt( pointer ); if (c == escape) { escapeIsOn = true; } @@ -300,14 +315,14 @@ private int locateCharacter(char character, char escape) { escapeIsOn = false; } else { - found = this.json.position() - 1; - break; + // found + return pointer; } } } + pointer++; } - this.json.reset(); - return found; + return -1; } /** @@ -317,8 +332,8 @@ private int locateCharacter(char character, char escape) { private int consumeNonStringValue() { int newViewLimit = 0; boolean allGood = false; - for (int i = this.json.position(); i < this.json.limit(); i++ ) { - char c = this.json.get(i); + for (int i = this.position; i < this.limit; i++ ) { + char c = this.jsonString.charAt(i); if ((StringJsonDocumentMarker.markerOf( c ) != StringJsonDocumentMarker.OTHER) || Character.isWhitespace( c )) { // hit a JSON marker or a space. @@ -330,12 +345,16 @@ private int consumeNonStringValue() { } if (allGood) { - this.jsonValueWindow.limit(newViewLimit); - this.jsonValueWindow.position( this.json.position() ); - this.json.position(newViewLimit); + this.jsonValueEnd = newViewLimit; + this.jsonValueStart = position; + this.position = newViewLimit; } - return allGood?(this.jsonValueWindow.remaining()):-1; + return allGood?(this.jsonValueEnd-this.jsonValueStart):-1; } + /** + * Consume a quotted value + * @return the length of this value. can be 0, -1 in case of error + */ private void consumeQuottedString() { // be sure we are at a meaningful place @@ -343,7 +362,7 @@ private void consumeQuottedString() { moveTo( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); // skip the quote we are positioned on. - this.json.get(); + this.position++; //locate ending quote int endingQuote = locateCharacter( StringJsonDocumentMarker.QUOTE.getMarkerCharacter(), '\\'); @@ -351,40 +370,50 @@ private void consumeQuottedString() { throw new IllegalStateException("Can't find ending quote of key name"); } - this.jsonValueWindow.limit( endingQuote ); - this.jsonValueWindow.position(this.json.position()); - this.json.position( endingQuote + 1); + this.jsonValueEnd = endingQuote; + this.jsonValueStart = position; + + this.position = endingQuote + 1; } + /** + * Ensures that the current state is on value. + * @throws IllegalStateException if not on "value" state + */ private void ensureValueState() throws IllegalStateException { - if ((this.processingStates.getCurrent() != PROCESSING_STATE.OBJECT ) && - this.processingStates.getCurrent() != PROCESSING_STATE.ARRAY) { + if ( (this.processingStates.getCurrent() != JsonProcessingState.OBJECT ) && + this.processingStates.getCurrent() != JsonProcessingState.ARRAY) { throw new IllegalStateException( "unexpected processing state: " + this.processingStates.getCurrent() ); } } + /** + * Ensures that we have a value ready to be exposed. i.e we just consume one. + * @throws IllegalStateException if no value available + */ private void ensureAvailableValue() throws IllegalStateException { - if (this.jsonValueWindow.limit() == 0 ) { + if (this.jsonValueEnd == 0 ) { throw new IllegalStateException( "No available value"); } } @Override public String getObjectKeyName() { - if (this.processingStates.getCurrent() != PROCESSING_STATE.OBJECT_KEY_NAME ) { + if ( this.processingStates.getCurrent() != JsonProcessingState.OBJECT_KEY_NAME ) { throw new IllegalStateException( "unexpected processing state: " + this.processingStates.getCurrent() ); } ensureAvailableValue(); - return this.jsonValueWindow.toString(); + return this.jsonString.substring( this.jsonValueStart, this.jsonValueEnd); } + @Override public String getStringValue() { ensureValueState(); ensureAvailableValue(); - if (hasEscape(this.jsonValueWindow)) { - return unescape(this.jsonValueWindow); + if ( currentValueHasEscape()) { + return unescape(this.jsonString, this.jsonValueStart , this.jsonValueEnd); } - return this.jsonValueWindow.toString(); + return this.jsonString.substring( this.jsonValueStart, this.jsonValueEnd); } @@ -392,79 +421,92 @@ public String getStringValue() { public BigDecimal getBigDecimalValue() { ensureValueState(); ensureAvailableValue(); - return BigDecimal.valueOf( Long.valueOf(this.jsonValueWindow.toString()) ); + return BigDecimalJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public BigInteger getBigIntegerValue() { ensureValueState(); ensureAvailableValue(); - return BigInteger.valueOf( Long.valueOf(this.jsonValueWindow.toString()) ); + return BigIntegerJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public double getDoubleValue() { ensureValueState(); ensureAvailableValue(); - return Double.valueOf(this.jsonValueWindow.toString()).doubleValue(); + return DoubleJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public float getFloatValue() { ensureValueState(); ensureAvailableValue(); - return Float.valueOf(this.jsonValueWindow.toString()).floatValue(); + return FloatJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public long getLongValue() { ensureValueState(); ensureAvailableValue(); - return Long.valueOf(this.jsonValueWindow.toString()).longValue(); + return LongJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public int getIntegerValue() { ensureValueState(); ensureAvailableValue(); - return Integer.valueOf(this.jsonValueWindow.toString()).intValue(); + return IntegerJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public short getShortValue() { ensureValueState(); ensureAvailableValue(); - return Short.valueOf(this.jsonValueWindow.toString()).shortValue(); + return ShortJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public byte getByteValue() { ensureValueState(); ensureAvailableValue(); - return Byte.valueOf(this.jsonValueWindow.toString()).byteValue(); + return ByteJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public boolean getBooleanValue() { ensureValueState(); ensureAvailableValue(); - return Boolean.parseBoolean( this.jsonValueWindow.toString() ); + return BooleanJavaType.INSTANCE.fromEncodedString( this.jsonString,this.jsonValueStart,this.jsonValueEnd ); } @Override public <T> T getValue(JavaType<T> javaType, WrapperOptions options) { - return javaType.fromEncodedString( this.jsonValueWindow.toString() ); + return javaType.fromEncodedString( this.jsonString.subSequence( this.jsonValueStart,this.jsonValueEnd )); } - private boolean hasEscape(CharBuffer jsonValueWindow) { - for (int i = 0;i<jsonValueWindow.remaining();i++) { - if (jsonValueWindow.charAt( i ) == '\\') return true; + /** + * Walks through JSON value currently located on JSON string and check if escape is used + * + * @return <code>true</code> if escape is found + */ + private boolean currentValueHasEscape() { + for (int i = this.jsonValueStart; i<this.jsonValueEnd; i++) { + if (this.jsonString.charAt( i ) == '\\') return true; } return false; } - private String unescape(CharBuffer string) { - final StringBuilder sb = new StringBuilder( string.remaining() ); - for ( int i = 0; i < string.length(); i++ ) { + + /** + * Returns unescaped string + * @param string the string to be unescaped + * @param start the begin index within the string + * @param end the end index within the string + * @return the unescaped string + */ + private static String unescape(String string, int start, int end) { + final StringBuilder sb = new StringBuilder( end - start ); + for ( int i = start; i < end; i++ ) { final char c = string.charAt( i ); if ( c == '\\' ) { i++; @@ -502,4 +544,5 @@ private String unescape(CharBuffer string) { return sb.toString(); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java index b537c05c7cc5..c4e0aae2c8b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -36,7 +36,7 @@ public class StringJsonDocumentWriter extends StringJsonDocument implements Json * @param appender the appender to receive the serialze JSON object */ public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { - this.processingStates.push( PROCESSING_STATE.NONE ); + this.processingStates.push( JsonProcessingState.NONE ); this.appender = appender; } @@ -46,20 +46,20 @@ public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { @Override public JsonDocumentWriter startObject() { // Note: startArray and startObject must not call moveProcessingStateMachine() - if (this.processingStates.getCurrent() == PROCESSING_STATE.STARTING_ARRAY) { + if ( this.processingStates.getCurrent() == JsonProcessingState.STARTING_ARRAY) { // are we building an array of objects? // i.e, [{},...] - // move to PROCESSING_STATE.ARRAY first - this.processingStates.push( PROCESSING_STATE.ARRAY); + // move to JsonProcessingState.ARRAY first + this.processingStates.push( JsonProcessingState.ARRAY); } - else if (this.processingStates.getCurrent() == PROCESSING_STATE.ARRAY) { + else if ( this.processingStates.getCurrent() == JsonProcessingState.ARRAY) { // That means that we ae building an array of object ([{},...]) // JSON object hee are treat as array item. // -> add the marker first this.appender.append(StringJsonDocumentMarker.SEPARATOR.getMarkerCharacter()); } this.appender.append( StringJsonDocumentMarker.OBJECT_START.getMarkerCharacter()); - this.processingStates.push( PROCESSING_STATE.STARTING_OBJECT ); + this.processingStates.push( JsonProcessingState.STARTING_OBJECT ); return this; } @@ -69,7 +69,7 @@ else if (this.processingStates.getCurrent() == PROCESSING_STATE.ARRAY) { @Override public JsonDocumentWriter endObject() { this.appender.append( StringJsonDocumentMarker.OBJECT_END.getMarkerCharacter() ); - this.processingStates.push( PROCESSING_STATE.ENDING_OBJECT); + this.processingStates.push( JsonProcessingState.ENDING_OBJECT); moveProcessingStateMachine(); return this; } @@ -79,7 +79,7 @@ public JsonDocumentWriter endObject() { */ @Override public JsonDocumentWriter startArray() { - this.processingStates.push( PROCESSING_STATE.STARTING_ARRAY ); + this.processingStates.push( JsonProcessingState.STARTING_ARRAY ); // Note: startArray and startObject do not call moveProcessingStateMachine() this.appender.append( StringJsonDocumentMarker.ARRAY_START.getMarkerCharacter() ); return this; @@ -91,7 +91,7 @@ public JsonDocumentWriter startArray() { @Override public JsonDocumentWriter endArray() { this.appender.append( StringJsonDocumentMarker.ARRAY_END.getMarkerCharacter() ); - this.processingStates.push( PROCESSING_STATE.ENDING_ARRAY); + this.processingStates.push( JsonProcessingState.ENDING_ARRAY); moveProcessingStateMachine(); return this; } @@ -104,7 +104,7 @@ public JsonDocumentWriter objectKey(String key) { throw new IllegalArgumentException( "key cannot be null or empty" ); } - if (this.processingStates.getCurrent().equals( PROCESSING_STATE.OBJECT )) { + if (this.processingStates.getCurrent().equals( JsonProcessingState.OBJECT )) { // we have started an object, and we are adding an item key: we do add a separator. this.appender.append( StringJsonDocumentMarker.SEPARATOR.getMarkerCharacter() ); } @@ -122,7 +122,7 @@ public JsonDocumentWriter objectKey(String key) { * Separator is to separate array items or key/value pairs in an object. */ private void addItemsSeparator() { - if (this.processingStates.getCurrent().equals( PROCESSING_STATE.ARRAY )) { + if (this.processingStates.getCurrent().equals( JsonProcessingState.ARRAY )) { // We started to serialize an array and already added item to it:add a separator anytime. this.appender.append( StringJsonDocumentMarker.SEPARATOR.getMarkerCharacter() ); } @@ -154,11 +154,11 @@ private void moveProcessingStateMachine() { switch (this.processingStates.getCurrent()) { case STARTING_OBJECT: //after starting an object, we start adding key/value pairs - this.processingStates.push( PROCESSING_STATE.OBJECT ); + this.processingStates.push( JsonProcessingState.OBJECT ); break; case STARTING_ARRAY: //after starting an array, we start adding value to it - this.processingStates.push( PROCESSING_STATE.ARRAY ); + this.processingStates.push( JsonProcessingState.ARRAY ); break; case ENDING_ARRAY: // when ending an array, we have one or two states. @@ -167,9 +167,9 @@ private void moveProcessingStateMachine() { // first pop ENDING_ARRAY this.processingStates.pop(); // if we have ARRAY, so that's not an empty array. pop that state - if (this.processingStates.getCurrent().equals( PROCESSING_STATE.ARRAY )) + if (this.processingStates.getCurrent().equals( JsonProcessingState.ARRAY )) this.processingStates.pop(); - assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_ARRAY ); + assert this.processingStates.pop().equals( JsonProcessingState.STARTING_ARRAY ); break; case ENDING_OBJECT: // when ending an object, we have one or two states. @@ -178,9 +178,9 @@ private void moveProcessingStateMachine() { // first pop ENDING_OBJECT this.processingStates.pop(); // if we have OBJECT, so that's not an empty object. pop that state - if (this.processingStates.getCurrent().equals( PROCESSING_STATE.OBJECT )) + if (this.processingStates.getCurrent().equals( JsonProcessingState.OBJECT )) this.processingStates.pop(); - assert this.processingStates.pop().equals( PROCESSING_STATE.STARTING_OBJECT ); + assert this.processingStates.pop().equals( JsonProcessingState.STARTING_OBJECT ); break; default: //nothing to do for the other ones. @@ -217,18 +217,6 @@ public JsonDocumentWriter stringValue(String value) { return this; } - @Override - public JsonDocumentWriter numberValue(Number value) { - if (value == null ) { - throw new IllegalArgumentException( "value cannot be null" ); - } - addItemsSeparator(); - this.appender.append( value.toString() ); - moveProcessingStateMachine(); - return this; - } - - @Override public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { addItemsSeparator(); @@ -360,7 +348,7 @@ private void convertedBasicValueToString( case SqlTypes.ARRAY: case SqlTypes.JSON_ARRAY: // Caller handles this. We should never end up here actually. - break; + throw new IllegalStateException("unexpected JSON array type"); default: throw new UnsupportedOperationException( "Unsupported JdbcType nested in JSON: " + jdbcType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java index 8b70c7138d98..a7c84e6c694c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonXmlFormatMapper.java @@ -204,27 +204,6 @@ else if ( javaType.getJavaTypeClass().isArray() ) { return writeValueAsString( value, javaType, javaType.getJavaType() ); } - @Override - public boolean supportsSourceType(Class<?> sourceType) { - return CharSequence.class.isAssignableFrom(sourceType); - } - - @Override - public boolean supportsTargetType(Class<?> targetType) { - return String.class.isAssignableFrom( targetType ); - } - - @Override - public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) - throws IOException { - target = toString(value, javaType, options); - } - - @Override - public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - return fromString((CharSequence) source, javaType, options); - } - private <T> String writeValueAsString(Object value, JavaType<T> javaType, Type type) { try { return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java index 0c4981b610eb..d890c9215b15 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java @@ -42,11 +42,11 @@ public void testArray() { StringBuilder sb = new StringBuilder(); StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); writer.startArray(); - writer.numberValue( Integer.valueOf( 1 ) ); - writer.numberValue( Integer.valueOf( 2 ) ); - writer.numberValue( Integer.valueOf( 3 ) ); + writer.booleanValue( false ); + writer.booleanValue( true ); + writer.booleanValue( false ); writer.endArray(); - assertEquals( "[1,2,3]" , writer.toString() ); + assertEquals( "[false,true,false]" , writer.toString() ); } @Test @@ -54,13 +54,12 @@ public void testMixedArrayDocument() { StringBuilder sb = new StringBuilder(); StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); writer.startArray(); - writer.numberValue( Integer.valueOf( 1 ) ); writer.nullValue(); writer.booleanValue( false ); writer.stringValue( "foo" ); writer.endArray(); assertEqualsIgnoreSpace( """ - [1,null,false,"foo"] + [null,false,"foo"] """ , writer.toString() ); } @Test @@ -88,8 +87,6 @@ public void testNonStringValueDocument() { writer.startObject(); writer.objectKey( "aNull" ); writer.nullValue(); - writer.objectKey( "aNumber" ); - writer.numberValue( Integer.valueOf( 12 ) ); writer.objectKey( "aBoolean" ); writer.booleanValue( true ); writer.endObject(); @@ -97,8 +94,7 @@ public void testNonStringValueDocument() { assertEqualsIgnoreSpace( """ { "aNull":null, - "aNumber" : 12 , - "aBoolean" : true + "aBoolean" : true } """ , writer.toString() ); @@ -115,16 +111,16 @@ public void testArrayValueDocument() { writer.endArray(); writer.objectKey( "anArray" ); writer.startArray(); - writer.numberValue( Integer.valueOf( 1 ) ); - writer.numberValue( Integer.valueOf( 2 ) ); - writer.numberValue( Integer.valueOf( 3 ) ); + writer.stringValue( "1" ); + writer.stringValue( "2" ); + writer.stringValue( "3" ); writer.endArray(); writer.endObject(); assertEqualsIgnoreSpace( """ { "anEmptyArray" : [], - "anArray" : [1,2,3] + "anArray" : ["1","2","3"] } """, writer.toString() ); } @@ -133,12 +129,12 @@ public void testObjectArrayMultipleValueDocument() { StringBuilder sb = new StringBuilder(); StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); writer.startObject(); - writer.objectKey( "anArray" ).startArray().numberValue( Integer.valueOf( 1 ) ).nullValue().stringValue( "2" ).startObject() + writer.objectKey( "anArray" ).startArray().nullValue().stringValue( "2" ).startObject() .objectKey( "foo" ).stringValue( "bar" ).endObject().endArray().endObject(); assertEqualsIgnoreSpace( """ { - "anArray" : [1, null, "2" , {\"foo\":\"bar\"} ] + "anArray" : [null, "2" , {\"foo\":\"bar\"} ] } """ , sb.toString() ); @@ -149,21 +145,20 @@ public void testNestedDocument() { StringBuilder sb = new StringBuilder(); StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); writer.startObject().objectKey( "nested" ).startObject() - .objectKey( "converted_gender" ).stringValue( "M" ).objectKey( "theInteger" ).numberValue( Integer.valueOf( -1 ) ).endObject() + .objectKey( "converted_gender" ).stringValue( "M" ) + .endObject() .objectKey( "doubleNested" ).startObject() .objectKey( "theNested" ).startObject() .objectKey( "theLeaf" ) .startObject().objectKey( "stringField" ).stringValue( "String \"<abc>A&B</abc>\"" ).endObject() .endObject() .endObject() - .objectKey( "integerField" ).numberValue( Integer.valueOf( 10 ) ) .endObject(); assertEqualsIgnoreSpace( """ { "nested": { - "converted_gender": "M", - "theInteger": -1 + "converted_gender": "M" }, "doubleNested": { "theNested": { @@ -171,8 +166,7 @@ public void testNestedDocument() { "stringField": "String \\"<abc>A&B</abc>\\"" } } - }, - "integerField": 10 + } } """,writer.toString()); From 8015c4923464a7b06c405472acd64477d755377c Mon Sep 17 00:00:00 2001 From: BidyadharM <bidyadhar.mohanty@oracle.com> Date: Thu, 27 Mar 2025 13:49:23 +0530 Subject: [PATCH 53/81] HHH-17404 : Address review comment to remove try-catch for SqlTypes.TIMESTAMP_WITH_TIMEZONE. --- .../hibernate/type/format/OsonDocumentWriter.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index 804fd0c48f0e..fcc6cb09038f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -191,16 +191,8 @@ private void serializeValue(Object value, generator.write(writeTimeStamp); break; case SqlTypes.TIMESTAMP_WITH_TIMEZONE: - try { - OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options ); - generator.write( dateTime ); - } - catch (Exception e) { - Timestamp tswtz = javaType.unwrap( value, Timestamp.class, options ); - TIMESTAMP TSWTZ = new TIMESTAMP(tswtz); - OracleJsonTimestamp writeTimeStampWTZ = new OracleJsonTimestampImpl(TSWTZ.shareBytes()); - generator.write(writeTimeStampWTZ); - } + OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options ); + generator.write( dateTime ); break; case SqlTypes.TIMESTAMP_UTC: OffsetDateTime odt = javaType.unwrap( value, OffsetDateTime.class, options ); From 32cfdfc728d310aa5d0570d5b5a24201ec11461b Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Thu, 27 Mar 2025 19:33:29 +0100 Subject: [PATCH 54/81] HHH-17404 add flag to disable OSON extension --- .../cfg/DialectSpecificSettings.java | 6 +- .../type/descriptor/jdbc/JsonHelper.java | 99 ++++++++----------- .../type/format/StringJsonDocumentReader.java | 34 ++++--- .../jackson/JacksonJsonFormatMapper.java | 28 ------ .../resolver/DialectSpecificConfigTest.java | 23 +++++ 5 files changed, 86 insertions(+), 104 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java index 8e9db591048c..114faabf6303 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java @@ -53,10 +53,10 @@ public interface DialectSpecificSettings { String ORACLE_USE_BINARY_FLOATS = "hibernate.dialect.oracle.use_binary_floats"; /** - * Specifies whether usage of the Oracle JSON binary format (aka OSON) should be disabled. + * Specifies whether usage of the Oracle JSON binary format (also known as OSON) should be disabled. * <p> - * Starting to 21c, if the ojdbc-provider-jackson-oson extension is available. JSON data in an oracle - * database are stored using the OSON binary format. This setting can be used to fallback to the old implementation + * Starting in 21c, if the ojdbc-provider-jackson-oson extension is available, JSON data in an oracle + * database is stored using the OSON binary format. This setting can be used to fallback to the old implementation * based on String serialization. * * @settingDefault {@code false} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 98c6a7b3b3e9..5859173f76df 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -69,39 +69,10 @@ public static void serializeArray(MappingType elementMappingType, Object[] value } for ( Object value : values ) { try { - if (value == null) { - writer.nullValue(); - } - else if ( elementMappingType instanceof EmbeddableMappingType ) { - JsonHelper.serialize( (EmbeddableMappingType) elementMappingType, value, options, writer ); - } else if ( elementMappingType instanceof BasicType<?> ) { - //noinspection unchecked - final BasicType<Object> basicType = (BasicType<Object>) elementMappingType; - - if ( isArrayType(basicType.getJdbcType())) { - final int length = Array.getLength( value ); - if ( length != 0 ) { - //noinspection unchecked - final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) basicType.getJdbcJavaType() ).getElementJavaType(); - final JdbcType elementJdbcType = ( (ArrayJdbcType) basicType.getJdbcType() ).getElementJdbcType(); - final Object domainArray = basicType.convertToRelationalValue( value ); - for ( int j = 0; j < length; j++ ) { - writer.serializeJsonValue(Array.get(domainArray,j), elementJavaType, elementJdbcType, options); - } - } - } - else { - writer.serializeJsonValue(basicType.convertToRelationalValue( value), - (JavaType<Object>)basicType.getJdbcJavaType(),basicType.getJdbcType(), options); - } - } - else { - throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + elementMappingType.getClass().getName() ); - } + serialize(elementMappingType, value, options, writer); } catch (IOException e) { - // TODO : do better than this - throw new RuntimeException( e ); + throw new IllegalArgumentException( "Could not serialize JSON array value" , e ); } } writer.endArray(); @@ -158,6 +129,42 @@ public static void serialize(EmbeddableMappingType embeddableMappingType, writer.endObject(); } + private static void serialize(MappingType mappedType, Object value, WrapperOptions options, JsonDocumentWriter writer) + throws IOException { + if ( value == null ) { + writer.nullValue(); + } + else if ( mappedType instanceof EmbeddableMappingType ) { + serialize( (EmbeddableMappingType) mappedType, value, options, writer ); + } + else if ( mappedType instanceof BasicType<?> ) { + //noinspection unchecked + final BasicType<Object> basicType = (BasicType<Object>) mappedType; + + if ( isArrayType(basicType.getJdbcType())) { + final int length = Array.getLength( value ); + writer.startArray(); + if ( length != 0 ) { + //noinspection unchecked + final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) basicType.getJdbcJavaType() ).getElementJavaType(); + final JdbcType elementJdbcType = ( (ArrayJdbcType) basicType.getJdbcType() ).getElementJdbcType(); + final Object domainArray = basicType.convertToRelationalValue( value ); + for ( int j = 0; j < length; j++ ) { + writer.serializeJsonValue(Array.get(domainArray,j), elementJavaType, elementJdbcType, options); + } + } + writer.endArray(); + } + else { + writer.serializeJsonValue(basicType.convertToRelationalValue( value), + (JavaType<Object>)basicType.getJdbcJavaType(),basicType.getJdbcType(), options); + } + } + else { + throw new UnsupportedOperationException( "Support for mapping type not yet implemented: " + mappedType.getClass().getName() ); + } + } + /** * JSON object attirbute serialization * @see #serialize(EmbeddableMappingType, Object, WrapperOptions, JsonDocumentWriter) @@ -175,40 +182,18 @@ private static void serializeMapping(EmbeddableMappingType embeddableMappingType if ( attributeMapping instanceof SelectableMapping ) { final String name = ( (SelectableMapping) attributeMapping ).getSelectableName(); writer.objectKey( name ); - if (values[i] == null) { - writer.nullValue(); - } - else if (attributeMapping.getMappedType() instanceof BasicType<?>) { - final BasicType<Object> basicType = (BasicType<Object>) attributeMapping.getMappedType(); - if ( isArrayType(basicType.getJdbcType())) { - final int length = Array.getLength( values[i] ); - writer.startArray(); - if ( length != 0 ) { - //noinspection unchecked - final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) basicType.getJdbcJavaType() ).getElementJavaType(); - final JdbcType elementJdbcType = ( (ArrayJdbcType) basicType.getJdbcType() ).getElementJdbcType(); - final Object domainArray = basicType.convertToRelationalValue( values[i] ); - for ( int j = 0; j < length; j++ ) { - writer.serializeJsonValue(Array.get(domainArray,j), elementJavaType, elementJdbcType, options); - } - } - writer.endArray(); - } - else { - writer.serializeJsonValue(basicType.convertToRelationalValue( values[i]), - (JavaType<Object>)basicType.getJdbcJavaType(),basicType.getJdbcType(), options); - } - } - else if ( attributeMapping.getMappedType() instanceof EmbeddableMappingType ) { + + if ( attributeMapping.getMappedType() instanceof EmbeddableMappingType ) { writer.startObject(); serializeMapping( (EmbeddableMappingType)attributeMapping.getMappedType(), values[i], options,writer); writer.endObject(); + } else { + serialize(attributeMapping.getMappedType(), values[i], options, writer); } } else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { if ( values[i] == null ) { - //writer.nullValue(); continue; } final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index f9fdc85d5d74..3f04d6561c82 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -25,6 +25,8 @@ */ public class StringJsonDocumentReader extends StringJsonDocument implements JsonDocumentReader { + private static final char ESCAPE_CHAR = '\\'; + private final String jsonString; private final int limit; private int position; @@ -294,30 +296,30 @@ private void moveTo(char character) throws IllegalStateException { } /** - * Goes through the json string to locate a character. + * Goes through the JSON string to locate a character. + * Escaped characters are taken into account. + * ex: on 'AB\"C"' this method returns 5 (not 3) + * * @param character character to be found - * @param escape character to be found * @return the position of the character or -1 if not found. */ - private int locateCharacter(char character, char escape) { - assert character != escape; + private int locateCharacter(char character) { int pointer = this.position; - boolean escapeIsOn = false; - while ( pointer< this.limit) { + while ( pointer < this.limit) { final char c = this.jsonString.charAt( pointer ); - if (c == escape) { - escapeIsOn = true; + if (c == ESCAPE_CHAR) { + // We encountered an escape character. + // We should just skip the next one as it is either the expected character + // but as escaped one, we should ignore it, either this is something else + // and we should ignore it also + pointer += 2; + continue; } else { if ( c == character ) { - if (escapeIsOn) { - escapeIsOn = false; - } - else { - // found - return pointer; - } + // found + return pointer; } } pointer++; @@ -365,7 +367,7 @@ private void consumeQuottedString() { this.position++; //locate ending quote - int endingQuote = locateCharacter( StringJsonDocumentMarker.QUOTE.getMarkerCharacter(), '\\'); + int endingQuote = locateCharacter( StringJsonDocumentMarker.QUOTE.getMarkerCharacter()); if (endingQuote == -1) { throw new IllegalStateException("Can't find ending quote of key name"); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index 5e9f0f232afd..b71107c0949e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -4,14 +4,11 @@ */ package org.hibernate.type.format.jackson; -import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.format.AbstractJsonFormatMapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; import java.lang.reflect.Type; /** @@ -51,29 +48,4 @@ public <T> String toString(T value, Type type) { throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e ); } } - - @Override - public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) - throws IOException { - - try { - objectMapper.writerFor( - objectMapper.constructType( javaType.getJavaType() ) ).writeValueAsString( value ); - } - catch (JsonProcessingException e) { - throw new IllegalArgumentException( "Could not serialize object of java type: " + javaType.getJavaType(), e ); - } - - } - - @Override - public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - try { - return objectMapper.readValue( ((CharSequence)source).toString(), objectMapper.constructType( javaType.getJavaType() ) ); - } - catch (JsonProcessingException e) { - throw new IllegalArgumentException( "Could not deserialize string to java type: " + javaType.getJavaType(), e ); - } - - } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java index 2100ce863849..5002351bcce1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java @@ -24,6 +24,7 @@ import static org.hibernate.cfg.DialectSpecificSettings.MYSQL_NO_BACKSLASH_ESCAPES; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE; +import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.cfg.DialectSpecificSettings.SYBASE_ANSI_NULL; import static org.hibernate.dialect.DatabaseVersion.NO_VERSION; @@ -55,6 +56,28 @@ public void testOracleIsAutonomous() { assertThat( ( (OracleDialect) dialect ).isAutonomous() ).isTrue(); } + @Test + public void testOracleIsOsonEnabled() { + final Dialect dialect = resolveDialect( + "Oracle", + values -> values.put( "emptyOne", "true" ) + ); + + assertThat( dialect ).isInstanceOf( OracleDialect.class ); + assertThat( ( (OracleDialect) dialect ).isOracleOsonDisabled() ).isFalse(); + } + + @Test + public void testOracleIsOsonDisabled() { + final Dialect dialect = resolveDialect( + "Oracle", + values -> values.put( ORACLE_OSON_DISABLED, "true" ) + ); + + assertThat( dialect ).isInstanceOf( OracleDialect.class ); + assertThat( ( (OracleDialect) dialect ).isOracleOsonDisabled() ).isTrue(); + } + @Test public void testSybaseASEIsAnsiNull() { final Dialect dialect = resolveDialect( From 13f45eb8d703b05b0598dd07680e17c0dc6c5812 Mon Sep 17 00:00:00 2001 From: BidyadharM <bidyadhar.mohanty@oracle.com> Date: Fri, 28 Mar 2025 14:53:07 +0530 Subject: [PATCH 55/81] HHH-17404 : Since thhe UUID is stored as string , retrieve it accordingly. --- .../dialect/aggregate/OracleAggregateSupport.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index a36edcf8f1b7..0eb12dbdef3e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -209,12 +209,10 @@ public String aggregateComponentCustomReadExpression( ); } case UUID: - if (this.dateTypesStoreAsString) { - return template.replace( - placeholder, - "hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))" - ); - } + return template.replace( + placeholder, + "hextoraw(replace(json_value(" + parentPartExpression + columnExpression + "'),'-',''))" + ); case BINARY: case VARBINARY: case LONG32VARBINARY: From 1071b7618f2a4abdb1d6d797a506812ae2883f4a Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Tue, 1 Apr 2025 17:59:39 +0200 Subject: [PATCH 56/81] HHH-17404 remove use of reflection --- .../org/hibernate/dialect/OracleDialect.java | 12 +++- .../OracleOsonJacksonArrayJdbcType.java | 20 +------ .../dialect/OracleOsonJacksonJdbcType.java | 57 +++++++------------ .../dialect/OracleServerConfiguration.java | 16 +----- .../aggregate/OracleAggregateSupport.java | 2 +- .../type/format/StringJsonDocumentReader.java | 15 +++-- .../jackson/JacksonOsonFormatMapper.java | 24 +------- .../resolver/DialectSpecificConfigTest.java | 23 -------- .../src/main/groovy/local.java-module.gradle | 3 + settings.gradle | 2 + 10 files changed, 55 insertions(+), 119 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 54ece231e171..0d1b586b042a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -12,6 +12,7 @@ import org.hibernate.Timeouts; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; +import org.hibernate.cfg.MappingSettings; import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.OracleAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; @@ -105,6 +106,7 @@ import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.format.jackson.JacksonIntegration; +import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.hibernate.type.spi.TypeConfiguration; import java.sql.CallableStatement; @@ -121,6 +123,7 @@ import static java.util.regex.Pattern.CASE_INSENSITIVE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS; +import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.dialect.DialectLogging.DIALECT_MESSAGE_LOGGER; import static org.hibernate.dialect.type.OracleJdbcHelper.getArrayJdbcTypeConstructor; import static org.hibernate.dialect.type.OracleJdbcHelper.getNestedTableJdbcTypeConstructor; @@ -268,9 +271,16 @@ public boolean isExtended() { public boolean isApplicationContinuity() { return applicationContinuity; } - public boolean isOracleOsonDisabled() {return isOracleOsonDisabled;} + private static boolean isJacksonJsonFormatMapper(ConfigurationService configService) { + // Mirror the behavior of SessionFactoryOptionsBuilder#determineJsonFormatMapper + final String mapperName = configService.getSetting( MappingSettings.JSON_FORMAT_MAPPER, + StandardConverters.STRING,JacksonJsonFormatMapper.SHORT_NAME); + return JacksonJsonFormatMapper.SHORT_NAME.equalsIgnoreCase( mapperName ) + || mapperName == null && JacksonIntegration.getJsonJacksonFormatMapperOrNull() != null; + } + @Override protected DatabaseVersion getMinimumSupportedVersion() { return MINIMUM_VERSION; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index be465fab4378..d847e534d771 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -4,9 +4,9 @@ */ package org.hibernate.dialect; -import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; +import oracle.jdbc.provider.oson.OsonFactory; import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; @@ -24,7 +24,6 @@ import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.OsonDocumentWriter; -import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; import java.io.ByteArrayOutputStream; import java.io.InputStream; @@ -45,20 +44,7 @@ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { - private static final Object osonFactory; - static { - try { - Class osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); - osonFactory = osonFactoryKlass.getDeclaredConstructor().newInstance(); - } - catch (Exception | LinkageError e) { - // should not happen as OracleOsonJacksonJdbcType is loaded - // only when Oracle OSON JDBC extension is present - // see OracleDialect class. - throw new ExceptionInInitializerError( "OracleOsonJacksonJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); - } - } - + private static final OsonFactory osonFactory = new OsonFactory(); public OracleOsonJacksonArrayJdbcType(JdbcType elementJdbcType) { super(elementJdbcType); @@ -134,7 +120,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getJsonFormatMapper(); - JsonParser osonParser = ((JsonFactory)osonFactory).createParser( osonBytes ); + JsonParser osonParser = osonFactory.createParser( osonBytes ); return mapper.readFromSource( getJavaType(), osonParser, options); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 55f6dfd66f5b..bfe5b5dfb0e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; import oracle.jdbc.driver.DatabaseError; +import oracle.jdbc.provider.oson.OsonFactory; import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; @@ -27,11 +28,10 @@ import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.OsonDocumentReader; import org.hibernate.type.format.OsonDocumentWriter; -import org.hibernate.type.format.jackson.JacksonOsonFormatMapper; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -50,20 +50,7 @@ public class OracleOsonJacksonJdbcType extends OracleJsonJdbcType { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJacksonJdbcType.class ); - private static final Object osonFactory; - static { - try { - Class osonFactoryKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonFactory" ); - osonFactory = osonFactoryKlass.getDeclaredConstructor().newInstance(); - } - catch (Exception | LinkageError e) { - // should not happen as OracleOsonJacksonJdbcType is loaded - // only when Oracle OSON JDBC extension is present - // see OracleDialect class. - throw new ExceptionInInitializerError( "OracleOsonJacksonJdbcType class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); - } - } - + private static final OsonFactory osonFactory = new OsonFactory(); private OracleOsonJacksonJdbcType(EmbeddableMappingType embeddableMappingType) { super( embeddableMappingType ); @@ -106,7 +93,7 @@ private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) } ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (JsonGenerator osonGen = ((JsonFactory)osonFactory).createGenerator( out )) { + try (JsonGenerator osonGen = osonFactory.createGenerator( out )) { mapper.writeToTarget( value, javaType, osonGen, options ); } return out.toByteArray(); @@ -176,18 +163,6 @@ private X fromOson(InputStream osonBytes, WrapperOptions options) throws Excepti } } - private X doExtraction(byte[] bytes, WrapperOptions options) throws SQLException { - if ( bytes == null ) { - return null; - } - - try { - return fromOson( new ByteArrayInputStream(bytes) ,options); - } - catch (Exception e) { - throw new SQLException( e ); - } - } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { if ( datum == null ) { return null; @@ -209,9 +184,13 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // this may happen if we are fetching data from an existing schema - // that use CBLOB for JSON column + // that use CBLOB for JSON column In that case we assume byte are + // UTF-8 bytes (i.e not OSON) LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return doExtraction(rs.getBytes( paramIndex ), options); + return OracleOsonJacksonJdbcType.this.fromString( + new String( rs.getBytes( paramIndex ), StandardCharsets.UTF_8 ), + getJavaType(), + options); } else { throw exc; } @@ -226,9 +205,13 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // this may happen if we are fetching data from an existing schema - // that use CBLOB for JSON column + // that use CBLOB for JSON column. In that case we assume byte are + // UTF-8 bytes (i.e not OSON) LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return doExtraction(statement.getBytes( index ), options); + return OracleOsonJacksonJdbcType.this.fromString( + new String( statement.getBytes( index ), StandardCharsets.UTF_8 ), + getJavaType(), + options); } else { throw exc; } @@ -244,9 +227,13 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // this may happen if we are fetching data from an existing schema - // that use CBLOB for JSON column + // that use CBLOB for JSON column In that case we assume byte are + // // UTF-8 bytes (i.e not OSON) LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return doExtraction(statement.getBytes( name ), options); + return OracleOsonJacksonJdbcType.this.fromString( + new String( statement.getBytes( name ), StandardCharsets.UTF_8 ), + getJavaType(), + options); } else { throw exc; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java index c2f41088fe2d..a9caf5c387b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java @@ -20,7 +20,6 @@ import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_APPLICATION_CONTINUITY; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE; -import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; /** @@ -34,7 +33,6 @@ public class OracleServerConfiguration { private final boolean autonomous; private final boolean extended; private final boolean applicationContinuity; - private final boolean osonDisabled; private final int driverMajorVersion; private final int driverMinorVersion; @@ -50,10 +48,6 @@ public boolean isApplicationContinuity() { return applicationContinuity; } - public boolean isOSONEnabled() { - return osonDisabled; - } - public int getDriverMajorVersion() { return driverMajorVersion; } @@ -63,7 +57,7 @@ public int getDriverMinorVersion() { } public OracleServerConfiguration(boolean autonomous, boolean extended) { - this( autonomous, extended, false, false, 19, 0 ); + this( autonomous, extended, false, 19, 0 ); } public OracleServerConfiguration( @@ -71,20 +65,18 @@ public OracleServerConfiguration( boolean extended, int driverMajorVersion, int driverMinorVersion) { - this( autonomous, extended, false, false, driverMajorVersion, driverMinorVersion ); + this( autonomous, extended, false, driverMajorVersion, driverMinorVersion ); } public OracleServerConfiguration( boolean autonomous, boolean extended, boolean applicationContinuity, - boolean osonDisabled, int driverMajorVersion, int driverMinorVersion) { this.autonomous = autonomous; this.extended = extended; this.applicationContinuity = applicationContinuity; - this.osonDisabled = osonDisabled; this.driverMajorVersion = driverMajorVersion; this.driverMinorVersion = driverMinorVersion; } @@ -96,12 +88,10 @@ public static OracleServerConfiguration fromDialectResolutionInfo(DialectResolut final boolean defaultExtended = getBoolean( ORACLE_EXTENDED_STRING_SIZE, configuration, false ); final boolean defaultAutonomous = getBoolean( ORACLE_AUTONOMOUS_DATABASE, configuration, false ); final boolean defaultContinuity = getBoolean( ORACLE_APPLICATION_CONTINUITY, configuration, false ); - final boolean defaultOsonDisabled = getBoolean( ORACLE_OSON_DISABLED , configuration, false ); boolean extended; boolean autonomous; boolean applicationContinuity; - boolean osonDisabled = defaultOsonDisabled; int majorVersion; int minorVersion; @@ -138,7 +128,7 @@ public static OracleServerConfiguration fromDialectResolutionInfo(DialectResolut } } - return new OracleServerConfiguration( autonomous, extended, applicationContinuity, osonDisabled,majorVersion, minorVersion ); + return new OracleServerConfiguration( autonomous, extended, applicationContinuity, majorVersion, minorVersion ); } private static boolean isExtended(Statement statement) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index 0eb12dbdef3e..d4c296ff0c13 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -72,7 +72,7 @@ public class OracleAggregateSupport extends AggregateSupportImpl { private final JsonSupport jsonSupport; private final boolean dateTypesStoreAsString; - OracleAggregateSupport(boolean checkConstraintSupport, JsonSupport jsonSupport, boolean dateTypesStoreAsString) { + private OracleAggregateSupport(boolean checkConstraintSupport, JsonSupport jsonSupport, boolean dateTypesStoreAsString) { this.checkConstraintSupport = checkConstraintSupport; this.jsonSupport = jsonSupport; // this flag tell us if data is serialized/de-serialized as String. As opposed to using OSON diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index 3f04d6561c82..d5336103f002 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -166,7 +166,7 @@ public JsonDocumentItemType next() { case QUOTE: // that's the start of an attribute key or a quoted value // put back the quote moveBufferPosition(-1); - consumeQuottedString(); + consumeQuotedString(); // That's a quote: // - if we are at the beginning of an array that's a quoted value // - if we are in the middle of an array, that's a quoted value @@ -202,7 +202,7 @@ public JsonDocumentItemType next() { break; case OTHER: // here we are in front of a boolean, a null or a numeric value. - // if none of these cases we going to raise IllegalStateException + // if none of these cases we're going to raise IllegalStateException // put back what we've read moveBufferPosition(-1); final int valueSize = consumeNonStringValue(); @@ -296,9 +296,8 @@ private void moveTo(char character) throws IllegalStateException { } /** - * Goes through the JSON string to locate a character. - * Escaped characters are taken into account. - * ex: on 'AB\"C"' this method returns 5 (not 3) + * Goes through the JSON string to locate a non-escaped instance of a given character. + * Ex: on 'AB\"C"' this method returns 5 (not 3) * * @param character character to be found * @return the position of the character or -1 if not found. @@ -328,7 +327,7 @@ private int locateCharacter(char character) { } /** - * Consume a non-quotted value + * Consume a non-quoted value * @return the length of this value. can be 0, -1 in case of error */ private int consumeNonStringValue() { @@ -354,10 +353,10 @@ private int consumeNonStringValue() { return allGood?(this.jsonValueEnd-this.jsonValueStart):-1; } /** - * Consume a quotted value + * Consumes a quoted value * @return the length of this value. can be 0, -1 in case of error */ - private void consumeQuottedString() { + private void consumeQuotedString() { // be sure we are at a meaningful place // key name are unquoted diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index f6cc9fbe62c4..86498ae67554 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -6,8 +6,8 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.SerializationFeature; +import oracle.jdbc.provider.oson.OsonModule; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; @@ -15,7 +15,7 @@ /** - * Implementation of FormatMapper for Orale OSON support + * Implementation of FormatMapper for Oracle OSON support * * @author Emmanuel Jannetti * @author Bidyadhar Mohanty @@ -25,30 +25,12 @@ public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { public static final String SHORT_NAME = "jackson"; - private static final Class osonModuleKlass; - static { - try { - osonModuleKlass = JacksonOsonFormatMapper.class.getClassLoader().loadClass( "oracle.jdbc.provider.oson.OsonModule" ); - } - catch (ClassNotFoundException | LinkageError e) { - // should not happen as JacksonOsonFormatMapper is loaded - // only when Oracle OSON JDBC extension is present - // see OracleDialect class. - throw new ExceptionInInitializerError( "JacksonOsonFormatMapper class loaded without OSON extension: " + e.getClass()+" "+ e.getMessage()); - } - } - /** * Creates a new JacksonOsonFormatMapper */ public JacksonOsonFormatMapper() { super(); - try { - objectMapper.registerModule( (Module) osonModuleKlass.getDeclaredConstructor().newInstance() ); - } - catch (Exception e) { - throw new RuntimeException( "Cannot instanciate " + osonModuleKlass.getCanonicalName(), e ); - } + objectMapper.registerModule( new OsonModule() ); objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java index 5002351bcce1..2100ce863849 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/resolver/DialectSpecificConfigTest.java @@ -24,7 +24,6 @@ import static org.hibernate.cfg.DialectSpecificSettings.MYSQL_NO_BACKSLASH_ESCAPES; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE; -import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.cfg.DialectSpecificSettings.SYBASE_ANSI_NULL; import static org.hibernate.dialect.DatabaseVersion.NO_VERSION; @@ -56,28 +55,6 @@ public void testOracleIsAutonomous() { assertThat( ( (OracleDialect) dialect ).isAutonomous() ).isTrue(); } - @Test - public void testOracleIsOsonEnabled() { - final Dialect dialect = resolveDialect( - "Oracle", - values -> values.put( "emptyOne", "true" ) - ); - - assertThat( dialect ).isInstanceOf( OracleDialect.class ); - assertThat( ( (OracleDialect) dialect ).isOracleOsonDisabled() ).isFalse(); - } - - @Test - public void testOracleIsOsonDisabled() { - final Dialect dialect = resolveDialect( - "Oracle", - values -> values.put( ORACLE_OSON_DISABLED, "true" ) - ); - - assertThat( dialect ).isInstanceOf( OracleDialect.class ); - assertThat( ( (OracleDialect) dialect ).isOracleOsonDisabled() ).isTrue(); - } - @Test public void testSybaseASEIsAnsiNull() { final Dialect dialect = resolveDialect( diff --git a/local-build-plugins/src/main/groovy/local.java-module.gradle b/local-build-plugins/src/main/groovy/local.java-module.gradle index 40499736c718..291d4a8a4f2c 100644 --- a/local-build-plugins/src/main/groovy/local.java-module.gradle +++ b/local-build-plugins/src/main/groovy/local.java-module.gradle @@ -48,6 +48,9 @@ dependencies { compileOnly libs.loggingAnnotations // Used for compiling some Oracle specific JdbcTypes compileOnly jdbcLibs.oracle + compileOnly (jdbcLibs.oracleJdbcJacksonOsonExtension) { + exclude group: 'com.oracle.database.jdbc', module: 'ojdbc8' + } // JUnit dependencies made up of: // * JUnit 5 diff --git a/settings.gradle b/settings.gradle index e6363ceeda6c..7a6754bf3256 100644 --- a/settings.gradle +++ b/settings.gradle @@ -229,6 +229,7 @@ dependencyResolutionManagement { def mssqlVersion = version "mssql", "12.8.1.jre11" def mysqlVersion = version "mysql", "9.2.0" def oracleVersion = version "oracle", "23.7.0.25.01" + def oracleJacksonOsonExtension = version "oracleJacksonOsonExtension", "1.0.3" def pgsqlVersion = version "pgsql", "42.7.4" def sybaseVersion = version "sybase", "1.3.1" def tidbVersion = version "tidb", mysqlVersion @@ -247,6 +248,7 @@ dependencyResolutionManagement { library( "oracle", "com.oracle.database.jdbc", "ojdbc17" ).versionRef( oracleVersion ) library( "oracleXml", "com.oracle.database.xml", "xdb" ).versionRef( oracleVersion ) library( "oracleXmlParser", "com.oracle.database.xml", "xmlparserv2" ).versionRef( oracleVersion ) + library( "oracleJdbcJacksonOsonExtension", "com.oracle.database.jdbc", "ojdbc-provider-jackson-oson" ).versionRef( oracleJacksonOsonExtension ) library( "mssql", "com.microsoft.sqlserver", "mssql-jdbc" ).versionRef( mssqlVersion ) library( "db2", "com.ibm.db2", "jcc" ).versionRef( db2Version ) library( "hana", "com.sap.cloud.db.jdbc", "ngdbc" ).versionRef( hanaVersion ) From c1e27c559d528f19e91ec645c6081b07c3492860 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Mon, 7 Apr 2025 18:45:17 +0200 Subject: [PATCH 57/81] HHH-17404 : add schema compatibility test --- .../dialect/OracleOsonJacksonJdbcType.java | 6 +- .../mapping/hhh17404/JsonCBLOBToOsonTest.java | 95 +++++++++++++++++++ .../util/StringJsonDocumentReaderTest.java | 35 +++++++ settings.gradle | 2 +- 4 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/JsonCBLOBToOsonTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index bfe5b5dfb0e4..385eb40215dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -182,7 +182,7 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); return doExtraction(ojd,options); } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // this may happen if we are fetching data from an existing schema // that use CBLOB for JSON column In that case we assume byte are // UTF-8 bytes (i.e not OSON) @@ -203,7 +203,7 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); return doExtraction(ojd,options); } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // this may happen if we are fetching data from an existing schema // that use CBLOB for JSON column. In that case we assume byte are // UTF-8 bytes (i.e not OSON) @@ -225,7 +225,7 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); return doExtraction(ojd,options); } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // this may happen if we are fetching data from an existing schema // that use CBLOB for JSON column In that case we assume byte are // // UTF-8 bytes (i.e not OSON) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/JsonCBLOBToOsonTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/JsonCBLOBToOsonTest.java new file mode 100644 index 000000000000..06a11ecca312 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/JsonCBLOBToOsonTest.java @@ -0,0 +1,95 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.hhh17404; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.orm.test.mapping.basic.JsonMappingTests; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.type.SqlTypes; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hibernate.testing.orm.junit.DialectContext.getDialect; + +/** + * This test class is about testing that legacy schema that use BLO for JSON column + * can be safely read even when Oracle Oson extention is in place. + * In Such a situation, the JSON type will expect JSON a JSON column and should + * silently fall back to String deserialization. + * + * @author Emmanuel Jannetti + */ +@DomainModel(annotatedClasses = JsonCBLOBToOsonTest.JsonEntity.class) +@SessionFactory +@RequiresDialect( value = OracleDialect.class, majorVersion = 23 ) +public class JsonCBLOBToOsonTest { + + @Entity(name = "JsonEntity") + @Table(name = "TEST_OSON_COMPAT") + public static class JsonEntity { + @Id + private Integer id; + @JdbcTypeCode( SqlTypes.JSON ) + private JsonMappingTests.StringNode jsonName; + + public JsonEntity() { + super(); + } + public JsonEntity(Integer id, JsonMappingTests.StringNode node) { + this.id = id; + this.jsonName = node; + } + } + + @BeforeEach + public void setup(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + // force creation of a BLOB column type by creating the table ourselves + session.createNativeQuery( getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ) + .executeUpdate(); + session.createNativeQuery( "CREATE TABLE TEST_OSON_COMPAT (id NUMBER, jsonName BLOB CHECK (jsonName is json) ,primary key (id))" ) + .executeUpdate(); + + String insert = "INSERT INTO TEST_OSON_COMPAT (id, jsonName) VALUES(:id,:json)"; + String jsonstr = "{\"string\":\"john\"}"; + session.createNativeQuery(insert).setParameter("id",1) + .setParameter( "json", jsonstr).executeUpdate(); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.createNativeQuery( getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ).executeUpdate(); + } + ); + } + + @Test + public void verifyReadWorks(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + JsonEntity entity = session.find( JsonCBLOBToOsonTest.JsonEntity.class, 1 ); + assertThat( entity.jsonName.getString(), is( "john" ) ); + + } + ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java index 2371a39ffab3..a2cd7b75202f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentReaderTest.java @@ -381,4 +381,39 @@ public void testNestedArrayDocument() { assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); } + @Test + public void testUnicode() { + final StringJsonDocumentReader reader = new StringJsonDocumentReader( """ + { + "myUnicode1": "\\u0074\\u0068\\u0069\\u0073\\u005f\\u0069\\u0073\\u005f\\u0075\\u006e\\u0069\\u0063\\u006f\\u0064\\u0065", + "myUnicode2": "this_\\u0069\\u0073_unicode", + "myUnicode3": "this_is_unicode" + } + + + """); + + assertTrue(reader.hasNext(), "should have more element"); + assertEquals( JsonDocumentItemType.OBJECT_START, reader.next()); + + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals("myUnicode1", reader.getObjectKeyName()); + assertEquals( JsonDocumentItemType.VALUE, reader.next()); + assertEquals("this_is_unicode", reader.getStringValue()); + + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals("myUnicode2", reader.getObjectKeyName()); + assertEquals( JsonDocumentItemType.VALUE, reader.next()); + assertEquals("this_is_unicode", reader.getStringValue()); + + assertEquals( JsonDocumentItemType.VALUE_KEY,reader.next()); + assertEquals("myUnicode3", reader.getObjectKeyName()); + assertEquals( JsonDocumentItemType.VALUE, reader.next()); + assertEquals("this_is_unicode", reader.getStringValue()); + + assertEquals( JsonDocumentItemType.OBJECT_END, reader.next()); + + } + + } diff --git a/settings.gradle b/settings.gradle index 7a6754bf3256..48e3a3b747f6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -229,7 +229,7 @@ dependencyResolutionManagement { def mssqlVersion = version "mssql", "12.8.1.jre11" def mysqlVersion = version "mysql", "9.2.0" def oracleVersion = version "oracle", "23.7.0.25.01" - def oracleJacksonOsonExtension = version "oracleJacksonOsonExtension", "1.0.3" + def oracleJacksonOsonExtension = version "oracleJacksonOsonExtension", "1.0.4" def pgsqlVersion = version "pgsql", "42.7.4" def sybaseVersion = version "sybase", "1.3.1" def tidbVersion = version "tidb", mysqlVersion From 16e751a52c5f557bae2ddfed5cefdd6deb0b8c6d Mon Sep 17 00:00:00 2001 From: BidyadharM <bidyadhar.mohanty@oracle.com> Date: Thu, 10 Apr 2025 01:32:55 +0530 Subject: [PATCH 58/81] HHH-17404 : Removed Duration mapping to intervalDS misssed during previous uploads. --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 0d1b586b042a..a1f5231a1cef 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -842,9 +842,6 @@ protected String columnType(int sqlTypeCode) { case VARBINARY: return "raw($l)"; - case DURATION: - return "interval day to second"; - default: return super.columnType( sqlTypeCode ); } From 8727f6e3be44f9cd1a958176aeeb279f6391b7e7 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Wed, 9 Apr 2025 22:17:02 +0200 Subject: [PATCH 59/81] HHH-17404 rename oson flag in oracle dialect --- .../org/hibernate/dialect/OracleDialect.java | 8 +-- .../OracleOsonJacksonArrayJdbcType.java | 67 ++++++++++++++++--- .../dialect/OracleOsonJacksonJdbcType.java | 18 ++--- .../type/descriptor/jdbc/JsonHelper.java | 13 +++- 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index a1f5231a1cef..c788b42fa9d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -199,8 +199,7 @@ public class OracleDialect extends Dialect { private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 19 ); - private static final String JACKSON_MAPPER_NAME = "jackson"; - private static boolean OracleOsonExtensionUsed = false; + private boolean osonExtensionEnabled = false; private final OracleUserDefinedTypeExporter userDefinedTypeExporter = new OracleUserDefinedTypeExporter( this ); private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); @@ -1046,8 +1045,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON Jackson extension used" ); - // as we speak this is not supported by OSON extension - OracleOsonExtensionUsed = true; + osonExtensionEnabled = true; } else { if (DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.isDebugEnabled()) { @@ -1108,7 +1106,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this ,!OracleOsonExtensionUsed); + return OracleAggregateSupport.valueOf( this ,!osonExtensionEnabled ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index d847e534d771..a1566c46f678 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -6,11 +6,14 @@ import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; +import oracle.jdbc.driver.DatabaseError; import oracle.jdbc.provider.oson.OsonFactory; import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; @@ -27,6 +30,7 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -43,6 +47,7 @@ */ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJacksonArrayJdbcType.class ); private static final OsonFactory osonFactory = new OsonFactory(); @@ -121,6 +126,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getJsonFormatMapper(); JsonParser osonParser = osonFactory.createParser( osonBytes ); + return mapper.readFromSource( getJavaType(), osonParser, options); } @@ -139,25 +145,66 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - return doExtraction( ojd, options); + try { + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction( ojd, options); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return OracleOsonJacksonArrayJdbcType.this.fromString( + new String( rs.getBytes( paramIndex ), StandardCharsets.UTF_8 ), + getJavaType(), + options); + } else { + throw exc; + } + } } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - - - OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); - return doExtraction( ojd, options); + try { + OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); + return doExtraction( ojd, options); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return OracleOsonJacksonArrayJdbcType.this.fromString( + new String( statement.getBytes( index ), StandardCharsets.UTF_8 ), + getJavaType(), + options); + } else { + throw exc; + } + } } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - - OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); - return doExtraction( ojd, options); + try { + OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); + return doExtraction( ojd, options); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return OracleOsonJacksonArrayJdbcType.this.fromString( + new String( statement.getBytes( name ), StandardCharsets.UTF_8 ), + getJavaType(), + options); + } else { + throw exc; + } + } } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index 385eb40215dc..fbff6af6a8ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -183,9 +183,9 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro return doExtraction(ojd,options); } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // this may happen if we are fetching data from an existing schema - // that use CBLOB for JSON column In that case we assume byte are - // UTF-8 bytes (i.e not OSON) + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); return OracleOsonJacksonJdbcType.this.fromString( new String( rs.getBytes( paramIndex ), StandardCharsets.UTF_8 ), @@ -204,9 +204,9 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt return doExtraction(ojd,options); } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // this may happen if we are fetching data from an existing schema - // that use CBLOB for JSON column. In that case we assume byte are - // UTF-8 bytes (i.e not OSON) + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); return OracleOsonJacksonJdbcType.this.fromString( new String( statement.getBytes( index ), StandardCharsets.UTF_8 ), @@ -226,9 +226,9 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o return doExtraction(ojd,options); } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // this may happen if we are fetching data from an existing schema - // that use CBLOB for JSON column In that case we assume byte are - // // UTF-8 bytes (i.e not OSON) + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); return OracleOsonJacksonJdbcType.this.fromString( new String( statement.getBytes( name ), StandardCharsets.UTF_8 ), diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 5859173f76df..18f4d96bcf98 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -408,16 +408,25 @@ public static <X> X deserialize( return (X) values; } + // This is also used by Hibernate Reactive public static <X> X arrayFromString( JavaType<X> javaType, JdbcType elementJdbcType, String string, WrapperOptions options) throws SQLException { - if ( string == null ) { return null; } + return deserializeArray( javaType, elementJdbcType, new StringJsonDocumentReader( string ), options ); + } + + public static <X> X deserializeArray( + JavaType<X> javaType, + JdbcType elementJdbcType, + JsonDocumentReader reader, + WrapperOptions options) throws SQLException { + final CustomArrayList arrayList = new CustomArrayList(); final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) javaType).getElementJavaType(); @@ -429,7 +438,7 @@ public static <X> X arrayFromString( else { jdbcJavaType = options.getTypeConfiguration().getJavaTypeRegistry().resolveDescriptor( preferredJavaTypeClass ); } - JsonDocumentReader reader = new StringJsonDocumentReader(string); + JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,false); assert reader.hasNext():"Invalid array string"; From 564e13600ce41a1c8761718e2f88744b7fb61bb9 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Tue, 15 Apr 2025 20:06:37 +0200 Subject: [PATCH 60/81] HHH-17404 add array of embeddable support --- .../org/hibernate/dialect/OracleDialect.java | 1 - .../OracleOsonJacksonArrayJdbcType.java | 17 +- .../type/descriptor/jdbc/JsonHelper.java | 20 +- .../embeddable/EmbeddableAggregate.java | 15 + .../embeddable/JsonEmbeddableArrayTest.java | 331 ++++++++++++++++++ 5 files changed, 380 insertions(+), 4 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index c788b42fa9d9..6b0cd41fe328 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -145,7 +145,6 @@ import static org.hibernate.type.SqlTypes.DATE; import static org.hibernate.type.SqlTypes.DECIMAL; import static org.hibernate.type.SqlTypes.DOUBLE; -import static org.hibernate.type.SqlTypes.DURATION; import static org.hibernate.type.SqlTypes.FLOAT; import static org.hibernate.type.SqlTypes.GEOMETRY; import static org.hibernate.type.SqlTypes.INTEGER; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index a1566c46f678..be5d13b46df7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -4,6 +4,7 @@ */ package org.hibernate.dialect; +import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; import oracle.jdbc.driver.DatabaseError; @@ -12,6 +13,7 @@ import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; +import oracle.sql.json.OracleJsonParser; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -26,6 +28,7 @@ import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.FormatMapper; +import org.hibernate.type.format.OsonDocumentReader; import org.hibernate.type.format.OsonDocumentWriter; import java.io.ByteArrayOutputStream; @@ -125,9 +128,19 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { FormatMapper mapper = options.getJsonFormatMapper(); - JsonParser osonParser = osonFactory.createParser( osonBytes ); + OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( osonBytes ); + final JdbcType elementJdbcType = getElementJdbcType(); + if (elementJdbcType instanceof JsonJdbcType) { + if (((JsonJdbcType) elementJdbcType).getEmbeddableMappingType() != null) { + // embeddable array case. + return JsonHelper.deserializeArray( javaType, + elementJdbcType, new OsonDocumentReader( osonParser ), options ); + } + } + try (JsonParser oParser = ((JsonFactory)osonFactory).createParser( osonBytes )) { + return mapper.readFromSource( getJavaType(), oParser, options); + } - return mapper.readFromSource( getJavaType(), osonParser, options); } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 18f4d96bcf98..abbd2512b2e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -36,6 +36,7 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.JsonDocumentItemType; import org.hibernate.type.format.JsonDocumentReader; import org.hibernate.type.format.JsonDocumentWriter; @@ -264,7 +265,15 @@ private static <X> X consumeJsonDocumentItems(JsonDocumentReader reader, Embedda objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()+ ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; objectArrays.push( objectArrayResult ); - while(reader.hasNext()) { + // We loop on two conditions: + // - the parser still has tokens left + // - the type stack is not empty + // Even if the reader has some tokens left, if the type stack is empty, + // that means that we have to stop parsing. That may be the case while parsing an object of object array, + // the array is not empty, but we ae done parsing that specific object. + // When we encounter OBJECT_END the current type is popped out of the stack. When parsing one object of an array we may end up + // having an empty stack. Next Objects are parsed in the next round. + while(reader.hasNext() && !embeddableMappingTypes.isEmpty()) { JsonDocumentItemType type = reader.next(); switch (type) { case VALUE_KEY: @@ -462,6 +471,15 @@ public static <X> X deserializeArray( case VALUE: arrayList.add( adapter.fromValue(jdbcJavaType, elementJdbcType ,reader, options) ); break; + case OBJECT_START: + assert elementJdbcType instanceof JsonJdbcType; + Object o = deserialize( + ((JsonJdbcType) elementJdbcType).getEmbeddableMappingType(), + reader, + true, + options); + arrayList.add(o); + break; default: throw new UnsupportedOperationException( "Unexpected JSON type " + type ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java index 069ed1ac1559..4edb530ade4e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/EmbeddableAggregate.java @@ -292,6 +292,14 @@ public void setMutableValue(MutableValue mutableValue) { this.mutableValue = mutableValue; } + static void assertArraysEquals(EmbeddableAggregate [] a1, EmbeddableAggregate []a2) { + Assertions.assertTrue( (a1 == null && a2 == null) || (a1 != null && a2 != null) ); + Assertions.assertEquals( a1.length, a2.length ); + for (int i = 0; i < a1.length; i++) { + assertEquals(a1[i], a2[i]); + } + } + static void assertEquals(EmbeddableAggregate a1, EmbeddableAggregate a2) { Assertions.assertEquals( a1.theInt, a2.theInt ); Assertions.assertEquals( a1.theDouble, a2.theDouble ); @@ -338,6 +346,13 @@ static void assertEquals(EmbeddableAggregate a1, EmbeddableAggregate a2) { } } + public static EmbeddableAggregate[] createAggregateArray1() { + return new EmbeddableAggregate[] {createAggregate1(),createAggregate2()}; + } + public static EmbeddableAggregate[] createAggregateArray2() { + return new EmbeddableAggregate[] {createAggregate3()}; + } + public static EmbeddableAggregate createAggregate1() { final EmbeddableAggregate aggregate = new EmbeddableAggregate(); aggregate.theBoolean = true; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java new file mode 100644 index 000000000000..3c5b2ad1d786 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java @@ -0,0 +1,331 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.embeddable; + +import java.net.URL; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.type.SqlTypes; + +import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; +import org.hibernate.testing.orm.domain.gambit.MutableValue; +import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; +import org.hibernate.testing.orm.junit.DialectFeatureChecks; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Tuple; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJsonAggregate.class) +public class JsonEmbeddableArrayTest extends BaseSessionFactoryFunctionalTest { + + @Override + protected Class<?>[] getAnnotatedClasses() { + return new Class<?>[] { + JsonEmbeddableArrayTest.JsonArrayHolder.class + }; + } + + @BeforeEach + public void setUp() { + inTransaction( + session -> { + session.persist( new JsonArrayHolder( 1L, EmbeddableAggregate.createAggregateArray1() )); + session.persist( new JsonArrayHolder( 2L, EmbeddableAggregate.createAggregateArray2() )); + } + ); + } + + @AfterEach + protected void cleanupTest() { + inTransaction( + session -> { + session.createMutationQuery( "delete from JsonArrayHolder h" ).executeUpdate(); + } + ); + } + + @Test + public void testUpdate() { + sessionFactoryScope().inTransaction( + entityManager -> { + JsonArrayHolder jsonArrayHolder = entityManager.find( JsonArrayHolder.class, 1L ); + jsonArrayHolder.setAggregateArray( EmbeddableAggregate.createAggregateArray2() ); + entityManager.flush(); + entityManager.clear(); + EmbeddableAggregate.assertArraysEquals( + EmbeddableAggregate.createAggregateArray2(), + entityManager.find( JsonArrayHolder.class, 1L ).getAggregateArray() ); + } + ); + } + + @Test + public void testFetch() { + sessionFactoryScope().inSession( + entityManager -> { + List<JsonArrayHolder> jsonArrayHolders = entityManager.createQuery( "from JsonArrayHolder b where b.id = 1", JsonArrayHolder.class ).getResultList(); + assertEquals( 1, jsonArrayHolders.size() ); + assertEquals( 1L, jsonArrayHolders.get( 0 ).getId() ); + EmbeddableAggregate.assertArraysEquals( EmbeddableAggregate.createAggregateArray1(), jsonArrayHolders.get( 0 ).getAggregateArray() ); + } + ); + } + + @Test + public void testFetchNull() { + sessionFactoryScope().inSession( + entityManager -> { + List<JsonArrayHolder> jsonArrayHolders = entityManager.createQuery( "from JsonArrayHolder b where b.id = 2", JsonArrayHolder.class ).getResultList(); + assertEquals( 1, jsonArrayHolders.size() ); + assertEquals( 2L, jsonArrayHolders.get( 0 ).getId() ); + EmbeddableAggregate.assertEquals( EmbeddableAggregate.createAggregateArray2()[0], jsonArrayHolders.get( 0 ).getAggregateArray()[0] ); + } + ); + } + + @Test + public void testDomainResult() { + sessionFactoryScope().inSession( + entityManager -> { + List<EmbeddableAggregate[]> structs = entityManager.createQuery( "select b.aggregateArray from JsonArrayHolder b where b.id = 1", EmbeddableAggregate[].class ).getResultList(); + assertEquals( 1, structs.size() ); + EmbeddableAggregate.assertArraysEquals( EmbeddableAggregate.createAggregateArray1(), structs.get( 0 ) ); + } + ); + } + + @Test + public void testSelectionItems() { + sessionFactoryScope().inSession( + entityManager -> { + List<Tuple> tuples = entityManager.createQuery( + "select " + + "b.aggregateArray[0].theInt," + + "b.aggregateArray[0].theDouble," + + "b.aggregateArray[0].theBoolean," + + "b.aggregateArray[0].theNumericBoolean," + + "b.aggregateArray[0].theStringBoolean," + + "b.aggregateArray[0].theString," + + "b.aggregateArray[0].theInteger," + + "b.aggregateArray[0].theUrl," + + "b.aggregateArray[0].theClob," + + "b.aggregateArray[0].theBinary," + + "b.aggregateArray[0].theDate," + + "b.aggregateArray[0].theTime," + + "b.aggregateArray[0].theTimestamp," + + "b.aggregateArray[0].theInstant," + + "b.aggregateArray[0].theUuid," + + "b.aggregateArray[0].gender," + + "b.aggregateArray[0].convertedGender," + + "b.aggregateArray[0].ordinalGender," + + "b.aggregateArray[0].theDuration," + + "b.aggregateArray[0].theLocalDateTime," + + "b.aggregateArray[0].theLocalDate," + + "b.aggregateArray[0].theLocalTime," + + "b.aggregateArray[0].theZonedDateTime," + + "b.aggregateArray[0].theOffsetDateTime," + + "b.aggregateArray[0].mutableValue " + + "from JsonArrayHolder b where b.id = 1", + Tuple.class + ).getResultList(); + assertEquals( 1, tuples.size() ); + final Tuple tuple = tuples.get( 0 ); + final EmbeddableAggregate struct = new EmbeddableAggregate(); + struct.setTheInt( tuple.get( 0, int.class ) ); + struct.setTheDouble( tuple.get( 1, Double.class ) ); + struct.setTheBoolean( tuple.get( 2, Boolean.class ) ); + struct.setTheNumericBoolean( tuple.get( 3, Boolean.class ) ); + struct.setTheStringBoolean( tuple.get( 4, Boolean.class ) ); + struct.setTheString( tuple.get( 5, String.class ) ); + struct.setTheInteger( tuple.get( 6, Integer.class ) ); + struct.setTheUrl( tuple.get( 7, URL.class ) ); + struct.setTheClob( tuple.get( 8, String.class ) ); + struct.setTheBinary( tuple.get( 9, byte[].class ) ); + struct.setTheDate( tuple.get( 10, Date.class ) ); + struct.setTheTime( tuple.get( 11, Time.class ) ); + struct.setTheTimestamp( tuple.get( 12, Timestamp.class ) ); + struct.setTheInstant( tuple.get( 13, Instant.class ) ); + struct.setTheUuid( tuple.get( 14, UUID.class ) ); + struct.setGender( tuple.get( 15, EntityOfBasics.Gender.class ) ); + struct.setConvertedGender( tuple.get( 16, EntityOfBasics.Gender.class ) ); + struct.setOrdinalGender( tuple.get( 17, EntityOfBasics.Gender.class ) ); + struct.setTheDuration( tuple.get( 18, Duration.class ) ); + struct.setTheLocalDateTime( tuple.get( 19, LocalDateTime.class ) ); + struct.setTheLocalDate( tuple.get( 20, LocalDate.class ) ); + struct.setTheLocalTime( tuple.get( 21, LocalTime.class ) ); + struct.setTheZonedDateTime( tuple.get( 22, ZonedDateTime.class ) ); + struct.setTheOffsetDateTime( tuple.get( 23, OffsetDateTime.class ) ); + struct.setMutableValue( tuple.get( 24, MutableValue.class ) ); + EmbeddableAggregate.assertEquals( EmbeddableAggregate.createAggregate1(), struct ); + } + ); + } + + @Test + public void testDeleteWhere() { + sessionFactoryScope().inTransaction( + entityManager -> { + entityManager.createMutationQuery( "delete JsonArrayHolder b where b.aggregateArray is not null" ).executeUpdate(); + assertNull( entityManager.find( JsonArrayHolder.class, 1L ) ); + + } + ); + } + + @Test + public void testUpdateAggregate() { + sessionFactoryScope().inTransaction( + entityManager -> { + entityManager.createMutationQuery( "update JsonArrayHolder b set b.aggregateArray = null" ).executeUpdate(); + assertNull( entityManager.find( JsonArrayHolder.class, 1L ).getAggregateArray() ); + } + ); + } + + @Test + public void testUpdateAggregateMember() { + sessionFactoryScope().inTransaction( + entityManager -> { + entityManager.createMutationQuery( "update JsonArrayHolder b set b.aggregateArray[0].theString = null where b.id = 1" ).executeUpdate(); + EmbeddableAggregate[] struct = EmbeddableAggregate.createAggregateArray1(); + struct[0].setTheString( null ); + EmbeddableAggregate.assertArraysEquals( struct, entityManager.find( JsonArrayHolder.class, 1L ).getAggregateArray() ); + } + ); + } + + @Test + public void testUpdateMultipleAggregateMembers() { + sessionFactoryScope().inTransaction( + entityManager -> { + entityManager.createMutationQuery( "update JsonArrayHolder b set b.aggregateArray.theString = null, b.aggregateArray[0].theUuid = null" ).executeUpdate(); + EmbeddableAggregate[] struct = EmbeddableAggregate.createAggregateArray1(); + struct[0].setTheString( null ); + struct[0].setTheUuid( null ); + EmbeddableAggregate.assertArraysEquals( struct, entityManager.find( JsonArrayHolder.class, 1L ).getAggregateArray() ); + } + ); + } + + @Test + public void testUpdateAllAggregateMembers() { + sessionFactoryScope().inTransaction( + entityManager -> { + EmbeddableAggregate[] struct = EmbeddableAggregate.createAggregateArray1(); + entityManager.createMutationQuery( + "update JsonArrayHolder b set " + + "b.aggregateArray[0].theInt = :theInt," + + "b.aggregateArray[0].theDouble = :theDouble," + + "b.aggregateArray[0].theBoolean = :theBoolean," + + "b.aggregateArray[0].theNumericBoolean = :theNumericBoolean," + + "b.aggregateArray[0].theStringBoolean = :theStringBoolean," + + "b.aggregateArray[0].theString = :theString," + + "b.aggregateArray[0].theInteger = :theInteger," + + "b.aggregateArray[0].theUrl = :theUrl," + + "b.aggregateArray[0].theClob = :theClob," + + "b.aggregateArray[0].theBinary = :theBinary," + + "b.aggregateArray[0].theDate = :theDate," + + "b.aggregateArray[0].theTime = :theTime," + + "b.aggregateArray[0].theTimestamp = :theTimestamp," + + "b.aggregateArray[0].theInstant = :theInstant," + + "b.aggregateArray[0].theUuid = :theUuid," + + "b.aggregateArray[0].gender = :gender," + + "b.aggregateArray[0].convertedGender = :convertedGender," + + "b.aggregateArray[0].ordinalGender = :ordinalGender," + + "b.aggregateArray[0].theDuration = :theDuration," + + "b.aggregateArray[0].theLocalDateTime = :theLocalDateTime," + + "b.aggregateArray[0].theLocalDate = :theLocalDate," + + "b.aggregateArray[0].theLocalTime = :theLocalTime," + + "b.aggregateArray[0].theZonedDateTime = :theZonedDateTime," + + "b.aggregateArray[0].theOffsetDateTime = :theOffsetDateTime," + + "b.aggregateArray[0].mutableValue = :mutableValue " + + "where b.id = 2" + ) + .setParameter( "theInt", struct[0].getTheInt() ) + .setParameter( "theDouble", struct[0].getTheDouble() ) + .setParameter( "theBoolean", struct[0].isTheBoolean() ) + .setParameter( "theNumericBoolean", struct[0].isTheNumericBoolean() ) + .setParameter( "theStringBoolean", struct[0].isTheStringBoolean() ) + .setParameter( "theString", struct[0].getTheString() ) + .setParameter( "theInteger", struct[0].getTheInteger() ) + .setParameter( "theUrl", struct[0].getTheUrl() ) + .setParameter( "theClob", struct[0].getTheClob() ) + .setParameter( "theBinary", struct[0].getTheBinary() ) + .setParameter( "theDate", struct[0].getTheDate() ) + .setParameter( "theTime", struct[0].getTheTime() ) + .setParameter( "theTimestamp", struct[0].getTheTimestamp() ) + .setParameter( "theInstant", struct[0].getTheInstant() ) + .setParameter( "theUuid", struct[0].getTheUuid() ) + .setParameter( "gender", struct[0].getGender() ) + .setParameter( "convertedGender", struct[0].getConvertedGender() ) + .setParameter( "ordinalGender", struct[0].getOrdinalGender() ) + .setParameter( "theDuration", struct[0].getTheDuration() ) + .setParameter( "theLocalDateTime", struct[0].getTheLocalDateTime() ) + .setParameter( "theLocalDate", struct[0].getTheLocalDate() ) + .setParameter( "theLocalTime", struct[0].getTheLocalTime() ) + .setParameter( "theZonedDateTime", struct[0].getTheZonedDateTime() ) + .setParameter( "theOffsetDateTime", struct[0].getTheOffsetDateTime() ) + .setParameter( "mutableValue", struct[0].getMutableValue() ) + .executeUpdate(); + EmbeddableAggregate.assertArraysEquals( EmbeddableAggregate.createAggregateArray1(), entityManager.find( JsonArrayHolder.class, 2L ).getAggregateArray() ); + } + ); + } + + @Entity(name = "JsonArrayHolder") + public static class JsonArrayHolder { + + @Id + private Long id; + @JdbcTypeCode(SqlTypes.JSON_ARRAY) + private EmbeddableAggregate [] aggregateArray; + + public JsonArrayHolder() { + } + + public JsonArrayHolder(Long id, EmbeddableAggregate[] aggregateArray) { + this.id = id; + this.aggregateArray = aggregateArray; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public EmbeddableAggregate[] getAggregateArray() { + return aggregateArray; + } + + public void setAggregateArray(EmbeddableAggregate[] aggregateArray) { + this.aggregateArray = aggregateArray; + } + + } + +} From cabbed683f2511306773caa7a2e0bf245f26ef5a Mon Sep 17 00:00:00 2001 From: Christian Beikov <christian.beikov@gmail.com> Date: Thu, 17 Apr 2025 11:42:59 +0200 Subject: [PATCH 61/81] HHH-17404 Fix compile errors --- .../type/format/OsonDocumentReader.java | 36 +++++++++---------- .../type/format/StringJsonDocumentReader.java | 18 +++++----- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java index bf01bfd1fd55..37a7c4c4e17a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentReader.java @@ -50,52 +50,52 @@ public JsonDocumentItemType next() { currentKeyName = null; currentValue = null; switch (evt) { - case OracleJsonParser.Event.START_OBJECT: + case START_OBJECT: return JsonDocumentItemType.OBJECT_START; - case OracleJsonParser.Event.END_OBJECT: + case END_OBJECT: return JsonDocumentItemType.OBJECT_END; - case OracleJsonParser.Event.START_ARRAY: + case START_ARRAY: return JsonDocumentItemType.ARRAY_START; - case OracleJsonParser.Event.END_ARRAY: + case END_ARRAY: return JsonDocumentItemType.ARRAY_END; - case OracleJsonParser.Event.KEY_NAME: + case KEY_NAME: currentKeyName = this.parser.getString(); return JsonDocumentItemType.VALUE_KEY; - case OracleJsonParser.Event.VALUE_TIMESTAMPTZ: + case VALUE_TIMESTAMPTZ: currentValue = this.parser.getOffsetDateTime(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_DATE: - case OracleJsonParser.Event.VALUE_TIMESTAMP: + case VALUE_DATE: + case VALUE_TIMESTAMP: currentValue = this.parser.getLocalDateTime(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_INTERVALDS: + case VALUE_INTERVALDS: currentValue = this.parser.getDuration(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_INTERVALYM: + case VALUE_INTERVALYM: currentValue = this.parser.getPeriod(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_STRING: + case VALUE_STRING: currentValue = this.parser.getString(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_TRUE: + case VALUE_TRUE: currentValue = Boolean.TRUE; return JsonDocumentItemType.BOOLEAN_VALUE; - case OracleJsonParser.Event.VALUE_FALSE: + case VALUE_FALSE: currentValue = Boolean.FALSE; return JsonDocumentItemType.BOOLEAN_VALUE; - case OracleJsonParser.Event.VALUE_NULL: + case VALUE_NULL: currentValue = null; return JsonDocumentItemType.NULL_VALUE; - case OracleJsonParser.Event.VALUE_DECIMAL: + case VALUE_DECIMAL: currentValue = this.parser.getBigDecimal(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_DOUBLE: + case VALUE_DOUBLE: currentValue = this.parser.getDouble(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_FLOAT: + case VALUE_FLOAT: currentValue = this.parser.getFloat(); return JsonDocumentItemType.VALUE; - case OracleJsonParser.Event.VALUE_BINARY: + case VALUE_BINARY: currentValue = this.parser.getBytes(); return JsonDocumentItemType.VALUE; default : diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index d5336103f002..7b025facacb2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -115,10 +115,10 @@ private void moveStateMachine(StringJsonDocumentMarker marker) { break; case QUOTE: switch ( currentState ) { - case JsonProcessingState.STARTING_ARRAY: + case STARTING_ARRAY: this.processingStates.push( JsonProcessingState.ARRAY ); break; - case JsonProcessingState.STARTING_OBJECT: + case STARTING_OBJECT: this.processingStates.push( JsonProcessingState.OBJECT ); this.processingStates.push( JsonProcessingState.OBJECT_KEY_NAME ); break; @@ -175,18 +175,18 @@ public JsonDocumentItemType next() { // - if we just hit ':' that's a quoted value // - if we just hit ',' that's a quoted key switch ( this.processingStates.getCurrent() ) { - case JsonProcessingState.STARTING_ARRAY: + case STARTING_ARRAY: //this.processingStates.push( JsonProcessingState.ARRAY ); return JsonDocumentItemType.VALUE; - case JsonProcessingState.ARRAY: + case ARRAY: return JsonDocumentItemType.VALUE; - case JsonProcessingState.STARTING_OBJECT: + case STARTING_OBJECT: //this.processingStates.push( JsonProcessingState.OBJECT ); //this.processingStates.push( JsonProcessingState.OBJECT_KEY_NAME ); return JsonDocumentItemType.VALUE_KEY; - case JsonProcessingState.OBJECT: // we are processing object attribute value elements + case OBJECT: // we are processing object attribute value elements return JsonDocumentItemType.VALUE; - case JsonProcessingState.OBJECT_KEY_NAME: // we are processing object elements key + case OBJECT_KEY_NAME: // we are processing object elements key return JsonDocumentItemType.VALUE_KEY; default: throw new IllegalStateException( "unexpected quote read in current processing state " + @@ -211,8 +211,8 @@ public JsonDocumentItemType next() { this.jsonString.charAt( this.position ))); } switch ( this.processingStates.getCurrent() ) { - case JsonProcessingState.ARRAY: - case JsonProcessingState.OBJECT: + case ARRAY: + case OBJECT: return getUnquotedValueType(this.jsonString.charAt( this.jsonValueStart)); default: throw new IllegalStateException( "unexpected read ["+ From a95a6e1fbdeafd4e6ce2b32f7f82ee77e2ffa246 Mon Sep 17 00:00:00 2001 From: Christian Beikov <christian.beikov@gmail.com> Date: Thu, 17 Apr 2025 11:45:51 +0200 Subject: [PATCH 62/81] HHH-17404 Address dialect and aggregate support comments --- .../dialect/OracleLegacyDialect.java | 2 +- .../org/hibernate/dialect/OracleDialect.java | 93 +++++-------------- .../aggregate/OracleAggregateSupport.java | 80 +++++++--------- 3 files changed, 56 insertions(+), 119 deletions(-) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index 2670fb217ba1..07e85788a6fa 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -992,7 +992,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this ,true); + return OracleAggregateSupport.valueOf( this ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 6b0cd41fe328..4104d39cc0b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -6,10 +6,8 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.TemporalType; -import jakarta.persistence.Timeout; import org.hibernate.Length; import org.hibernate.QueryTimeoutException; -import org.hibernate.Timeouts; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.cfg.MappingSettings; @@ -124,13 +122,14 @@ import static java.util.regex.Pattern.CASE_INSENSITIVE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; -import static org.hibernate.dialect.DialectLogging.DIALECT_MESSAGE_LOGGER; import static org.hibernate.dialect.type.OracleJdbcHelper.getArrayJdbcTypeConstructor; import static org.hibernate.dialect.type.OracleJdbcHelper.getNestedTableJdbcTypeConstructor; +import static org.hibernate.dialect.DialectLogging.DIALECT_LOGGER; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.internal.util.JdbcExceptionHelper.extractErrorCode; import static org.hibernate.internal.util.StringHelper.isEmpty; import static org.hibernate.internal.util.StringHelper.isNotEmpty; +import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; import static org.hibernate.query.common.TemporalUnit.DAY; import static org.hibernate.query.common.TemporalUnit.HOUR; import static org.hibernate.query.common.TemporalUnit.MINUTE; @@ -198,8 +197,6 @@ public class OracleDialect extends Dialect { private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 19 ); - private boolean osonExtensionEnabled = false; - private final OracleUserDefinedTypeExporter userDefinedTypeExporter = new OracleUserDefinedTypeExporter( this ); private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); private final SequenceSupport oracleSequenceSupport = OracleSequenceSupport.getInstance(this); @@ -223,9 +220,6 @@ protected void applyAggregateColumnCheck(StringBuilder buf, AggregateColumn aggr // Is the database accessed using a database service protected by Application Continuity. protected final boolean applicationContinuity; - // Is the database OSON format should be disabled. - protected final boolean isOracleOsonDisabled; - protected final int driverMajorVersion; protected final int driverMinorVersion; private boolean useBinaryFloat; @@ -239,7 +233,6 @@ public OracleDialect(DatabaseVersion version) { autonomous = false; extended = false; applicationContinuity = false; - isOracleOsonDisabled = false; driverMajorVersion = 19; driverMinorVersion = 0; } @@ -248,14 +241,13 @@ public OracleDialect(DialectResolutionInfo info) { this( info, OracleServerConfiguration.fromDialectResolutionInfo( info ) ); } - public OracleDialect(DialectResolutionInfo info, OracleServerConfiguration configuration) { + public OracleDialect(DialectResolutionInfo info, OracleServerConfiguration serverConfiguration) { super( info ); - autonomous = configuration.isAutonomous(); - extended = configuration.isExtended(); - applicationContinuity = configuration.isApplicationContinuity(); - isOracleOsonDisabled = configuration.isOSONEnabled(); - driverMinorVersion = configuration.getDriverMinorVersion(); - driverMajorVersion = configuration.getDriverMajorVersion(); + autonomous = serverConfiguration.isAutonomous(); + extended = serverConfiguration.isExtended(); + applicationContinuity = serverConfiguration.isApplicationContinuity(); + this.driverMinorVersion = serverConfiguration.getDriverMinorVersion(); + this.driverMajorVersion = serverConfiguration.getDriverMajorVersion(); } public boolean isAutonomous() { @@ -269,7 +261,6 @@ public boolean isExtended() { public boolean isApplicationContinuity() { return applicationContinuity; } - public boolean isOracleOsonDisabled() {return isOracleOsonDisabled;} private static boolean isJacksonJsonFormatMapper(ConfigurationService configService) { // Mirror the behavior of SessionFactoryOptionsBuilder#determineJsonFormatMapper @@ -302,11 +293,10 @@ public void appendBooleanValueString(SqlAppender appender, boolean bool) { @Override public void initializeFunctionRegistry(FunctionContributions functionContributions) { - super.initializeFunctionRegistry( functionContributions ); + super.initializeFunctionRegistry(functionContributions); final TypeConfiguration typeConfiguration = functionContributions.getTypeConfiguration(); - final var functionFactory = new CommonFunctionFactory( functionContributions ); - + CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions); functionFactory.ascii(); functionFactory.char_chr(); functionFactory.cosh(); @@ -983,11 +973,6 @@ public boolean supportsBitType() { return false; } - @Override - public boolean supportsUserDefinedTypes() { - return true; - } - @Override public String getArrayTypeName(String javaElementTypeName, String elementTypeName, Integer maxLength) { return ( javaElementTypeName == null ? elementTypeName : javaElementTypeName ) + "Array"; @@ -1032,29 +1017,21 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry } - final String mapperName = configurationService.getSetting( "hibernate.type.json_format_mapper", - StandardConverters.STRING,JACKSON_MAPPER_NAME); - if ( getVersion().isSameOrAfter( 21 ) ) { - - if ( !isOracleOsonDisabled() && JacksonIntegration.isOracleOsonExtensionAvailable() && JACKSON_MAPPER_NAME.equalsIgnoreCase( mapperName )) { + final boolean osonDisabled = getBoolean( ORACLE_OSON_DISABLED , configurationService.getSettings() ); + if ( !osonDisabled && JacksonIntegration.isOracleOsonExtensionAvailable() && isJacksonJsonFormatMapper( configurationService )) { // We must check that that extension is available and actually used. typeContributions.contributeJdbcType( OracleOsonJacksonJdbcType.INSTANCE ); typeContributions.contributeJdbcTypeConstructor( OracleOsonArrayJdbcTypeConstructor.INSTANCE ); - DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, - "Oracle OSON Jackson extension used" ); - osonExtensionEnabled = true; + DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON Jackson extension used" ); } else { - if (DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.isDebugEnabled()) { - DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, - "Oracle OSON Jackson extension not used" ); - DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, + if (DIALECT_LOGGER.isDebugEnabled()) { + DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON Jackson extension not used" ); + DIALECT_LOGGER.log( Logger.Level.DEBUG, "JacksonIntegration.isOracleOsonExtensionAvailable(): " + JacksonIntegration.isOracleOsonExtensionAvailable()); - DIALECT_MESSAGE_LOGGER.DIALECT_LOGGER.log( Logger.Level.DEBUG, - "hibernate.type.json_format_mapper : " + mapperName); } typeContributions.contributeJdbcType( OracleJsonJdbcType.INSTANCE ); typeContributions.contributeJdbcTypeConstructor( OracleJsonArrayJdbcTypeConstructor.NATIVE_INSTANCE ); @@ -1105,7 +1082,7 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry @Override public AggregateSupport getAggregateSupport() { - return OracleAggregateSupport.valueOf( this ,!osonExtensionEnabled ); + return OracleAggregateSupport.valueOf( this ); } @Override @@ -1521,36 +1498,12 @@ public String getForUpdateSkipLockedString(String aliases) { return " for update of " + aliases + " skip locked"; } - private String withTimeout(String lockString, Timeout timeout) { - return withTimeout( lockString, timeout.milliseconds() ); - } - - @Override - public String getWriteLockString(Timeout timeout) { - return withTimeout( getForUpdateString(), timeout ); - } - - @Override - public String getWriteLockString(String aliases, Timeout timeout) { - return withTimeout( getForUpdateString(aliases), timeout ); - } - - @Override - public String getReadLockString(Timeout timeout) { - return getWriteLockString( timeout ); - } - - @Override - public String getReadLockString(String aliases, Timeout timeout) { - return getWriteLockString( aliases, timeout ); - } - private String withTimeout(String lockString, int timeout) { return switch (timeout) { - case Timeouts.NO_WAIT_MILLI -> supportsNoWait() ? lockString + " nowait" : lockString; - case Timeouts.SKIP_LOCKED_MILLI -> supportsSkipLocked() ? lockString + " skip locked" : lockString; - case Timeouts.WAIT_FOREVER_MILLI -> lockString; - default -> supportsWait() ? lockString + " wait " + Timeouts.getTimeoutInSeconds( timeout ) : lockString; + case NO_WAIT -> supportsNoWait() ? lockString + " nowait" : lockString; + case SKIP_LOCKED -> supportsSkipLocked() ? lockString + " skip locked" : lockString; + case WAIT_FOREVER -> lockString; + default -> supportsWait() ? lockString + " wait " + getTimeoutInSeconds( timeout ) : lockString; }; } @@ -1726,10 +1679,10 @@ public String generatedAs(String generatedAs) { } @Override - public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData metadata) + public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) throws SQLException { builder.setAutoQuoteInitialUnderscore( true ); - return super.buildIdentifierHelper( builder, metadata ); + return super.buildIdentifierHelper( builder, dbMetaData ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java index d4c296ff0c13..55f290752e0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/aggregate/OracleAggregateSupport.java @@ -49,14 +49,13 @@ public class OracleAggregateSupport extends AggregateSupportImpl { - protected static final AggregateSupport V23_INSTANCE = new OracleAggregateSupport( true, JsonSupport.OSON, true ); + protected static final AggregateSupport V23_INSTANCE = new OracleAggregateSupport( true, JsonSupport.OSON ); // Special instance used when an Oracle OSON extension is available and used - protected static final AggregateSupport V23_OSON_EXT_INSTANCE = new OracleAggregateSupport( true, JsonSupport.OSON,false); - protected static final AggregateSupport V21_INSTANCE = new OracleAggregateSupport( false, JsonSupport.OSON, true ); - protected static final AggregateSupport V19_INSTANCE = new OracleAggregateSupport( false, JsonSupport.MERGEPATCH , true); - protected static final AggregateSupport V18_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY_AND_PATH, true ); - protected static final AggregateSupport V12_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY , true); - protected static final AggregateSupport LEGACY_INSTANCE = new OracleAggregateSupport( false, JsonSupport.NONE , true); + protected static final AggregateSupport V21_INSTANCE = new OracleAggregateSupport( false, JsonSupport.OSON ); + protected static final AggregateSupport V19_INSTANCE = new OracleAggregateSupport( false, JsonSupport.MERGEPATCH ); + protected static final AggregateSupport V18_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY_AND_PATH ); + protected static final AggregateSupport V12_INSTANCE = new OracleAggregateSupport( false, JsonSupport.QUERY ); + protected static final AggregateSupport LEGACY_INSTANCE = new OracleAggregateSupport( false, JsonSupport.NONE ); private static final String JSON_QUERY_START = "json_query("; private static final String JSON_QUERY_JSON_END = "' returning json)"; @@ -70,17 +69,13 @@ public class OracleAggregateSupport extends AggregateSupportImpl { private final boolean checkConstraintSupport; private final JsonSupport jsonSupport; - private final boolean dateTypesStoreAsString; - private OracleAggregateSupport(boolean checkConstraintSupport, JsonSupport jsonSupport, boolean dateTypesStoreAsString) { + private OracleAggregateSupport(boolean checkConstraintSupport, JsonSupport jsonSupport) { this.checkConstraintSupport = checkConstraintSupport; this.jsonSupport = jsonSupport; - // this flag tell us if data is serialized/de-serialized as String. As opposed to using OSON - // In other words, this flag tells us if the Oracle OSON JDBC extension is used or not. - this.dateTypesStoreAsString = dateTypesStoreAsString; } - public static AggregateSupport valueOf(Dialect dialect, boolean useDateStoredAsString) { + public static AggregateSupport valueOf(Dialect dialect) { final DatabaseVersion version = dialect.getVersion(); return switch ( version.getMajor() ) { case 12, 13, 14, 15, 16, 17 -> V12_INSTANCE; @@ -88,12 +83,16 @@ public static AggregateSupport valueOf(Dialect dialect, boolean useDateStoredAsS case 19, 20 -> V19_INSTANCE; case 21, 22 -> V21_INSTANCE; default -> version.isSameOrAfter( 23 ) - ? useDateStoredAsString?OracleAggregateSupport.V23_INSTANCE: - OracleAggregateSupport.V23_OSON_EXT_INSTANCE - : OracleAggregateSupport.LEGACY_INSTANCE; + ? OracleAggregateSupport.V23_INSTANCE + : OracleAggregateSupport.LEGACY_INSTANCE; }; } + private boolean supportsOson() { + // OSON is supported when check constraints are supported + return checkConstraintSupport; + } + @Override public String aggregateComponentCustomReadExpression( String template, @@ -147,17 +146,17 @@ public String aggregateComponentCustomReadExpression( ); case DATE: - if (this.dateTypesStoreAsString) { + if (supportsOson()) { + // Oracle OSON extension is used, value is not stored as string return template.replace( placeholder, - "to_date(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD')" + "json_value(" + parentPartExpression + columnExpression + "' returning date)" ); } else { - // Oracle OSON extension is used, value is not stored as string return template.replace( placeholder, - "json_value(" + parentPartExpression + columnExpression + "' returning date)" + "to_date(substr(json_value(" + parentPartExpression + columnExpression + "'),1,10),'YYYY-MM-DD')" ); } @@ -167,45 +166,31 @@ public String aggregateComponentCustomReadExpression( "to_timestamp(json_value(" + parentPartExpression + columnExpression + "'),'hh24:mi:ss')" ); case TIMESTAMP: - if (this.dateTypesStoreAsString) { - return template.replace( - placeholder, - "to_timestamp(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9')" - ); - } - else { - - return template.replace( - placeholder, - "json_value(" + parentPartExpression + columnExpression + "' returning timestamp)" - ); - } - case DURATION: - if (this.dateTypesStoreAsString) { + if (supportsOson()) { return template.replace( placeholder, - "cast(json_value(" + parentPartExpression + columnExpression + "') as " + column.getColumnDefinition() + ')' + "json_value(" + parentPartExpression + columnExpression + "' returning timestamp(9))" ); } else { return template.replace( placeholder, - "json_value(" + parentPartExpression + columnExpression + "' returning interval day to second)" + "to_timestamp(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9')" ); } case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_UTC: - if (this.dateTypesStoreAsString) { + if (supportsOson()) { + // Oracle OSON extension is used, value is not stored as string return template.replace( placeholder, - "to_timestamp_tz(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9TZH:TZM')" + "json_value(" + parentPartExpression + columnExpression + "' returning timestamp(9) with time zone)" ); } else { - // Oracle OSON extension is used, value is not stored as string return template.replace( placeholder, - "json_value(" + parentPartExpression + columnExpression + "')" + "to_timestamp_tz(json_value(" + parentPartExpression + columnExpression + "'),'YYYY-MM-DD\"T\"hh24:mi:ss.FF9TZH:TZM')" ); } case UUID: @@ -236,13 +221,8 @@ public String aggregateComponentCustomReadExpression( final BasicPluralType<?, ?> pluralType = (BasicPluralType<?, ?>) column.getJdbcMapping(); final OracleArrayJdbcType jdbcType = (OracleArrayJdbcType) pluralType.getJdbcType(); switch ( jdbcType.getElementJdbcType().getDefaultSqlTypeCode() ) { - - case DATE: - return template.replace( - placeholder, - "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' - ); case BOOLEAN: + case DATE: case TIME: case TIMESTAMP: case TIMESTAMP_WITH_TIMEZONE: @@ -251,11 +231,15 @@ public String aggregateComponentCustomReadExpression( case VARBINARY: case LONG32VARBINARY: case UUID: - default: return template.replace( placeholder, jdbcType.getSqlTypeName() + "_from_json(json_query(" + parentPartExpression + columnExpression + "' returning " + jsonTypeName + "))" ); + default: + return template.replace( + placeholder, + "json_value(" + parentPartExpression + columnExpression + "' returning " + column.getColumnDefinition() + ')' + ); } case JSON: case JSON_ARRAY: From 44048c3cc97892b10d06ba29658379ac9c21c662 Mon Sep 17 00:00:00 2001 From: Christian Beikov <christian.beikov@gmail.com> Date: Thu, 17 Apr 2025 11:52:59 +0200 Subject: [PATCH 63/81] HHH-17404 Address issues in StringJsonDocumentReader --- .../type/format/StringJsonDocumentReader.java | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index 7b025facacb2..fa034cc4c594 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -222,8 +222,10 @@ public JsonDocumentItemType next() { } } } - // no way we get here. - return null; + throw new IllegalStateException( "unexpected end of JSON ["+ + this.jsonString.substring( this.jsonValueStart,this.jsonValueEnd )+ + "] in current processing state " + + this.processingStates.getCurrent() ); } /** @@ -295,31 +297,17 @@ private void moveTo(char character) throws IllegalStateException { throw new IllegalStateException("character [" + character + "] is not the next non-blank character"); } - /** - * Goes through the JSON string to locate a non-escaped instance of a given character. - * Ex: on 'AB\"C"' this method returns 5 (not 3) - * - * @param character character to be found - * @return the position of the character or -1 if not found. - */ - private int locateCharacter(char character) { + private int nextQuote() { int pointer = this.position; while ( pointer < this.limit) { final char c = this.jsonString.charAt( pointer ); if (c == ESCAPE_CHAR) { - // We encountered an escape character. - // We should just skip the next one as it is either the expected character - // but as escaped one, we should ignore it, either this is something else - // and we should ignore it also - pointer += 2; - continue; + pointer++; } - else { - if ( c == character ) { - // found - return pointer; - } + else if (c == '"') { + // found + return pointer; } pointer++; } @@ -366,7 +354,7 @@ private void consumeQuotedString() { this.position++; //locate ending quote - int endingQuote = locateCharacter( StringJsonDocumentMarker.QUOTE.getMarkerCharacter()); + int endingQuote = nextQuote(); if (endingQuote == -1) { throw new IllegalStateException("Can't find ending quote of key name"); } From b6f711ac1a1c32faa591b1803b7442633d133d26 Mon Sep 17 00:00:00 2001 From: Christian Beikov <christian.beikov@gmail.com> Date: Thu, 17 Apr 2025 14:12:33 +0200 Subject: [PATCH 64/81] HHH-17404 Cleanup JsonHelper and fix nested parsing --- .../OracleOsonJacksonArrayJdbcType.java | 73 ++- .../dialect/OracleOsonJacksonJdbcType.java | 61 +- .../descriptor/jdbc/JsonArrayJdbcType.java | 34 +- .../type/descriptor/jdbc/JsonHelper.java | 542 +++++++----------- .../type/descriptor/jdbc/JsonJdbcType.java | 18 +- .../type/format/StringJsonDocumentWriter.java | 200 ++++++- .../embeddable/JsonEmbeddableArrayTest.java | 2 +- .../util/StringJsonDocumentWriterTest.java | 30 +- 8 files changed, 506 insertions(+), 454 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java index be5d13b46df7..28ee7d868faf 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java @@ -4,16 +4,12 @@ */ package org.hibernate.dialect; -import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; import oracle.jdbc.driver.DatabaseError; -import oracle.jdbc.provider.oson.OsonFactory; import oracle.sql.json.OracleJsonDatum; -import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; -import oracle.sql.json.OracleJsonParser; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -22,12 +18,12 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JsonJdbcType; -import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.OsonDocumentReader; import org.hibernate.type.format.OsonDocumentWriter; @@ -39,6 +35,9 @@ import java.sql.ResultSet; import java.sql.SQLException; +import static org.hibernate.dialect.OracleOsonJacksonJdbcType.OSON_JACKSON_FACTORY; +import static org.hibernate.dialect.OracleOsonJacksonJdbcType.OSON_JSON_FACTORY; + /** * * Type mapping of (JSON) array of JSON SQL data type for Oracle database. @@ -52,8 +51,6 @@ public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJacksonArrayJdbcType.class ); - private static final OsonFactory osonFactory = new OsonFactory(); - public OracleOsonJacksonArrayJdbcType(JdbcType elementJdbcType) { super(elementJdbcType); } @@ -70,29 +67,31 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { return new BasicBinder<>( javaType, this ) { - private <X> byte[] toOsonStream(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { + private <T> byte[] toOsonStream(T value, JavaType<T> javaType, WrapperOptions options) throws Exception { final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out )) { - OsonDocumentWriter writer = new OsonDocumentWriter( generator ); - - if ( getElementJdbcType() instanceof JsonJdbcType jsonElementJdbcType ) { - final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); - JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer ); + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (OracleJsonGenerator generator = OSON_JSON_FACTORY.createJsonBinaryGenerator( out )) { + final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) javaType).getElementJavaType(); + if ( elementJavaType instanceof UnknownBasicJavaType<?> ) { + options.getJsonFormatMapper().writeToTarget( value, javaType, generator, options); } else { - assert !( getElementJdbcType() instanceof AggregateJdbcType ); - final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); - JsonHelper.serializeArray( - elementJavaType, - getElementJdbcType(), - domainObjects, - options, - writer - ); + final OsonDocumentWriter writer = new OsonDocumentWriter( generator ); + if ( getElementJdbcType() instanceof JsonJdbcType jsonElementJdbcType ) { + final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); + JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer ); + } + else { + assert !(getElementJdbcType() instanceof AggregateJdbcType); + JsonHelper.serializeArray( + elementJavaType, + getElementJdbcType(), + domainObjects, + options, + writer + ); + } } - generator.close(); return out.toByteArray(); } @@ -127,20 +126,20 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { - FormatMapper mapper = options.getJsonFormatMapper(); - OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( osonBytes ); - final JdbcType elementJdbcType = getElementJdbcType(); - if (elementJdbcType instanceof JsonJdbcType) { - if (((JsonJdbcType) elementJdbcType).getEmbeddableMappingType() != null) { - // embeddable array case. - return JsonHelper.deserializeArray( javaType, - elementJdbcType, new OsonDocumentReader( osonParser ), options ); + if ( ((BasicPluralJavaType<?>) getJavaType()).getElementJavaType() instanceof UnknownBasicJavaType<?> ) { + try (JsonParser oParser = OSON_JACKSON_FACTORY.createParser( osonBytes )) { + return options.getJsonFormatMapper().readFromSource( getJavaType(), oParser, options ); } } - try (JsonParser oParser = ((JsonFactory)osonFactory).createParser( osonBytes )) { - return mapper.readFromSource( getJavaType(), oParser, options); + else { + // embeddable array case. + return JsonHelper.deserializeArray( + javaType, + getElementJdbcType(), + new OsonDocumentReader( OSON_JSON_FACTORY.createJsonBinaryParser( osonBytes ) ), + options + ); } - } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java index fbff6af6a8ad..d2a1894a30c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java @@ -4,7 +4,6 @@ */ package org.hibernate.dialect; -import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; @@ -13,7 +12,6 @@ import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; -import oracle.sql.json.OracleJsonParser; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -25,7 +23,6 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; -import org.hibernate.type.format.FormatMapper; import org.hibernate.type.format.OsonDocumentReader; import org.hibernate.type.format.OsonDocumentWriter; @@ -50,7 +47,8 @@ public class OracleOsonJacksonJdbcType extends OracleJsonJdbcType { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJacksonJdbcType.class ); - private static final OsonFactory osonFactory = new OsonFactory(); + static final OsonFactory OSON_JACKSON_FACTORY = new OsonFactory(); + static final OracleJsonFactory OSON_JSON_FACTORY = new OracleJsonFactory(); private OracleOsonJacksonJdbcType(EmbeddableMappingType embeddableMappingType) { super( embeddableMappingType ); @@ -78,23 +76,23 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { return new BasicBinder<>( javaType, this ) { - private <X> byte[] toOson(X value, JavaType<X> javaType, WrapperOptions options) throws Exception { - - FormatMapper mapper = options.getJsonFormatMapper(); - + private <T> byte[] toOson(T value, JavaType<T> javaType, WrapperOptions options) throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); if (getEmbeddableMappingType() != null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); // OracleJsonFactory is used and not OracleOsonFactory as Jackson is not involved here - try (OracleJsonGenerator generator = new OracleJsonFactory().createJsonBinaryGenerator( out )) { - OsonDocumentWriter writer = new OsonDocumentWriter( generator); - JsonHelper.serialize( getEmbeddableMappingType(), value,options,writer); + try (OracleJsonGenerator generator = OSON_JSON_FACTORY.createJsonBinaryGenerator( out )) { + JsonHelper.serialize( + getEmbeddableMappingType(), + value, + options, + new OsonDocumentWriter( generator ) + ); } - return out.toByteArray(); } - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - try (JsonGenerator osonGen = osonFactory.createGenerator( out )) { - mapper.writeToTarget( value, javaType, osonGen, options ); + else { + try (JsonGenerator osonGen = OSON_JACKSON_FACTORY.createGenerator( out )) { + options.getJsonFormatMapper().writeToTarget( value, javaType, osonGen, options ); + } } return out.toByteArray(); } @@ -133,33 +131,18 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { return new BasicExtractor<>( javaType, this ) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { - - FormatMapper mapper = options.getJsonFormatMapper(); - - if (getEmbeddableMappingType() != null && - getJavaType().getJavaTypeClass() == Object[].class) { - // We are dealing with embeddable (@Embeddable) and we request - // an array of objects. We use JsonParser to fetch values - // and build the array.(as opposed to let Jackson do it as we do not - // have a proper object definition at that stage). - OracleJsonParser osonParser = new OracleJsonFactory().createJsonBinaryParser( osonBytes ); - Object[] objects = JsonHelper.deserialize( + if ( getEmbeddableMappingType() != null ) { + return JsonHelper.deserialize( getEmbeddableMappingType(), - new OsonDocumentReader(osonParser), + new OsonDocumentReader( OSON_JSON_FACTORY.createJsonBinaryParser( osonBytes ) ), javaType.getJavaTypeClass() != Object[].class, options ); - return (X) objects; } - - JavaType <X> type = getJavaType(); - if (getEmbeddableMappingType() != null) { - // We are dealing with embeddable (@Embeddable) - type = (JavaType<X>) getEmbeddableMappingType().getJavaType(); - } - - try (JsonParser osonParser = ((JsonFactory)osonFactory).createParser( osonBytes )) { - return mapper.readFromSource( type, osonParser, options ); + else { + try (JsonParser osonParser = OSON_JACKSON_FACTORY.createParser( osonBytes )) { + return options.getJsonFormatMapper().readFromSource( getJavaType(), osonParser, options ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java index 6e2b3e640dab..5784a5efd18d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonArrayJdbcType.java @@ -16,6 +16,7 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.spi.UnknownBasicJavaType; import org.hibernate.type.format.StringJsonDocumentWriter; /** @@ -59,24 +60,33 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o if ( string == null ) { return null; } - return JsonHelper.arrayFromString( javaType, this.getElementJdbcType(), string, options ); + if ( ((BasicPluralJavaType<?>) javaType).getElementJavaType() instanceof UnknownBasicJavaType<?> ) { + return options.getJsonFormatMapper().fromString( string, javaType, options ); + } + else { + return JsonHelper.arrayFromString( javaType, this.getElementJdbcType(), string, options ); + } } protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) { - final JdbcType elementJdbcType = getElementJdbcType(); - final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); - if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { - final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); - JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer); + final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); + if ( elementJavaType instanceof UnknownBasicJavaType<?> ) { + return options.getJsonFormatMapper().toString( value, javaType, options); } else { - assert !( elementJdbcType instanceof AggregateJdbcType ); - final JavaType<?> elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType(); - JsonHelper.serializeArray( elementJavaType, elementJdbcType, domainObjects, options, writer ); + final JdbcType elementJdbcType = getElementJdbcType(); + final Object[] domainObjects = javaType.unwrap( value, Object[].class, options ); + final StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); + if ( elementJdbcType instanceof JsonJdbcType jsonElementJdbcType ) { + final EmbeddableMappingType embeddableMappingType = jsonElementJdbcType.getEmbeddableMappingType(); + JsonHelper.serializeArray( embeddableMappingType, domainObjects, options, writer ); + } + else { + assert !(elementJdbcType instanceof AggregateJdbcType); + JsonHelper.serializeArray( elementJavaType, elementJdbcType, domainObjects, options, writer ); + } + return writer.getJson(); } - return sb.toString(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index abbd2512b2e1..52274ed6654b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -6,7 +6,6 @@ import java.io.IOException; -import java.io.OutputStream; import java.lang.reflect.Array; import java.sql.SQLException; import java.util.AbstractCollection; @@ -17,16 +16,18 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; + +import org.checkerframework.checker.nullness.qual.Nullable; import org.hibernate.Internal; import org.hibernate.internal.build.AllowReflection; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.type.BasicPluralType; import org.hibernate.type.BasicType; import org.hibernate.type.SqlTypes; @@ -238,32 +239,80 @@ else if ( attributeMapping instanceof EmbeddedAttributeMapping ) { */ private static <X> X consumeJsonDocumentItems(JsonDocumentReader reader, EmbeddableMappingType embeddableMappingType, boolean returnEmbeddable, WrapperOptions options) throws SQLException { - // final result of a mapped object array - Object [] objectArrayResult; - // current mapping to be used - SelectableMapping currentSelectableMapping = null; - String currentKeyName = null; - List<Object> subArrayObjectList = null; - BasicPluralType<?, ?> subArrayObjectTypes = null; - - // mapping definitions are in a tree - // Each mapping definition may contain sub mappings (sub embeddable mapping) - // This stack is used to keep a pointer on the current mapping to be used to assign correct types. - // see onStartObject()/onEndObject() methods - StandardStack<EmbeddableMappingType> embeddableMappingTypes = new StandardStack<>(); - // As for mapping definitions, when "sub embeddable" is encountered, the array - // that needs to be filled with Objects is the one we allocate in the final result array slot. - // We use a stack to keep track of array ref - StandardStack<Object[]> objectArrays = new StandardStack<>(); - - // index within objectArrayResult - int currentSelectableIndexInResultArray = -1; - - JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,returnEmbeddable); - - embeddableMappingTypes.push(embeddableMappingType); - objectArrayResult = new Object[embeddableMappingType.getJdbcValueCount()+ ( embeddableMappingType.isPolymorphic() ? 1 : 0 )]; - objectArrays.push( objectArrayResult ); + record SelectableData(String selectableName, int selectableIndex, SelectableMapping selectableMapping){} + record ParseLevel( + @Nullable SelectableData selectableData, + @Nullable EmbeddableMappingType embeddableMappingType, + @Nullable BasicPluralType<?, ?> arrayType, + @Nullable List<Object> subArrayObjectList, + @Nullable Object [] objectArray + ) { + ParseLevel(EmbeddableMappingType embeddableMappingType) { + this(null, embeddableMappingType); + } + ParseLevel(@Nullable SelectableData selectableData, EmbeddableMappingType embeddableMappingType) { + this( + selectableData, + embeddableMappingType, + null, + null, + new Object[embeddableMappingType.getJdbcValueCount()+ ( embeddableMappingType.isPolymorphic() ? 1 : 0 )] + ); + } + ParseLevel(@Nullable SelectableData selectableData, BasicPluralType<?, ?> arrayType) { + this( selectableData, null, arrayType, new ArrayList<>(), null ); + } + + public void addValue(@Nullable SelectableData selectableData, @Nullable Object value) { + if ( embeddableMappingType != null ) { + assert selectableData != null; + objectArray[selectableData.selectableIndex] = value; + } + else { + assert subArrayObjectList != null; + subArrayObjectList.add(value); + } + } + + public JdbcMapping determineJdbcMapping(@Nullable SelectableData currentSelectableData) { + if ( currentSelectableData != null ) { + return currentSelectableData.selectableMapping.getJdbcMapping(); + } + else if ( arrayType != null ) { + return arrayType.getElementType(); + } + else { + assert selectableData != null; + return selectableData.selectableMapping.getJdbcMapping(); + } + } + + public static String determineSelectablePath(StandardStack<ParseLevel> parseLevel, @Nullable SelectableData currentSelectableData) { + if ( currentSelectableData != null ) { + return currentSelectableData.selectableName; + } + else { + return determineSelectablePath( parseLevel, 0 ); + } + } + + private static String determineSelectablePath(StandardStack<ParseLevel> stack, int level) { + final ParseLevel parseLevel = stack.peek( level ); + assert parseLevel != null; + if ( parseLevel.selectableData != null ) { + return parseLevel.selectableData.selectableName; + } + else { + assert parseLevel.arrayType != null; + return determineSelectablePath( stack, level + 1 ) + ".{element}"; + } + } + } + final StandardStack<ParseLevel> parseLevel = new StandardStack<>(); + final JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,returnEmbeddable); + + parseLevel.push(new ParseLevel( embeddableMappingType )); + SelectableData currentSelectableData = null; // We loop on two conditions: // - the parser still has tokens left @@ -273,117 +322,141 @@ private static <X> X consumeJsonDocumentItems(JsonDocumentReader reader, Embedda // the array is not empty, but we ae done parsing that specific object. // When we encounter OBJECT_END the current type is popped out of the stack. When parsing one object of an array we may end up // having an empty stack. Next Objects are parsed in the next round. - while(reader.hasNext() && !embeddableMappingTypes.isEmpty()) { - JsonDocumentItemType type = reader.next(); - switch (type) { - case VALUE_KEY: - currentKeyName = reader.getObjectKeyName(); - - currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); - if ( currentSelectableIndexInResultArray >= 0 ) { - // we may not have a selectable mapping for that key - currentSelectableMapping = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( currentSelectableIndexInResultArray ); - } - else { + while(reader.hasNext() && !parseLevel.isEmpty()) { + final ParseLevel currentLevel = parseLevel.getCurrent(); + assert currentLevel != null; + switch (reader.next()) { + case VALUE_KEY -> { + final EmbeddableMappingType currentEmbeddableMappingType = currentLevel.embeddableMappingType; + assert currentEmbeddableMappingType != null + : "Value keys are only valid for objects"; + + assert currentSelectableData == null; + + final String selectableName = reader.getObjectKeyName(); + final int selectableIndex = currentEmbeddableMappingType.getSelectableIndex( selectableName ); + if ( selectableIndex < 0 ) { throw new IllegalArgumentException( String.format( "Could not find selectable [%s] in embeddable type [%s] for JSON processing.", - currentKeyName, - embeddableMappingTypes.getCurrent().getMappedJavaType().getJavaTypeClass().getName() + selectableName, + currentEmbeddableMappingType.getMappedJavaType().getJavaTypeClass().getName() ) ); } - break; - case ARRAY_START: - assert (subArrayObjectList == null && subArrayObjectTypes == null) : "ARRAY_START item received twice in a row"; - - // initialize an array to gather values - subArrayObjectList = new ArrayList<>(); - assert (currentSelectableMapping.getJdbcMapping() instanceof BasicPluralType<?, ?>) - : "Array event received for non plural type"; - // initialize array's element type - subArrayObjectTypes = (BasicPluralType<?, ?>) currentSelectableMapping.getJdbcMapping(); - break; - case ARRAY_END: - assert (subArrayObjectList != null && subArrayObjectTypes != null) : "ARRAY_END item received twice in a row"; + final SelectableMapping selectableMapping = + currentEmbeddableMappingType.getJdbcValueSelectable( selectableIndex ); + currentSelectableData = new SelectableData( selectableName, selectableIndex, selectableMapping ); + } + case ARRAY_START -> { + assert currentSelectableData != null; + + if ( !(currentSelectableData.selectableMapping.getJdbcMapping() instanceof BasicPluralType<?, ?> pluralType) ) { + throw new IllegalArgumentException( + String.format( + "Can't parse JSON array for selectable [%s] which is not of type BasicPluralType.", + ParseLevel.determineSelectablePath( parseLevel, currentSelectableData ) + ) + ); + } + parseLevel.push( new ParseLevel( currentSelectableData, pluralType ) ); + currentSelectableData = null; + } + case ARRAY_END -> { + assert currentLevel.arrayType != null; + assert currentLevel.selectableData != null; + + parseLevel.pop(); + final ParseLevel parentLevel = parseLevel.getCurrent(); + + assert parentLevel.embeddableMappingType != null; // flush array values - objectArrays.getCurrent()[currentSelectableIndexInResultArray] = subArrayObjectTypes.getJdbcJavaType().wrap( subArrayObjectList, options ); - // reset until we encounter next array element - subArrayObjectList = null; - subArrayObjectTypes = null; - break; - case OBJECT_START: - if (currentKeyName != null) { - // We are dealing with a sub-object, allocate space for it then, - // otherwise, we have nothing to do. - // Push the new (sub)mapping definition. - assert embeddableMappingTypes.getCurrent() != null; - currentSelectableIndexInResultArray = embeddableMappingTypes.getCurrent().getSelectableIndex( currentKeyName ); - assert currentSelectableIndexInResultArray != -1: "Cannot get index of " + currentKeyName; - - final SelectableMapping selectable = embeddableMappingTypes.getCurrent().getJdbcValueSelectable( - currentSelectableIndexInResultArray ); - final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) selectable.getJdbcMapping() - .getJdbcType(); - final EmbeddableMappingType subMappingType = aggregateJdbcType.getEmbeddableMappingType(); - assert objectArrays.getCurrent() != null; - objectArrays.getCurrent()[currentSelectableIndexInResultArray] = - new Object[subMappingType.getJdbcValueCount()]; - embeddableMappingTypes.push( subMappingType ); - objectArrays.push( (Object[]) objectArrays.getCurrent()[currentSelectableIndexInResultArray] ); + parentLevel.addValue( + currentLevel.selectableData, + currentLevel.arrayType.getJdbcJavaType().wrap( currentLevel.subArrayObjectList, options ) + ); + } + case OBJECT_START -> { + final JdbcMapping jdbcMapping = currentLevel.determineJdbcMapping( currentSelectableData ); + + if ( !(jdbcMapping.getJdbcType() instanceof AggregateJdbcType aggregateJdbcType) ) { + throw new IllegalArgumentException( + String.format( + "Can't parse JSON object for selectable [%s] which is not of type AggregateJdbcType.", + ParseLevel.determineSelectablePath( parseLevel, currentSelectableData ) + ) + ); } - break; - case OBJECT_END: + parseLevel.push( + new ParseLevel( currentSelectableData, aggregateJdbcType.getEmbeddableMappingType() ) ); + currentSelectableData = null; + } + case OBJECT_END -> { + final EmbeddableMappingType currentEmbeddableMappingType = currentLevel.embeddableMappingType; + assert currentEmbeddableMappingType != null; + // go back in the mapping definition tree - embeddableMappingTypes.pop(); - objectArrays.pop(); - break; - case NULL_VALUE: - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( null ); - } - else { - objectArrays.getCurrent()[currentSelectableIndexInResultArray] = null; - } - break; - case NUMERIC_VALUE: - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( adapter.fromNumericValue( subArrayObjectTypes.getElementType().getJdbcJavaType(), - subArrayObjectTypes.getElementType().getJdbcType(),reader,options)); - } - else { - objectArrays.getCurrent()[currentSelectableIndexInResultArray] = adapter.fromNumericValue( currentSelectableMapping.getJdbcMapping().getJdbcJavaType(), - currentSelectableMapping.getJdbcMapping().getJdbcType(),reader,options); - } - break; - case BOOLEAN_VALUE: - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add( reader.getBooleanValue()?Boolean.TRUE:Boolean.FALSE); + parseLevel.pop(); + final Object objectValue; + if ( returnEmbeddable ) { + final StructAttributeValues attributeValues = StructHelper.getAttributeValues( + embeddableMappingType, + currentLevel.objectArray, + options + ); + objectValue = instantiate( embeddableMappingType, attributeValues ); } else { - objectArrays.getCurrent()[currentSelectableIndexInResultArray] = reader.getBooleanValue()?Boolean.TRUE:Boolean.FALSE; + objectValue = currentLevel.objectArray; } - break; - case VALUE: - if ( subArrayObjectList != null ) { - // dealing with arrays - subArrayObjectList.add(adapter.fromValue( subArrayObjectTypes.getElementType().getJdbcJavaType(), - subArrayObjectTypes.getElementType().getJdbcType(),reader,options)); + if ( parseLevel.isEmpty() ) { + //noinspection unchecked + return (X) objectValue; } else { - objectArrays.getCurrent()[currentSelectableIndexInResultArray] = adapter.fromValue( currentSelectableMapping.getJdbcMapping().getJdbcJavaType(), - currentSelectableMapping.getJdbcMapping().getJdbcType(),reader,options); + parseLevel.getCurrent().addValue( currentLevel.selectableData, objectValue ); } - - break; - default: - assert false: "Unexpected type " + type; + } + case NULL_VALUE -> { + currentLevel.addValue( currentSelectableData, null ); + currentSelectableData = null; + } + case NUMERIC_VALUE -> { + final JdbcMapping jdbcMapping = currentLevel.determineJdbcMapping( currentSelectableData ); + currentLevel.addValue( + currentSelectableData, + adapter.fromNumericValue( + jdbcMapping.getJdbcJavaType(), + jdbcMapping.getJdbcType(), + reader, + options + ) + ); + currentSelectableData = null; + } + case BOOLEAN_VALUE -> { + currentLevel.addValue( + currentSelectableData, + reader.getBooleanValue() ? Boolean.TRUE : Boolean.FALSE + ); + currentSelectableData = null; + } + case VALUE -> { + final JdbcMapping jdbcMapping = currentLevel.determineJdbcMapping( currentSelectableData ); + currentLevel.addValue( + currentSelectableData, + adapter.fromValue( + jdbcMapping.getJdbcJavaType(), + jdbcMapping.getJdbcType(), + reader, + options + ) + ); + currentSelectableData = null; + } } } - return (X) objectArrayResult; + throw new IllegalArgumentException( "Expected JSON object end, but none found." ); } /** @@ -401,20 +474,16 @@ public static <X> X deserialize( JsonDocumentReader reader, boolean returnEmbeddable, WrapperOptions options) throws SQLException { - - - final Object[] values = consumeJsonDocumentItems(reader, embeddableMappingType, returnEmbeddable, options); - if ( returnEmbeddable ) { - final StructAttributeValues attributeValues = StructHelper.getAttributeValues( - embeddableMappingType, - values, - options - ); - //noinspection unchecked - return (X) instantiate( embeddableMappingType, attributeValues ); + final JsonDocumentItemType event; + if ( !reader.hasNext() || ( event = reader.next() ) == JsonDocumentItemType.NULL_VALUE ) { + return null; + } + if ( event != JsonDocumentItemType.OBJECT_START ) { + throw new IllegalArgumentException("Malformed JSON. Expected object but got: " + event); } - //noinspection unchecked - return (X) values; + final X result = consumeJsonDocumentItems( reader, embeddableMappingType, returnEmbeddable, options ); + assert !reader.hasNext(); + return result; } @@ -435,7 +504,13 @@ public static <X> X deserializeArray( JdbcType elementJdbcType, JsonDocumentReader reader, WrapperOptions options) throws SQLException { - + final JsonDocumentItemType event; + if ( !reader.hasNext() || ( event = reader.next() ) == JsonDocumentItemType.NULL_VALUE ) { + return null; + } + if ( event != JsonDocumentItemType.ARRAY_START ) { + throw new IllegalArgumentException("Malformed JSON. Expected array but got: " + event); + } final CustomArrayList arrayList = new CustomArrayList(); final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) javaType).getElementJavaType(); @@ -448,17 +523,13 @@ public static <X> X deserializeArray( jdbcJavaType = options.getTypeConfiguration().getJavaTypeRegistry().resolveDescriptor( preferredJavaTypeClass ); } - JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,false); - - assert reader.hasNext():"Invalid array string"; - assert reader.next() == JsonDocumentItemType.ARRAY_START:"Invalid start of array"; - boolean endArrayFound = false; + final JsonValueJDBCTypeAdapter adapter = JsonValueJDBCTypeAdapterFactory.getAdapter(reader,false); while(reader.hasNext()) { JsonDocumentItemType type = reader.next(); switch ( type ) { case ARRAY_END: - endArrayFound=true; - break; + assert !reader.hasNext(); + return javaType.wrap( arrayList, options ); case NULL_VALUE: arrayList.add( null ); break; @@ -473,197 +544,18 @@ public static <X> X deserializeArray( break; case OBJECT_START: assert elementJdbcType instanceof JsonJdbcType; - Object o = deserialize( - ((JsonJdbcType) elementJdbcType).getEmbeddableMappingType(), - reader, - true, - options); - arrayList.add(o); + final EmbeddableMappingType embeddableMappingType = ((JsonJdbcType) elementJdbcType).getEmbeddableMappingType(); + arrayList.add( consumeJsonDocumentItems(reader, embeddableMappingType, true, options) ); break; default: throw new UnsupportedOperationException( "Unexpected JSON type " + type ); } } - - assert endArrayFound:"Invalid end of array"; - return javaType.wrap( arrayList, options ); + throw new IllegalArgumentException( "Expected JSON array end, but none found." ); } - - public static class JsonAppender extends OutputStream implements SqlAppender { - - private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - - private final StringBuilder sb; - private boolean escape; - - public JsonAppender(StringBuilder sb) { - this.sb = sb; - } - - @Override - public void appendSql(String fragment) { - append( fragment ); - } - - @Override - public void appendSql(char fragment) { - append( fragment ); - } - - @Override - public void appendSql(int value) { - sb.append( value ); - } - - @Override - public void appendSql(long value) { - sb.append( value ); - } - - @Override - public void appendSql(boolean value) { - sb.append( value ); - } - - @Override - public String toString() { - return sb.toString(); - } - - public void startEscaping() { - assert !escape; - escape = true; - } - - public void endEscaping() { - assert escape; - escape = false; - } - - @Override - public JsonAppender append(char fragment) { - if ( escape ) { - appendEscaped( fragment ); - } - else { - sb.append( fragment ); - } - return this; - } - - @Override - public JsonAppender append(CharSequence csq) { - return append( csq, 0, csq.length() ); - } - - @Override - public JsonAppender append(CharSequence csq, int start, int end) { - if ( escape ) { - int len = end - start; - sb.ensureCapacity( sb.length() + len ); - for ( int i = start; i < end; i++ ) { - appendEscaped( csq.charAt( i ) ); - } - } - else { - sb.append( csq, start, end ); - } - return this; - } - - @Override - public void write(int v) { - final String hex = Integer.toHexString( v ); - sb.ensureCapacity( sb.length() + hex.length() + 1 ); - if ( ( hex.length() & 1 ) == 1 ) { - sb.append( '0' ); - } - sb.append( hex ); - } - - @Override - public void write(byte[] bytes) { - write(bytes, 0, bytes.length); - } - - @Override - public void write(byte[] bytes, int off, int len) { - sb.ensureCapacity( sb.length() + ( len << 1 ) ); - for ( int i = 0; i < len; i++ ) { - final int v = bytes[off + i] & 0xFF; - sb.append( HEX_ARRAY[v >>> 4] ); - sb.append( HEX_ARRAY[v & 0x0F] ); - } - } - - private void appendEscaped(char fragment) { - switch ( fragment ) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - // 8 is '\b' - // 9 is '\t' - // 10 is '\n' - case 11: - // 12 is '\f' - // 13 is '\r' - case 14: - case 15: - case 16: - case 17: - case 18: - case 19: - case 20: - case 21: - case 22: - case 23: - case 24: - case 25: - case 26: - case 27: - case 28: - case 29: - case 30: - case 31: - sb.append( "\\u" ).append( Integer.toHexString( fragment ) ); - break; - case '\b': - sb.append("\\b"); - break; - case '\t': - sb.append("\\t"); - break; - case '\n': - sb.append("\\n"); - break; - case '\f': - sb.append("\\f"); - break; - case '\r': - sb.append("\\r"); - break; - case '"': - sb.append( "\\\"" ); - break; - case '\\': - sb.append( "\\\\" ); - break; - default: - sb.append( fragment ); - break; - } - } - - } - private static class CustomArrayList extends AbstractCollection<Object> implements Collection<Object> { Object[] array = ArrayHelper.EMPTY_OBJECT_ARRAY; int size; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java index c927c262f38a..59b5b70d5935 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonJdbcType.java @@ -77,7 +77,7 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o return null; } if ( embeddableMappingType != null ) { - return (X) JsonHelper.deserialize( + return JsonHelper.deserialize( embeddableMappingType, new StringJsonDocumentReader(string), javaType.getJavaTypeClass() != Object[].class, @@ -90,11 +90,10 @@ protected <X> X fromString(String string, JavaType<X> javaType, WrapperOptions o @Override public Object createJdbcValue(Object domainValue, WrapperOptions options) throws SQLException { assert embeddableMappingType != null; - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + final StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); try { - JsonHelper.serialize( embeddableMappingType ,domainValue,options, writer ); - return writer.toString(); + JsonHelper.serialize( embeddableMappingType, domainValue, options, writer ); + return writer.getJson(); } catch (IOException e) { throw new SQLException( e ); @@ -104,16 +103,15 @@ public Object createJdbcValue(Object domainValue, WrapperOptions options) throws @Override public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) throws SQLException { assert embeddableMappingType != null; - return JsonHelper.deserialize( embeddableMappingType, new StringJsonDocumentReader( (String)rawJdbcValue ), false, options ); + return JsonHelper.deserialize( embeddableMappingType, new StringJsonDocumentReader( (String) rawJdbcValue ), false, options ); } protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) { if ( embeddableMappingType != null ) { try { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); - JsonHelper.serialize( embeddableMappingType, value, options, writer); - return writer.toString(); + final StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); + JsonHelper.serialize( embeddableMappingType, value, options, writer ); + return writer.getJson(); } catch (IOException e) { throw new RuntimeException("Failed to serialize JSON mapping", e ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java index c4e0aae2c8b1..48e3dd3bb090 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -4,7 +4,7 @@ */ package org.hibernate.type.format; -import org.hibernate.dialect.JsonHelper; +import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BooleanJavaType; @@ -14,30 +14,34 @@ import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; +import java.io.OutputStream; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; /** * Implementation of <code>JsonDocumentWriter</code> for String-based OSON document. - * This implementation will receive a {@link JsonHelper.JsonAppender } to a serialze JSON object to it + * This implementation will receive a {@link JsonAppender } to a serialze JSON object to it * @author Emmanuel Jannetti */ public class StringJsonDocumentWriter extends StringJsonDocument implements JsonDocumentWriter { + private final JsonAppender appender; - - private JsonHelper.JsonAppender appender; - - + /** + * Creates a new StringJsonDocumentWriter. + */ + public StringJsonDocumentWriter() { + this(new StringBuilder()); + } /** * Creates a new StringJsonDocumentWriter. - * @param appender the appender to receive the serialze JSON object + * @param sb the StringBuilder to receive the serialized JSON */ - public StringJsonDocumentWriter(JsonHelper.JsonAppender appender) { + public StringJsonDocumentWriter(StringBuilder sb) { this.processingStates.push( JsonProcessingState.NONE ); - this.appender = appender; + this.appender = new JsonAppender( sb ); } /** @@ -237,7 +241,7 @@ public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> java private void convertedBasicValueToString( Object value, WrapperOptions options, - JsonHelper.JsonAppender appender, + JsonAppender appender, JavaType<Object> javaType, JdbcType jdbcType) { switch ( jdbcType.getDefaultSqlTypeCode() ) { @@ -354,8 +358,184 @@ private void convertedBasicValueToString( } } + public String getJson() { + return appender.toString(); + } + @Override public String toString() { return appender.toString(); } + + private static class JsonAppender extends OutputStream implements SqlAppender { + + private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + private final StringBuilder sb; + private boolean escape; + + public JsonAppender(StringBuilder sb) { + this.sb = sb; + } + + @Override + public void appendSql(String fragment) { + append( fragment ); + } + + @Override + public void appendSql(char fragment) { + append( fragment ); + } + + @Override + public void appendSql(int value) { + sb.append( value ); + } + + @Override + public void appendSql(long value) { + sb.append( value ); + } + + @Override + public void appendSql(boolean value) { + sb.append( value ); + } + + @Override + public String toString() { + return sb.toString(); + } + + public void startEscaping() { + assert !escape; + escape = true; + } + + public void endEscaping() { + assert escape; + escape = false; + } + + @Override + public JsonAppender append(char fragment) { + if ( escape ) { + appendEscaped( fragment ); + } + else { + sb.append( fragment ); + } + return this; + } + + @Override + public JsonAppender append(CharSequence csq) { + return append( csq, 0, csq.length() ); + } + + @Override + public JsonAppender append(CharSequence csq, int start, int end) { + if ( escape ) { + int len = end - start; + sb.ensureCapacity( sb.length() + len ); + for ( int i = start; i < end; i++ ) { + appendEscaped( csq.charAt( i ) ); + } + } + else { + sb.append( csq, start, end ); + } + return this; + } + + @Override + public void write(int v) { + final String hex = Integer.toHexString( v ); + sb.ensureCapacity( sb.length() + hex.length() + 1 ); + if ( ( hex.length() & 1 ) == 1 ) { + sb.append( '0' ); + } + sb.append( hex ); + } + + @Override + public void write(byte[] bytes) { + write(bytes, 0, bytes.length); + } + + @Override + public void write(byte[] bytes, int off, int len) { + sb.ensureCapacity( sb.length() + ( len << 1 ) ); + for ( int i = 0; i < len; i++ ) { + final int v = bytes[off + i] & 0xFF; + sb.append( HEX_ARRAY[v >>> 4] ); + sb.append( HEX_ARRAY[v & 0x0F] ); + } + } + + private void appendEscaped(char fragment) { + switch ( fragment ) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 8 is '\b' + // 9 is '\t' + // 10 is '\n' + case 11: + // 12 is '\f' + // 13 is '\r' + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + sb.append( "\\u" ).append( Integer.toHexString( fragment ) ); + break; + case '\b': + sb.append("\\b"); + break; + case '\t': + sb.append("\\t"); + break; + case '\n': + sb.append("\\n"); + break; + case '\f': + sb.append("\\f"); + break; + case '\r': + sb.append("\\r"); + break; + case '"': + sb.append( "\\\"" ); + break; + case '\\': + sb.append( "\\\\" ); + break; + default: + sb.append( fragment ); + break; + } + } + + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java index 3c5b2ad1d786..81c0f73d0ec4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java @@ -327,5 +327,5 @@ public void setAggregateArray(EmbeddableAggregate[] aggregateArray) { } } - + } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java index d890c9215b15..0788f86d4045 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/util/StringJsonDocumentWriterTest.java @@ -4,7 +4,6 @@ */ package org.hibernate.orm.test.util; -import org.hibernate.dialect.JsonHelper; import org.hibernate.type.format.StringJsonDocumentWriter; import org.junit.jupiter.api.Test; @@ -22,8 +21,7 @@ private static void assertEqualsIgnoreSpace(String expected, String actual) { @Test public void testEmptyDocument() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startObject(); writer.endObject(); assertEquals( "{}" , writer.toString()); @@ -31,16 +29,14 @@ public void testEmptyDocument() { @Test public void testEmptyArray() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startArray(); writer.endArray(); assertEquals( "[]" , writer.toString() ); } @Test public void testArray() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startArray(); writer.booleanValue( false ); writer.booleanValue( true ); @@ -51,8 +47,7 @@ public void testArray() { @Test public void testMixedArrayDocument() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startArray(); writer.nullValue(); writer.booleanValue( false ); @@ -64,8 +59,7 @@ public void testMixedArrayDocument() { } @Test public void testSimpleDocument() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startObject(); writer.objectKey( "key1" ); writer.stringValue( "value1" ); @@ -82,8 +76,7 @@ public void testSimpleDocument() { @Test public void testNonStringValueDocument() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter(new JsonHelper.JsonAppender(sb) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startObject(); writer.objectKey( "aNull" ); writer.nullValue(); @@ -103,8 +96,7 @@ public void testNonStringValueDocument() { @Test public void testArrayValueDocument() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startObject(); writer.objectKey( "anEmptyArray" ); writer.startArray(); @@ -126,8 +118,7 @@ public void testArrayValueDocument() { } @Test public void testObjectArrayMultipleValueDocument() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startObject(); writer.objectKey( "anArray" ).startArray().nullValue().stringValue( "2" ).startObject() .objectKey( "foo" ).stringValue( "bar" ).endObject().endArray().endObject(); @@ -136,14 +127,13 @@ public void testObjectArrayMultipleValueDocument() { { "anArray" : [null, "2" , {\"foo\":\"bar\"} ] } - """ , sb.toString() ); + """ , writer.toString() ); } @Test public void testNestedDocument() { - StringBuilder sb = new StringBuilder(); - StringJsonDocumentWriter writer = new StringJsonDocumentWriter( new JsonHelper.JsonAppender( sb ) ); + StringJsonDocumentWriter writer = new StringJsonDocumentWriter(); writer.startObject().objectKey( "nested" ).startObject() .objectKey( "converted_gender" ).stringValue( "M" ) .endObject() From c70daedccea4ff4c4e269ae81695a40e274072ea Mon Sep 17 00:00:00 2001 From: Christian Beikov <christian.beikov@gmail.com> Date: Thu, 17 Apr 2025 16:18:20 +0200 Subject: [PATCH 65/81] HHH-17404 Cleanup JdbcTypes and Jackson/OSON availability detection --- .../SessionFactoryOptionsBuilder.java | 11 +- .../internal/StrategySelectorBuilder.java | 14 +-- .../org/hibernate/dialect/OracleDialect.java | 30 ++--- ...Type.java => OracleOsonArrayJdbcType.java} | 112 +++++++++++++----- .../OracleOsonArrayJdbcTypeConstructor.java | 4 +- .../dialect/OracleOsonJacksonHelper.java | 59 +++++++++ ...nJdbcType.java => OracleOsonJdbcType.java} | 111 +++++++++++------ .../dialect/type/OracleJdbcHelper.java | 10 ++ .../type/format/OsonDocumentWriter.java | 13 +- .../type/format/OsonValueJDBCTypeAdapter.java | 1 - .../format/jackson/JacksonIntegration.java | 17 ++- .../jackson/JacksonJsonFormatMapper.java | 33 +++++- .../jackson/JacksonOsonFormatMapper.java | 42 +++++-- .../embeddable/JsonEmbeddableArrayTest.java | 5 + .../src/main/groovy/local.java-module.gradle | 3 + 15 files changed, 331 insertions(+), 134 deletions(-) rename hibernate-core/src/main/java/org/hibernate/dialect/{OracleOsonJacksonArrayJdbcType.java => OracleOsonArrayJdbcType.java} (68%) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonHelper.java rename hibernate-core/src/main/java/org/hibernate/dialect/{OracleOsonJacksonJdbcType.java => OracleOsonJdbcType.java} (69%) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 2684a90f168d..d0b19c8f6e5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -794,13 +794,10 @@ private static FormatMapper determineJsonFormatMapper(Object setting, StrategySe FormatMapper.class, setting, (Callable<FormatMapper>) () -> { - FormatMapper jsonJacksonFormatMapper = null; - if (JacksonIntegration.isOracleOsonExtensionAvailable()) { - jsonJacksonFormatMapper = getOsonJacksonFormatMapperOrNull(); - } - else { - jsonJacksonFormatMapper = getJsonJacksonFormatMapperOrNull(); - } + // Prefer the OSON Jackson FormatMapper by default if available + final FormatMapper jsonJacksonFormatMapper = JacksonIntegration.isJacksonOsonExtensionAvailable() + ? getOsonJacksonFormatMapperOrNull() + : getJsonJacksonFormatMapperOrNull(); return jsonJacksonFormatMapper != null ? jsonJacksonFormatMapper : getJakartaJsonBFormatMapperOrNull(); } ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index bf64c2676f7b..af0eda071fda 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -308,20 +308,18 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector) JsonBJsonFormatMapper.SHORT_NAME, JsonBJsonFormatMapper.class ); - if ( JacksonIntegration.isOracleOsonExtensionAvailable() ) { + strategySelector.registerStrategyImplementor( + FormatMapper.class, + JacksonJsonFormatMapper.SHORT_NAME, + JacksonJsonFormatMapper.class + ); + if ( JacksonIntegration.isJacksonOsonExtensionAvailable() ) { strategySelector.registerStrategyImplementor( FormatMapper.class, JacksonOsonFormatMapper.SHORT_NAME, JacksonOsonFormatMapper.class ); } - else { - strategySelector.registerStrategyImplementor( - FormatMapper.class, - JacksonJsonFormatMapper.SHORT_NAME, - JacksonJsonFormatMapper.class - ); - } } private static void addXmlFormatMappers(StrategySelectorImpl strategySelector) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 4104d39cc0b3..15acdafc090a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -10,7 +10,6 @@ import org.hibernate.QueryTimeoutException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; -import org.hibernate.cfg.MappingSettings; import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.OracleAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; @@ -103,8 +102,6 @@ import org.hibernate.type.descriptor.sql.internal.NamedNativeEnumDdlTypeImpl; import org.hibernate.type.descriptor.sql.internal.NamedNativeOrdinalEnumDdlTypeImpl; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; -import org.hibernate.type.format.jackson.JacksonIntegration; -import org.hibernate.type.format.jackson.JacksonJsonFormatMapper; import org.hibernate.type.spi.TypeConfiguration; import java.sql.CallableStatement; @@ -262,14 +259,6 @@ public boolean isApplicationContinuity() { return applicationContinuity; } - private static boolean isJacksonJsonFormatMapper(ConfigurationService configService) { - // Mirror the behavior of SessionFactoryOptionsBuilder#determineJsonFormatMapper - final String mapperName = configService.getSetting( MappingSettings.JSON_FORMAT_MAPPER, - StandardConverters.STRING,JacksonJsonFormatMapper.SHORT_NAME); - return JacksonJsonFormatMapper.SHORT_NAME.equalsIgnoreCase( mapperName ) - || mapperName == null && JacksonIntegration.getJsonJacksonFormatMapperOrNull() != null; - } - @Override protected DatabaseVersion getMinimumSupportedVersion() { return MINIMUM_VERSION; @@ -1016,22 +1005,21 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry ); } - if ( getVersion().isSameOrAfter( 21 ) ) { - final boolean osonDisabled = getBoolean( ORACLE_OSON_DISABLED , configurationService.getSettings() ); - if ( !osonDisabled && JacksonIntegration.isOracleOsonExtensionAvailable() && isJacksonJsonFormatMapper( configurationService )) { + final boolean osonDisabled = getBoolean( ORACLE_OSON_DISABLED, configurationService.getSettings() ); + if ( !osonDisabled && OracleJdbcHelper.isOsonAvailable( serviceRegistry ) ) { // We must check that that extension is available and actually used. - typeContributions.contributeJdbcType( OracleOsonJacksonJdbcType.INSTANCE ); + typeContributions.contributeJdbcType( OracleOsonJdbcType.INSTANCE ); typeContributions.contributeJdbcTypeConstructor( OracleOsonArrayJdbcTypeConstructor.INSTANCE ); - DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON Jackson extension used" ); + DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON extension enabled" ); } else { - if (DIALECT_LOGGER.isDebugEnabled()) { - DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON Jackson extension not used" ); - DIALECT_LOGGER.log( Logger.Level.DEBUG, - "JacksonIntegration.isOracleOsonExtensionAvailable(): " + - JacksonIntegration.isOracleOsonExtensionAvailable()); + if ( osonDisabled ) { + DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON extension disabled" ); + } + else { + DIALECT_LOGGER.log( Logger.Level.DEBUG, "Oracle OSON extension not available" ); } typeContributions.contributeJdbcType( OracleJsonJdbcType.INSTANCE ); typeContributions.contributeJdbcTypeConstructor( OracleJsonArrayJdbcTypeConstructor.NATIVE_INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java similarity index 68% rename from hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java rename to hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java index 28ee7d868faf..c0d7321b20a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java @@ -4,7 +4,6 @@ */ package org.hibernate.dialect; -import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; import oracle.jdbc.driver.DatabaseError; import oracle.sql.json.OracleJsonDatum; @@ -28,6 +27,7 @@ import org.hibernate.type.format.OsonDocumentWriter; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.sql.CallableStatement; @@ -35,8 +35,7 @@ import java.sql.ResultSet; import java.sql.SQLException; -import static org.hibernate.dialect.OracleOsonJacksonJdbcType.OSON_JACKSON_FACTORY; -import static org.hibernate.dialect.OracleOsonJacksonJdbcType.OSON_JSON_FACTORY; +import static org.hibernate.dialect.OracleOsonJdbcType.OSON_JSON_FACTORY; /** * @@ -47,21 +46,19 @@ * @author Emmanuel Jannetti * @author Bidyadhar Mohanty */ -public class OracleOsonJacksonArrayJdbcType extends OracleJsonArrayJdbcType { +public class OracleOsonArrayJdbcType extends OracleJsonArrayJdbcType { - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJacksonArrayJdbcType.class ); + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonArrayJdbcType.class ); - public OracleOsonJacksonArrayJdbcType(JdbcType elementJdbcType) { + public OracleOsonArrayJdbcType(JdbcType elementJdbcType) { super(elementJdbcType); } - @Override public String toString() { - return "OracleOsonJacksonArrayJdbcType"; + return "OracleOsonArrayJdbcType"; } - @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { @@ -73,7 +70,9 @@ private <T> byte[] toOsonStream(T value, JavaType<T> javaType, WrapperOptions op try (OracleJsonGenerator generator = OSON_JSON_FACTORY.createJsonBinaryGenerator( out )) { final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) javaType).getElementJavaType(); if ( elementJavaType instanceof UnknownBasicJavaType<?> ) { - options.getJsonFormatMapper().writeToTarget( value, javaType, generator, options); + try (Closeable osonGen = OracleOsonJacksonHelper.createWriteTarget( out )) { + options.getJsonFormatMapper().writeToTarget( value, javaType, osonGen, options ); + } } else { final OsonDocumentWriter writer = new OsonDocumentWriter( generator ); @@ -92,15 +91,31 @@ private <T> byte[] toOsonStream(T value, JavaType<T> javaType, WrapperOptions op ); } } - return out.toByteArray(); } + return out.toByteArray(); + } + private boolean useUtf8(WrapperOptions options) { + final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) getJavaType()).getElementJavaType(); + return elementJavaType instanceof UnknownBasicJavaType<?> + && !options.getJsonFormatMapper().supportsTargetType( OracleOsonJacksonHelper.WRITER_CLASS ); } + @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { try { - st.setObject( index, toOsonStream( value, getJavaType(), options ), OracleType.JSON ); + if ( useUtf8( options ) ) { + final String json = OracleOsonArrayJdbcType.this.toString( + value, + getJavaType(), + options + ); + st.setBytes( index, json.getBytes( StandardCharsets.UTF_8 ) ); + } + else { + st.setObject( index, toOsonStream( value, getJavaType(), options ), OracleType.JSON ); + } } catch (Exception e) { throw new SQLException( e ); @@ -111,7 +126,17 @@ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions o protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { try { - st.setObject( name, toOsonStream( value, getJavaType(), options ) , OracleType.JSON); + if ( useUtf8( options ) ) { + final String json = OracleOsonArrayJdbcType.this.toString( + value, + getJavaType(), + options + ); + st.setBytes( name, json.getBytes( StandardCharsets.UTF_8 ) ); + } + else { + st.setObject( name, toOsonStream( value, getJavaType(), options ), OracleType.JSON ); + } } catch (Exception e) { throw new SQLException( e ); @@ -127,7 +152,7 @@ public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { private X fromOson(InputStream osonBytes, WrapperOptions options) throws Exception { if ( ((BasicPluralJavaType<?>) getJavaType()).getElementJavaType() instanceof UnknownBasicJavaType<?> ) { - try (JsonParser oParser = OSON_JACKSON_FACTORY.createParser( osonBytes )) { + try (Closeable oParser = OracleOsonJacksonHelper.createReadSource( osonBytes )) { return options.getJsonFormatMapper().readFromSource( getJavaType(), oParser, options ); } } @@ -155,21 +180,40 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ } } + private boolean useUtf8(WrapperOptions options) { + final JavaType<?> elementJavaType = ((BasicPluralJavaType<?>) getJavaType()).getElementJavaType(); + return elementJavaType instanceof UnknownBasicJavaType<?> + && !options.getJsonFormatMapper().supportsTargetType( OracleOsonJacksonHelper.READER_CLASS ); + } + + private X fromString(byte[] json, WrapperOptions options) throws SQLException { + if ( json == null ) { + return null; + } + return OracleOsonArrayJdbcType.this.fromString( + new String( json, StandardCharsets.UTF_8 ), + getJavaType(), + options + ); + } + @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { try { - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - return doExtraction( ojd, options); + if ( useUtf8( options ) ) { + return fromString( rs.getBytes( paramIndex ), options ); + } + else { + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction( ojd, options ); + } } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return OracleOsonJacksonArrayJdbcType.this.fromString( - new String( rs.getBytes( paramIndex ), StandardCharsets.UTF_8 ), - getJavaType(), - options); + return fromString( rs.getBytes( paramIndex ), options ); } else { throw exc; } @@ -179,18 +223,20 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { try { - OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); - return doExtraction( ojd, options); + if ( useUtf8( options ) ) { + return fromString( statement.getBytes( index ), options ); + } + else { + OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); + return doExtraction( ojd, options ); + } } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return OracleOsonJacksonArrayJdbcType.this.fromString( - new String( statement.getBytes( index ), StandardCharsets.UTF_8 ), - getJavaType(), - options); + return fromString( statement.getBytes( index ), options ); } else { throw exc; } @@ -201,18 +247,20 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { try { - OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); - return doExtraction( ojd, options); + if ( useUtf8( options ) ) { + return fromString( statement.getBytes( name ), options ); + } + else { + OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); + return doExtraction( ojd, options ); + } } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return OracleOsonJacksonArrayJdbcType.this.fromString( - new String( statement.getBytes( name ), StandardCharsets.UTF_8 ), - getJavaType(), - options); + return fromString( statement.getBytes( name ), options ); } else { throw exc; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java index 806d9f98d8a8..0aa867c820bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcTypeConstructor.java @@ -12,7 +12,7 @@ import org.hibernate.type.spi.TypeConfiguration; /** - * Factory for {@link OracleOsonJacksonArrayJdbcType}. + * Factory for {@link OracleOsonArrayJdbcType}. * @author Emmanuel Jannetti */ public class OracleOsonArrayJdbcTypeConstructor implements JdbcTypeConstructor { @@ -33,7 +33,7 @@ public JdbcType resolveType( Dialect dialect, JdbcType elementType, ColumnTypeInformation columnTypeInformation) { - return new OracleOsonJacksonArrayJdbcType( elementType ); + return new OracleOsonArrayJdbcType( elementType ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonHelper.java new file mode 100644 index 000000000000..72c03f7557eb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonHelper.java @@ -0,0 +1,59 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.dialect; + +import com.fasterxml.jackson.core.JsonFactory; +import oracle.jdbc.provider.oson.OsonFactory; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.type.format.jackson.JacksonIntegration; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.hibernate.type.format.jackson.JacksonIntegration.isJacksonOsonExtensionAvailable; + +public class OracleOsonJacksonHelper { + + public static final @Nullable Class<? extends Closeable> READER_CLASS = loadOrNull( "com.fasterxml.jackson.core.JsonParser" ); + public static final @Nullable Class<? extends Closeable> WRITER_CLASS = loadOrNull( "com.fasterxml.jackson.core.JsonGenerator" ); + + private static @Nullable Class<? extends Closeable> loadOrNull(String name) { + try { + //N.B. intentionally not using the context classloader + // as we're storing these in static references; + // IMO it's reasonable to expect that such dependencies are made reachable from the ORM classloader. + // (we can change this if it's more problematic than expected). + //noinspection unchecked + return (Class<? extends Closeable>) JacksonIntegration.class.getClassLoader().loadClass( name ); + } + catch (ClassNotFoundException | LinkageError e) { + return null; + } + } + + private OracleOsonJacksonHelper() { + } + + public static Closeable createWriteTarget(OutputStream out) throws IOException { + return FactoryHolder.JACKSON_FACTORY.createGenerator( out ); + } + + + public static Closeable createReadSource(InputStream osonBytes) throws IOException { + return FactoryHolder.JACKSON_FACTORY.createParser( osonBytes ); + } + + private static final class FactoryHolder { + // Intentionally storing the jackson typed factory in a different class, + // to avoid linkage errors for the outer class if Jackson is not available + private static final JsonFactory JACKSON_FACTORY = isJacksonOsonExtensionAvailable() + ? new OsonFactory() + : new JsonFactory(); + + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java similarity index 69% rename from hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java rename to hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java index d2a1894a30c8..6a4ffc8840d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJacksonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java @@ -4,11 +4,8 @@ */ package org.hibernate.dialect; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; import oracle.jdbc.OracleType; import oracle.jdbc.driver.DatabaseError; -import oracle.jdbc.provider.oson.OsonFactory; import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; @@ -27,6 +24,7 @@ import org.hibernate.type.format.OsonDocumentWriter; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.sql.CallableStatement; @@ -37,26 +35,24 @@ /** * * Type mapping JSON SQL data type for Oracle database. - * This implementation is used when Jackson mapper is used and that the JDBC OSON extension - * is available. + * This implementation is used when the JDBC OSON extension is available. * * @author Emmanuel Jannetti */ -public class OracleOsonJacksonJdbcType extends OracleJsonJdbcType { - public static final OracleOsonJacksonJdbcType INSTANCE = new OracleOsonJacksonJdbcType( null ); +public class OracleOsonJdbcType extends OracleJsonJdbcType { + public static final OracleOsonJdbcType INSTANCE = new OracleOsonJdbcType( null ); - private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJacksonJdbcType.class ); + private static final CoreMessageLogger LOG = CoreLogging.messageLogger( OracleOsonJdbcType.class ); - static final OsonFactory OSON_JACKSON_FACTORY = new OsonFactory(); static final OracleJsonFactory OSON_JSON_FACTORY = new OracleJsonFactory(); - private OracleOsonJacksonJdbcType(EmbeddableMappingType embeddableMappingType) { + private OracleOsonJdbcType(EmbeddableMappingType embeddableMappingType) { super( embeddableMappingType ); } @Override public String toString() { - return "OracleOsonJacksonJdbcType"; + return "OracleOsonJdbcType"; } @Override @@ -64,7 +60,7 @@ public AggregateJdbcType resolveAggregateJdbcType( EmbeddableMappingType mappingType, String sqlType, RuntimeModelCreationContext creationContext) { - return new OracleOsonJacksonJdbcType( mappingType ); + return new OracleOsonJdbcType( mappingType ); } @Override @@ -90,18 +86,33 @@ private <T> byte[] toOson(T value, JavaType<T> javaType, WrapperOptions options) } } else { - try (JsonGenerator osonGen = OSON_JACKSON_FACTORY.createGenerator( out )) { + try (Closeable osonGen = OracleOsonJacksonHelper.createWriteTarget( out )) { options.getJsonFormatMapper().writeToTarget( value, javaType, osonGen, options ); } } return out.toByteArray(); } + private boolean useUtf8(WrapperOptions options) { + return getEmbeddableMappingType() == null + && !options.getJsonFormatMapper().supportsTargetType( OracleOsonJacksonHelper.WRITER_CLASS ); + } + @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { try { - st.setObject( index, toOson( value, getJavaType(), options ), OracleType.JSON); + if ( useUtf8( options ) ) { + final String json = OracleOsonJdbcType.this.toString( + value, + getJavaType(), + options + ); + st.setBytes( index, json.getBytes( StandardCharsets.UTF_8 ) ); + } + else { + st.setObject( index, toOson( value, getJavaType(), options ), OracleType.JSON ); + } } catch (Exception e) { throw new SQLException( e ); @@ -112,7 +123,17 @@ protected void doBind(PreparedStatement st, X value, int index, WrapperOptions o protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException { try { - st.setObject( name, toOson( value, getJavaType(), options ), OracleType.JSON); + if ( useUtf8( options ) ) { + final String json = OracleOsonJdbcType.this.toString( + value, + getJavaType(), + options + ); + st.setBytes( name, json.getBytes( StandardCharsets.UTF_8 ) ); + } + else { + st.setObject( name, toOson( value, getJavaType(), options ), OracleType.JSON ); + } } catch (Exception e) { throw new SQLException( e ); @@ -140,12 +161,17 @@ private X fromOson(InputStream osonBytes, WrapperOptions options) throws Excepti ); } else { - try (JsonParser osonParser = OSON_JACKSON_FACTORY.createParser( osonBytes )) { + try (Closeable osonParser = OracleOsonJacksonHelper.createReadSource( osonBytes )) { return options.getJsonFormatMapper().readFromSource( getJavaType(), osonParser, options ); } } } + private boolean useUtf8(WrapperOptions options) { + return getEmbeddableMappingType() == null + && !options.getJsonFormatMapper().supportsTargetType( OracleOsonJacksonHelper.READER_CLASS ); + } + private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { if ( datum == null ) { return null; @@ -159,21 +185,34 @@ private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQ } } + private X fromString(byte[] json, WrapperOptions options) throws SQLException { + if ( json == null ) { + return null; + } + return OracleOsonJdbcType.this.fromString( + new String( json, StandardCharsets.UTF_8 ), + getJavaType(), + options + ); + } + @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { try { - OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); - return doExtraction(ojd,options); + if ( useUtf8( options ) ) { + return fromString( rs.getBytes( paramIndex ), options ); + } + else { + OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); + return doExtraction( ojd, options ); + } } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return OracleOsonJacksonJdbcType.this.fromString( - new String( rs.getBytes( paramIndex ), StandardCharsets.UTF_8 ), - getJavaType(), - options); + return fromString( rs.getBytes( paramIndex ), options ); } else { throw exc; } @@ -183,18 +222,20 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { try { - OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); - return doExtraction(ojd,options); + if ( useUtf8( options ) ) { + return fromString( statement.getBytes( index ), options ); + } + else { + OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); + return doExtraction( ojd, options ); + } } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return OracleOsonJacksonJdbcType.this.fromString( - new String( statement.getBytes( index ), StandardCharsets.UTF_8 ), - getJavaType(), - options); + return fromString( statement.getBytes( index ), options); } else { throw exc; } @@ -205,18 +246,20 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { try { - OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); - return doExtraction(ojd,options); + if ( useUtf8( options ) ) { + return fromString( statement.getBytes( name ), options ); + } + else { + OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); + return doExtraction( ojd, options ); + } } catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return OracleOsonJacksonJdbcType.this.fromString( - new String( statement.getBytes( name ), StandardCharsets.UTF_8 ), - getJavaType(), - options); + return fromString( statement.getBytes( name ), options); } else { throw exc; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java index c37ccbe2ad58..be26134db251 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java @@ -31,6 +31,16 @@ public static boolean isUsable(ServiceRegistry serviceRegistry) { return false; } } + public static boolean isOsonAvailable(ServiceRegistry serviceRegistry) { + final ClassLoaderService classLoaderService = serviceRegistry.requireService( ClassLoaderService.class ); + try { + classLoaderService.classForName( "oracle.sql.json.OracleJsonFactory" ); + return true; + } + catch (ClassLoadingException ex) { + return false; + } + } public static JdbcTypeConstructor getArrayJdbcTypeConstructor(ServiceRegistry serviceRegistry) { return create( serviceRegistry, "org.hibernate.dialect.type.OracleArrayJdbcTypeConstructor" ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index fcc6cb09038f..6514537e0cc6 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -18,9 +18,7 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.sql.Time; import java.sql.Timestamp; -import java.time.Duration; import java.time.OffsetDateTime; /** @@ -171,8 +169,7 @@ private void serializeValue(Object value, case SqlTypes.MATERIALIZED_NCLOB: case SqlTypes.ENUM: case SqlTypes.NAMED_ENUM: - // correct? - generator.write( javaType.unwrap( value,String.class,options ) ); + generator.write( javaType.toString( value ) ); break; case SqlTypes.DATE: DATE dd = new DATE(javaType.unwrap( value,java.sql.Date.class,options )); @@ -182,8 +179,7 @@ private void serializeValue(Object value, case SqlTypes.TIME: case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_UTC: - Time time = javaType.unwrap( value, Time.class,options ); - generator.write( time.toString() ); + generator.write( javaType.toString( value ) ); break; case SqlTypes.TIMESTAMP: TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( value, Timestamp.class, options )); @@ -205,11 +201,8 @@ private void serializeValue(Object value, break; case SqlTypes.DURATION: - Duration duration = javaType.unwrap( value, Duration.class, options ); - generator.write( duration ); - break; case SqlTypes.UUID: - generator.write( javaType.unwrap( value, String.class, options ) ); + generator.write( javaType.toString( value ) ); break; case SqlTypes.BINARY: case SqlTypes.VARBINARY: diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java index d94af51b883d..46a4c283d0ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonValueJDBCTypeAdapter.java @@ -31,7 +31,6 @@ public Object fromValue(JavaType<?> jdbcJavaType, JdbcType jdbcType, JsonDocumen case SqlTypes.VARBINARY: case SqlTypes.LONGVARBINARY: case SqlTypes.LONG32VARBINARY: - case SqlTypes.UUID: valueToBeWrapped = source.getValue( PrimitiveByteArrayJavaType.INSTANCE, options ); break; case SqlTypes.DATE: diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java index d3efc479f628..4d8552d59402 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonIntegration.java @@ -12,11 +12,11 @@ public final class JacksonIntegration { // when GraalVM native image is initializing them. private static final boolean JACKSON_XML_AVAILABLE = ableToLoadJacksonXMLMapper(); private static final boolean JACKSON_JSON_AVAILABLE = ableToLoadJacksonJSONMapper(); - private static final boolean JACKSON_OSON_AVAILABLE = ableToLoadJacksonOSONGenerator(); - private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper() : null; - private static final JacksonXmlFormatMapper XML_FORMAT_MAPPER_PORTABLE = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper( false ) : null; - private static final JacksonJsonFormatMapper JSON_FORMAT_MAPPER = JACKSON_JSON_AVAILABLE ? new JacksonJsonFormatMapper() : null; - private static final JacksonJsonFormatMapper OSON_FORMAT_MAPPER = JACKSON_OSON_AVAILABLE ? new JacksonOsonFormatMapper() : null; + private static final boolean JACKSON_OSON_AVAILABLE = ableToLoadJacksonOSONFactory(); + private static final FormatMapper XML_FORMAT_MAPPER = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper() : null; + private static final FormatMapper XML_FORMAT_MAPPER_PORTABLE = JACKSON_XML_AVAILABLE ? new JacksonXmlFormatMapper( false ) : null; + private static final FormatMapper JSON_FORMAT_MAPPER = JACKSON_JSON_AVAILABLE ? new JacksonJsonFormatMapper() : null; + private static final FormatMapper OSON_FORMAT_MAPPER = JACKSON_OSON_AVAILABLE ? new JacksonOsonFormatMapper() : null; private JacksonIntegration() { @@ -36,9 +36,9 @@ private static boolean ableToLoadJacksonXMLMapper() { * in the classpath. * @return true if we can load the OSON support, false otherwise. */ - private static boolean ableToLoadJacksonOSONGenerator() { + private static boolean ableToLoadJacksonOSONFactory() { return ableToLoadJacksonJSONMapper() && - canLoad( "oracle.jdbc.provider.oson.OsonGenerator" ); + canLoad( "oracle.jdbc.provider.oson.OsonFactory" ); } public static FormatMapper getXMLJacksonFormatMapperOrNull(boolean legacyFormat) { @@ -57,11 +57,10 @@ public static FormatMapper getOsonJacksonFormatMapperOrNull() { * * @return true if we can load the OSON support, false otherwise. */ - public static boolean isOracleOsonExtensionAvailable() { + public static boolean isJacksonOsonExtensionAvailable() { return JACKSON_OSON_AVAILABLE; } - private static boolean canLoad(String name) { try { //N.B. intentionally not using the context classloader diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java index b71107c0949e..cc1f0c0aac42 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonJsonFormatMapper.java @@ -4,31 +4,58 @@ */ package org.hibernate.type.format.jackson; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.format.AbstractJsonFormatMapper; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import java.lang.reflect.Type; /** * @author Christian Beikov * @author Yanming Zhou */ -public class JacksonJsonFormatMapper extends AbstractJsonFormatMapper { +public final class JacksonJsonFormatMapper extends AbstractJsonFormatMapper { public static final String SHORT_NAME = "jackson"; - protected final ObjectMapper objectMapper; + private final ObjectMapper objectMapper; public JacksonJsonFormatMapper() { - this(new ObjectMapper().findAndRegisterModules()); + this( new ObjectMapper().findAndRegisterModules() ); } public JacksonJsonFormatMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } + @Override + public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) + throws IOException { + objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ) + .writeValue( (JsonGenerator) target, value ); + } + + @Override + public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { + return objectMapper.readValue( (JsonParser) source, objectMapper.constructType( javaType.getJavaType() ) ); + } + + @Override + public boolean supportsSourceType(Class<?> sourceType) { + return JsonParser.class.isAssignableFrom( sourceType ); + } + + @Override + public boolean supportsTargetType(Class<?> targetType) { + return JsonGenerator.class.isAssignableFrom( targetType ); + } + @Override public <T> T fromString(CharSequence charSequence, Type type) { try { diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java index 86498ae67554..e2c36ee25c84 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/jackson/JacksonOsonFormatMapper.java @@ -6,12 +6,16 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import oracle.jdbc.provider.oson.OsonModule; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.AbstractJsonFormatMapper; import java.io.IOException; +import java.lang.reflect.Type; /** @@ -20,30 +24,35 @@ * @author Emmanuel Jannetti * @author Bidyadhar Mohanty */ -public class JacksonOsonFormatMapper extends JacksonJsonFormatMapper { +public final class JacksonOsonFormatMapper extends AbstractJsonFormatMapper { - public static final String SHORT_NAME = "jackson"; + public static final String SHORT_NAME = "jackson-oson"; + private final ObjectMapper objectMapper; /** * Creates a new JacksonOsonFormatMapper */ public JacksonOsonFormatMapper() { - super(); + this( new ObjectMapper().findAndRegisterModules() ); + } + + public JacksonOsonFormatMapper(ObjectMapper objectMapper) { objectMapper.registerModule( new OsonModule() ); - objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.disable( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS ); + this.objectMapper = objectMapper; } @Override public <T> void writeToTarget(T value, JavaType<T> javaType, Object target, WrapperOptions options) throws IOException { - objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ).writeValue( (JsonGenerator) target, value); - + objectMapper.writerFor( objectMapper.constructType( javaType.getJavaType() ) ) + .writeValue( (JsonGenerator) target, value ); } @Override public <T> T readFromSource(JavaType<T> javaType, Object source, WrapperOptions options) throws IOException { - return objectMapper.readValue( (JsonParser)source, objectMapper.constructType( javaType.getJavaType()) ); + return objectMapper.readValue( (JsonParser) source, objectMapper.constructType( javaType.getJavaType() ) ); } @Override @@ -56,5 +65,24 @@ public boolean supportsTargetType(Class<?> targetType) { return JsonGenerator.class.isAssignableFrom( targetType ); } + @Override + public <T> T fromString(CharSequence charSequence, Type type) { + try { + return objectMapper.readValue( charSequence.toString(), objectMapper.constructType( type ) ); + } + catch (JsonProcessingException e) { + throw new IllegalArgumentException( "Could not deserialize string to java type: " + type, e ); + } + } + + @Override + public <T> String toString(T value, Type type) { + try { + return objectMapper.writerFor( objectMapper.constructType( type ) ).writeValueAsString( value ); + } + catch (JsonProcessingException e) { + throw new IllegalArgumentException( "Could not serialize object of java type: " + type, e ); + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java index 81c0f73d0ec4..7f0a874dcb2d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/JsonEmbeddableArrayTest.java @@ -19,6 +19,7 @@ import java.util.UUID; import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.type.SqlTypes; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; @@ -117,6 +118,7 @@ public void testDomainResult() { } @Test + @FailureExpected(jiraKey = "HHH-18717", reason = "Requires array functions to work with JSON_ARRAY") public void testSelectionItems() { sessionFactoryScope().inSession( entityManager -> { @@ -205,6 +207,7 @@ public void testUpdateAggregate() { } @Test + @FailureExpected(jiraKey = "HHH-18717", reason = "Requires array functions to work with JSON_ARRAY") public void testUpdateAggregateMember() { sessionFactoryScope().inTransaction( entityManager -> { @@ -217,6 +220,7 @@ public void testUpdateAggregateMember() { } @Test + @FailureExpected(jiraKey = "HHH-18717", reason = "Requires array functions to work with JSON_ARRAY") public void testUpdateMultipleAggregateMembers() { sessionFactoryScope().inTransaction( entityManager -> { @@ -230,6 +234,7 @@ public void testUpdateMultipleAggregateMembers() { } @Test + @FailureExpected(jiraKey = "HHH-18717", reason = "Requires array functions to work with JSON_ARRAY") public void testUpdateAllAggregateMembers() { sessionFactoryScope().inTransaction( entityManager -> { diff --git a/local-build-plugins/src/main/groovy/local.java-module.gradle b/local-build-plugins/src/main/groovy/local.java-module.gradle index 291d4a8a4f2c..c548f82ff3a4 100644 --- a/local-build-plugins/src/main/groovy/local.java-module.gradle +++ b/local-build-plugins/src/main/groovy/local.java-module.gradle @@ -101,6 +101,9 @@ dependencies { testRuntimeOnly jdbcLibs.oracle testRuntimeOnly jdbcLibs.oracleXml testRuntimeOnly jdbcLibs.oracleXmlParser + testRuntimeOnly (jdbcLibs.oracleJdbcJacksonOsonExtension) { + exclude group: 'com.oracle.database.jdbc', module: 'ojdbc8' + } } else if ( db.startsWith( 'altibase' ) ) { testRuntimeOnly jdbcLibs.altibase From ebf2fd010f69730a602fbd5db479413676289641 Mon Sep 17 00:00:00 2001 From: Christian Beikov <christian.beikov@gmail.com> Date: Thu, 17 Apr 2025 17:34:09 +0200 Subject: [PATCH 66/81] HHH-17404 Parameterize DDL type for tests --- ....java => OracleOsonCompatibilityTest.java} | 66 +++++++++++++------ 1 file changed, 45 insertions(+), 21 deletions(-) rename hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/{JsonCBLOBToOsonTest.java => OracleOsonCompatibilityTest.java} (66%) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/JsonCBLOBToOsonTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java similarity index 66% rename from hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/JsonCBLOBToOsonTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java index 06a11ecca312..d9fac7534f25 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/JsonCBLOBToOsonTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java @@ -8,18 +8,20 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.cfg.DialectSpecificSettings; import org.hibernate.dialect.OracleDialect; import org.hibernate.orm.test.mapping.basic.JsonMappingTests; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; import org.hibernate.type.SqlTypes; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hibernate.testing.orm.junit.DialectContext.getDialect; @@ -32,36 +34,43 @@ * * @author Emmanuel Jannetti */ -@DomainModel(annotatedClasses = JsonCBLOBToOsonTest.JsonEntity.class) -@SessionFactory +@DomainModel(annotatedClasses = OracleOsonCompatibilityTest.JsonEntity.class) +@SessionFactory(exportSchema = false) @RequiresDialect( value = OracleDialect.class, majorVersion = 23 ) -public class JsonCBLOBToOsonTest { - - @Entity(name = "JsonEntity") - @Table(name = "TEST_OSON_COMPAT") - public static class JsonEntity { - @Id - private Integer id; - @JdbcTypeCode( SqlTypes.JSON ) - private JsonMappingTests.StringNode jsonName; +public abstract class OracleOsonCompatibilityTest { - public JsonEntity() { - super(); + @ServiceRegistry(settings = @Setting(name = DialectSpecificSettings.ORACLE_OSON_DISABLED, value = "true")) + public static class OracleOsonAsUtf8CompatibilityTest extends OracleOsonCompatibilityTest { + public OracleOsonAsUtf8CompatibilityTest() { + super( "JSON" ); } - public JsonEntity(Integer id, JsonMappingTests.StringNode node) { - this.id = id; - this.jsonName = node; + } + public static class OracleBlobAsOsonCompatibilityTest extends OracleOsonCompatibilityTest { + public OracleBlobAsOsonCompatibilityTest() { + super( "BLOB" ); } } + public static class OracleClobAsOsonCompatibilityTest extends OracleOsonCompatibilityTest { + public OracleClobAsOsonCompatibilityTest() { + super( "CLOB" ); + } + } + + + private final String jsonType; + + public OracleOsonCompatibilityTest(String jsonType) { + this.jsonType = jsonType; + } @BeforeEach public void setup(SessionFactoryScope scope) { scope.inTransaction( (session) -> { // force creation of a BLOB column type by creating the table ourselves - session.createNativeQuery( getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ) + session.createNativeQuery( session.getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ) .executeUpdate(); - session.createNativeQuery( "CREATE TABLE TEST_OSON_COMPAT (id NUMBER, jsonName BLOB CHECK (jsonName is json) ,primary key (id))" ) + session.createNativeQuery( "CREATE TABLE TEST_OSON_COMPAT (id NUMBER, jsonName " + jsonType + " CHECK (jsonName is json) ,primary key (id))" ) .executeUpdate(); String insert = "INSERT INTO TEST_OSON_COMPAT (id, jsonName) VALUES(:id,:json)"; @@ -85,11 +94,26 @@ public void tearDown(SessionFactoryScope scope) { public void verifyReadWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { - JsonEntity entity = session.find( JsonCBLOBToOsonTest.JsonEntity.class, 1 ); + JsonEntity entity = session.find( OracleOsonCompatibilityTest.JsonEntity.class, 1 ); assertThat( entity.jsonName.getString(), is( "john" ) ); - } ); } + @Entity(name = "JsonEntity") + @Table(name = "TEST_OSON_COMPAT") + public static class JsonEntity { + @Id + private Integer id; + @JdbcTypeCode( SqlTypes.JSON ) + private JsonMappingTests.StringNode jsonName; + + public JsonEntity() { + super(); + } + public JsonEntity(Integer id, JsonMappingTests.StringNode node) { + this.id = id; + this.jsonName = node; + } + } } From 33c699c00f2a34f7d157113e37803e35bf392477 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Wed, 30 Apr 2025 10:34:55 +0200 Subject: [PATCH 67/81] HHH-17404 add compatibility test --- .../SessionFactoryOptionsBuilder.java | 7 +- .../internal/StrategySelectorBuilder.java | 2 +- .../hibernate/dialect/OracleOsonJdbcType.java | 113 +++++++++++------- .../dialect/type/OracleJdbcHelper.java | 2 +- .../hhh17404/OracleOsonCompatibilityTest.java | 92 ++++++++++++-- 5 files changed, 161 insertions(+), 55 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index d0b19c8f6e5f..c405f088996d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -94,6 +94,7 @@ import static org.hibernate.cfg.CacheSettings.JPA_SHARED_CACHE_RETRIEVE_MODE; import static org.hibernate.cfg.CacheSettings.JPA_SHARED_CACHE_STORE_MODE; import static org.hibernate.cfg.CacheSettings.QUERY_CACHE_LAYOUT; +import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.cfg.PersistenceSettings.UNOWNED_ASSOCIATION_TRANSIENT_CHECK; import static org.hibernate.cfg.QuerySettings.DEFAULT_NULL_ORDERING; import static org.hibernate.cfg.QuerySettings.JSON_FUNCTIONS_ENABLED; @@ -132,6 +133,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private final String uuid = LocalObjectUuidHelper.generateLocalObjectUuid(); private final StandardServiceRegistry serviceRegistry; + private static boolean osonExtensionEnabled; // integration private Object beanManagerReference; @@ -305,10 +307,13 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo settings.get( AvailableSettings.JAKARTA_VALIDATION_FACTORY ) ); + osonExtensionEnabled = !getBoolean( ORACLE_OSON_DISABLED ,settings); + jsonFormatMapper = determineJsonFormatMapper( settings.get( AvailableSettings.JSON_FORMAT_MAPPER ), strategySelector ); + xmlFormatMapper = determineXmlFormatMapper( settings.get( AvailableSettings.XML_FORMAT_MAPPER ), strategySelector, @@ -795,7 +800,7 @@ private static FormatMapper determineJsonFormatMapper(Object setting, StrategySe setting, (Callable<FormatMapper>) () -> { // Prefer the OSON Jackson FormatMapper by default if available - final FormatMapper jsonJacksonFormatMapper = JacksonIntegration.isJacksonOsonExtensionAvailable() + final FormatMapper jsonJacksonFormatMapper = (osonExtensionEnabled && JacksonIntegration.isJacksonOsonExtensionAvailable()) ? getOsonJacksonFormatMapperOrNull() : getJsonJacksonFormatMapperOrNull(); return jsonJacksonFormatMapper != null ? jsonJacksonFormatMapper : getJakartaJsonBFormatMapperOrNull(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index af0eda071fda..ffbb80518df0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -313,7 +313,7 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector) JacksonJsonFormatMapper.SHORT_NAME, JacksonJsonFormatMapper.class ); - if ( JacksonIntegration.isJacksonOsonExtensionAvailable() ) { + if (JacksonIntegration.isJacksonOsonExtensionAvailable() ) { strategySelector.registerStrategyImplementor( FormatMapper.class, JacksonOsonFormatMapper.SHORT_NAME, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java index 6a4ffc8840d7..4906d5f35477 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java @@ -31,6 +31,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; /** * @@ -169,7 +170,7 @@ private X fromOson(InputStream osonBytes, WrapperOptions options) throws Excepti private boolean useUtf8(WrapperOptions options) { return getEmbeddableMappingType() == null - && !options.getJsonFormatMapper().supportsTargetType( OracleOsonJacksonHelper.READER_CLASS ); + && !options.getJsonFormatMapper().supportsSourceType(OracleOsonJacksonHelper.READER_CLASS ); } private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { @@ -196,74 +197,106 @@ private X fromString(byte[] json, WrapperOptions options) throws SQLException { ); } + private byte[] getBytesFromResultSetByIndex(ResultSet rs, int index) throws SQLException { + // This can be a BLOB or a CLOB. getBytes is not supported on CLOB + // and getString is not supported on BLOB. W have to try both + try { + return rs.getBytes( index); + } catch (SQLFeatureNotSupportedException nse) { + return rs.getString( index).getBytes(); + } + } + + private byte[] getBytesFromStatementByIndex(CallableStatement st, int index) throws SQLException { + // This can be a BLOB or a CLOB. getBytes is not supported on CLOB + // and getString is not supported on BLOB. W have to try both + try { + return st.getBytes( index); + } catch (SQLFeatureNotSupportedException nse) { + + return st.getString( index).getBytes(); + } + } + private byte[] getBytesFromStatementByName(CallableStatement st, String columnName) throws SQLException { + // This can be a BLOB or a CLOB. getBytes is not supported on CLOB + // and getString is not supported on BLOB. W have to try both + try { + return st.getBytes( columnName); + } catch (SQLFeatureNotSupportedException nse) { + return st.getString( columnName).getBytes(); + } + } + @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - try { if ( useUtf8( options ) ) { - return fromString( rs.getBytes( paramIndex ), options ); + return fromString(getBytesFromResultSetByIndex(rs, paramIndex), options ); } else { + try { OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); return doExtraction( ojd, options ); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.BLOB.getName(), OracleType.JSON.getName() ); + return fromString(getBytesFromResultSetByIndex(rs, paramIndex), options ); + } else { + throw exc; + } + } } - } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // This may happen if we are fetching data from an existing schema - // that uses BLOB for JSON column In that case we assume bytes are - // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation - LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return fromString( rs.getBytes( paramIndex ), options ); - } else { - throw exc; - } - } } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - try { if ( useUtf8( options ) ) { - return fromString( statement.getBytes( index ), options ); + return fromString(getBytesFromStatementByIndex(statement, index), options); } else { + try { OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); return doExtraction( ojd, options ); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return fromString(getBytesFromStatementByIndex(statement, index), options); + } else { + throw exc; + } + } } - } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // This may happen if we are fetching data from an existing schema - // that uses BLOB for JSON column In that case we assume bytes are - // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation - LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return fromString( statement.getBytes( index ), options); - } else { - throw exc; - } - } } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - try { + if ( useUtf8( options ) ) { - return fromString( statement.getBytes( name ), options ); + return fromString(getBytesFromStatementByName(statement, name), options); } else { + try { OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); return doExtraction( ojd, options ); + } catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return fromString(getBytesFromStatementByName(statement, name), options); + } else { + throw exc; + } + } } - } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // This may happen if we are fetching data from an existing schema - // that uses BLOB for JSON column In that case we assume bytes are - // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation - LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return fromString( statement.getBytes( name ), options); - } else { - throw exc; - } - } + } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java index be26134db251..e800b215dfbe 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJdbcHelper.java @@ -34,7 +34,7 @@ public static boolean isUsable(ServiceRegistry serviceRegistry) { public static boolean isOsonAvailable(ServiceRegistry serviceRegistry) { final ClassLoaderService classLoaderService = serviceRegistry.requireService( ClassLoaderService.class ); try { - classLoaderService.classForName( "oracle.sql.json.OracleJsonFactory" ); + classLoaderService.classForName( "oracle.jdbc.provider.oson.OsonFactory" ); return true; } catch (ClassLoadingException ex) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java index d9fac7534f25..d2a752cfa906 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java @@ -4,6 +4,9 @@ */ package org.hibernate.orm.test.mapping.hhh17404; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; @@ -22,6 +25,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.UUID; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hibernate.testing.orm.junit.DialectContext.getDialect; @@ -67,16 +75,37 @@ public OracleOsonCompatibilityTest(String jsonType) { public void setup(SessionFactoryScope scope) { scope.inTransaction( (session) -> { - // force creation of a BLOB column type by creating the table ourselves + // force creation of a column type by creating the table ourselves session.createNativeQuery( session.getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ) .executeUpdate(); - session.createNativeQuery( "CREATE TABLE TEST_OSON_COMPAT (id NUMBER, jsonName " + jsonType + " CHECK (jsonName is json) ,primary key (id))" ) - .executeUpdate(); + StringBuilder create = new StringBuilder(); + create.append("CREATE TABLE TEST_OSON_COMPAT ("); + create.append( "id NUMBER").append(','); + create.append( "payload ").append(jsonType).append(" CHECK (payload is null or json)").append(','); + create.append( "primary key (id))"); + session.createNativeQuery(create.toString()).executeUpdate(); + + String insert = "INSERT INTO TEST_OSON_COMPAT (id, payload) VALUES(:id,:json)"; - String insert = "INSERT INTO TEST_OSON_COMPAT (id, jsonName) VALUES(:id,:json)"; - String jsonstr = "{\"string\":\"john\"}"; - session.createNativeQuery(insert).setParameter("id",1) - .setParameter( "json", jsonstr).executeUpdate(); + LocalDateTime theLocalDateTime = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ); + LocalDate theLocalDate = LocalDate.of( 2000, 1, 1 ); + LocalTime theLocalTime = LocalTime.of( 1, 0, 0 ); + UUID uuid = UUID.fromString("53886a8a-7082-4879-b430-25cb94415be8"); + String theString = "john"; + + StringBuilder j = new StringBuilder(); + j.append( "{" ); + j.append( "\"jsonString\":\"").append(theString).append("\"," ); + j.append( "\"theUuid\":\"").append(uuid).append("\"," ); + j.append( "\"theLocalDateTime\":\"").append(theLocalDateTime).append("\"," ); + j.append( "\"theLocalDate\":\"").append(theLocalDate).append("\"," ); + j.append( "\"theLocalTime\":\"").append(theLocalTime).append("\"" ); + j.append( "}" ); + + session.createNativeQuery(insert) + .setParameter("id",1) + .setParameter( "json", j.toString()) + .executeUpdate(); } ); } @@ -95,7 +124,8 @@ public void verifyReadWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { JsonEntity entity = session.find( OracleOsonCompatibilityTest.JsonEntity.class, 1 ); - assertThat( entity.jsonName.getString(), is( "john" ) ); + assertThat( entity.payload.jsonString.getString(), is( "john" ) ); + assertThat( entity.payload.theUuid, is( "53886a8a-7082-4879-b430-25cb94415be8" ) ); } ); } @@ -105,15 +135,53 @@ public void verifyReadWorks(SessionFactoryScope scope) { public static class JsonEntity { @Id private Integer id; + @JdbcTypeCode( SqlTypes.JSON ) - private JsonMappingTests.StringNode jsonName; + private JsonEntityPayload payload; public JsonEntity() { - super(); } - public JsonEntity(Integer id, JsonMappingTests.StringNode node) { + + public JsonEntity(Integer id, JsonEntityPayload payload) { this.id = id; - this.jsonName = node; + this.payload = payload; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public JsonEntityPayload getPayload() { + return payload; + } + + public void setPayload(JsonEntityPayload payload) { + this.payload = payload; + } + + } + @Embeddable + @Access( AccessType.PROPERTY ) + public static class JsonEntityPayload { + private JsonMappingTests.StringNode jsonString; + private UUID theUuid; + private LocalDateTime theLocalDateTime; + private LocalDate theLocalDate; + private LocalTime theLocalTime; + + public JsonEntityPayload() { + + } + public JsonEntityPayload(JsonMappingTests.StringNode jsonString, UUID theUuid, LocalDateTime theLocalDateTime, LocalDate theLocalDate, LocalTime theLocalTime) { + this.jsonString = jsonString; + this.theUuid = theUuid; + this.theLocalDateTime = theLocalDateTime; + this.theLocalDate = theLocalDate; + this.theLocalTime = theLocalTime; } } } From 96cd77d105a978bdbb0b09160871aa198c324101 Mon Sep 17 00:00:00 2001 From: Marco Belladelli <marcobladel@gmail.com> Date: Wed, 30 Apr 2025 11:43:43 +0200 Subject: [PATCH 68/81] HHH-17404 Oson compatibility test fixes --- .../hhh17404/OracleOsonCompatibilityTest.java | 61 +++++-------------- 1 file changed, 15 insertions(+), 46 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java index d2a752cfa906..d9d71b87eb3e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java @@ -4,8 +4,6 @@ */ package org.hibernate.orm.test.mapping.hhh17404; -import jakarta.persistence.Access; -import jakarta.persistence.AccessType; import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -13,7 +11,6 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.cfg.DialectSpecificSettings; import org.hibernate.dialect.OracleDialect; -import org.hibernate.orm.test.mapping.basic.JsonMappingTests; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.ServiceRegistry; @@ -21,7 +18,6 @@ import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; import org.hibernate.type.SqlTypes; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -32,11 +28,10 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.hibernate.testing.orm.junit.DialectContext.getDialect; /** - * This test class is about testing that legacy schema that use BLO for JSON column - * can be safely read even when Oracle Oson extention is in place. + * This test class is about testing that legacy schema that use BLOB or CLOB for JSON columns + * can be safely read even when Oracle Oson extension is in place. * In Such a situation, the JSON type will expect JSON a JSON column and should * silently fall back to String deserialization. * @@ -53,11 +48,13 @@ public OracleOsonAsUtf8CompatibilityTest() { super( "JSON" ); } } + public static class OracleBlobAsOsonCompatibilityTest extends OracleOsonCompatibilityTest { public OracleBlobAsOsonCompatibilityTest() { super( "BLOB" ); } } + public static class OracleClobAsOsonCompatibilityTest extends OracleOsonCompatibilityTest { public OracleClobAsOsonCompatibilityTest() { super( "CLOB" ); @@ -76,14 +73,14 @@ public void setup(SessionFactoryScope scope) { scope.inTransaction( (session) -> { // force creation of a column type by creating the table ourselves - session.createNativeQuery( session.getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ) + session.createNativeMutationQuery( session.getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ) .executeUpdate(); StringBuilder create = new StringBuilder(); create.append("CREATE TABLE TEST_OSON_COMPAT ("); create.append( "id NUMBER").append(','); - create.append( "payload ").append(jsonType).append(" CHECK (payload is null or json)").append(','); + create.append( "payload ").append(jsonType).append(','); create.append( "primary key (id))"); - session.createNativeQuery(create.toString()).executeUpdate(); + session.createNativeMutationQuery(create.toString()).executeUpdate(); String insert = "INSERT INTO TEST_OSON_COMPAT (id, payload) VALUES(:id,:json)"; @@ -102,30 +99,22 @@ public void setup(SessionFactoryScope scope) { j.append( "\"theLocalTime\":\"").append(theLocalTime).append("\"" ); j.append( "}" ); - session.createNativeQuery(insert) - .setParameter("id",1) - .setParameter( "json", j.toString()) + final Object json = jsonType.equals( "BLOB" ) ? j.toString().getBytes() : j.toString(); + session.createNativeMutationQuery( insert ) + .setParameter( "id", 1 ) + .setParameter( "json", json ) .executeUpdate(); } ); } - @AfterEach - public void tearDown(SessionFactoryScope scope) { - scope.inTransaction( - (session) -> { - session.createNativeQuery( getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ).executeUpdate(); - } - ); - } - @Test public void verifyReadWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { JsonEntity entity = session.find( OracleOsonCompatibilityTest.JsonEntity.class, 1 ); - assertThat( entity.payload.jsonString.getString(), is( "john" ) ); - assertThat( entity.payload.theUuid, is( "53886a8a-7082-4879-b430-25cb94415be8" ) ); + assertThat( entity.payload.jsonString, is( "john" ) ); + assertThat( entity.payload.theUuid.toString(), is( "53886a8a-7082-4879-b430-25cb94415be8" ) ); } ); } @@ -139,14 +128,6 @@ public static class JsonEntity { @JdbcTypeCode( SqlTypes.JSON ) private JsonEntityPayload payload; - public JsonEntity() { - } - - public JsonEntity(Integer id, JsonEntityPayload payload) { - this.id = id; - this.payload = payload; - } - public Integer getId() { return id; } @@ -162,26 +143,14 @@ public JsonEntityPayload getPayload() { public void setPayload(JsonEntityPayload payload) { this.payload = payload; } - } + @Embeddable - @Access( AccessType.PROPERTY ) public static class JsonEntityPayload { - private JsonMappingTests.StringNode jsonString; + private String jsonString; private UUID theUuid; private LocalDateTime theLocalDateTime; private LocalDate theLocalDate; private LocalTime theLocalTime; - - public JsonEntityPayload() { - - } - public JsonEntityPayload(JsonMappingTests.StringNode jsonString, UUID theUuid, LocalDateTime theLocalDateTime, LocalDate theLocalDate, LocalTime theLocalTime) { - this.jsonString = jsonString; - this.theUuid = theUuid; - this.theLocalDateTime = theLocalDateTime; - this.theLocalDate = theLocalDate; - this.theLocalTime = theLocalTime; - } } } From e3132d0a97a16e1d06152c6350586f8d41f146b2 Mon Sep 17 00:00:00 2001 From: Marco Belladelli <marcobladel@gmail.com> Date: Wed, 30 Apr 2025 11:44:13 +0200 Subject: [PATCH 69/81] HHH-17404 Apply code-style to OracleOsonJdbcType and misc cleanups --- .../SessionFactoryOptionsBuilder.java | 13 +- .../internal/StrategySelectorBuilder.java | 2 +- .../hibernate/dialect/OracleOsonJdbcType.java | 137 +++++++++--------- 3 files changed, 77 insertions(+), 75 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index c405f088996d..642449a502fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -133,7 +133,6 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private final String uuid = LocalObjectUuidHelper.generateLocalObjectUuid(); private final StandardServiceRegistry serviceRegistry; - private static boolean osonExtensionEnabled; // integration private Object beanManagerReference; @@ -307,10 +306,9 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo settings.get( AvailableSettings.JAKARTA_VALIDATION_FACTORY ) ); - osonExtensionEnabled = !getBoolean( ORACLE_OSON_DISABLED ,settings); - jsonFormatMapper = determineJsonFormatMapper( settings.get( AvailableSettings.JSON_FORMAT_MAPPER ), + !getBoolean( ORACLE_OSON_DISABLED ,settings), strategySelector ); @@ -794,15 +792,16 @@ private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( .getDefaultConnectionHandlingMode(); } - private static FormatMapper determineJsonFormatMapper(Object setting, StrategySelector strategySelector) { + private static FormatMapper determineJsonFormatMapper(Object setting, boolean osonExtensionEnabled, StrategySelector strategySelector) { return strategySelector.resolveDefaultableStrategy( FormatMapper.class, setting, (Callable<FormatMapper>) () -> { // Prefer the OSON Jackson FormatMapper by default if available - final FormatMapper jsonJacksonFormatMapper = (osonExtensionEnabled && JacksonIntegration.isJacksonOsonExtensionAvailable()) - ? getOsonJacksonFormatMapperOrNull() - : getJsonJacksonFormatMapperOrNull(); + final FormatMapper jsonJacksonFormatMapper = + (osonExtensionEnabled && JacksonIntegration.isJacksonOsonExtensionAvailable()) + ? getOsonJacksonFormatMapperOrNull() + : getJsonJacksonFormatMapperOrNull(); return jsonJacksonFormatMapper != null ? jsonJacksonFormatMapper : getJakartaJsonBFormatMapperOrNull(); } ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java index ffbb80518df0..af0eda071fda 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorBuilder.java @@ -313,7 +313,7 @@ private static void addJsonFormatMappers(StrategySelectorImpl strategySelector) JacksonJsonFormatMapper.SHORT_NAME, JacksonJsonFormatMapper.class ); - if (JacksonIntegration.isJacksonOsonExtensionAvailable() ) { + if ( JacksonIntegration.isJacksonOsonExtensionAvailable() ) { strategySelector.registerStrategyImplementor( FormatMapper.class, JacksonOsonFormatMapper.SHORT_NAME, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java index 4906d5f35477..558cbfc4b547 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java @@ -34,7 +34,6 @@ import java.sql.SQLFeatureNotSupportedException; /** - * * Type mapping JSON SQL data type for Oracle database. * This implementation is used when the JDBC OSON extension is available. * @@ -67,7 +66,7 @@ public AggregateJdbcType resolveAggregateJdbcType( @Override public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { - if(javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class) { + if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) { return super.getBinder( javaType ); } @@ -75,7 +74,7 @@ public <X> ValueBinder<X> getBinder(JavaType<X> javaType) { private <T> byte[] toOson(T value, JavaType<T> javaType, WrapperOptions options) throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (getEmbeddableMappingType() != null) { + if ( getEmbeddableMappingType() != null ) { // OracleJsonFactory is used and not OracleOsonFactory as Jackson is not involved here try (OracleJsonGenerator generator = OSON_JSON_FACTORY.createJsonBinaryGenerator( out )) { JsonHelper.serialize( @@ -146,7 +145,7 @@ protected void doBind(CallableStatement st, X value, String name, WrapperOptions @Override public <X> ValueExtractor<X> getExtractor(JavaType<X> javaType) { - if(javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class) { + if ( javaType.getJavaType() == String.class || javaType.getJavaType() == Object.class ) { return super.getExtractor( javaType ); } @@ -170,16 +169,16 @@ private X fromOson(InputStream osonBytes, WrapperOptions options) throws Excepti private boolean useUtf8(WrapperOptions options) { return getEmbeddableMappingType() == null - && !options.getJsonFormatMapper().supportsSourceType(OracleOsonJacksonHelper.READER_CLASS ); + && !options.getJsonFormatMapper().supportsSourceType( OracleOsonJacksonHelper.READER_CLASS ); } - private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { + private X doExtraction(OracleJsonDatum datum, WrapperOptions options) throws SQLException { if ( datum == null ) { return null; } InputStream osonBytes = datum.getStream(); try { - return fromOson( osonBytes ,options); + return fromOson( osonBytes, options ); } catch (Exception e) { throw new SQLException( e ); @@ -201,9 +200,10 @@ private byte[] getBytesFromResultSetByIndex(ResultSet rs, int index) throws SQLE // This can be a BLOB or a CLOB. getBytes is not supported on CLOB // and getString is not supported on BLOB. W have to try both try { - return rs.getBytes( index); - } catch (SQLFeatureNotSupportedException nse) { - return rs.getString( index).getBytes(); + return rs.getBytes( index ); + } + catch (SQLFeatureNotSupportedException nse) { + return rs.getString( index ).getBytes(); } } @@ -211,97 +211,100 @@ private byte[] getBytesFromStatementByIndex(CallableStatement st, int index) thr // This can be a BLOB or a CLOB. getBytes is not supported on CLOB // and getString is not supported on BLOB. W have to try both try { - return st.getBytes( index); - } catch (SQLFeatureNotSupportedException nse) { + return st.getBytes( index ); + } + catch (SQLFeatureNotSupportedException nse) { - return st.getString( index).getBytes(); + return st.getString( index ).getBytes(); } } + private byte[] getBytesFromStatementByName(CallableStatement st, String columnName) throws SQLException { // This can be a BLOB or a CLOB. getBytes is not supported on CLOB // and getString is not supported on BLOB. W have to try both try { - return st.getBytes( columnName); - } catch (SQLFeatureNotSupportedException nse) { - return st.getString( columnName).getBytes(); + return st.getBytes( columnName ); + } + catch (SQLFeatureNotSupportedException nse) { + return st.getString( columnName ).getBytes(); } } @Override protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - if ( useUtf8( options ) ) { - return fromString(getBytesFromResultSetByIndex(rs, paramIndex), options ); - } - else { - try { + if ( useUtf8( options ) ) { + return fromString( getBytesFromResultSetByIndex( rs, paramIndex ), options ); + } + else { + try { OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); return doExtraction( ojd, options ); - } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // This may happen if we are fetching data from an existing schema - // that uses BLOB for JSON column In that case we assume bytes are - // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation - LOG.invalidJSONColumnType( OracleType.BLOB.getName(), OracleType.JSON.getName() ); - return fromString(getBytesFromResultSetByIndex(rs, paramIndex), options ); - } else { - throw exc; - } + } + catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE ) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column. In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.BLOB.getName(), OracleType.JSON.getName() ); + return fromString( getBytesFromResultSetByIndex( rs, paramIndex ), options ); + } + else { + throw exc; } } + } } @Override protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - if ( useUtf8( options ) ) { - return fromString(getBytesFromStatementByIndex(statement, index), options); - } - else { - try { + if ( useUtf8( options ) ) { + return fromString( getBytesFromStatementByIndex( statement, index ), options ); + } + else { + try { OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); return doExtraction( ojd, options ); - } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // This may happen if we are fetching data from an existing schema - // that uses BLOB for JSON column In that case we assume bytes are - // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation - LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return fromString(getBytesFromStatementByIndex(statement, index), options); - } else { - throw exc; - } + } + catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE ) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return fromString( getBytesFromStatementByIndex( statement, index ), options ); + } + else { + throw exc; } } + } } @Override protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { - - if ( useUtf8( options ) ) { - return fromString(getBytesFromStatementByName(statement, name), options); - } - else { - try { + if ( useUtf8( options ) ) { + return fromString( getBytesFromStatementByName( statement, name ), options ); + } + else { + try { OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); return doExtraction( ojd, options ); - } catch (SQLException exc) { - if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { - // This may happen if we are fetching data from an existing schema - // that uses BLOB for JSON column In that case we assume bytes are - // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation - LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); - return fromString(getBytesFromStatementByName(statement, name), options); - } else { - throw exc; - } + } + catch (SQLException exc) { + if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE ) { + // This may happen if we are fetching data from an existing schema + // that uses BLOB for JSON column In that case we assume bytes are + // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation + LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); + return fromString( getBytesFromStatementByName( statement, name ), options ); + } + else { + throw exc; } } - - + } } - }; } - - } From 38da5faeb4b01d2b1f39539ac998e97f250bc9b8 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Wed, 30 Apr 2025 16:00:38 +0200 Subject: [PATCH 70/81] HHH-17404 rebase with upstream --- .../java/org/hibernate/dialect/OracleOsonArrayJdbcType.java | 2 ++ .../main/java/org/hibernate/dialect/OracleOsonJdbcType.java | 2 ++ .../java/org/hibernate/dialect/type/OracleJsonJdbcType.java | 2 +- .../java/org/hibernate/type/descriptor/jdbc/JsonHelper.java | 4 ---- .../org/hibernate/type/format/StringJsonDocumentReader.java | 2 ++ .../type/format/StringJsonValueJDBCTypeAdapter.java | 6 +++--- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java index c0d7321b20a2..46f28693af49 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java @@ -9,6 +9,7 @@ import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonGenerator; +import org.hibernate.dialect.type.OracleJsonArrayJdbcType; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -22,6 +23,7 @@ import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonHelper; import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.OsonDocumentReader; import org.hibernate.type.format.OsonDocumentWriter; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java index 558cbfc4b547..93e2ebe9aa4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonJdbcType.java @@ -9,6 +9,7 @@ import oracle.sql.json.OracleJsonDatum; import oracle.sql.json.OracleJsonFactory; import oracle.sql.json.OracleJsonGenerator; +import org.hibernate.dialect.type.OracleJsonJdbcType; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -20,6 +21,7 @@ import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JsonHelper; import org.hibernate.type.format.OsonDocumentReader; import org.hibernate.type.format.OsonDocumentWriter; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java index 36bc1ed331a0..943ccb9bbd8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/type/OracleJsonJdbcType.java @@ -24,7 +24,7 @@ public class OracleJsonJdbcType extends OracleJsonBlobJdbcType { */ public static final OracleJsonJdbcType INSTANCE = new OracleJsonJdbcType( null ); - OracleJsonJdbcType(EmbeddableMappingType embeddableMappingType) { + protected OracleJsonJdbcType(EmbeddableMappingType embeddableMappingType) { super( embeddableMappingType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 52274ed6654b..7caa8711f845 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -34,10 +34,6 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; -import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; -import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.format.JsonDocumentItemType; import org.hibernate.type.format.JsonDocumentReader; import org.hibernate.type.format.JsonDocumentWriter; diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index fa034cc4c594..380a719cc827 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -129,6 +129,8 @@ private void moveStateMachine(StringJsonDocumentMarker marker) { this.processingStates.push( JsonProcessingState.ARRAY ); } break; + default: + throw new IllegalStateException( "Unexpected JsonProcessingState " + marker ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java index 90c41e3d58eb..41eba74f2911 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonValueJDBCTypeAdapter.java @@ -4,8 +4,6 @@ */ package org.hibernate.type.format; -import org.hibernate.dialect.StructAttributeValues; -import org.hibernate.dialect.StructHelper; import org.hibernate.internal.util.CharSequenceHelper; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.type.SqlTypes; @@ -19,10 +17,12 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.StructAttributeValues; +import org.hibernate.type.descriptor.jdbc.StructHelper; import java.sql.SQLException; -import static org.hibernate.dialect.StructHelper.instantiate; +import static org.hibernate.type.descriptor.jdbc.StructHelper.instantiate; /** * JDBC type adapter for String-based JSON document reader. From 6ec53e2ffa5ecb0b3b3c331063415019c7d181c3 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Wed, 30 Apr 2025 17:20:22 +0200 Subject: [PATCH 71/81] HHH-14707 fix code style --- .../dialect/OracleOsonArrayJdbcType.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java index 46f28693af49..ee241225562d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleOsonArrayJdbcType.java @@ -209,14 +209,16 @@ protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) thro OracleJsonDatum ojd = rs.getObject( paramIndex, OracleJsonDatum.class ); return doExtraction( ojd, options ); } - } catch (SQLException exc) { + } + catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); return fromString( rs.getBytes( paramIndex ), options ); - } else { + } + else { throw exc; } } @@ -232,14 +234,16 @@ protected X doExtract(CallableStatement statement, int index, WrapperOptions opt OracleJsonDatum ojd = statement.getObject( index, OracleJsonDatum.class ); return doExtraction( ojd, options ); } - } catch (SQLException exc) { + } + catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); return fromString( statement.getBytes( index ), options ); - } else { + } + else { throw exc; } } @@ -256,14 +260,16 @@ protected X doExtract(CallableStatement statement, String name, WrapperOptions o OracleJsonDatum ojd = statement.getObject( name, OracleJsonDatum.class ); return doExtraction( ojd, options ); } - } catch (SQLException exc) { + } + catch (SQLException exc) { if ( exc.getErrorCode() == DatabaseError.JDBC_ERROR_BASE + DatabaseError.EOJ_INVALID_COLUMN_TYPE) { // This may happen if we are fetching data from an existing schema // that uses BLOB for JSON column In that case we assume bytes are // UTF-8 bytes (i.e not OSON) and we fall back to previous String-based implementation LOG.invalidJSONColumnType( OracleType.CLOB.getName(), OracleType.JSON.getName() ); return fromString( statement.getBytes( name ), options ); - } else { + } + else { throw exc; } } From e9c0d2d2941abb3e6d9cf41fb4e9a380c642930e Mon Sep 17 00:00:00 2001 From: Marco Belladelli <marcobladel@gmail.com> Date: Wed, 30 Apr 2025 17:54:12 +0200 Subject: [PATCH 72/81] HHH-14707 Fix xml embeddable aggregate array support --- .../java/org/hibernate/type/descriptor/jdbc/XmlHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlHelper.java index dda672dfc016..36e494fb83b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlHelper.java @@ -335,7 +335,7 @@ else if ( !string.startsWith( COLLECTION_START_TAG ) || !string.endsWith( COLLEC final ArrayList<Object> arrayList = new ArrayList<>(); final int end = fromArrayString( string, - false, + true, options, COLLECTION_START_TAG.length(), arrayList, @@ -646,7 +646,7 @@ private static int fromArrayString( else { arrayList.add( array ); } - i = end + 1; + i = end; } else { throw new IllegalArgumentException( "XML not properly formed: " + string.substring( start ) ); From d8dfee5b372fc4ae94693396d92abd46e093ec18 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Wed, 30 Apr 2025 18:54:25 +0200 Subject: [PATCH 73/81] HHH-17404 code style --- .../java/org/hibernate/type/descriptor/jdbc/JsonHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index 7caa8711f845..d1c4857b8e66 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -185,7 +185,8 @@ private static void serializeMapping(EmbeddableMappingType embeddableMappingType writer.startObject(); serializeMapping( (EmbeddableMappingType)attributeMapping.getMappedType(), values[i], options,writer); writer.endObject(); - } else { + } + else { serialize(attributeMapping.getMappedType(), values[i], options, writer); } From fcfee69c37c87055ce5646061d6204b439822a3c Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Fri, 2 May 2025 10:46:25 +0200 Subject: [PATCH 74/81] HHH-17404 fix code scna warning about switch default case --- .../org/hibernate/type/format/StringJsonDocumentReader.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index 380a719cc827..b1e5da28d09c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -222,6 +222,11 @@ public JsonDocumentItemType next() { "] in current processing state " + this.processingStates.getCurrent() ); } + default: { + throw new IllegalStateException( "unexpected marker ["+ + marker + + "] at position " + this.position ); + } } } throw new IllegalStateException( "unexpected end of JSON ["+ From 40ebb50db71eda5f28a2b9bcf5d7f0dec8491c88 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Fri, 2 May 2025 20:26:19 +0200 Subject: [PATCH 75/81] HHH-17404 enhance compatibility test on type chakcing --- .../hhh17404/OracleOsonCompatibilityTest.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java index d9d71b87eb3e..dcf727b26ccc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java @@ -64,6 +64,12 @@ public OracleClobAsOsonCompatibilityTest() { private final String jsonType; + private final LocalDateTime theLocalDateTime = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ); + private final LocalDate theLocalDate = LocalDate.of( 2000, 1, 1 ); + private final LocalTime theLocalTime = LocalTime.of( 1, 0, 0 ); + private final UUID uuid = UUID.fromString("53886a8a-7082-4879-b430-25cb94415be8"); + private final String theString = "john"; + public OracleOsonCompatibilityTest(String jsonType) { this.jsonType = jsonType; } @@ -84,11 +90,7 @@ public void setup(SessionFactoryScope scope) { String insert = "INSERT INTO TEST_OSON_COMPAT (id, payload) VALUES(:id,:json)"; - LocalDateTime theLocalDateTime = LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ); - LocalDate theLocalDate = LocalDate.of( 2000, 1, 1 ); - LocalTime theLocalTime = LocalTime.of( 1, 0, 0 ); - UUID uuid = UUID.fromString("53886a8a-7082-4879-b430-25cb94415be8"); - String theString = "john"; + StringBuilder j = new StringBuilder(); j.append( "{" ); @@ -115,6 +117,9 @@ public void verifyReadWorks(SessionFactoryScope scope) { JsonEntity entity = session.find( OracleOsonCompatibilityTest.JsonEntity.class, 1 ); assertThat( entity.payload.jsonString, is( "john" ) ); assertThat( entity.payload.theUuid.toString(), is( "53886a8a-7082-4879-b430-25cb94415be8" ) ); + assertThat( entity.payload.theLocalDateTime, is( theLocalDateTime ) ); + assertThat( entity.payload.theLocalTime, is( theLocalTime ) ); + assertThat( entity.payload.theLocalDate, is( theLocalDate ) ); } ); } From 4b0b76ef8d54e7c5b83230e744ff46d6fff275a9 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Tue, 6 May 2025 14:57:27 +0200 Subject: [PATCH 76/81] HHH-17404 add comment in the what's new doc --- whats-new.adoc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/whats-new.adoc b/whats-new.adoc index b01ad852eb73..9b1f2b30f64f 100644 --- a/whats-new.adoc +++ b/whats-new.adoc @@ -374,3 +374,19 @@ Support for `hbm.xml` mappings will be removed in 8.0. We offer a transformation of `hbm.xml` files into `mapping.xml` files, which is available both at build-time (Gradle plugin) and at run-time (using `hibernate.transform_hbm_xml.enabled=true`). + +[[OSON-support]] +== OSON support + + +Starting in 21c, link:https://docs.oracle.com/en/database/oracle/oracle-database/23/adjsn/json-oracle-database.html[Oracle JSON binary format] (also known as OSON) can now be used in Hibernate to store `JSON` data. It brings a performance boost by replacing the actual to/from String conversion. +To enable the OSON support, the link:https://github.com/oracle/ojdbc-extensions/blob/main/ojdbc-provider-jackson-oson/README.md[Oracle JDBC extension] must be added to the application classpath. +Here is an example using Gradle build system +``` +runtimeOnly ('com.oracle.database.jdbc:ojdbc-provider-jackson-oson:1.0.4') +{ + exclude group: 'com.oracle.database.jdbc', module: 'ojdbc8' +} +``` + +The use of OSON can be disabled by setting the following hibernate property `hibernate.dialect.oracle.oson_format_disabled=true` From 9f4a5adacf5e445589a34264c1d67ee8fd396cb4 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Fri, 16 May 2025 10:32:13 +0200 Subject: [PATCH 77/81] HHH-17404 add test claeanup phase --- .../type/format/StringJsonDocumentReader.java | 14 ++++++-------- .../hhh17404/OracleOsonCompatibilityTest.java | 10 ++++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java index b1e5da28d09c..6d02753a05a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentReader.java @@ -114,14 +114,12 @@ private void moveStateMachine(StringJsonDocumentMarker marker) { assert this.processingStates.getCurrent() == JsonProcessingState.OBJECT; break; case QUOTE: - switch ( currentState ) { - case STARTING_ARRAY: - this.processingStates.push( JsonProcessingState.ARRAY ); - break; - case STARTING_OBJECT: - this.processingStates.push( JsonProcessingState.OBJECT ); - this.processingStates.push( JsonProcessingState.OBJECT_KEY_NAME ); - break; + if (currentState == JsonProcessingState.STARTING_ARRAY) { + this.processingStates.push( JsonProcessingState.ARRAY ); + } + if (currentState == JsonProcessingState.STARTING_OBJECT) { + this.processingStates.push( JsonProcessingState.OBJECT ); + this.processingStates.push( JsonProcessingState.OBJECT_KEY_NAME ); } break; case OTHER: diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java index dcf727b26ccc..c5f6e0db56dd 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/hhh17404/OracleOsonCompatibilityTest.java @@ -18,6 +18,7 @@ import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; import org.hibernate.type.SqlTypes; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -110,6 +111,15 @@ public void setup(SessionFactoryScope scope) { ); } + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.createNativeQuery( session.getDialect().getDropTableString( "TEST_OSON_COMPAT" ) ).executeUpdate(); + } + ); + } + @Test public void verifyReadWorks(SessionFactoryScope scope) { scope.inTransaction( From 520cf75a633833fdb8ac68becfb7191d4fe69ccc Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Fri, 6 Jun 2025 17:45:32 +0200 Subject: [PATCH 78/81] HHH-17404 rebase with upstream --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 15acdafc090a..f6f686726c35 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -117,6 +117,9 @@ import org.jboss.logging.Logger; import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static org.hibernate.LockOptions.NO_WAIT; +import static org.hibernate.LockOptions.SKIP_LOCKED; +import static org.hibernate.LockOptions.WAIT_FOREVER; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.dialect.type.OracleJdbcHelper.getArrayJdbcTypeConstructor; From 8d56bfe77e8d16a97c410a446370fa48ce1729b6 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Fri, 6 Jun 2025 18:56:52 +0200 Subject: [PATCH 79/81] HHH-17404 add new contributor to AUTHORS list removed some unchecked cast warning --- AUTHORS.txt | 1 + .../type/descriptor/jdbc/JsonHelper.java | 8 +--- .../type/format/JsonDocumentWriter.java | 4 +- .../type/format/OsonDocumentWriter.java | 36 +++++++++--------- .../type/format/StringJsonDocumentWriter.java | 38 +++++++++++++------ 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index c6e19d08e3b3..234330ef0bf7 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -5,6 +5,7 @@ # Corporate contributors Red Hat, Inc. +Oracle, Corporation. # Individual contributors diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java index d1c4857b8e66..5021a6e6c1f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonHelper.java @@ -95,7 +95,7 @@ public static void serializeArray(JavaType<?> elementJavaType, JdbcType elementJ writer.nullValue(); } else { - writer.serializeJsonValue( value ,(JavaType<Object>) elementJavaType,elementJdbcType,options); + writer.serializeJsonValue( value ,(JavaType<?>) elementJavaType,elementJdbcType,options); } } writer.endArray(); @@ -135,15 +135,11 @@ private static void serialize(MappingType mappedType, Object value, WrapperOptio else if ( mappedType instanceof EmbeddableMappingType ) { serialize( (EmbeddableMappingType) mappedType, value, options, writer ); } - else if ( mappedType instanceof BasicType<?> ) { - //noinspection unchecked - final BasicType<Object> basicType = (BasicType<Object>) mappedType; - + else if ( mappedType instanceof BasicType<?> basicType) { if ( isArrayType(basicType.getJdbcType())) { final int length = Array.getLength( value ); writer.startArray(); if ( length != 0 ) { - //noinspection unchecked final JavaType<Object> elementJavaType = ( (BasicPluralJavaType<Object>) basicType.getJdbcJavaType() ).getElementJavaType(); final JdbcType elementJdbcType = ( (ArrayJdbcType) basicType.getJdbcType() ).getElementJdbcType(); final Object domainArray = basicType.convertToRelationalValue( value ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java index 93cef54dac51..03bac2085eb7 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/JsonDocumentWriter.java @@ -83,8 +83,8 @@ public interface JsonDocumentWriter { * @param options the wrapping options * @return this instance */ - JsonDocumentWriter serializeJsonValue(Object value, - JavaType<Object> javaType, + <T> JsonDocumentWriter serializeJsonValue(Object value, + JavaType<T> javaType, JdbcType jdbcType, WrapperOptions options); } diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java index 6514537e0cc6..a88eca7a908a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/OsonDocumentWriter.java @@ -100,7 +100,7 @@ public JsonDocumentWriter stringValue(String value) { } @Override - public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { + public <T> JsonDocumentWriter serializeJsonValue(Object value, JavaType<T> javaType, JdbcType jdbcType, WrapperOptions options) { serializeValue(value, javaType, jdbcType, options); return this; } @@ -114,8 +114,8 @@ public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> java * @param jdbcType the JDBC SQL type of the value * @param options the wapping options. */ - private void serializeValue(Object value, - JavaType<Object> javaType, + private <T> void serializeValue(Object value, + JavaType<T> javaType, JdbcType jdbcType, WrapperOptions options) { switch ( jdbcType.getDefaultSqlTypeCode() ) { @@ -132,23 +132,23 @@ private void serializeValue(Object value, generator.write( ((Enum<?>) value ).ordinal() ); break; } - generator.write( javaType.unwrap( value,Integer.class,options ) ); + generator.write( javaType.unwrap( (T)value,Integer.class,options ) ); break; case SqlTypes.BOOLEAN: - generator.write( javaType.unwrap( value,Boolean.class,options ) ); + generator.write( javaType.unwrap( (T)value,Boolean.class,options ) ); break; case SqlTypes.BIT: - generator.write( javaType.unwrap( value,Integer.class,options ) ); + generator.write( javaType.unwrap( (T)value,Integer.class,options ) ); break; case SqlTypes.BIGINT: - generator.write( javaType.unwrap( value, BigInteger.class,options ) ); + generator.write( javaType.unwrap( (T)value, BigInteger.class,options ) ); break; case SqlTypes.FLOAT: - generator.write( javaType.unwrap( value,Float.class,options ) ); + generator.write( javaType.unwrap( (T)value,Float.class,options ) ); break; case SqlTypes.REAL: case SqlTypes.DOUBLE: - generator.write( javaType.unwrap( value,Double.class,options ) ); + generator.write( javaType.unwrap( (T)value,Double.class,options ) ); break; case SqlTypes.CHAR: case SqlTypes.NCHAR: @@ -169,40 +169,40 @@ private void serializeValue(Object value, case SqlTypes.MATERIALIZED_NCLOB: case SqlTypes.ENUM: case SqlTypes.NAMED_ENUM: - generator.write( javaType.toString( value ) ); + generator.write( javaType.toString( (T)value ) ); break; case SqlTypes.DATE: - DATE dd = new DATE(javaType.unwrap( value,java.sql.Date.class,options )); + DATE dd = new DATE(javaType.unwrap( (T)value,java.sql.Date.class,options )); OracleJsonDate jsonDate = new OracleJsonDateImpl(dd.shareBytes()); generator.write(jsonDate); break; case SqlTypes.TIME: case SqlTypes.TIME_WITH_TIMEZONE: case SqlTypes.TIME_UTC: - generator.write( javaType.toString( value ) ); + generator.write( javaType.toString( (T)value ) ); break; case SqlTypes.TIMESTAMP: - TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( value, Timestamp.class, options )); + TIMESTAMP TS = new TIMESTAMP(javaType.unwrap( (T)value, Timestamp.class, options )); OracleJsonTimestamp writeTimeStamp = new OracleJsonTimestampImpl(TS.shareBytes()); generator.write(writeTimeStamp); break; case SqlTypes.TIMESTAMP_WITH_TIMEZONE: - OffsetDateTime dateTime = javaType.unwrap( value, OffsetDateTime.class, options ); + OffsetDateTime dateTime = javaType.unwrap( (T)value, OffsetDateTime.class, options ); generator.write( dateTime ); break; case SqlTypes.TIMESTAMP_UTC: - OffsetDateTime odt = javaType.unwrap( value, OffsetDateTime.class, options ); + OffsetDateTime odt = javaType.unwrap( (T)value, OffsetDateTime.class, options ); generator.write( odt ); break; case SqlTypes.NUMERIC: case SqlTypes.DECIMAL: - BigDecimal bd = javaType.unwrap( value, BigDecimal.class, options ); + BigDecimal bd = javaType.unwrap( (T)value, BigDecimal.class, options ); generator.write( bd ); break; case SqlTypes.DURATION: case SqlTypes.UUID: - generator.write( javaType.toString( value ) ); + generator.write( javaType.toString( (T)value ) ); break; case SqlTypes.BINARY: case SqlTypes.VARBINARY: @@ -211,7 +211,7 @@ private void serializeValue(Object value, case SqlTypes.BLOB: case SqlTypes.MATERIALIZED_BLOB: // how to handle - byte[] bytes = javaType.unwrap( value, byte[].class, options ); + byte[] bytes = javaType.unwrap( (T)value, byte[].class, options ); generator.write( bytes ); break; case SqlTypes.ARRAY: diff --git a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java index 48e3dd3bb090..c08aa01485fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/format/StringJsonDocumentWriter.java @@ -108,7 +108,7 @@ public JsonDocumentWriter objectKey(String key) { throw new IllegalArgumentException( "key cannot be null or empty" ); } - if (this.processingStates.getCurrent().equals( JsonProcessingState.OBJECT )) { + if (JsonProcessingState.OBJECT.equals(this.processingStates.getCurrent())) { // we have started an object, and we are adding an item key: we do add a separator. this.appender.append( StringJsonDocumentMarker.SEPARATOR.getMarkerCharacter() ); } @@ -222,13 +222,24 @@ public JsonDocumentWriter stringValue(String value) { } @Override - public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> javaType, JdbcType jdbcType, WrapperOptions options) { + public <T> JsonDocumentWriter serializeJsonValue(Object value, JavaType<T> javaType, JdbcType jdbcType, WrapperOptions options) { addItemsSeparator(); convertedBasicValueToString(value, options,this.appender,javaType,jdbcType); moveProcessingStateMachine(); return this; } + private <T> void convertedCastBasicValueToString(Object value, + WrapperOptions options, + JsonAppender appender, + JavaType<T> javaType, + JdbcType jdbcType) { + assert javaType.isInstance( value ); + //noinspection unchecked + convertedBasicValueToString( (T) value, options, appender, javaType, jdbcType ); + } + + /** * Converts a value to String according to its mapping type. * This method serializes the value and writes it into the underlying appender @@ -238,12 +249,15 @@ public JsonDocumentWriter serializeJsonValue(Object value, JavaType<Object> java * @param jdbcType the JDBC SQL type of the value * @param options the wapping options. */ - private void convertedBasicValueToString( + private <T> void convertedBasicValueToString( Object value, WrapperOptions options, JsonAppender appender, - JavaType<Object> javaType, + JavaType<T> javaType, JdbcType jdbcType) { + + assert javaType.isInstance( value ); + switch ( jdbcType.getDefaultSqlTypeCode() ) { case SqlTypes.TINYINT: case SqlTypes.SMALLINT: @@ -264,7 +278,7 @@ private void convertedBasicValueToString( case SqlTypes.REAL: case SqlTypes.DOUBLE: // These types fit into the native representation of JSON, so let's use that - javaType.appendEncodedString( appender, value ); + javaType.appendEncodedString( appender, (T)value ); break; case SqlTypes.CHAR: case SqlTypes.NCHAR: @@ -290,7 +304,7 @@ private void convertedBasicValueToString( // These literals can contain the '"' character, so we need to escape it appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); appender.startEscaping(); - javaType.appendEncodedString( appender, value ); + javaType.appendEncodedString( appender, (T)value ); appender.endEscaping(); appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; @@ -298,7 +312,7 @@ private void convertedBasicValueToString( appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); JdbcDateJavaType.INSTANCE.appendEncodedString( appender, - javaType.unwrap( value, java.sql.Date.class, options ) + javaType.unwrap( (T)value, java.sql.Date.class, options ) ); appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; @@ -308,7 +322,7 @@ private void convertedBasicValueToString( appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); JdbcTimeJavaType.INSTANCE.appendEncodedString( appender, - javaType.unwrap( value, java.sql.Time.class, options ) + javaType.unwrap( (T)value, java.sql.Time.class, options ) ); appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; @@ -316,7 +330,7 @@ private void convertedBasicValueToString( appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); JdbcTimestampJavaType.INSTANCE.appendEncodedString( appender, - javaType.unwrap( value, java.sql.Timestamp.class, options ) + javaType.unwrap( (T)value, java.sql.Timestamp.class, options ) ); appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; @@ -324,7 +338,7 @@ private void convertedBasicValueToString( case SqlTypes.TIMESTAMP_UTC: appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); DateTimeFormatter.ISO_OFFSET_DATE_TIME.formatTo( - javaType.unwrap( value, OffsetDateTime.class, options ), + javaType.unwrap( (T)value, OffsetDateTime.class, options ), appender ); appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); @@ -335,7 +349,7 @@ private void convertedBasicValueToString( case SqlTypes.UUID: // These types need to be serialized as JSON string, but don't have a need for escaping appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); - javaType.appendEncodedString( appender, value ); + javaType.appendEncodedString( appender, (T)value ); appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.BINARY: @@ -346,7 +360,7 @@ private void convertedBasicValueToString( case SqlTypes.MATERIALIZED_BLOB: // These types need to be serialized as JSON string, and for efficiency uses appendString directly appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); - appender.write( javaType.unwrap( value, byte[].class, options ) ); + appender.write( javaType.unwrap( (T)value, byte[].class, options ) ); appender.append( StringJsonDocumentMarker.QUOTE.getMarkerCharacter() ); break; case SqlTypes.ARRAY: From 288683e832d29246ac81b805a5b8f971983f0bc6 Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Sat, 14 Jun 2025 00:00:18 +0200 Subject: [PATCH 80/81] HHH-17404 fix merge regresssion about struct support --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index f6f686726c35..0ed224331224 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -965,6 +965,11 @@ public boolean supportsBitType() { return false; } + @Override + public boolean supportsUserDefinedTypes() { + return true; + } + @Override public String getArrayTypeName(String javaElementTypeName, String elementTypeName, Integer maxLength) { return ( javaElementTypeName == null ? elementTypeName : javaElementTypeName ) + "Array"; From b2e77304fcb9d5fffeba60818eec4bfd332d049f Mon Sep 17 00:00:00 2001 From: Emmanuel JANNETTI <emmanuel.jannetti@gmail.com> Date: Mon, 16 Jun 2025 14:09:35 +0200 Subject: [PATCH 81/81] HHH-17404 fix regression on OracleDialect on timeouts --- .../org/hibernate/dialect/OracleDialect.java | 40 +++++++++++++++---- .../locktimeout/OracleLockTimeoutTest.java | 3 +- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 0ed224331224..5afbee744c52 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -6,8 +6,11 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.TemporalType; +import jakarta.persistence.Timeout; import org.hibernate.Length; import org.hibernate.QueryTimeoutException; +import org.hibernate.Timeouts; +import org.hibernate.QueryTimeoutException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.aggregate.AggregateSupport; @@ -117,9 +120,6 @@ import org.jboss.logging.Logger; import static java.util.regex.Pattern.CASE_INSENSITIVE; -import static org.hibernate.LockOptions.NO_WAIT; -import static org.hibernate.LockOptions.SKIP_LOCKED; -import static org.hibernate.LockOptions.WAIT_FOREVER; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_USE_BINARY_FLOATS; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_OSON_DISABLED; import static org.hibernate.dialect.type.OracleJdbcHelper.getArrayJdbcTypeConstructor; @@ -1470,7 +1470,7 @@ public RowLockStrategy getWriteRowLockStrategy() { } @Override - public String getForUpdateNowaitString() { + public String getForUpdateNowaitString(){ return " for update nowait"; } @@ -1494,12 +1494,36 @@ public String getForUpdateSkipLockedString(String aliases) { return " for update of " + aliases + " skip locked"; } + private String withTimeout(String lockString, Timeout timeout) { + return withTimeout( lockString, timeout.milliseconds() ); + } + + @Override + public String getWriteLockString(Timeout timeout) { + return withTimeout( getForUpdateString(), timeout ); + } + + @Override + public String getWriteLockString(String aliases, Timeout timeout) { + return withTimeout( getForUpdateString(aliases), timeout ); + } + + @Override + public String getReadLockString(Timeout timeout) { + return getWriteLockString( timeout ); + } + + @Override + public String getReadLockString(String aliases, Timeout timeout) { + return getWriteLockString( aliases, timeout ); + } + private String withTimeout(String lockString, int timeout) { return switch (timeout) { - case NO_WAIT -> supportsNoWait() ? lockString + " nowait" : lockString; - case SKIP_LOCKED -> supportsSkipLocked() ? lockString + " skip locked" : lockString; - case WAIT_FOREVER -> lockString; - default -> supportsWait() ? lockString + " wait " + getTimeoutInSeconds( timeout ) : lockString; + case Timeouts.NO_WAIT_MILLI -> supportsNoWait() ? lockString + " nowait" : lockString; + case Timeouts.SKIP_LOCKED_MILLI -> supportsSkipLocked() ? lockString + " skip locked" : lockString; + case Timeouts.WAIT_FOREVER_MILLI -> lockString; + default -> supportsWait() ? lockString + " wait " + Timeouts.getTimeoutInSeconds( timeout ) : lockString; }; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/OracleLockTimeoutTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/OracleLockTimeoutTest.java index 9b6bbc2e7646..10db83e4c285 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/OracleLockTimeoutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/locktimeout/OracleLockTimeoutTest.java @@ -9,7 +9,7 @@ import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.OracleDialect; - +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; @@ -18,6 +18,7 @@ /** * @author Vlad Mihalcea */ +@RequiresDialect(OracleDialect.class) public class OracleLockTimeoutTest extends BaseUnitTestCase { private final Dialect dialect = new OracleDialect( DatabaseVersion.make( 12 ) );