HHH-17460 - Ongoing JPA 32 work

This commit is contained in:
Steve Ebersole 2024-03-12 14:38:45 -05:00
parent a223cc6439
commit 9b46ced2b3
5 changed files with 192 additions and 40 deletions

View File

@ -440,20 +440,19 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
final List<AnnotationUsage<AttributeOverride>> overrides = element.getRepeatedAnnotationUsages( AttributeOverride.class );
if ( CollectionHelper.isNotEmpty( overrides ) ) {
final Map<String, List<AnnotationUsage<Column>>> columnOverrideList = new HashMap<>();
for ( AnnotationUsage<AttributeOverride> depAttr : overrides ) {
final String qualifiedName = StringHelper.qualify( path, depAttr.getString( "name" ) );
final AnnotationUsage<Column> column = depAttr.getNestedUsage( "column" );
if ( columnOverrideList.containsKey( qualifiedName ) ) {
if ( columnOverrideMap.containsKey( qualifiedName ) ) {
// already an entry, just add to that List
columnOverrideList.get( qualifiedName ).add( column );
columnOverrideMap.get( qualifiedName ).add( column );
}
else {
// not yet an entry, create the list and add
final List<AnnotationUsage<Column>> list = new ArrayList<>();
list.add( column );
columnOverrideList.put( qualifiedName, list );
columnOverrideMap.put( qualifiedName, list );
}
}
}

View File

@ -6,11 +6,10 @@
*/
package org.hibernate.boot.model.internal;
import jakarta.persistence.CheckConstraint;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.MapsId;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Formula;
@ -20,21 +19,27 @@ import org.hibernate.annotations.JoinColumnsOrFormulas;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.models.spi.AnnotationDescriptor;
import org.hibernate.models.spi.AnnotationDescriptorRegistry;
import org.hibernate.models.spi.AnnotationUsage;
import org.hibernate.models.spi.MemberDetails;
import org.hibernate.models.spi.MutableAnnotationUsage;
import org.hibernate.models.spi.SourceModelBuildingContext;
import jakarta.persistence.CheckConstraint;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import java.lang.annotation.Annotation;
import java.util.List;
import jakarta.persistence.PrimaryKeyJoinColumn;
import jakarta.persistence.PrimaryKeyJoinColumns;
import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromAnnotation;
import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromNoAnnotation;
@ -282,28 +287,23 @@ class ColumnsBuilder {
// masquerade as a regular JoinColumn (when a @OneToOne maps to
// the primary key of the child table, it's more elegant and more
// spec-compliant to map the association with @PrimaryKeyJoinColumn)
if ( property.hasAnnotationUsage( PrimaryKeyJoinColumn.class ) ) {
// final AnnotationUsage<PrimaryKeyJoinColumn> nested = property.getAnnotationUsage( PrimaryKeyJoinColumn.class );
// return new JoinColumn[] { new JoinColumnAdapter( column ) };
throw new UnsupportedOperationException( "Not yet implemented" );
}
else if ( property.hasAnnotationUsage( PrimaryKeyJoinColumns.class ) ) {
// final AnnotationUsage<PrimaryKeyJoinColumns> primaryKeyJoinColumns = property.getAnnotationUsage( PrimaryKeyJoinColumns.class );
// final List<PrimaryKeyJoinColumn> nested = primaryKeyJoinColumns.getList( "value" );
// final JoinColumn[] joinColumns = new JoinColumn[primaryKeyJoinColumns.value().length];
// final PrimaryKeyJoinColumn[] columns = primaryKeyJoinColumns.value();
// if ( columns.length == 0 ) {
// throw new AnnotationException( "Property '" + getPath( propertyHolder, inferredData)
// + "' has an empty '@PrimaryKeyJoinColumns' annotation" );
// }
// for ( int i = 0; i < columns.length; i++ ) {
// joinColumns[i] = new JoinColumnAdapter( columns[i] );
// }
// return joinColumns;
throw new UnsupportedOperationException( "Not yet implemented" );
//
// todo : another option better leveraging hibernate-models would be to simply use an untyped AnnotationUsage
if ( property.hasAnnotationUsage( PrimaryKeyJoinColumns.class ) ) {
final List<AnnotationUsage<JoinColumn>> adapters = new ArrayList<>();
property.forEachAnnotationUsage( PrimaryKeyJoinColumn.class, (usage) -> {
adapters.add( makePrimaryKeyJoinColumnAdapter( usage, property ) );
} );
return adapters;
}
else {
return null;
final AnnotationUsage<PrimaryKeyJoinColumn> pkJoinColumnAnn = property.getAnnotationUsage( PrimaryKeyJoinColumn.class );
if ( pkJoinColumnAnn != null ) {
return List.of( makePrimaryKeyJoinColumnAdapter( pkJoinColumnAnn, property ) );
}
else {
return null;
}
}
}
else {
@ -311,6 +311,33 @@ class ColumnsBuilder {
}
}
private AnnotationUsage<JoinColumn> makePrimaryKeyJoinColumnAdapter(
AnnotationUsage<PrimaryKeyJoinColumn> pkJoinColumnAnn,
MemberDetails property) {
final SourceModelBuildingContext hibernateModelsContext = buildingContext.getMetadataCollector().getSourceModelBuildingContext();
final AnnotationDescriptorRegistry descriptorRegistry = hibernateModelsContext.getAnnotationDescriptorRegistry();
final AnnotationDescriptor<JoinColumn> joinColumnDescriptor = descriptorRegistry.getDescriptor( JoinColumn.class );
return joinColumnDescriptor.createUsage(
property,
(usage) -> {
transferAttribute( "name", pkJoinColumnAnn, usage );
transferAttribute( "referencedColumnName", pkJoinColumnAnn, usage );
transferAttribute( "columnDefinition", pkJoinColumnAnn, usage );
transferAttribute( "options", pkJoinColumnAnn, usage );
transferAttribute( "foreignKey", pkJoinColumnAnn, usage );
},
hibernateModelsContext
);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void transferAttribute(
String attributeName,
AnnotationUsage source,
MutableAnnotationUsage target) {
target.setAttributeValue( attributeName, source.getAttributeValue( attributeName ) );
}
/**
* Useful to override a column either by {@code @MapsId} or by {@code @IdClass}
*/

View File

@ -113,6 +113,7 @@ import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.AssociationOverride;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.CheckConstraint;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
@ -153,6 +154,7 @@ import jakarta.persistence.TemporalType;
import jakarta.persistence.UniqueConstraint;
import static java.util.Collections.emptyList;
import static org.hibernate.boot.models.xml.internal.XmlProcessingHelper.makeNestedAnnotation;
import static org.hibernate.internal.util.NullnessHelper.coalesce;
/**
@ -348,7 +350,7 @@ public class XmlAnnotationHelper {
List<AnnotationUsage<Parameter>> parameterAnnList = new ArrayList<>( jaxbParameters.size() );
jaxbParameters.forEach( (jaxbParam) -> {
final MutableAnnotationUsage<Parameter> annotationUsage = XmlProcessingHelper.makeNestedAnnotation( Parameter.class, target, xmlDocumentContext );
final MutableAnnotationUsage<Parameter> annotationUsage = makeNestedAnnotation( Parameter.class, target, xmlDocumentContext );
parameterAnnList.add( annotationUsage );
annotationUsage.setAttributeValue( "name", jaxbParam.getName() );
annotationUsage.setAttributeValue( "value", jaxbParam.getValue() );
@ -765,15 +767,33 @@ public class XmlAnnotationHelper {
return;
}
final MutableAnnotationUsage<AttributeOverrides> attributeOverridesAnn = XmlProcessingHelper.makeAnnotation(
AttributeOverrides.class,
memberDetails,
xmlDocumentContext
);
memberDetails.addAnnotationUsage( attributeOverridesAnn );
final ArrayList<MutableAnnotationUsage<AttributeOverride>> overrideAnnList = CollectionHelper.arrayList( jaxbOverrides.size() );
attributeOverridesAnn.setAttributeValue( "value", overrideAnnList );
jaxbOverrides.forEach( (jaxbOverride) -> {
final MutableAnnotationUsage<AttributeOverride> annotationUsage = XmlProcessingHelper.makeAnnotation(
final MutableAnnotationUsage<AttributeOverride> attributeOverrideAnn = XmlProcessingHelper.makeNestedAnnotation(
AttributeOverride.class,
memberDetails,
xmlDocumentContext
);
memberDetails.addAnnotationUsage( annotationUsage );
annotationUsage.setAttributeValue( "name", prefixIfNotAlready( jaxbOverride.getName(), namePrefix ) );
annotationUsage.setAttributeValue( "column", createColumnAnnotation( jaxbOverride.getColumn(), memberDetails, xmlDocumentContext ) );
overrideAnnList.add( attributeOverrideAnn );
attributeOverrideAnn.setAttributeValue( "name", prefixIfNotAlready( jaxbOverride.getName(), namePrefix ) );
final MutableAnnotationUsage<Column> columnAnn = makeNestedAnnotation(
Column.class,
memberDetails,
xmlDocumentContext
);
attributeOverrideAnn.setAttributeValue( "column", columnAnn );
ColumnProcessing.applyColumnDetails( jaxbOverride.getColumn(), memberDetails, columnAnn, xmlDocumentContext );
} );
}
@ -1209,7 +1229,7 @@ public class XmlAnnotationHelper {
XmlDocumentContext xmlDocumentContext) {
final List<AnnotationUsage<SqlFragmentAlias>> sqlFragmentAliases = new ArrayList<>( aliases.size() );
for ( JaxbHbmFilterImpl.JaxbAliasesImpl alias : aliases ) {
final MutableAnnotationUsage<SqlFragmentAlias> aliasAnn = XmlProcessingHelper.makeNestedAnnotation( SqlFragmentAlias.class, target, xmlDocumentContext );
final MutableAnnotationUsage<SqlFragmentAlias> aliasAnn = makeNestedAnnotation( SqlFragmentAlias.class, target, xmlDocumentContext );
aliasAnn.setAttributeValue( "alias", alias.getAlias() );
applyAttributeIfSpecified( aliasAnn, "table", alias.getTable() );
if ( StringHelper.isNotEmpty( alias.getEntity() ) ) {
@ -1491,7 +1511,7 @@ public class XmlAnnotationHelper {
final List<MutableAnnotationUsage<NamedAttributeNode>> namedAttributeNodeAnnotations =
new ArrayList<>( namedAttributeNodes.size() );
for ( JaxbNamedAttributeNodeImpl namedAttributeNode : namedAttributeNodes ) {
final MutableAnnotationUsage<NamedAttributeNode> namedAttributeNodeAnn = XmlProcessingHelper.makeNestedAnnotation(
final MutableAnnotationUsage<NamedAttributeNode> namedAttributeNodeAnn = makeNestedAnnotation(
NamedAttributeNode.class,
target,
xmlDocumentContext

View File

@ -48,7 +48,7 @@ public class BasicAttributeProcessing {
final MutableAnnotationUsage<Formula> formulaAnn = XmlProcessingHelper.getOrMakeAnnotation( Formula.class, memberDetails, xmlDocumentContext );
formulaAnn.setAttributeValue( "value", jaxbBasic.getFormula() );
}
else {
else if ( jaxbBasic.getColumn() != null ) {
final MutableAnnotationUsage<Column> columnAnn = XmlProcessingHelper.getOrMakeAnnotation( Column.class, memberDetails, xmlDocumentContext );
ColumnProcessing.applyColumnDetails( jaxbBasic.getColumn(), memberDetails, columnAnn, xmlDocumentContext );
}

View File

@ -0,0 +1,106 @@
/*
* 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.models.xml.override;
import java.util.List;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.internal.BootstrapContextImpl;
import org.hibernate.boot.internal.InFlightMetadataCollectorImpl;
import org.hibernate.boot.internal.MetadataBuilderImpl.MetadataBuildingOptionsImpl;
import org.hibernate.boot.model.process.spi.ManagedResources;
import org.hibernate.boot.model.process.spi.MetadataBuildingProcess;
import org.hibernate.boot.model.source.internal.annotations.DomainModelSource;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.models.spi.AnnotationUsage;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.ClassDetailsRegistry;
import org.hibernate.models.spi.FieldDetails;
import org.hibernate.orm.test.jpa.xml.Employee;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.boot.model.process.spi.MetadataBuildingProcess.processManagedResources;
/**
* @author Steve Ebersole
*/
public class AttributeOverrideXmlTests {
@Test
@ServiceRegistry
void testBasicHandling(ServiceRegistryScope serviceRegistryScope) {
final StandardServiceRegistry registry = serviceRegistryScope.getRegistry();
final MetadataSources metadataSources = new MetadataSources().addResource( "org/hibernate/orm/test/jpa/xml/orm3.xml" );
final MetadataBuildingOptionsImpl options = new MetadataBuildingOptionsImpl( registry );
final BootstrapContextImpl bootstrapContext = new BootstrapContextImpl( registry, options );
options.setBootstrapContext( bootstrapContext );
final ManagedResources managedResources = MetadataBuildingProcess.prepare( metadataSources, bootstrapContext );
final InFlightMetadataCollectorImpl metadataCollector = new InFlightMetadataCollectorImpl( bootstrapContext, options );
final DomainModelSource domainModelSource = processManagedResources(
managedResources,
metadataCollector,
bootstrapContext
);
final ClassDetailsRegistry classDetailsRegistry = domainModelSource.getClassDetailsRegistry();
final ClassDetails employeeClassDetails = classDetailsRegistry.getClassDetails( Employee.class.getName() );
assertThat( employeeClassDetails.getFields() ).hasSize( 4 );
final FieldDetails homeAddressField = employeeClassDetails.findFieldByName( "homeAddress" );
checkOverrides( homeAddressField, "home_" );
final FieldDetails mailAddressField = employeeClassDetails.findFieldByName( "mailAddress" );
checkOverrides( mailAddressField, "mail_" );
final ClassDetails embeddable = homeAddressField.getType().determineRawClass();
assertThat( embeddable ).isNotNull();
assertThat( embeddable.getFields() ).hasSize( 4 );
final FieldDetails cityField = embeddable.findFieldByName( "city" );
assertThat( cityField.hasAnnotationUsage( Column.class ) ).isFalse();
}
private static void checkOverrides(FieldDetails embedded, String prefix) {
final AnnotationUsage<AttributeOverrides> overridesUsage = embedded.getAnnotationUsage( AttributeOverrides.class );
assertThat( overridesUsage ).isNotNull();
final List<AnnotationUsage<AttributeOverride>> overrideList = overridesUsage.getList( "value" );
assertThat( overrideList ).hasSize( 4 );
for ( AnnotationUsage<AttributeOverride> overrideUsage : overrideList ) {
final String attributeName = overrideUsage.getString( "name" );
final AnnotationUsage<Column> columnUsage = overrideUsage.getNestedUsage( "column" );
final String columnName = columnUsage.getString( "name" );
if ( attributeName.equals( "street" ) ) {
assertThat( columnName ).isEqualTo( prefix + "street" );
}
else if ( attributeName.equals( "city" ) ) {
assertThat( columnName ).isEqualTo( prefix + "city" );
}
else if ( attributeName.equals( "state" ) ) {
assertThat( columnName ).isEqualTo( prefix + "state" );
}
else if ( attributeName.equals( "zip" ) ) {
assertThat( columnName ).isEqualTo( prefix + "zip" );
}
}
}
}