Skip to content

HHH-19610 make GraphParser support subTypeSubGraph #10521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,36 @@ type is the `LegalEntity` base type we might have:
====
[source, java, indent=0]
----
responsibleParty:Corporation(ceo)
----
====
[WARNING]
====
Another syntax you may have seen is the one below.
[source, java, indent=0]
----
responsibleParty(Corporation: ceo)
----
This syntax is deprecated and will no longer be supported in the next major release.

====
Additionally, it is possible to define subgraphs that apply to a specific **root subtype** using the syntax:

====
[source, java, indent=0]
----
:Corporation(name, ceo), :NonProfit(sector)
----

====
This allows expressing that the graph should apply when the root entity itself is a Corporation.

We can even duplicate the attribute names to apply different subtype subgraphs:

====
[source, java, indent=0]
----
responsibleParty(taxIdNumber), responsibleParty(Corporation: ceo), responsibleParty(NonProfit: sector)
responsibleParty(taxIdNumber), responsibleParty:Corporation(ceo), responsibleParty:NonProfit(sector)
----
====

Expand Down Expand Up @@ -380,7 +400,7 @@ to the `@jakarta.persistence.NamedEntityGraph`, supporting the text representati
may be placed on an entity or on a package.


.@NamedEntityGraph example
.@NamedEntityGraph example of annotation placed on an entity
====
[source, java, indent=0]
----
Expand All @@ -390,6 +410,37 @@ class Book {
// ...
}
----

====
.@NamedEntityGraph example of annotation placed on a package
====
[source, java, indent=0]
----
// package-info.java
@NamedEntityGraph(root = Book.class, name = "book-title", graph = "title")
package org.somepackage;

import org.hibernate.annotations.NamedEntityGraph;
----

====
[WARNING]
====
You may have already seen the syntax below, which specifies the type of graph via the graph attribute.
[source, java, indent=0]
----
@NamedEntityGraph(
name = "book-title",
graph = "Book: title" // Here
)
package org.somepackage;

import org.hibernate.annotations.NamedEntityGraph;
----

This syntax is deprecated and will no longer be supported in the next major release.


====


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,32 @@ package org.hibernate.grammars.graph;
*/
}


graph
: typeIndicator? attributeList
;
: typeIndicator? graphElementList
;

graphElementList
: graphElement (COMMA graphElement)*
;


graphElement
: treatedSubGraph
| attributeNode
;

treatedSubGraph
: subTypeIndicator LPAREN attributeList RPAREN
;

typeIndicator
: TYPE_NAME COLON
;

subTypeIndicator
: COLON TYPE_NAME
;

attributeList
: attributeNode (COMMA attributeNode)*
;
Expand All @@ -47,6 +64,10 @@ attributeQualifier
;

subGraph
: LPAREN typeIndicator? attributeList RPAREN
;
: subGraphWithTypeIndicator
| treatedSubGraph
;

subGraphWithTypeIndicator
: LPAREN typeIndicator? attributeList RPAREN
;
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
@Retention(RUNTIME)
@Repeatable(NamedEntityGraphs.class)
public @interface NamedEntityGraph {

/**
* Names the entity that is the root of the {@linkplain #graph graph}.
* When the annotation is applied to a class, the class itself is assumed.
* When applied to a package, this attribute is required.
*/
Class<?> root() default void.class;

/**
* The name used to identify the entity graph in calls to
* {@linkplain org.hibernate.Session#getEntityGraph(String)}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
import org.hibernate.grammars.graph.GraphLanguageLexer;
import org.hibernate.grammars.graph.GraphLanguageParser;
import org.hibernate.graph.InvalidGraphException;
import org.hibernate.graph.InvalidNamedEntityGraphParameterException;
import org.hibernate.graph.internal.parse.EntityNameResolver;
import org.hibernate.graph.internal.parse.GraphParsing;
import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.internal.log.DeprecationLogger;
import org.hibernate.metamodel.model.domain.EntityDomainType;

import java.util.function.Function;
Expand Down Expand Up @@ -60,24 +62,67 @@ public <T> EntityDomainType<T> resolveEntityName(String entityName) {
}
};

final EntityDomainType<T> entityDomainType = resolveEntityDomainType(
graphContext,
entityDomainClassResolver,
entityDomainNameResolver
);

final String name = this.name == null ? entityDomainType.getName() : this.name;

return GraphParsing.parse( name, entityDomainType, graphContext.graphElementList(), entityNameResolver );

}

private <T> EntityDomainType<T> resolveEntityDomainType(
GraphLanguageParser.GraphContext graphContext,
Function<Class<T>, EntityDomainType<?>> entityDomainClassResolver,
Function<String, EntityDomainType<?>> entityDomainNameResolver) {
var typeIndicator = graphContext.typeIndicator();
final Class<?> annotationRootAttribute = annotation.root();

if ( entityType == null ) {
if ( graphContext.typeIndicator() == null ) {
throw new InvalidGraphException( "Expecting graph text to include an entity name : " + annotation.graph() );

if ( typeIndicator == null && void.class.equals( annotationRootAttribute ) ) {
throw new InvalidNamedEntityGraphParameterException(
"The 'root' parameter of the @NamedEntityGraph should be passed. Graph : " + annotation.name()
);
}

if ( typeIndicator != null ) {
DeprecationLogger.DEPRECATION_LOGGER.deprecatedNamedEntityGraphTextThatContainTypeIndicator();

//noinspection unchecked
return (EntityDomainType<T>) entityDomainNameResolver.apply(
typeIndicator.TYPE_NAME().toString() );

}
final String jpaEntityName = graphContext.typeIndicator().TYPE_NAME().toString();

//noinspection unchecked
final EntityDomainType<T> entityDomainType = (EntityDomainType<T>) entityDomainNameResolver.apply( jpaEntityName );
final String name = this.name == null ? jpaEntityName : this.name;
return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver );
return (EntityDomainType<T>) entityDomainClassResolver.apply( (Class<T>) annotationRootAttribute );
}
else {
if ( graphContext.typeIndicator() != null ) {
throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + annotation.graph() );


if ( typeIndicator != null ) {
throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + annotation.graph() );
}

if ( !void.class.equals( annotationRootAttribute ) ) {

if ( !annotationRootAttribute.equals( entityType ) ) {
throw new InvalidNamedEntityGraphParameterException(
"The 'root' parameter of the @NamedEntityGraph annotation must reference the entity '"
+ entityType.getName()
+ "', but '" + annotationRootAttribute.getName() + "' was provided."
+ " Graph :" + annotation.name()
);
}

//noinspection unchecked
final EntityDomainType<T> entityDomainType = (EntityDomainType<T>) entityDomainClassResolver.apply( (Class<T>) entityType );
final String name = this.name == null ? entityDomainType.getName() : this.name;
return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver );
return (EntityDomainType<T>) entityDomainClassResolver.apply( (Class<T>) annotationRootAttribute );
}

//noinspection unchecked
return (EntityDomainType<T>) entityDomainClassResolver.apply( (Class<T>) entityType );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
public class NamedEntityGraphAnnotation implements NamedEntityGraph {
private String name;
private String graph;
private Class<?> root;

/**
* Used in creating dynamic annotation instances (e.g. from XML)
Expand All @@ -31,6 +32,7 @@ public NamedEntityGraphAnnotation(ModelsContext modelContext) {
public NamedEntityGraphAnnotation(NamedEntityGraph annotation, ModelsContext modelContext) {
this.name = annotation.name();
this.graph = annotation.graph();
this.root = annotation.root();
}

/**
Expand All @@ -39,13 +41,23 @@ public NamedEntityGraphAnnotation(NamedEntityGraph annotation, ModelsContext mod
public NamedEntityGraphAnnotation(Map<String, Object> attributeValues, ModelsContext modelContext) {
this.name = (String) attributeValues.get( "name" );
this.graph = (String) attributeValues.get( "graph" );
this.root = (Class<?>) attributeValues.get( "root" );
}

@Override
public Class<? extends Annotation> annotationType() {
return NamedEntityGraph.class;
}

@Override
public Class<?> root() {
return this.root;
}

public void root(Class<?> root) {
this.root = root;
}

@Override
public String name() {
return name;
Expand Down
20 changes: 11 additions & 9 deletions hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,18 @@ public static <T> RootGraph<T> parse(
* @see org.hibernate.SessionFactory#parseEntityGraph(Class, CharSequence)
*
* @since 7.0
* @deprecated This usage is deprecated. You must specify the entityType {see {@link #parse(Class, CharSequence, SessionFactory)} or {@link #parse(String, CharSequence, SessionFactory)}
*/
public static <T> RootGraph<T> parse(
final CharSequence graphText,
final SessionFactory sessionFactory) {
if ( graphText == null ) {
return null;
}
return GraphParsing.parse(
graphText.toString(),
sessionFactory.unwrap( SessionFactoryImplementor.class )
@Deprecated(forRemoval = true)
public static <T> RootGraph<T> parse(
final CharSequence graphText,
final SessionFactory sessionFactory) {
if ( graphText == null ) {
return null;
}
return GraphParsing.parse(
graphText.toString(),
sessionFactory.unwrap( SessionFactoryImplementor.class )
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.graph;

import org.hibernate.HibernateException;

public class InvalidNamedEntityGraphParameterException extends HibernateException {
private static final long serialVersionUID = 1L;

public InvalidNamedEntityGraphParameterException(String message) {
super( message );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
package org.hibernate.graph.internal.parse;

import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ManagedDomainType;

/**
* @author Steve Ebersole
*/
@FunctionalInterface
public interface EntityNameResolver {
<T> EntityDomainType<T> resolveEntityName(String entityName);

static <T> ManagedDomainType<T> managedType(String subtypeName, EntityNameResolver entityNameResolver) {
final EntityDomainType<T> entityDomainType = entityNameResolver.resolveEntityName( subtypeName );
if ( entityDomainType == null ) {
throw new IllegalArgumentException( "Unknown managed type: " + subtypeName );
}
return entityDomainType;
}
}
Loading