From ae8b3f9a33f204086cffcb857a4863f20c2953ad Mon Sep 17 00:00:00 2001 From: marko-bekhta Date: Tue, 25 Jul 2023 11:08:32 +0200 Subject: [PATCH] HHH-16990 Add support for more hibernate-specific features to XML mappings - org.hibernate.annotations.Type - org.hibernate.annotations.JdbcTypeCode - org.hibernate.annotations.UuidGenerator --- .../UuidGeneratorStyleMarshalling.java | 24 +++++ .../JPAXMLOverriddenAnnotationReader.java | 50 ++++++++++- .../boot/model/internal/PropertyBinder.java | 19 +++- .../hibernate/xsd/mapping/mapping-3.1.0.xsd | 49 ++++++++++- .../src/main/xjb/mapping-bindings.xjb | 6 ++ ...rnateOrmSpecificAttributesMappingTest.java | 87 +++++++++++++++++++ .../hibernate-orm-specific-attributes.xml | 22 +++++ 7 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/marshall/UuidGeneratorStyleMarshalling.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java create mode 100644 hibernate-core/src/test/resources/xml/jaxb/mapping/partial/hibernate-orm-specific-attributes.xml diff --git a/hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/marshall/UuidGeneratorStyleMarshalling.java b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/marshall/UuidGeneratorStyleMarshalling.java new file mode 100644 index 0000000000..94db3f0919 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/jaxb/mapping/marshall/UuidGeneratorStyleMarshalling.java @@ -0,0 +1,24 @@ +/* + * 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.jaxb.mapping.marshall; + +import java.util.Locale; + +import org.hibernate.annotations.UuidGenerator; + +/** + * JAXB marshalling for {@link UuidGenerator.Style} + */ +public class UuidGeneratorStyleMarshalling { + public static UuidGenerator.Style fromXml(String name) { + return name == null ? null : UuidGenerator.Style.valueOf( name.toUpperCase( Locale.ROOT ) ); + } + + public static String toXml(UuidGenerator.Style style) { + return style == null ? null : style.name().toLowerCase( Locale.ROOT ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JPAXMLOverriddenAnnotationReader.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JPAXMLOverriddenAnnotationReader.java index 42c254e8ae..9a162a151e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JPAXMLOverriddenAnnotationReader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/JPAXMLOverriddenAnnotationReader.java @@ -28,8 +28,12 @@ import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.Columns; +import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.ManyToAny; +import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Subselect; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.UuidGenerator; import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor; import org.hibernate.annotations.common.annotationfactory.AnnotationFactory; import org.hibernate.annotations.common.reflection.AnnotationReader; @@ -46,6 +50,8 @@ import org.hibernate.boot.jaxb.mapping.JaxbCascadeType; import org.hibernate.boot.jaxb.mapping.JaxbCollectionTable; import org.hibernate.boot.jaxb.mapping.JaxbColumn; import org.hibernate.boot.jaxb.mapping.JaxbColumnResult; +import org.hibernate.boot.jaxb.mapping.JaxbColumnType; +import org.hibernate.boot.jaxb.mapping.JaxbConfigurationParameter; import org.hibernate.boot.jaxb.mapping.JaxbConstructorResult; import org.hibernate.boot.jaxb.mapping.JaxbConvert; import org.hibernate.boot.jaxb.mapping.JaxbDiscriminatorColumn; @@ -93,6 +99,7 @@ import org.hibernate.boot.jaxb.mapping.JaxbSynchronizedTable; import org.hibernate.boot.jaxb.mapping.JaxbTable; import org.hibernate.boot.jaxb.mapping.JaxbTableGenerator; import org.hibernate.boot.jaxb.mapping.JaxbUniqueConstraint; +import org.hibernate.boot.jaxb.mapping.JaxbUuidGenerator; import org.hibernate.boot.jaxb.mapping.JaxbVersion; import org.hibernate.boot.jaxb.mapping.LifecycleCallbackContainer; import org.hibernate.boot.jaxb.mapping.ManagedType; @@ -255,6 +262,7 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader { annotationToXml.put( ExcludeDefaultListeners.class, "exclude-default-listeners" ); annotationToXml.put( ExcludeSuperclassListeners.class, "exclude-superclass-listeners" ); // annotationToXml.put( AccessType.class, "access" ); + // FIXME: adding same annotation as a key multiple times: annotationToXml.put( AttributeOverride.class, "attribute-override" ); annotationToXml.put( AttributeOverrides.class, "attribute-override" ); annotationToXml.put( AttributeOverride.class, "association-override" ); @@ -306,7 +314,9 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader { annotationToXml.put( Convert.class, "convert" ); annotationToXml.put( Converts.class, "convert" ); annotationToXml.put( ConstructorResult.class, "constructor-result" ); - + annotationToXml.put( Type.class, "type" ); + annotationToXml.put( JdbcTypeCode.class, "jdbc-type-code" ); + annotationToXml.put( UuidGenerator.class, "uuid-generator" ); } private final XMLContext xmlContext; @@ -1592,6 +1602,8 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader { getFetchType( basic, element.getFetch() ); copyAttribute( basic, "optional", element.isOptional(), false ); annotationList.add( AnnotationFactory.create( basic ) ); + getType( annotationList, element.getType() ); + getJdbcTypeCode( annotationList, element.getJdbcTypeCode() ); } if ( elementsForProperty.isEmpty() && defaults.canUseJavaAnnotations() ) { //no annotation presence constraint, basic is the default @@ -1618,6 +1630,40 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader { } } + private void getType(List annotationList, JaxbColumnType type) { + if ( type != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( Type.class ); + ad.setValue( "value", classLoaderAccess.classForName( type.getValue() ) ); + Parameter[] parameters = new Parameter[type.getParameters().size()]; + for ( int i = 0; i < parameters.length; i++ ) { + JaxbConfigurationParameter parameter = type.getParameters().get( i ); + AnnotationDescriptor param = new AnnotationDescriptor( Parameter.class ); + param.setValue( "name", parameter.getName() ); + param.setValue( "value", parameter.getValue() ); + parameters[i] = AnnotationFactory.create( param ); + } + ad.setValue( "parameters", parameters ); + + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getUuidGenerator(List annotationList, JaxbUuidGenerator uuidGenerator) { + if ( uuidGenerator != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( UuidGenerator.class ); + ad.setValue( "style", uuidGenerator.getStyle() ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + + private void getJdbcTypeCode(List annotationList, Integer jdbcTypeCode) { + if ( jdbcTypeCode != null ) { + AnnotationDescriptor ad = new AnnotationDescriptor( JdbcTypeCode.class ); + ad.setValue( "value", jdbcTypeCode ); + annotationList.add( AnnotationFactory.create( ad ) ); + } + } + private void getEnumerated(List annotationList, EnumType enumType) { if ( enumType != null ) { AnnotationDescriptor ad = new AnnotationDescriptor( Enumerated.class ); @@ -1710,6 +1756,8 @@ public class JPAXMLOverriddenAnnotationReader implements AnnotationReader { AnnotationDescriptor id = new AnnotationDescriptor( Id.class ); annotationList.add( AnnotationFactory.create( id ) ); getAccessType( annotationList, element.getAccess() ); + getType( annotationList, element.getType() ); + getUuidGenerator( annotationList, element.getUuidGenerator() ); } } if ( elementsForProperty.isEmpty() && defaults.canUseJavaAnnotations() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index d3c4521d92..5fb232dfb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -8,6 +8,7 @@ package org.hibernate.boot.model.internal; import java.lang.annotation.Annotation; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -1304,7 +1305,7 @@ public class PropertyBinder { final XProperty idProperty = inferredData.getProperty(); final List idGeneratorAnnotations = findContainingAnnotations( idProperty, IdGeneratorType.class ); final List generatorAnnotations = findContainingAnnotations( idProperty, ValueGenerationType.class ); - generatorAnnotations.removeAll( idGeneratorAnnotations ); + removeIdGenerators( generatorAnnotations, idGeneratorAnnotations ); if ( idGeneratorAnnotations.size() + generatorAnnotations.size() > 1 ) { throw new AnnotationException( "Property '"+ getPath( propertyHolder, inferredData ) + "' has too many generator annotations " + combine( idGeneratorAnnotations, generatorAnnotations ) ); @@ -1330,4 +1331,20 @@ public class PropertyBinder { } } } + + // Since these collections may contain Proxies created by common-annotations module we cannot reliably use simple remove/removeAll + // collection methods as those proxies do not implement hashcode/equals and even a simple `a.equals(a)` will return `false`. + // Instead, we will check the annotation types, since generator annotations should not be "repeatable" we should have only + // at most one annotation for a generator: + private static void removeIdGenerators(List generatorAnnotations, List idGeneratorAnnotations) { + for ( Annotation id : idGeneratorAnnotations ) { + Iterator iterator = generatorAnnotations.iterator(); + while ( iterator.hasNext() ) { + Annotation gen = iterator.next(); + if ( gen.annotationType().equals( id.annotationType() ) ) { + iterator.remove(); + } + } + } + } } diff --git a/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd b/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd index 6af69908c9..4a1a5d775f 100644 --- a/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd +++ b/hibernate-core/src/main/resources/org/hibernate/xsd/mapping/mapping-3.1.0.xsd @@ -519,7 +519,6 @@ See `@org.hibernate.annotations.TenantId` - See `@org.hibernate.annotations.TenantId` @@ -598,6 +597,8 @@ See `@org.hibernate.annotations.Nationalized` See `@org.hibernate.annotations.OptimisticLock` See `@org.hibernate.annotations.AttributeAccessor` + See `@org.hibernate.annotations.Type` + See `@org.hibernate.annotations.JdbcTypeCode` @@ -614,6 +615,7 @@ + @@ -623,6 +625,7 @@ + @@ -1058,6 +1061,9 @@ See `@jakarta.persistence.Id` See `@org.hibernate.annotations.AttributeAccessor` + See `@org.hibernate.annotations.Type` + See `@org.hibernate.annotations.JdbcTypeCode` + See `@org.hibernate.annotations.UuidGenerator` @@ -1068,12 +1074,15 @@ + + + @@ -2815,4 +2824,42 @@ + + + + + + See `@org.hibernate.annotations.Type` + + + + + + + + + + + + + + org.hibernate.annotations.UuidGenerator.Style enum values + + + + + + + + + + + + + See `@org.hibernate.annotations.UuidGenerator` + + + + + diff --git a/hibernate-core/src/main/xjb/mapping-bindings.xjb b/hibernate-core/src/main/xjb/mapping-bindings.xjb index 569167aadd..a734709a94 100644 --- a/hibernate-core/src/main/xjb/mapping-bindings.xjb +++ b/hibernate-core/src/main/xjb/mapping-bindings.xjb @@ -423,6 +423,12 @@ printMethod="org.hibernate.boot.jaxb.mapping.marshall.CollectionClassificationMarshalling.toXml" /> + + + + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java new file mode 100644 index 0000000000..f4589a879b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/boot/jaxb/mapping/HibernateOrmSpecificAttributesMappingTest.java @@ -0,0 +1,87 @@ +/* + * 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.boot.jaxb.mapping; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.sql.Types; +import java.util.Set; +import java.util.UUID; + +import org.hibernate.boot.model.process.internal.UserTypeResolution; +import org.hibernate.generator.Generator; +import org.hibernate.id.uuid.UuidGenerator; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Property; +import org.hibernate.type.SqlTypes; +import org.hibernate.usertype.UserTypeSupport; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.junit.jupiter.api.Test; + +@DomainModel(annotatedClasses = HibernateOrmSpecificAttributesMappingTest.MyEntity.class, xmlMappings = "xml/jaxb/mapping/partial/hibernate-orm-specific-attributes.xml") +public class HibernateOrmSpecificAttributesMappingTest { + @Test + public void verifyMapping(DomainModelScope scope) { + scope.withHierarchy( HibernateOrmSpecificAttributesMappingTest.MyEntity.class, (entityDescriptor) -> { + Generator generator = entityDescriptor.getIdentifierProperty().createGenerator( null ); + assertThat( generator ) + .isInstanceOf( UuidGenerator.class ); + + Property name = entityDescriptor.getProperty( "name" ); + assertThat( name.getValue() ) + .isInstanceOf( BasicValue.class ); + assertThat( ( (BasicValue) name.getValue() ).getExplicitJdbcTypeCode() ) + .isEqualTo( SqlTypes.CLOB ); + + Property tags = entityDescriptor.getProperty( "tags" ); + assertThat( tags.getValue() ) + .isInstanceOf( BasicValue.class ); + assertThat( ( (BasicValue) tags.getValue() ).getResolution() ) + .isInstanceOf( UserTypeResolution.class ); + } ); + } + + public static class MyEntity { + private UUID id; + + private String name; + + private Set tags; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getTags() { + return tags; + } + + public void setTags(Set tags) { + this.tags = tags; + } + } + + public static class DelimitedStringsJavaType extends UserTypeSupport { + public DelimitedStringsJavaType() { + super( Set.class, Types.VARCHAR ); + } + } +} diff --git a/hibernate-core/src/test/resources/xml/jaxb/mapping/partial/hibernate-orm-specific-attributes.xml b/hibernate-core/src/test/resources/xml/jaxb/mapping/partial/hibernate-orm-specific-attributes.xml new file mode 100644 index 0000000000..2faaf08e02 --- /dev/null +++ b/hibernate-core/src/test/resources/xml/jaxb/mapping/partial/hibernate-orm-specific-attributes.xml @@ -0,0 +1,22 @@ + + + + + org.hibernate.orm.test.boot.jaxb.mapping + + + + + + + + + + + + \ No newline at end of file