diff --git a/src/main/java/org/hibernate/boot/models/categorize/xml/internal/ManagedTypeProcessor.java b/src/main/java/org/hibernate/boot/models/categorize/xml/internal/ManagedTypeProcessor.java index c838aa0..60f21ba 100644 --- a/src/main/java/org/hibernate/boot/models/categorize/xml/internal/ManagedTypeProcessor.java +++ b/src/main/java/org/hibernate/boot/models/categorize/xml/internal/ManagedTypeProcessor.java @@ -32,6 +32,7 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbIdImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbManagedType; import org.hibernate.boot.jaxb.mapping.spi.JaxbMappedSuperclassImpl; +import org.hibernate.boot.jaxb.mapping.spi.JaxbNamedEntityGraphImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbOneToOneImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbPersistentAttribute; import org.hibernate.boot.jaxb.mapping.spi.JaxbPluralAttribute; @@ -59,6 +60,7 @@ import jakarta.persistence.AccessType; import jakarta.persistence.Embeddable; import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.NamedEntityGraph; import static org.hibernate.internal.util.NullnessHelper.coalesce; import static org.hibernate.internal.util.NullnessHelper.nullif; @@ -475,6 +477,11 @@ private static void processEntityMetadata( applyTenantId( classDetails, jaxbEntity, classAccessType, xmlDocumentContext ); + final List namedEntityGraphs = jaxbEntity.getNamedEntityGraphs(); + for ( JaxbNamedEntityGraphImpl namedEntityGraph : namedEntityGraphs ) { + XmlAnnotationHelper.applyNamedEntityGraph( namedEntityGraph, classDetails, xmlDocumentContext ); + } + // todo : secondary-tables } diff --git a/src/main/java/org/hibernate/boot/models/categorize/xml/internal/XmlAnnotationHelper.java b/src/main/java/org/hibernate/boot/models/categorize/xml/internal/XmlAnnotationHelper.java index 0854322..35084d7 100644 --- a/src/main/java/org/hibernate/boot/models/categorize/xml/internal/XmlAnnotationHelper.java +++ b/src/main/java/org/hibernate/boot/models/categorize/xml/internal/XmlAnnotationHelper.java @@ -69,6 +69,9 @@ import org.hibernate.boot.jaxb.mapping.spi.JaxbLobImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbMapKeyColumnImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbMapKeyJoinColumnImpl; +import org.hibernate.boot.jaxb.mapping.spi.JaxbNamedAttributeNodeImpl; +import org.hibernate.boot.jaxb.mapping.spi.JaxbNamedEntityGraphImpl; +import org.hibernate.boot.jaxb.mapping.spi.JaxbNamedSubgraphImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbNationalizedImpl; import org.hibernate.boot.jaxb.mapping.spi.JaxbNaturalId; import org.hibernate.boot.jaxb.mapping.spi.JaxbSequenceGeneratorImpl; @@ -125,6 +128,9 @@ import jakarta.persistence.Lob; import jakarta.persistence.MapKeyColumn; import jakarta.persistence.MapKeyJoinColumn; +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.NamedSubgraph; import jakarta.persistence.PostLoad; import jakarta.persistence.PostPersist; import jakarta.persistence.PostRemove; @@ -1352,4 +1358,142 @@ private static String prefixIfNotAlready(String value, String prefix) { } return value; } + + static void applyNamedEntityGraph( + JaxbNamedEntityGraphImpl namedEntityGraph, + MutableClassDetails target, + XmlDocumentContext xmlDocumentContext) { + if ( namedEntityGraph != null ) { + final MutableAnnotationUsage namedEntityGraphAnn = XmlProcessingHelper.getOrMakeAnnotation( + NamedEntityGraph.class, + target, + xmlDocumentContext + ); + + final AnnotationDescriptor namedEntityGraphAnnotationDescriptor = namedEntityGraphAnn.getAnnotationDescriptor(); + applyOr( + namedEntityGraph, + JaxbNamedEntityGraphImpl::getName, + "name", + namedEntityGraphAnn, + namedEntityGraphAnnotationDescriptor + ); + + applyOr( + namedEntityGraph, + JaxbNamedEntityGraphImpl::isIncludeAllAttributes, + "includeAllAttributes", + namedEntityGraphAnn, + namedEntityGraphAnnotationDescriptor + ); + + namedEntityGraphAnn.setAttributeValue( + "attributeNodes", + makeNamedAttributeNodes( namedEntityGraph.getNamedAttributeNode(), target, xmlDocumentContext ) + ); + + namedEntityGraphAnn.setAttributeValue( + "subgraphs", + makeNamedSubgraphs( + target, + xmlDocumentContext, + namedEntityGraph.getSubgraph() + ) + ); + + namedEntityGraphAnn.setAttributeValue( + "subclassSubgraphs", + makeNamedSubgraphs( + target, + xmlDocumentContext, + namedEntityGraph.getSubclassSubgraph() + ) + ); + + } + + } + + private static List> makeNamedSubgraphs( + MutableClassDetails target, + XmlDocumentContext xmlDocumentContext, + List subclassSubgraphNodes) { + final List> subgraphAnnotations = + new ArrayList<>( subclassSubgraphNodes.size() ); + for ( JaxbNamedSubgraphImpl subclassSubgraphNode : subclassSubgraphNodes ) { + final String subGraphsNodeName = subclassSubgraphNode.getName(); + final MutableAnnotationUsage namedSubgraphNodeAnn = XmlProcessingHelper.getOrMakeNamedAnnotation( + NamedSubgraph.class, + subGraphsNodeName, + target, + xmlDocumentContext + ); + applyAttributeIfSpecified( namedSubgraphNodeAnn, "name", subGraphsNodeName ); + + final String clazz = subclassSubgraphNode.getClazz(); + if ( clazz == null ) { + namedSubgraphNodeAnn.setAttributeValue( + "type", + resolveJavaType( + namedSubgraphNodeAnn.getAnnotationDescriptor() + .getAttribute( "type" ) + .getAttributeMethod() + .getDefaultValue().toString(), + xmlDocumentContext + ) + ); + } + else { + namedSubgraphNodeAnn.setAttributeValue( + "type", + resolveJavaType( subclassSubgraphNode.getClazz(), xmlDocumentContext ) + + ); + } + namedSubgraphNodeAnn.setAttributeValue( + "attributeNodes", + makeNamedAttributeNodes( subclassSubgraphNode.getNamedAttributeNode(), target, xmlDocumentContext ) + ); + + subgraphAnnotations.add( namedSubgraphNodeAnn ); + } + return subgraphAnnotations; + } + + private static List> makeNamedAttributeNodes( + List namedAttributeNodes, + MutableClassDetails target, + XmlDocumentContext xmlDocumentContext) { + final List> namedAttributeNodeAnnotations = + new ArrayList<>( namedAttributeNodes.size() ); + for ( JaxbNamedAttributeNodeImpl namedAttributeNode : namedAttributeNodes ) { + final MutableAnnotationUsage namedAttributeNodeAnn = XmlProcessingHelper.makeNestedAnnotation( + NamedAttributeNode.class, + target, + xmlDocumentContext + ); + applyAttributeIfSpecified( namedAttributeNodeAnn, "value", namedAttributeNode.getName() ); + final AnnotationDescriptor namedAttributeNodeDescriptor = xmlDocumentContext + .getModelBuildingContext() + .getAnnotationDescriptorRegistry() + .getDescriptor( NamedAttributeNode.class ); + applyOr( + namedAttributeNode, + JaxbNamedAttributeNodeImpl::getSubgraph, + "subgraph", + namedAttributeNodeAnn, + namedAttributeNodeDescriptor + ); + applyOr( + namedAttributeNode, + JaxbNamedAttributeNodeImpl::getKeySubgraph, + "keySubgraph", + namedAttributeNodeAnn, + namedAttributeNodeDescriptor + ); + namedAttributeNodeAnnotations.add( namedAttributeNodeAnn ); + + } + return namedAttributeNodeAnnotations; + } } diff --git a/src/test/java/org/hibernate/models/orm/xml/dynamic/NamedEntityGraphTest.java b/src/test/java/org/hibernate/models/orm/xml/dynamic/NamedEntityGraphTest.java new file mode 100644 index 0000000..69c1782 --- /dev/null +++ b/src/test/java/org/hibernate/models/orm/xml/dynamic/NamedEntityGraphTest.java @@ -0,0 +1,129 @@ +package org.hibernate.models.orm.xml.dynamic; + +import java.util.List; +import java.util.Set; + +import org.hibernate.boot.internal.BootstrapContextImpl; +import org.hibernate.boot.internal.MetadataBuilderImpl; +import org.hibernate.boot.model.process.spi.ManagedResources; +import org.hibernate.boot.models.categorize.spi.CategorizedDomainModel; +import org.hibernate.boot.models.categorize.spi.EntityHierarchy; +import org.hibernate.boot.models.categorize.spi.EntityTypeMetadata; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.models.orm.process.ManagedResourcesImpl; +import org.hibernate.models.spi.AnnotationUsage; +import org.hibernate.models.spi.ClassDetails; + +import org.junit.jupiter.api.Test; + +import jakarta.persistence.NamedAttributeNode; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.NamedSubgraph; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.boot.models.categorize.spi.ManagedResourcesProcessor.processManagedResources; + +public class NamedEntityGraphTest { + @Test + void testNamedEntityGraph() { + final ManagedResources managedResources = new ManagedResourcesImpl.Builder() + .addXmlMappings( "mappings/dynamic/dynamic-named-entity-graph.xml" ) + .build(); + try (StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().build()) { + final BootstrapContextImpl bootstrapContext = new BootstrapContextImpl( + serviceRegistry, + new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry ) + ); + final CategorizedDomainModel categorizedDomainModel = processManagedResources( + managedResources, + bootstrapContext + ); + + final Set entityHierarchies = categorizedDomainModel.getEntityHierarchies(); + assertThat( entityHierarchies ).hasSize( 1 ); + + entityHierarchies.forEach( + entityHierarchy -> { + final EntityTypeMetadata root = entityHierarchy.getRoot(); + final String entityName = root.getEntityName(); + + final AnnotationUsage namedEntityGraphAnnotationUsage = root.getClassDetails() + .getAnnotationUsage( NamedEntityGraph.class ); + + if ( entityName.equals( "Address" ) ) { + assertThat( namedEntityGraphAnnotationUsage ).isNull(); + } + else { + assertThat( namedEntityGraphAnnotationUsage ).isNotNull(); + + final String graphName = namedEntityGraphAnnotationUsage.getAttributeValue( "name" ); + assertThat( graphName ).isEqualTo( "employee" ); + + final boolean includeAllAttributes = namedEntityGraphAnnotationUsage.getAttributeValue( + "includeAllAttributes" ); + assertThat( includeAllAttributes ).isTrue(); + + List> namedAttributeNodeUsage = namedEntityGraphAnnotationUsage + .getAttributeValue( "attributeNodes" ); + assertThat( namedAttributeNodeUsage ).size().isEqualTo( 2 ); + + // check NamedEntityGraph attributeNodes + + AnnotationUsage firstAttributeNode = namedAttributeNodeUsage.get( 0 ); + checkAttributeNode( firstAttributeNode, "name", "", "" ); + + AnnotationUsage secondAttributeNode = namedAttributeNodeUsage.get( 1 ); + checkAttributeNode( secondAttributeNode, "address", "employee.address", "" ); + + // check NamedEntityGraph subgraphs + final List> subgraphUsages = namedEntityGraphAnnotationUsage + .getAttributeValue( "subgraphs" ); + assertThat( subgraphUsages ).size().isEqualTo( 2 ); + + AnnotationUsage firstSubgraph = subgraphUsages.get( 0 ); + assertThat( firstSubgraph.getString( "name" ) ).isEqualTo( "first.subgraph" ); + assertThat( firstSubgraph.getAttributeValue( "type" ).getName() ) + .isEqualTo( void.class.getName() ); + + // check first NamedSubgraph attributeNodes + + namedAttributeNodeUsage = firstSubgraph.getAttributeValue( "attributeNodes" ); + assertThat( namedAttributeNodeUsage ).size().isEqualTo( 1 ); + + checkAttributeNode( namedAttributeNodeUsage.get( 0 ), "city", "", "" ); + + AnnotationUsage secondSubgraph = subgraphUsages.get( 1 ); + assertThat( secondSubgraph.getString( "name" ) ).isEqualTo( "second.subgraph" ); + assertThat( secondSubgraph.getAttributeValue( "type" ).getName() ) + .isEqualTo( String.class.getName() ); + + namedAttributeNodeUsage = secondSubgraph.getAttributeValue( "attributeNodes" ); + assertThat( namedAttributeNodeUsage ).size().isEqualTo( 3 ); + + // check second NamedSubgraph attributeNodes + checkAttributeNode( namedAttributeNodeUsage.get( 0 ), "city", "sub1", "" ); + checkAttributeNode( namedAttributeNodeUsage.get( 1 ), "name", "sub", "" ); + checkAttributeNode( namedAttributeNodeUsage.get( 2 ), "surname", "", "" ); + + + final List> subClassSubgraphUsages = namedEntityGraphAnnotationUsage + .getAttributeValue( "subclassSubgraphs" ); + assertThat( subClassSubgraphUsages ).size().isEqualTo( 0 ); + + } + } + ); + } + } + + private static void checkAttributeNode( + AnnotationUsage firstAttributeNode, + String expectedValueName, + String expectedSubgraph, + String expectedKeySubgraph) { + assertThat( firstAttributeNode.getString( "value" ) ).isEqualTo( expectedValueName ); + assertThat( firstAttributeNode.getString( "subgraph" ) ).isEqualTo( expectedSubgraph ); + assertThat( firstAttributeNode.getString( "keySubgraph" ) ).isEqualTo( expectedKeySubgraph ); + } +} diff --git a/src/test/resources/mappings/dynamic/dynamic-named-entity-graph.xml b/src/test/resources/mappings/dynamic/dynamic-named-entity-graph.xml new file mode 100644 index 0000000..e6f703a --- /dev/null +++ b/src/test/resources/mappings/dynamic/dynamic-named-entity-graph.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file