From e38a4de536f3d72b1f725b55f5b928227eb84127 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 1 May 2020 11:29:44 -0500 Subject: [PATCH] Verified and fixed BasicValue resolutions based on type-defs and UserTypes --- .../InFlightMetadataCollectorImpl.java | 43 +-- .../MetadataBuildingContextRootImpl.java | 8 +- .../hibernate/boot/internal/MetadataImpl.java | 1 + .../hibernate/boot/model/TypeDefinition.java | 18 +- .../boot/model/TypeDefinitionRegistry.java | 89 +---- .../TypeDefinitionRegistryStandardImpl.java | 118 ++++++ .../source/internal/hbm/MappingDocument.java | 5 +- .../internal/hbm/TypeDefinitionBinder.java | 2 +- .../boot/spi/InFlightMetadataCollector.java | 8 + .../org/hibernate/cfg/AnnotationBinder.java | 14 +- .../org/hibernate/mapping/BasicValue.java | 16 +- .../basics/CustomTypeResolutionTests.java | 352 ++++++++++++++++++ .../schema}/SchemaManagementScriptTests.java | 12 +- .../MetadataBuildingContextTestingImpl.java | 8 +- 14 files changed, 543 insertions(+), 151 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistryStandardImpl.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/CustomTypeResolutionTests.java rename hibernate-core/src/test/java/org/hibernate/orm/test/{grammar/importsql => bootstrap/schema}/SchemaManagementScriptTests.java (89%) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index 4a744352da..64d027a8c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -33,11 +33,12 @@ import org.hibernate.MappingException; import org.hibernate.SessionFactory; import org.hibernate.annotations.AnyMetaDef; import org.hibernate.annotations.common.reflection.XClass; -import org.hibernate.annotations.common.util.StringHelper; import org.hibernate.boot.CacheRegionDefinition; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; +import org.hibernate.boot.model.TypeDefinitionRegistry; +import org.hibernate.boot.model.TypeDefinitionRegistryStandardImpl; import org.hibernate.boot.model.convert.internal.AttributeConverterManager; import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler; @@ -131,10 +132,11 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector private final Map entityBindingMap = new HashMap<>(); private final Map collectionBindingMap = new HashMap<>(); - private final Map typeDefinitionMap = new HashMap<>(); private final Map filterDefinitionMap = new HashMap<>(); private final Map imports = new HashMap<>(); + private final TypeDefinitionRegistry typeDefRegistry = new TypeDefinitionRegistryStandardImpl(); + private Database database; private final Map namedQueryMap = new HashMap<>(); @@ -340,39 +342,20 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hibernate Type handling + + @Override + public TypeDefinitionRegistry getTypeDefinitionRegistry() { + return typeDefRegistry; + } + @Override public TypeDefinition getTypeDefinition(String registrationKey) { - return typeDefinitionMap.get( registrationKey ); + return typeDefRegistry.resolve( registrationKey ); } @Override public void addTypeDefinition(TypeDefinition typeDefinition) { - if ( typeDefinition == null ) { - throw new IllegalArgumentException( "Type definition is null" ); - } - - // Need to register both by name and registration keys. - if ( !StringHelper.isEmpty( typeDefinition.getName() ) ) { - addTypeDefinition( typeDefinition.getName(), typeDefinition ); - } - - if ( typeDefinition.getRegistrationKeys() != null ) { - for ( String registrationKey : typeDefinition.getRegistrationKeys() ) { - addTypeDefinition( registrationKey, typeDefinition ); - } - } - } - - private void addTypeDefinition(String registrationKey, TypeDefinition typeDefinition) { - final TypeDefinition previous = typeDefinitionMap.put( - registrationKey, typeDefinition ); - if ( previous != null ) { - log.debugf( - "Duplicate typedef name [%s] now -> %s", - registrationKey, - typeDefinition.getTypeImplementorClass().getName() - ); - } + typeDefRegistry.register( typeDefinition ); } @Override @@ -2259,7 +2242,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector entityBindingMap, mappedSuperClasses, collectionBindingMap, - typeDefinitionMap, + typeDefRegistry.copyRegistrationMap(), filterDefinitionMap, fetchProfileMap, imports, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java index f48afb284f..721d54c0c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataBuildingContextRootImpl.java @@ -6,7 +6,7 @@ */ package org.hibernate.boot.internal; -import org.hibernate.boot.model.TypeDefinitionRegistry; +import org.hibernate.boot.model.TypeDefinitionRegistryStandardImpl; import org.hibernate.boot.model.naming.ObjectNameNormalizer; import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; @@ -24,7 +24,7 @@ public class MetadataBuildingContextRootImpl implements MetadataBuildingContext private final MappingDefaults mappingDefaults; private final InFlightMetadataCollector metadataCollector; private final ObjectNameNormalizer objectNameNormalizer; - private final TypeDefinitionRegistry typeDefinitionRegistry; + private final TypeDefinitionRegistryStandardImpl typeDefinitionRegistry; public MetadataBuildingContextRootImpl( BootstrapContext bootstrapContext, @@ -40,7 +40,7 @@ public class MetadataBuildingContextRootImpl implements MetadataBuildingContext return MetadataBuildingContextRootImpl.this; } }; - this.typeDefinitionRegistry = new TypeDefinitionRegistry(); + this.typeDefinitionRegistry = new TypeDefinitionRegistryStandardImpl(); } @Override @@ -74,7 +74,7 @@ public class MetadataBuildingContextRootImpl implements MetadataBuildingContext } @Override - public TypeDefinitionRegistry getTypeDefinitionRegistry() { + public TypeDefinitionRegistryStandardImpl getTypeDefinitionRegistry() { return typeDefinitionRegistry; } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java index 4a8a2a644b..d02e454b4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java @@ -24,6 +24,7 @@ import org.hibernate.SessionFactory; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; +import org.hibernate.boot.model.TypeDefinitionRegistry; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.Namespace; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java index a14d7766b9..4bc670dcc7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinition.java @@ -25,7 +25,7 @@ import org.hibernate.resource.beans.spi.ManagedBean; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.type.BasicType; import org.hibernate.type.CustomType; -import org.hibernate.type.Type; +import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.java.MutabilityPlan; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -48,6 +48,7 @@ import org.hibernate.usertype.UserType; * @author Steve Ebersole * @author John Verhaeg */ +@SuppressWarnings({"rawtypes", "unchecked"}) public class TypeDefinition implements Serializable { private static final AtomicInteger nameCounter = new AtomicInteger(); @@ -96,8 +97,6 @@ public class TypeDefinition implements Serializable { } public BasicValue.Resolution resolve( - JavaTypeDescriptor explicitJtd, - SqlTypeDescriptor explicitStd, Map localConfigParameters, MutabilityPlan explicitMutabilityPlan, MetadataBuildingContext context) { @@ -137,8 +136,6 @@ public class TypeDefinition implements Serializable { return createResolution( name, typeInstance, - explicitJtd, - explicitStd, explicitMutabilityPlan, context ); @@ -213,7 +210,10 @@ public class TypeDefinition implements Serializable { @Override public MutabilityPlan getMutabilityPlan() { - throw new NotYetImplementedFor6Exception( getClass() ); + // a TypeDefinition does not explicitly provide a MutabilityPlan (yet?) + return resolvedBasicType.isMutable() + ? getDomainJavaDescriptor().getMutabilityPlan() + : ImmutableMutabilityPlan.instance(); } }; } @@ -226,8 +226,6 @@ public class TypeDefinition implements Serializable { public static BasicValue.Resolution createLocalResolution( String name, Class typeImplementorClass, - JavaTypeDescriptor explicitJtd, - SqlTypeDescriptor explicitStd, MutabilityPlan explicitMutabilityPlan, Map localTypeParams, MetadataBuildingContext buildingContext) { @@ -245,8 +243,6 @@ public class TypeDefinition implements Serializable { return createResolution( name, typeInstance, - explicitJtd, - explicitStd, explicitMutabilityPlan, buildingContext ); @@ -255,8 +251,6 @@ public class TypeDefinition implements Serializable { private static BasicValue.Resolution createResolution( String name, Object namedTypeInstance, - JavaTypeDescriptor explicitJtd, - SqlTypeDescriptor explicitStd, MutabilityPlan explicitMutabilityPlan, MetadataBuildingContext metadataBuildingContext) { if ( namedTypeInstance instanceof UserType ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistry.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistry.java index 1dddb037b9..d08676d80c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistry.java @@ -6,98 +6,27 @@ */ package org.hibernate.boot.model; -import java.util.HashMap; -import java.util.Locale; import java.util.Map; -import org.hibernate.internal.util.StringHelper; - -import org.jboss.logging.Logger; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; /** * @author Chris Cranford + * @author Steve Ebersole */ -public class TypeDefinitionRegistry { - private static final Logger log = Logger.getLogger( TypeDefinitionRegistry.class ); - private final TypeDefinitionRegistry parent; +public interface TypeDefinitionRegistry { - public enum DuplicationStrategy { + enum DuplicationStrategy { KEEP, OVERWRITE, DISALLOW } - private final Map typeDefinitionMap = new HashMap<>(); + TypeDefinition resolve(String typeName); + TypeDefinition resolveAutoApplied(BasicJavaDescriptor jtd); - public TypeDefinitionRegistry() { - this( null ); - } + TypeDefinitionRegistry register(TypeDefinition typeDefinition); + TypeDefinitionRegistry register(TypeDefinition typeDefinition, DuplicationStrategy duplicationStrategy); - public TypeDefinitionRegistry(TypeDefinitionRegistry parent) { - this.parent = parent; - } - - public TypeDefinition resolve(String typeName) { - final TypeDefinition localDefinition = typeDefinitionMap.get( typeName ); - if ( localDefinition != null ) { - return localDefinition; - } - - if ( parent != null ) { - return parent.resolve( typeName ); - } - - return null; - } - - public TypeDefinitionRegistry register(TypeDefinition typeDefinition) { - return register( typeDefinition, DuplicationStrategy.OVERWRITE ); - } - - public TypeDefinitionRegistry register(TypeDefinition typeDefinition, DuplicationStrategy duplicationStrategy) { - if ( typeDefinition == null ) { - throw new IllegalArgumentException( "TypeDefinition to register cannot be null" ); - } - - if ( typeDefinition.getTypeImplementorClass() == null ) { - throw new IllegalArgumentException( "TypeDefinition to register cannot define null #typeImplementorClass" ); - } - - if ( !StringHelper.isEmpty( typeDefinition.getName() ) ) { - register( typeDefinition.getName(), typeDefinition, duplicationStrategy ); - } - - if ( typeDefinition.getRegistrationKeys() != null ) { - for ( String registrationKey : typeDefinition.getRegistrationKeys() ) { - register( registrationKey, typeDefinition, duplicationStrategy ); - } - } - - return this; - } - - private void register(String name, TypeDefinition typeDefinition, DuplicationStrategy duplicationStrategy) { - if ( duplicationStrategy == DuplicationStrategy.KEEP ) { - if ( !typeDefinitionMap.containsKey( name ) ) { - typeDefinitionMap.put( name, typeDefinition ); - } - } - else { - final TypeDefinition existing = typeDefinitionMap.put( name, typeDefinition ); - if ( existing != null && existing != typeDefinition ) { - if ( duplicationStrategy == DuplicationStrategy.OVERWRITE ) { - log.debugf( "Overwrote existing registration [%s] for type definition.", name ); - } - else { - throw new IllegalArgumentException( - String.format( - Locale.ROOT, - "Attempted to overwrite registration [%s] for type definition.", - name - ) - ); - } - } - } - } + Map copyRegistrationMap(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistryStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistryStandardImpl.java new file mode 100644 index 0000000000..085730c773 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/TypeDefinitionRegistryStandardImpl.java @@ -0,0 +1,118 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.model; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.hibernate.internal.util.StringHelper; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; + +import org.jboss.logging.Logger; + +/** + * @author Chris Cranford + */ +public class TypeDefinitionRegistryStandardImpl implements TypeDefinitionRegistry { + private static final Logger log = Logger.getLogger( TypeDefinitionRegistryStandardImpl.class ); + + private final TypeDefinitionRegistry parent; + private final Map typeDefinitionMap = new HashMap<>(); + + public TypeDefinitionRegistryStandardImpl() { + this( null ); + } + + public TypeDefinitionRegistryStandardImpl(TypeDefinitionRegistry parent) { + this.parent = parent; + } + + @Override + public TypeDefinition resolve(String typeName) { + final TypeDefinition localDefinition = typeDefinitionMap.get( typeName ); + if ( localDefinition != null ) { + return localDefinition; + } + + if ( parent != null ) { + return parent.resolve( typeName ); + } + + return null; + } + + @Override + public TypeDefinition resolveAutoApplied(BasicJavaDescriptor jtd) { + // For now, check the definition map for a entry keyed by the JTD name. + // Ultimately should maybe have TypeDefinition or the registry keep explicit track of + // auto-applied defs + if ( jtd.getJavaType() == null ) { + return null; + } + + return typeDefinitionMap.get( jtd.getJavaType().getName() ); + } + + @Override + public TypeDefinitionRegistry register(TypeDefinition typeDefinition) { + return register( typeDefinition, DuplicationStrategy.OVERWRITE ); + } + + @Override + public TypeDefinitionRegistry register(TypeDefinition typeDefinition, DuplicationStrategy duplicationStrategy) { + if ( typeDefinition == null ) { + throw new IllegalArgumentException( "TypeDefinition to register cannot be null" ); + } + + if ( typeDefinition.getTypeImplementorClass() == null ) { + throw new IllegalArgumentException( "TypeDefinition to register cannot define null #typeImplementorClass" ); + } + + if ( !StringHelper.isEmpty( typeDefinition.getName() ) ) { + register( typeDefinition.getName(), typeDefinition, duplicationStrategy ); + } + + if ( typeDefinition.getRegistrationKeys() != null ) { + for ( String registrationKey : typeDefinition.getRegistrationKeys() ) { + register( registrationKey, typeDefinition, duplicationStrategy ); + } + } + + return this; + } + + private void register(String name, TypeDefinition typeDefinition, DuplicationStrategy duplicationStrategy) { + if ( duplicationStrategy == DuplicationStrategy.KEEP ) { + if ( !typeDefinitionMap.containsKey( name ) ) { + typeDefinitionMap.put( name, typeDefinition ); + } + } + else { + final TypeDefinition existing = typeDefinitionMap.put( name, typeDefinition ); + if ( existing != null && existing != typeDefinition ) { + if ( duplicationStrategy == DuplicationStrategy.OVERWRITE ) { + log.debugf( "Overwrote existing registration [%s] for type definition.", name ); + } + else { + throw new IllegalArgumentException( + String.format( + Locale.ROOT, + "Attempted to overwrite registration [%s] for type definition.", + name + ) + ); + } + } + } + } + + @Override + public Map copyRegistrationMap() { + return new HashMap<>( typeDefinitionMap ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java index 25f68c7429..fd25665ee6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/MappingDocument.java @@ -21,6 +21,7 @@ import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNamedQueryType; import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmTypeDefinitionType; import org.hibernate.boot.jaxb.hbm.spi.ResultSetMappingBindingDefinition; import org.hibernate.boot.model.TypeDefinitionRegistry; +import org.hibernate.boot.model.TypeDefinitionRegistryStandardImpl; import org.hibernate.boot.model.naming.ObjectNameNormalizer; import org.hibernate.boot.model.source.internal.OverriddenMappingDefaults; import org.hibernate.boot.model.source.spi.MetadataSourceProcessor; @@ -52,7 +53,7 @@ public class MappingDocument implements HbmLocalMetadataBuildingContext, Metadat private final ToolingHintContext toolingHintContext; - private final TypeDefinitionRegistry typeDefinitionRegistry; + private final TypeDefinitionRegistryStandardImpl typeDefinitionRegistry; public MappingDocument( @@ -78,7 +79,7 @@ public class MappingDocument implements HbmLocalMetadataBuildingContext, Metadat this.toolingHintContext = Helper.collectToolingHints( null, documentRoot ); - this.typeDefinitionRegistry = new TypeDefinitionRegistry( rootBuildingContext.getTypeDefinitionRegistry() ); + this.typeDefinitionRegistry = new TypeDefinitionRegistryStandardImpl( rootBuildingContext.getTypeDefinitionRegistry() ); } public JaxbHbmHibernateMapping getDocumentRoot() { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/TypeDefinitionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/TypeDefinitionBinder.java index facf757a88..c428d30031 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/TypeDefinitionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/TypeDefinitionBinder.java @@ -43,7 +43,7 @@ public class TypeDefinitionBinder { definition.getTypeImplementorClass().getName() ); - context.getMetadataCollector().addTypeDefinition( definition ); + context.getTypeDefinitionRegistry().register( definition ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java index f14594521b..c66a3f49ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/InFlightMetadataCollector.java @@ -22,6 +22,7 @@ import org.hibernate.boot.internal.ClassmateContext; import org.hibernate.boot.internal.NamedProcedureCallDefinitionImpl; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; +import org.hibernate.boot.model.TypeDefinitionRegistry; import org.hibernate.boot.model.convert.internal.InstanceBasedConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler; import org.hibernate.boot.model.convert.spi.ConverterDescriptor; @@ -171,9 +172,16 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor * @param typeDefinition The named type definition to add. * * @throws DuplicateMappingException If a TypeDefinition already exists with that name. + * + * @deprecated Use {@link #getTypeDefinitionRegistry()} instead + * + * @see #getTypeDefinitionRegistry() */ + @Deprecated void addTypeDefinition(TypeDefinition typeDefinition); + TypeDefinitionRegistry getTypeDefinitionRegistry(); + /** * Adds a filter definition to this repository. * diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index d1f39e6c48..e7ffe97387 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -1463,7 +1463,16 @@ public final class AnnotationBinder { if ( LOG.isDebugEnabled() ) { LOG.debugf( typeBindMessageF, defAnn.name() ); } - context.getMetadataCollector().addTypeDefinition( +// context.getMetadataCollector().addTypeDefinition( +// new TypeDefinition( +// defAnn.name(), +// defAnn.typeClass(), +// null, +// params, +// context.getMetadataCollector().getTypeConfiguration() +// ) +// ); + context.getTypeDefinitionRegistry().register( new TypeDefinition( defAnn.name(), defAnn.typeClass(), @@ -1478,7 +1487,8 @@ public final class AnnotationBinder { if ( LOG.isDebugEnabled() ) { LOG.debugf( typeBindMessageF, defAnn.defaultForType().getName() ); } - context.getMetadataCollector().addTypeDefinition( + + context.getTypeDefinitionRegistry().register( new TypeDefinition( defAnn.defaultForType().getName(), defAnn.typeClass(), diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index 6e16c372d2..29d3ba608f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -361,6 +361,13 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato // Use JTD if we know it to apply any specialized resolutions + final TypeDefinition autoAppliedTypeDef = getBuildingContext().getTypeDefinitionRegistry() + .resolveAutoApplied( (BasicJavaDescriptor) jtd ); + if ( autoAppliedTypeDef != null ) { + log.debugf( "BasicValue resolution matched auto-applied type-definition" ); + return autoAppliedTypeDef.resolve( getTypeParameters(), null, getBuildingContext() ); + } + if ( jtd instanceof EnumJavaTypeDescriptor ) { return InferredBasicValueResolver.fromEnum( (EnumJavaTypeDescriptor) jtd, @@ -499,8 +506,6 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato final TypeDefinition typeDefinition = context.getTypeDefinitionRegistry().resolve( name ); if ( typeDefinition != null ) { return typeDefinition.resolve( - explicitJtdAccess.apply( typeConfiguration ), - explicitStdAccess.apply( typeConfiguration ), localTypeParams, explicitMutabilityPlanAccess != null ? explicitMutabilityPlanAccess.apply( typeConfiguration ) @@ -527,8 +532,6 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato ); context.getTypeDefinitionRegistry().register( implicitDefinition ); return implicitDefinition.resolve( - explicitJtdAccess != null ? explicitJtdAccess.apply( typeConfiguration ) : null, - explicitStdAccess != null ? explicitStdAccess.apply( typeConfiguration ) : null, null, explicitMutabilityPlanAccess != null ? explicitMutabilityPlanAccess.apply( typeConfiguration ) @@ -540,8 +543,6 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato return TypeDefinition.createLocalResolution( name, typeNamedClass, - explicitJtdAccess.apply( typeConfiguration ), - explicitStdAccess.apply( typeConfiguration ), explicitMutabilityPlanAccess != null ? explicitMutabilityPlanAccess.apply( typeConfiguration ) : null, @@ -549,8 +550,9 @@ public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicato context ); } - catch (ClassLoadingException ignore) { + catch (ClassLoadingException e) { // allow the exception below to trigger + log.debugf( "Could not resolve type-name [%s] as Java type : %s", name, e ); } throw new MappingException( "Could not resolve named type : " + name ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/CustomTypeResolutionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/CustomTypeResolutionTests.java new file mode 100644 index 0000000000..43261820c7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/binding/annotations/basics/CustomTypeResolutionTests.java @@ -0,0 +1,352 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.bootstrap.binding.annotations.basics; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URL; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.hibernate.HibernateException; +import org.hibernate.SessionFactory; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.CustomType; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.java.UrlTypeDescriptor; +import org.hibernate.type.descriptor.sql.CharTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; +import org.hibernate.usertype.UserType; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; + +/** + * Resolution for custom-types + * + * @author Steve Ebersole + */ +@ServiceRegistry( + settings = @ServiceRegistry.Setting( name = AvailableSettings.HBM2DDL_AUTO, value = "create-drop" ) +) +@DomainModel( annotatedClasses = CustomTypeResolutionTests.Person.class ) +public class CustomTypeResolutionTests { + + @Test + public void testMappings(DomainModelScope scope) { + final PersistentClass entityBinding = scope.getDomainModel().getEntityBinding( Person.class.getName() ); + + final Property genderProperty = entityBinding.getProperty( "gender" ); + final BasicValue genderValue = (BasicValue) genderProperty.getValue(); + final BasicValue.Resolution genderValueResolution = genderValue.resolve(); + assertThat( genderValueResolution.getLegacyResolvedBasicType(), instanceOf( GenderType.class ) ); + + final Property string1Property = entityBinding.getProperty( "string1" ); + final BasicValue string1Value = (BasicValue) string1Property.getValue(); + final BasicValue.Resolution string1ValueResolution = string1Value.resolve(); + assertThat( string1ValueResolution.getLegacyResolvedBasicType(), instanceOf( CustomType.class ) ); + assertThat( string1ValueResolution.getJdbcMapping(), instanceOf( CustomType.class ) ); + assertThat( string1ValueResolution.getLegacyResolvedBasicType(), is( string1ValueResolution.getJdbcMapping() ) ); + final CustomType string1TypeWrapper = (CustomType) string1ValueResolution.getJdbcMapping(); + assertThat( string1TypeWrapper.getUserType(), instanceOf( UserTypeImpl.class ) ); + + final Property string2Property = entityBinding.getProperty( "string2" ); + final BasicValue string2Value = (BasicValue) string2Property.getValue(); + final BasicValue.Resolution string2ValueResolution = string2Value.resolve(); + assertThat( string2ValueResolution.getLegacyResolvedBasicType(), instanceOf( CustomType.class ) ); + assertThat( string2ValueResolution.getJdbcMapping(), instanceOf( CustomType.class ) ); + assertThat( string2ValueResolution.getLegacyResolvedBasicType(), is( string2ValueResolution.getJdbcMapping() ) ); + final CustomType string2TypeWrapper = (CustomType) string2ValueResolution.getJdbcMapping(); + assertThat( string2TypeWrapper.getUserType(), instanceOf( UserTypeImpl.class ) ); + + final Property url1Property = entityBinding.getProperty( "url1" ); + final BasicValue url1Value = (BasicValue) url1Property.getValue(); + final BasicValue.Resolution url1ValueResolution = url1Value.resolve(); + assertThat( url1ValueResolution.getLegacyResolvedBasicType(), instanceOf( CustomTypeImpl.class ) ); + + final Property url2Property = entityBinding.getProperty( "url2" ); + final BasicValue url2Value = (BasicValue) url2Property.getValue(); + final BasicValue.Resolution url2ValueResolution = url2Value.resolve(); + assertThat( url2ValueResolution.getLegacyResolvedBasicType(), instanceOf( CustomTypeImpl.class ) ); + } + + @Test + public void simpleUsageSmokeTest(DomainModelScope scope) { + try ( SessionFactory sessionFactory = scope.getDomainModel().buildSessionFactory()) { + // set up test data + sessionFactory.inTransaction( + session -> { + try { + session.persist( + new Person( + 1, + Gender.MALE, + "str1", + "str2", + new URL( "http://url1" ), + new URL( "http://url2" ) + ) + ); + } + catch (MalformedURLException e) { + throw new RuntimeException( e ); + } + } + ); + + // try to read it back + try { + sessionFactory.inTransaction( + session -> { + final Person loaded = session.byId( Person.class ).load( 1 ); + assertThat( loaded, notNullValue() ); + assertThat( loaded.gender, is( Gender.MALE ) ); + assertThat( loaded.string1, is( "str1" ) ); + assertThat( loaded.string2, is( "str2" ) ); + assertThat( loaded.url1.getHost(), is( "url1" ) ); + assertThat( loaded.url2.getHost(), is( "url2" ) ); + } + ); + } + finally { + sessionFactory.inTransaction( + session -> session.createQuery( "delete Person" ).executeUpdate() + ); + } + } + } + + + public enum Gender { MALE, FEMALE } + + @SuppressWarnings("unused") + @Entity( name = "Person" ) + @Table( name = "persons" ) + @TypeDef( name = "user-type", typeClass = UserTypeImpl.class ) + @TypeDef( name = "custom-type", typeClass = CustomTypeImpl.class ) + @TypeDef( name = "gender-type", typeClass = GenderType.class, defaultForType = Gender.class ) + public static class Person { + @Id + private Integer id; + + private Gender gender; + + @Type( type = "org.hibernate.orm.test.bootstrap.binding.annotations.basics.CustomTypeResolutionTests$UserTypeImpl" ) + private String string1; + + @Type( type = "user-type" ) + private String string2; + + @Type( type = "org.hibernate.orm.test.bootstrap.binding.annotations.basics.CustomTypeResolutionTests$CustomTypeImpl" ) + private URL url1; + + @Type( type = "custom-type" ) + private URL url2; + + public Person() { + } + + public Person(Integer id, Gender gender, String string1, String string2, URL url1, URL url2) { + this.id = id; + this.gender = gender; + this.string1 = string1; + this.string2 = string2; + this.url1 = url1; + this.url2 = url2; + } + } + + public static class UserTypeImpl implements UserType { + + @Override + public int[] sqlTypes() { + return new int[] { Types.VARCHAR }; + } + + @Override + public Class returnedClass() { + return String.class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + return Objects.equals( x, y ); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return Objects.hashCode( x ); + } + + @Override + public Object nullSafeGet( + ResultSet rs, + String[] names, + SharedSessionContractImplementor session, + Object owner) throws HibernateException, SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public void nullSafeSet( + PreparedStatement st, + Object value, + int index, + SharedSessionContractImplementor session) throws HibernateException, SQLException { + if ( value == null ) { + st.setNull( index, Types.VARCHAR ); + } + else { + st.setString( index, value.toString() ); + } + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(Object value) throws HibernateException { + return (String) value; + } + + @Override + public Object assemble(Serializable cached, Object owner) throws HibernateException { + return cached; + } + + @Override + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return original; + } + } + + public static class CustomTypeImpl extends AbstractSingleColumnStandardBasicType { + + public CustomTypeImpl() { + super( VarcharTypeDescriptor.INSTANCE, UrlTypeDescriptor.INSTANCE ); + } + + @Override + public String getName() { + return "Custom URL mapper"; + } + } + + public static class GenderJtd implements JavaTypeDescriptor { + /** + * Singleton access + */ + public static final GenderJtd INSTANCE = new GenderJtd(); + + @Override + public SqlTypeDescriptor getJdbcRecommendedSqlType(SqlTypeDescriptorIndicators context) { + return CharTypeDescriptor.INSTANCE; + } + + @Override + public Gender fromString(String string) { + if ( StringHelper.isEmpty( string ) ) { + return null; + } + + if ( "M".equalsIgnoreCase( string ) ) { + return Gender.MALE; + } + else if ( "F".equalsIgnoreCase( string ) ) { + return Gender.FEMALE; + } + + throw new IllegalArgumentException( "Unrecognized Gender code : " + string ); + } + + @Override + @SuppressWarnings("unchecked") + public X unwrap(Gender value, Class type, WrapperOptions options) { + // only defines support for String conversion as part of unwrap + if ( value == null ) { + return null; + } + + if ( type.isInstance( value ) ) { + return (X) value; + } + + if ( String.class.equals( type ) ) { + return (X) (value == Gender.MALE ? "M" : "F"); + } + + throw new IllegalArgumentException(); + } + + @Override + public Gender wrap(X value, WrapperOptions options) { + if ( value == null ) { + return null; + } + + if ( value instanceof String ) { + if ( "F".equalsIgnoreCase( (String) value ) ) { + return Gender.FEMALE; + } + + if ( "M".equalsIgnoreCase( (String) value ) ) { + return Gender.MALE; + } + } + + throw new IllegalArgumentException(); + } + + @Override + public Class getJavaTypeClass() { + return null; + } + } + + public static class GenderType extends AbstractSingleColumnStandardBasicType { + + public GenderType() { + super( VarcharTypeDescriptor.INSTANCE, GenderJtd.INSTANCE ); + } + + @Override + public String getName() { + return "Custom URL mapper"; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/grammar/importsql/SchemaManagementScriptTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/schema/SchemaManagementScriptTests.java similarity index 89% rename from hibernate-core/src/test/java/org/hibernate/orm/test/grammar/importsql/SchemaManagementScriptTests.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/schema/SchemaManagementScriptTests.java index 82f5b4bd32..75be67e01b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/grammar/importsql/SchemaManagementScriptTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/schema/SchemaManagementScriptTests.java @@ -1,17 +1,11 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.orm.test.grammar.importsql; +package org.hibernate.orm.test.bootstrap.schema; import java.io.BufferedReader; import java.io.Reader; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/boot/MetadataBuildingContextTestingImpl.java b/hibernate-testing/src/main/java/org/hibernate/testing/boot/MetadataBuildingContextTestingImpl.java index f03aa9186b..6776a36191 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/boot/MetadataBuildingContextTestingImpl.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/boot/MetadataBuildingContextTestingImpl.java @@ -9,7 +9,7 @@ package org.hibernate.testing.boot; import org.hibernate.boot.internal.BootstrapContextImpl; import org.hibernate.boot.internal.InFlightMetadataCollectorImpl; import org.hibernate.boot.internal.MetadataBuilderImpl; -import org.hibernate.boot.model.TypeDefinitionRegistry; +import org.hibernate.boot.model.TypeDefinitionRegistryStandardImpl; import org.hibernate.boot.model.naming.ObjectNameNormalizer; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -29,7 +29,7 @@ public class MetadataBuildingContextTestingImpl implements MetadataBuildingConte private final InFlightMetadataCollector metadataCollector; private final BootstrapContext bootstrapContext; private final ObjectNameNormalizer objectNameNormalizer; - private final TypeDefinitionRegistry typeDefinitionRegistry; + private final TypeDefinitionRegistryStandardImpl typeDefinitionRegistry; public MetadataBuildingContextTestingImpl() { this( new StandardServiceRegistryBuilder().build() ); @@ -48,7 +48,7 @@ public class MetadataBuildingContextTestingImpl implements MetadataBuildingConte } }; - this.typeDefinitionRegistry = new TypeDefinitionRegistry(); + this.typeDefinitionRegistry = new TypeDefinitionRegistryStandardImpl(); } @Override @@ -82,7 +82,7 @@ public class MetadataBuildingContextTestingImpl implements MetadataBuildingConte } @Override - public TypeDefinitionRegistry getTypeDefinitionRegistry() { + public TypeDefinitionRegistryStandardImpl getTypeDefinitionRegistry() { return typeDefinitionRegistry; } }