Skip to content

Commit

Permalink
HHH-16388 - Configuration setting for wrapper Byte[]/Character[] trea…
Browse files Browse the repository at this point in the history
…tment
  • Loading branch information
beikov committed Mar 30, 2023
1 parent b799da7 commit 214b647
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 14 deletions.
Expand Up @@ -78,6 +78,7 @@
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.ServiceException;
import org.hibernate.type.BasicType;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.UserType;

Expand All @@ -87,6 +88,8 @@
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.SharedCacheMode;

import static org.hibernate.cfg.AvailableSettings.WRAPPER_ARRAY_HANDLING;

/**
* @author Steve Ebersole
*/
Expand Down Expand Up @@ -582,6 +585,7 @@ public static class MetadataBuildingOptionsImpl
private final MappingDefaultsImpl mappingDefaults;
private final IdentifierGeneratorFactory identifierGeneratorFactory;
private final TimeZoneStorageType defaultTimezoneStorage;
private final WrapperArrayHandling wrapperArrayHandling;

// todo (6.0) : remove bootstrapContext property along with the deprecated methods
private BootstrapContext bootstrapContext;
Expand Down Expand Up @@ -619,6 +623,7 @@ public MetadataBuildingOptionsImpl(StandardServiceRegistry serviceRegistry) {
this.mappingDefaults = new MappingDefaultsImpl( serviceRegistry );

this.defaultTimezoneStorage = resolveTimeZoneStorageStrategy( configService );
this.wrapperArrayHandling = resolveWrapperArrayHandling( configService );
this.multiTenancyEnabled = JdbcEnvironmentImpl.isMultiTenancyEnabled( serviceRegistry );

this.xmlMappingEnabled = configService.getSetting(
Expand Down Expand Up @@ -868,6 +873,11 @@ private TimeZoneStorageStrategy toTimeZoneStorageStrategy(TimeZoneSupport timeZo
}
}

@Override
public WrapperArrayHandling getWrapperArrayHandling() {
return wrapperArrayHandling;
}

@Override
public List<BasicTypeRegistration> getBasicTypeRegistrations() {
return basicTypeRegistrations;
Expand Down Expand Up @@ -999,4 +1009,21 @@ private static TimeZoneStorageType resolveTimeZoneStorageStrategy(
TimeZoneStorageType.DEFAULT
);
}

private static WrapperArrayHandling resolveWrapperArrayHandling(
ConfigurationService configService) {
return configService.getSetting(
WRAPPER_ARRAY_HANDLING,
value -> {
if ( value == null ) {
throw new IllegalArgumentException( "Null value passed to convert" );
}

return value instanceof WrapperArrayHandling
? (WrapperArrayHandling) value
: WrapperArrayHandling.valueOf( value.toString().toUpperCase( Locale.ROOT ) );
},
WrapperArrayHandling.DISALLOW
);
}
}
Expand Up @@ -63,6 +63,10 @@
import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.descriptor.java.ByteArrayJavaType;
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType;
Expand Down Expand Up @@ -603,6 +607,21 @@ public void contributeAttributeConverter(Class<? extends AttributeConverter<?, ?
}
};

if ( options.getWrapperArrayHandling() == WrapperArrayHandling.LEGACY ) {
typeConfiguration.getJavaTypeRegistry().addDescriptor( ByteArrayJavaType.INSTANCE );
typeConfiguration.getJavaTypeRegistry().addDescriptor( CharacterArrayJavaType.INSTANCE );
final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();

basicTypeRegistry.addTypeReferenceRegistrationKey(
StandardBasicTypes.CHARACTER_ARRAY.getName(),
Character[].class.getName(), "Character[]"
);
basicTypeRegistry.addTypeReferenceRegistrationKey(
StandardBasicTypes.BINARY_WRAPPER.getName(),
Byte[].class.getName(), "Byte[]"
);
}

// add Dialect contributed types
final Dialect dialect = options.getServiceRegistry().getService( JdbcServices.class ).getDialect();
dialect.contribute( typeContributions, options.getServiceRegistry() );
Expand Down
Expand Up @@ -19,6 +19,7 @@
import org.hibernate.cfg.MetadataSourceType;
import org.hibernate.dialect.TimeZoneSupport;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.spi.TypeConfiguration;

import jakarta.persistence.SharedCacheMode;
Expand Down Expand Up @@ -67,6 +68,11 @@ public TimeZoneSupport getTimeZoneSupport() {
return delegate.getTimeZoneSupport();
}

@Override
public WrapperArrayHandling getWrapperArrayHandling() {
return delegate.getWrapperArrayHandling();
}

@Override
public List<BasicTypeRegistration> getBasicTypeRegistrations() {
return delegate.getBasicTypeRegistrations();
Expand Down
Expand Up @@ -23,6 +23,7 @@
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.metamodel.internal.ManagedTypeRepresentationResolverStandard;
import org.hibernate.metamodel.spi.ManagedTypeRepresentationResolver;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.spi.TypeConfiguration;

import jakarta.persistence.SharedCacheMode;
Expand Down Expand Up @@ -70,6 +71,13 @@ public interface MetadataBuildingOptions {
*/
TimeZoneSupport getTimeZoneSupport();

/**
* @return the {@link WrapperArrayHandling} to use for wrapper arrays {@code Byte[]} and {@code Character[]}.
*
* @see org.hibernate.cfg.AvailableSettings#WRAPPER_ARRAY_HANDLING
*/
WrapperArrayHandling getWrapperArrayHandling();

default ManagedTypeRepresentationResolver getManagedTypeRepresentationResolver() {
// for now always return the standard one
return ManagedTypeRepresentationResolverStandard.INSTANCE;
Expand Down
13 changes: 13 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java
Expand Up @@ -47,6 +47,7 @@
import org.hibernate.type.BasicType;
import org.hibernate.type.CustomType;
import org.hibernate.type.Type;
import org.hibernate.type.WrapperArrayHandling;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
Expand Down Expand Up @@ -857,6 +858,18 @@ public Object accept(ValueVisitor visitor) {
return visitor.accept(this);
}

@Internal
public boolean isDisallowedWrapperArray() {
return getBuildingContext().getBuildingOptions().getWrapperArrayHandling() == WrapperArrayHandling.DISALLOW
&& ( explicitJavaTypeAccess == null || explicitJavaTypeAccess.apply( getTypeConfiguration() ) == null )
&& isWrapperByteOrCharacterArray();
}

private boolean isWrapperByteOrCharacterArray() {
final Class<?> javaTypeClass = getResolution().getDomainJavaType().getJavaTypeClass();
return javaTypeClass == Byte[].class || javaTypeClass == Character[].class;
}

/**
* Resolved form of {@link BasicValue} as part of interpreting the
* boot-time model into the run-time model
Expand Down
20 changes: 19 additions & 1 deletion hibernate-core/src/main/java/org/hibernate/mapping/Property.java
Expand Up @@ -18,6 +18,7 @@
import org.hibernate.MappingException;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadeStyles;
import org.hibernate.engine.spi.Mapping;
Expand All @@ -33,6 +34,7 @@
import org.hibernate.generator.GeneratorCreationContext;
import org.hibernate.type.CompositeType;
import org.hibernate.type.Type;
import org.hibernate.type.WrapperArrayHandling;

/**
* A mapping model object representing a property or field of an {@linkplain PersistentClass entity}
Expand Down Expand Up @@ -282,7 +284,23 @@ public void setMetaAttributes(Map<String, MetaAttribute> metas) {
}

public boolean isValid(Mapping mapping) throws MappingException {
return getValue().isValid( mapping );
final Value value = getValue();
if ( value instanceof BasicValue && ( (BasicValue) value ).isDisallowedWrapperArray() ) {
throw new MappingException(
"The property " + persistentClass.getEntityName() + "#" + name +
" uses a wrapper type Byte[]/Character[] which indicates an issue in your domain model. " +
"These types have been treated like byte[]/char[] until Hibernate 6.2 which meant that " +
"null elements were not allowed, but on JDBC were processed like VARBINARY or VARCHAR. " +
"If you don't use nulls in your arrays, change the type of the property to byte[]/char[]. " +
"To allow explicit uses of the wrapper types Byte[]/Character[] which allows null element " +
"but has a different serialization format than before Hibernate 6.2, configure the " +
"setting " + AvailableSettings.WRAPPER_ARRAY_HANDLING + " to the value " + WrapperArrayHandling.ALLOW + ". " +
"To revert to the legacy treatment of these types, configure the value to " + WrapperArrayHandling.LEGACY + ". " +
"For more information on this matter, consult the migration guide of Hibernate 6.2 " +
"and the Javadoc of the org.hibernate.cfg.AvailableSettings.WRAPPER_ARRAY_HANDLING field."
);
}
return value.isValid( mapping );
}

public String toString() {
Expand Down
Expand Up @@ -12,6 +12,7 @@
import java.util.function.Supplier;

import org.hibernate.HibernateException;
import org.hibernate.Internal;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
Expand Down Expand Up @@ -277,6 +278,17 @@ public void unregister(String... keys) {
}
}

@Internal
public void addTypeReferenceRegistrationKey(String typeReferenceKey, String... additionalTypeReferenceKeys) {
final BasicTypeReference<?> basicTypeReference = typeReferencesByName.get( typeReferenceKey );
if ( basicTypeReference == null ) {
throw new IllegalArgumentException( "Couldn't find type reference with name: " + typeReferenceKey );
}
for ( String additionalTypeReferenceKey : additionalTypeReferenceKeys ) {
typeReferencesByName.put( additionalTypeReferenceKey, basicTypeReference );
}
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// priming
Expand Down
Expand Up @@ -14,6 +14,7 @@
import java.util.Date;
import java.util.Locale;

import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgresPlusDialect;
Expand All @@ -26,8 +27,10 @@
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.RequiresDialectFeatureGroup;
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.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -48,6 +51,9 @@
annotatedClasses = { SomeEntity.class, SomeOtherEntity.class }
)
@SessionFactory
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class BasicOperationsTest {

private static final String SOME_ENTITY_TABLE_NAME = "SOMEENTITY";
Expand Down
Expand Up @@ -9,8 +9,11 @@
import java.util.Arrays;

import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.type.WrapperArrayHandling;

import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
Expand All @@ -28,6 +31,12 @@
public class ImageTest extends BaseCoreFunctionalTestCase {
private static final int ARRAY_SIZE = 10000;

@Override
protected void configure(Configuration configuration) {
super.configure( configuration );
configuration.setProperty( AvailableSettings.WRAPPER_ARRAY_HANDLING, WrapperArrayHandling.ALLOW.name() );
}

@Test
public void testBoundedLongByteArrayAccess() {
byte[] original = buildRecursively(ARRAY_SIZE, true);
Expand Down
Expand Up @@ -13,6 +13,7 @@
import jakarta.persistence.Table;

import org.hibernate.annotations.JavaType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
Expand All @@ -24,8 +25,10 @@
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;

import org.hibernate.testing.orm.junit.DomainModel;
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.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

Expand All @@ -40,6 +43,9 @@
*/
@DomainModel(annotatedClasses = ByteArrayMappingTests.EntityOfByteArrays.class)
@SessionFactory
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class ByteArrayMappingTests {

@Test
Expand Down
Expand Up @@ -13,6 +13,7 @@
import jakarta.persistence.Table;

import org.hibernate.annotations.JavaType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
Expand All @@ -24,8 +25,10 @@
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;

import org.hibernate.testing.orm.junit.DomainModel;
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.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -39,6 +42,9 @@
*/
@DomainModel(annotatedClasses = CharacterArrayMappingTests.EntityWithCharArrays.class)
@SessionFactory
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class CharacterArrayMappingTests {
@Test
public void verifyMappings(SessionFactoryScope scope) {
Expand Down
Expand Up @@ -13,6 +13,7 @@

import org.hibernate.annotations.JavaType;
import org.hibernate.annotations.Nationalized;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.NationalizationSupport;
import org.hibernate.metamodel.mapping.JdbcMapping;
Expand All @@ -27,8 +28,10 @@
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
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.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
Expand All @@ -45,6 +48,9 @@
@DomainModel(annotatedClasses = CharacterArrayNationalizedMappingTests.EntityWithCharArrays.class)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsNationalizedData.class)
@ServiceRegistry(
settings = @Setting(name = AvailableSettings.WRAPPER_ARRAY_HANDLING, value = "ALLOW")
)
public class CharacterArrayNationalizedMappingTests {
@Test
public void verifyMappings(SessionFactoryScope scope) {
Expand Down

0 comments on commit 214b647

Please sign in to comment.