diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc index e9ca78b19744..40c9122a1a19 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc @@ -285,7 +285,7 @@ Hibernate allows the creation of Jakarta Persistence fetch/load graphs by parsin of the graph. Generally speaking, the textual representation of a graph is a comma-separated list of attribute names, optionally including any subgraph specifications. The starting point for such parsing operations is either `org.hibernate.graph.GraphParser` -or `SessionFactory#parseEntityGraph` +or `SessionFactory#parseEntityGraph`. [NOTE] ==== @@ -294,6 +294,21 @@ syntax described here is specific to Hibernate. We do hope to eventually make th the Jakarta Persistence specification proper. ==== +===== Graph parser mode configuration + +Since Hibernate 7.3, a new configuration setting controls which syntax the `GraphParser` uses: + +`hibernate.graph_parser_mode`:: +Determines which parsing syntax is active. ++ +* `legacy` — The historical syntax used before Hibernate 7.3. +* `modern` — Enables the new syntax that supports root-subtype subgraphs and improved readability. + +.Default value +[source,properties] +---- +hibernate.graph_parser_mode = legacy +---- .Parsing a simple graph ==== @@ -318,10 +333,14 @@ include::{example-dir-fetching}/GraphParsingTest.java[tags=fetching-strategies-d ---- ==== +===== Subtype-specific subgraphs (examples showing both supported forms) + Parsing can also handle subtype specific subgraphs. For example, given an entity hierarchy of `LegalEntity` <- (`Corporation` | `Person` | `NonProfit`) and an attribute named `responsibleParty` whose type is the `LegalEntity` base type we might have: +* **Legacy form (hibernate.graph_parser_mode = legacy)** + ==== [source, java, indent=0] ---- @@ -329,8 +348,21 @@ responsibleParty(Corporation: ceo) ---- ==== +* **Modern form (hibernate.graph_parser_mode = modern)** + +==== +[source, java, indent=0] +---- +responsibleParty:Corporation(ceo) +---- +==== + +Both forms produce the same runtime EntityGraph structure. + We can even duplicate the attribute names to apply different subtype subgraphs: +* **Legacy form (hibernate.graph_parser_mode = legacy)** + ==== [source, java, indent=0] ---- @@ -338,6 +370,15 @@ responsibleParty(taxIdNumber), responsibleParty(Corporation: ceo), responsiblePa ---- ==== +* **Modern form (hibernate.graph_parser_mode = modern)** + +==== +[source, java, indent=0] +---- +responsibleParty(taxIdNumber), responsibleParty:Corporation(ceo), responsibleParty:NonProfit(sector) +---- +==== + The duplicated attribute names are handled according to the Jakarta Persistence specification which says that duplicate specification of the attribute node results in the originally registered AttributeNode to be re-used effectively merging the 2 AttributeNode specifications together. In other words, the above specification @@ -355,6 +396,20 @@ invoiceGraph.addSubgraph( "responsibleParty", NonProfit.class ).addAttributeNode ---- ==== +===== Root-subtype subgraphs (modern-only feature) + +The modern parser mode also supports defining subgraphs that originate at a subtype of the **root entity**. +This feature is **only available** when `hibernate.graph_parser_mode=modern`. +Given an entity hierarchy of `LegalEntity` <- (`Corporation` | `Person` | `NonProfit`) with an attribute `ceo` that exists only on `Corporation` +and an attribute `sector` that exists only on `NonProfit`, you can define such subgraphs directly in a `LegalEntity` graph definition: + +==== +[source,java,indent=0] +---- +:Corporation(ceo), :NonProfit(sector) +---- +==== + [[fetching-strategies-dynamic-fetching-entity-graph-merging]] ==== Combining multiple Jakarta Persistence entity graphs into one @@ -391,6 +446,29 @@ class Book { ---- ==== +Since Hibernate 7.3 a `root` attribute is available on the Hibernate-specific `@NamedEntityGraph` annotation +to explicitly indicate the entity type that is the root of the graph. + +When `@NamedEntityGraph` is placed on a package, the `root` attribute **must** be specified. +If the annotation is placed on a package and the `root` is omitted: +* In `legacy` parser mode a deprecation warning will be emitted. +* In `modern` parser mode the omission is **not allowed** and results in an error. + +.Package-level @NamedEntityGraph example +==== +[source, java, indent=0] +---- +@org.hibernate.annotations.NamedEntityGraph( + name = "Book.graph", + root = Book.class, + graph = "title,isbn,author(name,phoneNumber)" +) +package com.example.model; +---- +==== + +This annotation works in conjunction with the `GraphParser` and respects the syntax +defined by `hibernate.graph_parser_mode`. [[fetching-strategies-dynamic-fetching-profile]] === Dynamic fetching via Hibernate profiles diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/ModernGraphLanguageLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/ModernGraphLanguageLexer.g4 new file mode 100644 index 000000000000..d74c82d71828 --- /dev/null +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/ModernGraphLanguageLexer.g4 @@ -0,0 +1,66 @@ +lexer grammar ModernGraphLanguageLexer; + +@header { +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.grammars.graph; +} + +@members { +/* + * Lexer for the Hibernate EntityGraph Language + * + * It is generated by Antlr via the lexer grammar file `ModernGraphLanguageLexer.g4` + */ +} + +channels { + WHITESPACE_CHANNEL +} + +WS : ( ' ' | '\t' | '\f' | EOL ) -> channel(WHITESPACE_CHANNEL); + +fragment EOL : [\r\n]+; + +COLON: ':'; + +COMMA: ','; + +DOT: '.'; + +LPAREN: '('; + +RPAREN: ')'; + +/** + * In this grammar, basically any string since we (atm) have no keywords + */ +ATTR_NAME : ATTR_NAME_START NAME_CONTINUATION*; + +TYPE_NAME : TYPE_NAME_START NAME_CONTINUATION*; + +fragment NON_ALPHANUM_EXTENTION + : '_' + | '$' + // HHH-558 : Allow unicode chars in identifiers + //| '\u0080'..'\ufffe' + ; + +fragment ATTR_NAME_START + : NON_ALPHANUM_EXTENTION + | 'a'..'z' + ; + +fragment TYPE_NAME_START + : NON_ALPHANUM_EXTENTION + | 'A'..'Z' + ; + +fragment NAME_CONTINUATION + : NON_ALPHANUM_EXTENTION + | 'a'..'z' + | 'A'..'Z' + | '0'..'9' + ; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/ModernGraphLanguageParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/ModernGraphLanguageParser.g4 new file mode 100644 index 000000000000..71263a4d8ab0 --- /dev/null +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/ModernGraphLanguageParser.g4 @@ -0,0 +1,64 @@ +parser grammar ModernGraphLanguageParser; + +options { + tokenVocab=ModernGraphLanguageLexer; +} + +@header { +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.grammars.graph; +} + +@members { +/* + * Antlr grammar describing the Hibernate EntityGraph Language - for parsing a structured + * textual representation of an entity graph + * + * `ModernGraphLanguageParser.g4` + */ +} + +graph + : graphElementList + ; + +graphElementList + : graphElement (COMMA graphElement)* + ; + + +graphElement + : subGraph + | attributeNode + ; + +subGraph + : subTypeIndicator? LPAREN attributeList RPAREN + ; + +typeIndicator + : TYPE_NAME COLON + ; + +subTypeIndicator + : COLON TYPE_NAME + ; + +attributeList + : attributeNode (COMMA attributeNode)* + ; + +attributeNode + : attributePath subGraph? + ; + +attributePath + : ATTR_NAME attributeQualifier? + ; + +attributeQualifier + : DOT ATTR_NAME + ; \ No newline at end of file diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageLexer.g4 similarity index 96% rename from hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageLexer.g4 rename to hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageLexer.g4 index 59bc9723cc5f..77c4f65f4f5f 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageLexer.g4 @@ -5,7 +5,7 @@ lexer grammar GraphLanguageLexer; * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.grammars.graph; +package org.hibernate.grammars.graph.legacy; } @members { diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageParser.g4 similarity index 93% rename from hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 rename to hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageParser.g4 index a0e6c33b9974..c714b41d8cc3 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageParser.g4 @@ -9,7 +9,7 @@ options { * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.grammars.graph; +package org.hibernate.grammars.graph.legacy; } @members { @@ -21,7 +21,6 @@ package org.hibernate.grammars.graph; */ } - graph : typeIndicator? attributeList ; @@ -48,5 +47,4 @@ attributeQualifier subGraph : LPAREN typeIndicator? attributeList RPAREN - ; - + ; \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/GraphParserMode.java b/hibernate-core/src/main/java/org/hibernate/GraphParserMode.java new file mode 100644 index 000000000000..d6b801b946a4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/GraphParserMode.java @@ -0,0 +1,62 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate; + +/** + * Enumeration of available graph parser syntax modes. + * + */ +public enum GraphParserMode { + /** + * Legacy syntax: attribute(SubType: attributes) + * This is the legacy syntax. + */ + LEGACY( "legacy" ), + + /** + * Modern syntax: attribute:SubType(attributes) + * This is the preferred new syntax. + */ + MODERN( "modern" ); + + private final String configValue; + + GraphParserMode(String configValue) { + this.configValue = configValue; + } + + public String getConfigValue() { + return configValue; + } + + /** + * Interpret the configured valueHandlingMode value. + * Valid values are either a {@link GraphParserMode} object or its String representation. + * For string values, the matching is case insensitive, so you can use either {@code MODERN} or {@code modern}. + * + * @param graphParserMode configured {@link GraphParserMode} representation + * + * @return associated {@link GraphParserMode} object + */ + public static GraphParserMode interpret(Object graphParserMode) { + if ( graphParserMode == null ) { + return LEGACY; + } + else if ( graphParserMode instanceof GraphParserMode mode ) { + return mode; + } + else if ( graphParserMode instanceof String string ) { + for ( GraphParserMode value : values() ) { + if ( value.name().equalsIgnoreCase( string ) ) { + return value; + } + } + } + throw new HibernateException( + "Unrecognized graph_parser_mode value : " + graphParserMode + + ". Supported values include 'modern' and 'legacy'." + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java b/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java index aa5aed0ac291..a6b655b20192 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java @@ -39,6 +39,14 @@ @Retention(RUNTIME) @Repeatable(NamedEntityGraphs.class) public @interface NamedEntityGraph { + + /** + * 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)}. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 7a54f65f4167..b11d20d932bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -24,12 +24,14 @@ import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.EntityNameResolver; import org.hibernate.FlushMode; +import org.hibernate.GraphParserMode; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.LockOptions; import org.hibernate.SessionEventListener; import org.hibernate.SessionFactoryObserver; import org.hibernate.context.spi.MultiTenancy; +import org.hibernate.cfg.GraphParserSettings; import org.hibernate.context.spi.TenantSchemaMapper; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; @@ -260,6 +262,9 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean delayBatchFetchLoaderCreations; @Deprecated(forRemoval = true) private boolean releaseResourcesOnCloseEnabled; + @Deprecated(forRemoval = true) + private final GraphParserMode graphParserMode; + public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, BootstrapContext context) { this.serviceRegistry = serviceRegistry; @@ -274,7 +279,7 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo final var dialect = jdbcServices.getJdbcEnvironment().getDialect(); - final Map settings = new HashMap<>(); + final Map settings = new HashMap<>(); settings.putAll( map( dialect.getDefaultProperties() ) ); settings.putAll( configurationService.getSettings() ); @@ -283,8 +288,10 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo () -> { final Object value = settings.get( CDI_BEAN_MANAGER ); if ( value != null ) { - DEPRECATION_LOGGER.deprecatedSetting( CDI_BEAN_MANAGER, - JAKARTA_CDI_BEAN_MANAGER ); + DEPRECATION_LOGGER.deprecatedSetting( + CDI_BEAN_MANAGER, + JAKARTA_CDI_BEAN_MANAGER + ); } return value; } @@ -303,7 +310,7 @@ public BootstrapContext getBootstrapContext() { }; jsonFormatMapper = jsonFormatMapper( settings.get( JSON_FORMAT_MAPPER ), - !getBoolean( ORACLE_OSON_DISABLED, settings), + !getBoolean( ORACLE_OSON_DISABLED, settings ), strategySelector, formatMapperCreationContext ); @@ -335,17 +342,21 @@ public BootstrapContext getBootstrapContext() { statelessInterceptorSupplier = determineStatelessInterceptor( settings, strategySelector ); statementInspector = - strategySelector.resolveStrategy( StatementInspector.class, - settings.get( STATEMENT_INSPECTOR ) ); + strategySelector.resolveStrategy( + StatementInspector.class, + settings.get( STATEMENT_INSPECTOR ) + ); baselineSessionEventsListenerBuilder = new BaselineSessionEventsListenerBuilder( getAutoSessionEventsListener( settings, strategySelector ) ); customEntityDirtinessStrategy = - strategySelector.resolveDefaultableStrategy( CustomEntityDirtinessStrategy.class, + strategySelector.resolveDefaultableStrategy( + CustomEntityDirtinessStrategy.class, settings.get( CUSTOM_ENTITY_DIRTINESS_STRATEGY ), - DefaultCustomEntityDirtinessStrategy.INSTANCE ); + DefaultCustomEntityDirtinessStrategy.INSTANCE + ); entityNotFoundDelegate = StandardEntityNotFoundDelegate.INSTANCE; @@ -421,15 +432,22 @@ public BootstrapContext getBootstrapContext() { configurationService.getSetting( USE_QUERY_CACHE, BOOLEAN, false ); cacheRegionPrefix = extractPropertyValue( CACHE_REGION_PREFIX, settings ); queryCacheLayout = - configurationService.getSetting( QUERY_CACHE_LAYOUT, + configurationService.getSetting( + QUERY_CACHE_LAYOUT, value -> CacheLayout.valueOf( value.toString().toUpperCase( Locale.ROOT ) ), - CacheLayout.FULL ); + CacheLayout.FULL + ); timestampsCacheFactory = - strategySelector.resolveDefaultableStrategy( TimestampsCacheFactory.class, - settings.get( QUERY_CACHE_FACTORY ), StandardTimestampsCacheFactory.INSTANCE ); + strategySelector.resolveDefaultableStrategy( + TimestampsCacheFactory.class, + settings.get( QUERY_CACHE_FACTORY ), StandardTimestampsCacheFactory.INSTANCE + ); minimalPutsEnabled = - configurationService.getSetting( USE_MINIMAL_PUTS, BOOLEAN, - regionFactory.isMinimalPutsEnabledByDefault() ); + configurationService.getSetting( + USE_MINIMAL_PUTS, + BOOLEAN, + regionFactory.isMinimalPutsEnabledByDefault() + ); structuredCacheEntriesEnabled = configurationService.getSetting( USE_STRUCTURED_CACHE, BOOLEAN, false ); directReferenceCacheEntriesEnabled = @@ -479,12 +497,12 @@ public BootstrapContext getBootstrapContext() { commentsEnabled = getBoolean( USE_SQL_COMMENTS, settings ); - preferUserTransaction = getBoolean( PREFER_USER_TRANSACTION, settings ); + preferUserTransaction = getBoolean( PREFER_USER_TRANSACTION, settings ); allowOutOfTransactionUpdateOperations = getBoolean( ALLOW_UPDATE_OUTSIDE_TRANSACTION, settings ); releaseResourcesOnCloseEnabled = getBoolean( DISCARD_PC_ON_CLOSE, settings ); - if ( releaseResourcesOnCloseEnabled) { + if ( releaseResourcesOnCloseEnabled ) { DEPRECATION_LOGGER.deprecatedSetting( DISCARD_PC_ON_CLOSE ); } @@ -533,6 +551,14 @@ public BootstrapContext getBootstrapContext() { defaultLockOptions = defaultLockOptions( defaultSessionProperties ); initialSessionFlushMode = defaultFlushMode( defaultSessionProperties ); + + var graphParserModeSetting = settings.get( GraphParserSettings.GRAPH_PARSER_MODE ); + + if ( graphParserModeSetting != null ) { + DEPRECATION_LOGGER.deprecatedSetting( GraphParserSettings.GRAPH_PARSER_MODE ); + } + + graphParserMode = GraphParserMode.interpret( graphParserModeSetting ); } @Deprecated(forRemoval = true) @@ -578,14 +604,16 @@ else if ( defaultNullPrecedence instanceof String string ) { } else if ( defaultNullPrecedence != null ) { throw new IllegalArgumentException( "Configuration property " + DEFAULT_NULL_ORDERING - + " value [" + defaultNullPrecedence + "] is not supported" ); + + " value [" + defaultNullPrecedence + "] is not supported" ); } else { return null; } } - private static Class getAutoSessionEventsListener(Map configurationSettings, StrategySelector strategySelector) { + private static Class getAutoSessionEventsListener( + Map configurationSettings, + StrategySelector strategySelector) { // todo : expose this from builder? final String name = (String) configurationSettings.get( AUTO_SESSION_EVENTS_LISTENER ); return name == null ? null : strategySelector.selectStrategyImplementor( SessionEventListener.class, name ); @@ -730,7 +758,7 @@ else if ( emptyConstructor != null ) { catch (Exception e) { throw new StrategySelectionException( "Could not instantiate named strategy class [" + - strategyClass.getName() + "]", + strategyClass.getName() + "]", e ); } @@ -791,7 +819,7 @@ private SqmTranslatorFactory resolveSqmTranslator( } private static Interceptor determineInterceptor( - Map configurationSettings, + Map configurationSettings, StrategySelector strategySelector) { return strategySelector.resolveStrategy( Interceptor.class, @@ -801,7 +829,7 @@ private static Interceptor determineInterceptor( @SuppressWarnings("unchecked") private static Supplier determineStatelessInterceptor( - Map configurationSettings, + Map configurationSettings, StrategySelector strategySelector) { final Object setting = configurationSettings.get( SESSION_SCOPED_INTERCEPTOR ); if ( setting == null ) { @@ -830,13 +858,17 @@ private static Supplier interceptorSupplier(Class configurationSettings, + Map configurationSettings, StandardServiceRegistry serviceRegistry) { final var specifiedHandlingMode = PhysicalConnectionHandlingMode.interpret( configurationSettings.get( CONNECTION_HANDLING ) ); @@ -846,7 +878,10 @@ private PhysicalConnectionHandlingMode interpretConnectionHandlingMode( .getDefaultConnectionHandlingMode(); } - private static FormatMapper jsonFormatMapper(Object setting, boolean osonExtensionEnabled, StrategySelector selector, FormatMapperCreationContext creationContext) { + private static FormatMapper jsonFormatMapper( + Object setting, + boolean osonExtensionEnabled, + StrategySelector selector, FormatMapperCreationContext creationContext) { return formatMapper( setting, selector, @@ -878,7 +913,10 @@ private static FormatMapper xmlFormatMapper(Object setting, StrategySelector sel ); } - private static FormatMapper formatMapper(Object setting, StrategySelector selector, Callable defaultResolver, FormatMapperCreationContext creationContext) { + private static FormatMapper formatMapper( + Object setting, + StrategySelector selector, + Callable defaultResolver, FormatMapperCreationContext creationContext) { return selector.resolveStrategy( FormatMapper.class, setting, defaultResolver, strategyClass -> { try { final Constructor creationContextConstructor = @@ -1007,15 +1045,19 @@ public SqmMultiTableInsertStrategy getCustomSqmMultiTableInsertStrategy() { } @Override - public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext) { + public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy( + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext creationContext) { if ( sqmMultiTableMutationStrategyConstructor != null ) { try { return sqmMultiTableMutationStrategyConstructor.newInstance( rootEntityDescriptor, creationContext ); } catch (Exception e) { throw new StrategySelectionException( - String.format( "Could not instantiate named strategy class [%s]", - sqmMultiTableMutationStrategyConstructor.getDeclaringClass().getName() ), + String.format( + "Could not instantiate named strategy class [%s]", + sqmMultiTableMutationStrategyConstructor.getDeclaringClass().getName() + ), e ); } @@ -1024,15 +1066,19 @@ public SqmMultiTableMutationStrategy resolveCustomSqmMultiTableMutationStrategy( } @Override - public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy(EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext creationContext) { + public SqmMultiTableInsertStrategy resolveCustomSqmMultiTableInsertStrategy( + EntityMappingType rootEntityDescriptor, + RuntimeModelCreationContext creationContext) { if ( sqmMultiTableInsertStrategyConstructor != null ) { try { return sqmMultiTableInsertStrategyConstructor.newInstance( rootEntityDescriptor, creationContext ); } catch (Exception e) { throw new StrategySelectionException( - String.format( "Could not instantiate named strategy class [%s]", - sqmMultiTableInsertStrategyConstructor.getDeclaringClass().getName() ), + String.format( + "Could not instantiate named strategy class [%s]", + sqmMultiTableInsertStrategyConstructor.getDeclaringClass().getName() + ), e ); } @@ -1057,7 +1103,7 @@ public StatementInspector getStatementInspector() { @Override public SessionFactoryObserver[] getSessionFactoryObservers() { - return sessionFactoryObserverList.toArray(new SessionFactoryObserver[0]); + return sessionFactoryObserverList.toArray( new SessionFactoryObserver[0] ); } @Override @@ -1080,12 +1126,14 @@ public boolean isInitializeLazyStateOutsideTransactionsEnabled() { return initializeLazyStateOutsideTransactions; } - @Override @Deprecated + @Override + @Deprecated public TempTableDdlTransactionHandling getTempTableDdlTransactionHandling() { return tempTableDdlTransactionHandling; } - @Override @Deprecated(forRemoval = true) + @Override + @Deprecated(forRemoval = true) public boolean isDelayBatchFetchLoaderCreationsEnabled() { return delayBatchFetchLoaderCreations; } @@ -1190,7 +1238,8 @@ public boolean isAutoEvictCollectionCache() { return autoEvictCollectionCache; } - @Override @Deprecated + @Override + @Deprecated public SchemaAutoTooling getSchemaAutoTooling() { return schemaAutoTooling; } @@ -1242,7 +1291,7 @@ public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() { @Override public EntityNameResolver[] getEntityNameResolvers() { - return entityNameResolvers.toArray(new EntityNameResolver[0]); + return entityNameResolvers.toArray( new EntityNameResolver[0] ); } @Override @@ -1497,8 +1546,10 @@ public void applyStatelessInterceptor(Class statelessInte return statelessInterceptorClass.newInstance(); } catch (InstantiationException | IllegalAccessException e) { - throw new HibernateException( "Could not supply stateless Interceptor of class '" - + statelessInterceptorClass.getName() + "'", e ); + throw new HibernateException( + "Could not supply stateless Interceptor of class '" + + statelessInterceptorClass.getName() + "'", e + ); } } ); @@ -1592,7 +1643,7 @@ public void enableNamedQueryCheckingOnStartup(boolean enabled) { } public void enableSecondLevelCacheSupport(boolean enabled) { - this.secondLevelCacheEnabled = enabled; + this.secondLevelCacheEnabled = enabled; } public void enableQueryCacheSupport(boolean enabled) { @@ -1782,8 +1833,13 @@ public Map getDefaultSessionProperties() { return defaultSessionProperties; } + @Override + public GraphParserMode getGraphParserMode() { + return graphParserMode; + } + private Map initializeDefaultSessionProperties(ConfigurationService configurationService) { - final HashMap settings = new HashMap<>(); + final HashMap settings = new HashMap<>(); //Static defaults: settings.putIfAbsent( HibernateHints.HINT_FLUSH_MODE, FlushMode.AUTO ); @@ -1821,4 +1877,5 @@ private Map initializeDefaultSessionProperties(ConfigurationServ } return unmodifiableMap( settings ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java b/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java index 026af3d8f896..7728d51f65a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/NamedGraphCreator.java @@ -6,6 +6,7 @@ import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.service.ServiceRegistry; import java.util.function.Function; @@ -16,5 +17,6 @@ public interface NamedGraphCreator { RootGraphImplementor createEntityGraph( Function, EntityDomainType> entityDomainClassResolver, - Function> entityDomainNameResolver); + Function> entityDomainNameResolver, + ServiceRegistry serviceRegistry); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InvalidNamedEntityGraphParameterException.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InvalidNamedEntityGraphParameterException.java new file mode 100644 index 000000000000..8432276aca0e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/InvalidNamedEntityGraphParameterException.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.boot.model.internal; + + +import org.hibernate.AnnotationException; + +/** + * Thrown by {@link NamedGraphCreatorParsed} to indicate an issue with the parameters provided + * to a {@code @NamedEntityGraph} annotation. + * + */ +public class InvalidNamedEntityGraphParameterException extends AnnotationException { + private static final long serialVersionUID = 1L; + + public InvalidNamedEntityGraphParameterException(String message) { + super( message ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java index 5e51ff06fcbb..9e715da349e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorJpa.java @@ -15,6 +15,7 @@ import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.graph.spi.SubGraphImplementor; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.service.ServiceRegistry; import java.util.function.Function; @@ -39,7 +40,8 @@ class NamedGraphCreatorJpa implements NamedGraphCreator { @Override public RootGraphImplementor createEntityGraph( Function, EntityDomainType> entityDomainClassResolver, - Function> entityDomainNameResolver) { + Function> entityDomainNameResolver, + ServiceRegistry serviceRegistry) { //noinspection unchecked final EntityDomainType rootEntityType = (EntityDomainType) entityDomainNameResolver.apply( jpaEntityName ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java index a264f3521fd2..818d21d58b22 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java @@ -7,16 +7,24 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.checkerframework.checker.nullness.qual.Nullable; + +import org.hibernate.GraphParserMode; import org.hibernate.UnknownEntityTypeException; import org.hibernate.annotations.NamedEntityGraph; import org.hibernate.boot.model.NamedGraphCreator; -import org.hibernate.grammars.graph.GraphLanguageLexer; -import org.hibernate.grammars.graph.GraphLanguageParser; +import org.hibernate.cfg.GraphParserSettings; +import org.hibernate.engine.config.spi.ConfigurationService; +import org.hibernate.grammars.graph.ModernGraphLanguageLexer; +import org.hibernate.grammars.graph.ModernGraphLanguageParser; +import org.hibernate.grammars.graph.legacy.GraphLanguageLexer; +import org.hibernate.grammars.graph.legacy.GraphLanguageParser; import org.hibernate.graph.InvalidGraphException; 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 org.hibernate.service.ServiceRegistry; import java.util.function.Function; @@ -43,16 +51,22 @@ class NamedGraphCreatorParsed implements NamedGraphCreator { @Override public RootGraphImplementor createEntityGraph( Function, EntityDomainType> entityDomainClassResolver, - Function> entityDomainNameResolver) { - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( annotation.graph() ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); + Function> entityDomainNameResolver, + ServiceRegistry serviceRegistry) { + + final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class ); + + final GraphParserMode graphParserMode = configurationService.getSetting( + GraphParserSettings.GRAPH_PARSER_MODE, + GraphParserMode::interpret + ); final EntityNameResolver entityNameResolver = new EntityNameResolver() { @Override public EntityDomainType resolveEntityName(String entityName) { //noinspection unchecked - final EntityDomainType entityDomainType = (EntityDomainType) entityDomainNameResolver.apply( entityName ); + final EntityDomainType entityDomainType = (EntityDomainType) entityDomainNameResolver.apply( + entityName ); if ( entityDomainType != null ) { return entityDomainType; } @@ -60,24 +74,102 @@ public EntityDomainType resolveEntityName(String entityName) { } }; + if ( graphParserMode.equals( GraphParserMode.LEGACY ) ) { + return parseGraphForLegacyParsingMode( entityDomainClassResolver, entityDomainNameResolver, entityNameResolver ); + } + + return parseGraphForModernParsingMode( entityDomainClassResolver, entityNameResolver ); + } + + private RootGraphImplementor parseGraphForLegacyParsingMode( + Function, EntityDomainType> entityDomainClassResolver, + Function> entityDomainNameResolver, + EntityNameResolver entityNameResolver) { + final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( annotation.graph() ) ); + final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); + final GraphLanguageParser.GraphContext graphContext = parser.graph(); + + final EntityDomainType entityDomainType = resolveEntityDomainTypeForLegacyParsingMode( + graphContext, + entityDomainClassResolver, + entityDomainNameResolver + ); + + final String graphName = this.name == null ? entityDomainType.getName() : this.name; + + return GraphParsing.parse( graphName, entityDomainType, graphContext.attributeList(), entityNameResolver ); + } + + private RootGraphImplementor parseGraphForModernParsingMode( + Function, EntityDomainType> entityDomainClassResolver, + EntityNameResolver entityNameResolver) { + final EntityDomainType entityDomainType = resolveEntityDomainTypeFromAnnotation( + entityDomainClassResolver + ); + + final ModernGraphLanguageLexer lexer = new ModernGraphLanguageLexer( + CharStreams.fromString( annotation.graph() ) ); + final ModernGraphLanguageParser parser = new ModernGraphLanguageParser( new CommonTokenStream( lexer ) ); + final ModernGraphLanguageParser.GraphContext graphContext = parser.graph(); + + final String graphName = this.name == null ? entityDomainType.getName() : this.name; + + return GraphParsing.parse( graphName, entityDomainType, graphContext.graphElementList(), entityNameResolver ); + } + + private EntityDomainType resolveEntityDomainTypeForLegacyParsingMode( + GraphLanguageParser.GraphContext graphContext, + Function, EntityDomainType> entityDomainClassResolver, + Function> entityDomainNameResolver) { + + final var typeIndicator = graphContext.typeIndicator(); + if ( typeIndicator != null ) { + if ( entityType != null ) { + throw new InvalidGraphException( + "Expecting graph text to not include an entity name : " + annotation.graph() ); + } + + DeprecationLogger.DEPRECATION_LOGGER.deprecatedNamedEntityGraphTextThatContainTypeIndicator(); + + //noinspection unchecked + return (EntityDomainType) entityDomainNameResolver.apply( + typeIndicator.TYPE_NAME().toString() ); + } + + return resolveEntityDomainTypeFromAnnotation( entityDomainClassResolver ); + } + + private EntityDomainType resolveEntityDomainTypeFromAnnotation(Function, EntityDomainType> entityDomainClassResolver) { + final Class annotationRootAttribute = annotation.root(); + final boolean isAnnotationRootAttributeVoid = void.class.equals( annotationRootAttribute ); + if ( entityType == null ) { - if ( graphContext.typeIndicator() == null ) { - throw new InvalidGraphException( "Expecting graph text to include an entity name : " + annotation.graph() ); + if ( isAnnotationRootAttributeVoid ) { + throw new InvalidNamedEntityGraphParameterException( + "The 'root' parameter of the @NamedEntityGraph should be passed. Graph : " + annotation.name() + ); } - final String jpaEntityName = graphContext.typeIndicator().TYPE_NAME().toString(); + //noinspection unchecked - final EntityDomainType entityDomainType = (EntityDomainType) entityDomainNameResolver.apply( jpaEntityName ); - final String name = this.name == null ? jpaEntityName : this.name; - return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver ); + return (EntityDomainType) entityDomainClassResolver.apply( (Class) annotationRootAttribute ); } - else { - if ( graphContext.typeIndicator() != null ) { - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + annotation.graph() ); + + if ( !isAnnotationRootAttributeVoid ) { + 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 entityDomainType = (EntityDomainType) entityDomainClassResolver.apply( (Class) entityType ); - final String name = this.name == null ? entityDomainType.getName() : this.name; - return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver ); + return (EntityDomainType) entityDomainClassResolver.apply( (Class) annotationRootAttribute ); } + + //noinspection unchecked + return (EntityDomainType) entityDomainClassResolver.apply( (Class) entityType ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java index 488734b42b83..6e6f17b9dcb5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java @@ -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) @@ -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(); } /** @@ -39,6 +41,7 @@ public NamedEntityGraphAnnotation(NamedEntityGraph annotation, ModelsContext mod public NamedEntityGraphAnnotation(Map attributeValues, ModelsContext modelContext) { this.name = (String) attributeValues.get( "name" ); this.graph = (String) attributeValues.get( "graph" ); + this.root = (Class) attributeValues.get( "root" ); } @Override @@ -46,6 +49,15 @@ public Class 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; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 3754a15b7ccf..6b638ecdd7bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -14,6 +14,7 @@ import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.EntityNameResolver; import org.hibernate.FlushMode; +import org.hibernate.GraphParserMode; import org.hibernate.Interceptor; import org.hibernate.LockOptions; import org.hibernate.SessionFactoryObserver; @@ -586,6 +587,11 @@ public Map getDefaultSessionProperties() { return delegate.getDefaultSessionProperties(); } + @Override + public GraphParserMode getGraphParserMode() { + return delegate.getGraphParserMode(); + } + @Override public CacheMode getInitialSessionCacheMode() { return delegate.getInitialSessionCacheMode(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index ce8f8b3fff5e..9cb5efbec715 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -14,6 +14,7 @@ import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.EntityNameResolver; import org.hibernate.FlushMode; +import org.hibernate.GraphParserMode; import org.hibernate.Incubating; import org.hibernate.Interceptor; import org.hibernate.Internal; @@ -795,4 +796,14 @@ default JavaType getDefaultTenantIdentifierJavaType() { * @see org.hibernate.Session#setProperty(String, Object) */ Map getDefaultSessionProperties(); + + /** + * The graph parser mode to use for parsing entity graph strings. + * + * @see org.hibernate.cfg.GraphParserSettings#GRAPH_PARSER_MODE + * + * @since 7.0 + */ + GraphParserMode getGraphParserMode(); + } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 33b751ca7eae..72c0ce3ae0d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -30,7 +30,7 @@ public interface AvailableSettings extends BatchSettings, BytecodeSettings, CacheSettings, EnvironmentSettings, FetchSettings, JdbcSettings, JpaComplianceSettings, ManagedBeanSettings, MappingSettings, MultiTenancySettings, PersistenceSettings, QuerySettings, SchemaToolingSettings, SessionEventSettings, StatisticsSettings, - TransactionSettings, ValidationSettings { + TransactionSettings, ValidationSettings, GraphParserSettings { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // JPA settings diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/GraphParserSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/GraphParserSettings.java new file mode 100644 index 000000000000..6a9f3a362eab --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/GraphParserSettings.java @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.cfg; + +/** + * Settings for configuring graph parser behavior. + * + */ +public interface GraphParserSettings { + + /** + * Setting to control which graph parser syntax to use. + *

+ * Valid values are: + *

    + *
  • {@code "legacy"} - Uses the legacy syntax: attribute(SubType: attributes) + * This generates deprecation warnings when the old syntax is detected. + *
  • {@code "modern"} - Uses the new syntax: attribute:SubType(attributes) + * This is the preferred syntax going forward. + *
+ *

+ * @settingDefault {@code "legacy"} for backward compatibility + * @since 7.0 + */ + @Deprecated(since = "7.0", forRemoval = true) + String GRAPH_PARSER_MODE = "hibernate.graph_parser_mode"; + +} diff --git a/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java index 0bfcb46624ad..0ebce8b77f11 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java @@ -116,16 +116,18 @@ public static RootGraph 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 RootGraph 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 RootGraph parse( + final CharSequence graphText, + final SessionFactory sessionFactory) { + if ( graphText == null ) { + return null; + } + return GraphParsing.parse( + graphText.toString(), + sessionFactory.unwrap( SessionFactoryImplementor.class ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java index 2c8be74ef601..a90aedb4429b 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java @@ -4,14 +4,15 @@ */ package org.hibernate.graph.internal.parse; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; import org.checkerframework.checker.nullness.qual.Nullable; + +import org.hibernate.GraphParserMode; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.grammars.graph.GraphLanguageLexer; -import org.hibernate.grammars.graph.GraphLanguageParser; -import org.hibernate.graph.InvalidGraphException; -import org.hibernate.graph.internal.RootGraphImpl; +import org.hibernate.grammars.graph.ModernGraphLanguageParser; +import org.hibernate.grammars.graph.legacy.GraphLanguageParser; +import org.hibernate.graph.internal.parse.strategy.ModernGraphParsingStrategy; +import org.hibernate.graph.internal.parse.strategy.GraphParsingStrategy; +import org.hibernate.graph.internal.parse.strategy.LegacyGraphParsingStrategy; import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.metamodel.model.domain.EntityDomainType; @@ -26,123 +27,51 @@ public static RootGraphImplementor parse( Class entityClass, String graphText, SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - - if ( graphContext.typeIndicator() != null ) { - // todo : an alternative here would be to simply validate that the entity type - // from the text matches the passed one... - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + graphText ); - } - final EntityDomainType entityType = sessionFactory.getJpaMetamodel().entity( entityClass ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); - } - - public static RootGraphImplementor parse( - EntityDomainType entityDomainType, - String graphText, - SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - - if ( graphContext.typeIndicator() != null ) { - // todo : an alternative here would be to simply validate that the entity type - // from the text matches the passed one... - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + graphText ); - } - - return parse( entityDomainType, graphContext.attributeList(), sessionFactory ); + return getGraphParsingStrategy( sessionFactory ).parse( + entityClass, + graphText, + sessionFactory + ); } public static RootGraphImplementor parse( String entityName, String graphText, SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - - if ( graphContext.typeIndicator() != null ) { - // todo : an alternative here would be to simply validate that the entity type - // from the text matches the passed one... - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + graphText ); - } + return getGraphParsingStrategy( sessionFactory ).parse( + entityName, + graphText, + sessionFactory + ); - //noinspection unchecked - final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity( entityName ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); } + @Deprecated(forRemoval = true) public static RootGraphImplementor parse( String graphText, SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - - if ( graphContext.typeIndicator() == null ) { - throw new InvalidGraphException( "Expecting graph text to include an entity name : " + graphText ); - } - - final String entityName = graphContext.typeIndicator().TYPE_NAME().getText(); - //noinspection unchecked - final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity( entityName ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); + return getGraphParsingStrategy( sessionFactory ).parse( graphText, sessionFactory ); } - public static RootGraphImplementor parse( - EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, - SessionFactoryImplementor sessionFactory) { - return parse( rootType, attributeListContext, new EntityNameResolverSessionFactory( sessionFactory ) ); - } public static RootGraphImplementor parse( + @Nullable String name, EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + GraphLanguageParser.AttributeListContext graphElementListContext, EntityNameResolver entityNameResolver) { - return parse( null, rootType, attributeListContext, entityNameResolver ); + + return new LegacyGraphParsingStrategy().parse( name, rootType, graphElementListContext, entityNameResolver ); } public static RootGraphImplementor parse( @Nullable String name, EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + ModernGraphLanguageParser.GraphElementListContext graphElementListContext, EntityNameResolver entityNameResolver) { - final RootGraphImpl targetGraph = new RootGraphImpl<>( name, rootType ); - - final GraphParser visitor = new GraphParser( entityNameResolver ); - visitor.getGraphStack().push( targetGraph ); - try { - visitor.visitAttributeList( attributeListContext ); - } - finally { - visitor.getGraphStack().pop(); - - assert visitor.getGraphStack().isEmpty(); - } - return targetGraph; + return new ModernGraphParsingStrategy().parse( name, rootType, graphElementListContext, entityNameResolver ); } /** @@ -153,26 +82,18 @@ public static void parseInto( GraphImplementor targetGraph, CharSequence graphString, SessionFactoryImplementor sessionFactory) { - final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphString.toString() ) ); - final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); - final GraphLanguageParser.GraphContext graphContext = parser.graph(); - if ( graphContext.typeIndicator() != null ) { - // todo : throw an exception? Log warning? Ignore? - // for now, ignore - } + getGraphParsingStrategy( sessionFactory ).parseInto( targetGraph, graphString.toString(), sessionFactory ); + } - // Build an instance of this class as a visitor - final GraphParser visitor = new GraphParser( sessionFactory ); + private static GraphParsingStrategy getGraphParsingStrategy(SessionFactoryImplementor sessionFactory) { + final GraphParserMode mode = sessionFactory.getSessionFactoryOptions().getGraphParserMode(); - visitor.getGraphStack().push( targetGraph ); - try { - visitor.visitAttributeList( graphContext.attributeList() ); + if ( mode == GraphParserMode.MODERN ) { + return new ModernGraphParsingStrategy(); } - finally { - visitor.getGraphStack().pop(); - assert visitor.getGraphStack().isEmpty(); - } + return new LegacyGraphParsingStrategy(); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/LegacyGraphParser.java similarity index 92% rename from hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java rename to hibernate-core/src/main/java/org/hibernate/graph/internal/parse/LegacyGraphParser.java index e02e5f06ee03..5e099c6cc58a 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/LegacyGraphParser.java @@ -5,8 +5,8 @@ package org.hibernate.graph.internal.parse; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.grammars.graph.GraphLanguageParser; -import org.hibernate.grammars.graph.GraphLanguageParserBaseVisitor; +import org.hibernate.grammars.graph.legacy.GraphLanguageParser; +import org.hibernate.grammars.graph.legacy.GraphLanguageParserBaseVisitor; import org.hibernate.graph.GraphNode; import org.hibernate.graph.InvalidGraphException; import org.hibernate.graph.spi.AttributeNodeImplementor; @@ -23,14 +23,14 @@ * * @author Steve Ebersole */ -public class GraphParser extends GraphLanguageParserBaseVisitor> { +public class LegacyGraphParser extends GraphLanguageParserBaseVisitor> { private final EntityNameResolver entityNameResolver; private final Stack> graphStack = new StandardStack<>(); private final Stack> attributeNodeStack = new StandardStack<>(); private final Stack graphSourceStack = new StandardStack<>(); - public GraphParser(EntityNameResolver entityNameResolver) { + public LegacyGraphParser(EntityNameResolver entityNameResolver) { this.entityNameResolver = entityNameResolver; } @@ -38,9 +38,9 @@ public GraphParser(EntityNameResolver entityNameResolver) { * @apiNote It is important that this form only be used after the session-factory is fully * initialized, especially the {@linkplain SessionFactoryImplementor#getJpaMetamodel()} JPA metamodel}. * - * @see GraphParser#GraphParser(EntityNameResolver) + * @see LegacyGraphParser#LegacyGraphParser(EntityNameResolver) */ - public GraphParser(SessionFactoryImplementor sessionFactory) { + public LegacyGraphParser(SessionFactoryImplementor sessionFactory) { this( new EntityNameResolverSessionFactory( sessionFactory ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/ModernGraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/ModernGraphParser.java new file mode 100644 index 000000000000..f8b4fcad86d0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/ModernGraphParser.java @@ -0,0 +1,198 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.graph.internal.parse; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.grammars.graph.ModernGraphLanguageParser; +import org.hibernate.grammars.graph.ModernGraphLanguageParserBaseVisitor; +import org.hibernate.graph.GraphNode; +import org.hibernate.graph.InvalidGraphException; +import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.SubGraphImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.Stack; +import org.hibernate.internal.util.collections.StandardStack; + +import static org.hibernate.graph.internal.GraphParserLogging.PARSING_LOGGER; + +/** + * Unified access to the Antlr parser for Hibernate's "modern graph language" + * + */ +public class ModernGraphParser extends ModernGraphLanguageParserBaseVisitor> { + private final EntityNameResolver entityNameResolver; + + private final Stack> graphStack = new StandardStack<>(); + private final Stack> attributeNodeStack = new StandardStack<>(); + private final Stack graphSourceStack = new StandardStack<>(); + + public ModernGraphParser(EntityNameResolver entityNameResolver) { + this.entityNameResolver = entityNameResolver; + } + + /** + * @apiNote It is important that this form only be used after the session-factory is fully + * initialized, especially the {@linkplain SessionFactoryImplementor#getJpaMetamodel()} JPA metamodel}. + * @see ModernGraphParser#ModernGraphParser(EntityNameResolver) + */ + public ModernGraphParser(SessionFactoryImplementor sessionFactory) { + this( new EntityNameResolverSessionFactory( sessionFactory ) ); + } + + public Stack> getGraphStack() { + return graphStack; + } + + @Override + public GraphNode visitSubGraph(ModernGraphLanguageParser.SubGraphContext subGraphContext) { + final String subTypeName = subGraphContext.subTypeIndicator() == null ? + null : + subGraphContext.subTypeIndicator().TYPE_NAME().getText(); + + if ( PARSING_LOGGER.isDebugEnabled() ) { + PARSING_LOGGER.debugf( + "%s Starting subtype graph : %s", + StringHelper.repeat( ">>", attributeNodeStack.depth() + 2 ), + subTypeName + ); + } + + final AttributeNodeImplementor attributeNode = attributeNodeStack.getCurrent(); + + SubGraphImplementor subGraph = createSubGraph( attributeNode, subTypeName ); + + graphStack.push( subGraph ); + + + try { + subGraphContext.attributeList().accept( this ); + } + finally { + graphStack.pop(); + } + + if ( PARSING_LOGGER.isDebugEnabled() ) { + PARSING_LOGGER.debugf( + "%s Finished subtype graph : %s", + StringHelper.repeat( "<<", attributeNodeStack.depth() + 2 ), + subGraph.getGraphedType().getTypeName() + ); + } + + return subGraph; + } + + private SubGraphImplementor createSubGraph(AttributeNodeImplementor attributeNode, String subTypeName) { + SubGraphImplementor subGraph; + + var shouldCreateTreatedSubgraph = attributeNode == null && subTypeName != null; + + if ( shouldCreateTreatedSubgraph ) { + var currentGraph = graphStack.getCurrent(); + + subGraph = currentGraph.addTreatedSubgraph( + entityNameResolver.resolveEntityName( subTypeName ) + ); + + } + else { + final SubGraphGenerator subGraphCreator = graphSourceStack.getCurrent(); + + subGraph = subGraphCreator.createSubGraph( + attributeNode, + subTypeName, + entityNameResolver + ); + } + + return subGraph; + } + + @Override + public AttributeNodeImplementor visitAttributeNode(ModernGraphLanguageParser.AttributeNodeContext attributeNodeContext) { + final String attributeName = attributeNodeContext.attributePath().ATTR_NAME().getText(); + + final SubGraphGenerator subGraphCreator; + + if ( attributeNodeContext.attributePath().attributeQualifier() == null ) { + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Start attribute : %s", + StringHelper.repeat( ">>", attributeNodeStack.depth() + 1 ), + attributeName + ); + } + + subGraphCreator = PathQualifierType.VALUE.getSubGraphCreator(); + } + else { + final String qualifierName = attributeNodeContext.attributePath() + .attributeQualifier() + .ATTR_NAME() + .getText(); + + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Start qualified attribute : %s.%s", + StringHelper.repeat( ">>", attributeNodeStack.depth() + 1 ), + attributeName, + qualifierName + ); + } + + final PathQualifierType pathQualifierType = resolvePathQualifier( qualifierName ); + subGraphCreator = pathQualifierType.getSubGraphCreator(); + } + + final AttributeNodeImplementor attributeNode = resolveAttributeNode( attributeName ); + + if ( attributeNodeContext.subGraph() != null ) { + attributeNodeStack.push( attributeNode ); + graphSourceStack.push( subGraphCreator ); + + try { + visitSubGraph( attributeNodeContext.subGraph() ); + + } + finally { + graphSourceStack.pop(); + attributeNodeStack.pop(); + } + } + + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Finished attribute : %s", + StringHelper.repeat( "<<", attributeNodeStack.depth() + 1 ), + attributeName + ); + } + + return attributeNode; + } + + private AttributeNodeImplementor resolveAttributeNode(String attributeName) { + final GraphImplementor currentGraph = graphStack.getCurrent(); + assert currentGraph != null; + + final AttributeNodeImplementor attributeNode = currentGraph.findOrCreateAttributeNode( attributeName ); + assert attributeNode != null; + + return attributeNode; + } + + private PathQualifierType resolvePathQualifier(String qualifier) { + if ( "key".equalsIgnoreCase( qualifier ) ) { + return PathQualifierType.KEY; + } + + if ( "value".equalsIgnoreCase( qualifier ) ) { + return PathQualifierType.VALUE; + } + + throw new InvalidGraphException( "Invalid path qualifier [" + qualifier + "] - expecting 'key' or 'value'" ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/GraphParsingStrategy.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/GraphParsingStrategy.java new file mode 100644 index 000000000000..c137f26eb133 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/GraphParsingStrategy.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.graph.internal.parse.strategy; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.metamodel.model.domain.EntityDomainType; + +public interface GraphParsingStrategy { + RootGraphImplementor parse(Class entityClass, String graphText, SessionFactoryImplementor sessionFactory); + + RootGraphImplementor parse( + EntityDomainType entityDomainType, + String graphText, + SessionFactoryImplementor sessionFactory); + + RootGraphImplementor parse(String entityName, String graphText, SessionFactoryImplementor sessionFactory); + + @Deprecated(forRemoval = true) + RootGraphImplementor parse(String graphText, SessionFactoryImplementor sessionFactory); + + void parseInto(GraphImplementor graph, String graphText, SessionFactoryImplementor sessionFactory); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/LegacyGraphParsingStrategy.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/LegacyGraphParsingStrategy.java new file mode 100644 index 000000000000..5740977308cd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/LegacyGraphParsingStrategy.java @@ -0,0 +1,120 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.graph.internal.parse.strategy; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.grammars.graph.legacy.GraphLanguageLexer; +import org.hibernate.grammars.graph.legacy.GraphLanguageParser; +import org.hibernate.graph.InvalidGraphException; +import org.hibernate.graph.internal.RootGraphImpl; +import org.hibernate.graph.internal.parse.EntityNameResolver; +import org.hibernate.graph.internal.parse.EntityNameResolverSessionFactory; +import org.hibernate.graph.internal.parse.LegacyGraphParser; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.metamodel.model.domain.EntityDomainType; + +@Deprecated(forRemoval = true) +public class LegacyGraphParsingStrategy implements GraphParsingStrategy { + + @Override + public RootGraphImplementor parse(Class entityClass, String graphText, SessionFactoryImplementor sessionFactory) { + if (graphText == null) return null; + final var parser = new GraphLanguageParser(new CommonTokenStream(new GraphLanguageLexer(CharStreams.fromString(graphText)))); + final var graph = parser.graph(); + if (graph.typeIndicator() != null) { + throw new InvalidGraphException("Expecting graph text to not include an entity name : " + graphText); + } + final EntityDomainType entityType = sessionFactory.getJpaMetamodel().entity(entityClass); + return parse(entityType, graph.attributeList(), sessionFactory); + } + + @Override + public RootGraphImplementor parse(EntityDomainType entityDomainType, String graphText, SessionFactoryImplementor sessionFactory) { + if (graphText == null) return null; + final var parser = new GraphLanguageParser(new CommonTokenStream(new GraphLanguageLexer(CharStreams.fromString(graphText)))); + final var graph = parser.graph(); + if (graph.typeIndicator() != null) { + throw new InvalidGraphException("Expecting graph text to not include an entity name : " + graphText); + } + return parse(entityDomainType, graph.attributeList(), sessionFactory); + } + + @Override + public RootGraphImplementor parse(String entityName, String graphText, SessionFactoryImplementor sessionFactory) { + if (graphText == null) return null; + final var parser = new GraphLanguageParser(new CommonTokenStream(new GraphLanguageLexer(CharStreams.fromString(graphText)))); + final var graph = parser.graph(); + if (graph.typeIndicator() != null) { + throw new InvalidGraphException("Expecting graph text to not include an entity name : " + graphText); + } + @SuppressWarnings("unchecked") + final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity(entityName); + return parse(entityType, graph.attributeList(), sessionFactory); + } + + @Override + @Deprecated(forRemoval = true) + public RootGraphImplementor parse(String graphText, SessionFactoryImplementor sessionFactory) { + if (graphText == null) return null; + final var parser = new GraphLanguageParser(new CommonTokenStream(new GraphLanguageLexer(CharStreams.fromString(graphText)))); + final var graph = parser.graph(); + if (graph.typeIndicator() == null) { + throw new InvalidGraphException("Expecting graph text to include an entity name : " + graphText); + } + final String entityName = graph.typeIndicator().TYPE_NAME().getText(); + @SuppressWarnings("unchecked") + final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity(entityName); + return parse(entityType, graph.attributeList(), sessionFactory); + } + + @Override + public void parseInto(GraphImplementor graph, String graphText, SessionFactoryImplementor sessionFactory) { + final GraphLanguageLexer lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); + final GraphLanguageParser parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); + final GraphLanguageParser.GraphContext graphContext = parser.graph(); + + if ( graphContext.typeIndicator() != null ) { + // todo : throw an exception? Log warning? Ignore? + // for now, ignore + } + + // Build an instance of this class as a visitor + final LegacyGraphParser visitor = new LegacyGraphParser( sessionFactory ); + + visitor.getGraphStack().push( graph ); + try { + visitor.visitAttributeList( graphContext.attributeList() ); + } + finally { + visitor.getGraphStack().pop(); + + assert visitor.getGraphStack().isEmpty(); + } + + } + + public RootGraphImplementor parse(EntityDomainType rootType, GraphLanguageParser.AttributeListContext graphElementListContext, SessionFactoryImplementor sessionFactory) { + return parse(null, rootType, graphElementListContext, new EntityNameResolverSessionFactory( sessionFactory)); + } + + public RootGraphImplementor parse(@Nullable String name, EntityDomainType rootType, GraphLanguageParser.AttributeListContext graphElementListContext, EntityNameResolver entityNameResolver) { + final RootGraphImpl targetGraph = new RootGraphImpl<>(name, rootType); + + final var visitor = new LegacyGraphParser( entityNameResolver); + visitor.getGraphStack().push(targetGraph); + try { + visitor.visitAttributeList(graphElementListContext); + } + finally { + visitor.getGraphStack().pop(); + assert visitor.getGraphStack().isEmpty(); + } + return targetGraph; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/ModernGraphParsingStrategy.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/ModernGraphParsingStrategy.java new file mode 100644 index 000000000000..a29c398f6558 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/ModernGraphParsingStrategy.java @@ -0,0 +1,121 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.graph.internal.parse.strategy; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.grammars.graph.ModernGraphLanguageLexer; +import org.hibernate.grammars.graph.ModernGraphLanguageParser; +import org.hibernate.graph.internal.RootGraphImpl; +import org.hibernate.graph.internal.parse.EntityNameResolver; +import org.hibernate.graph.internal.parse.EntityNameResolverSessionFactory; +import org.hibernate.graph.internal.parse.ModernGraphParser; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.metamodel.model.domain.EntityDomainType; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ModernGraphParsingStrategy implements GraphParsingStrategy { + + @Override + public RootGraphImplementor parse( + Class entityClass, + String graphText, + SessionFactoryImplementor sessionFactory) { + if ( graphText == null ) { + return null; + } + final var parser = new ModernGraphLanguageParser( new CommonTokenStream( new ModernGraphLanguageLexer( + CharStreams.fromString( graphText ) ) ) ); + final var graph = parser.graph(); + + + final EntityDomainType entityType = sessionFactory.getJpaMetamodel().entity( entityClass ); + return parse( entityType, graph.graphElementList(), sessionFactory ); + } + + @Override + public RootGraphImplementor parse( + EntityDomainType entityDomainType, + String graphText, + SessionFactoryImplementor sessionFactory) { + if ( graphText == null ) { + return null; + } + final var parser = new ModernGraphLanguageParser( new CommonTokenStream( new ModernGraphLanguageLexer( + CharStreams.fromString( graphText ) ) ) ); + final var graph = parser.graph(); + + return parse( entityDomainType, graph.graphElementList(), sessionFactory ); + } + + @Override + public RootGraphImplementor parse( + String entityName, + String graphText, + SessionFactoryImplementor sessionFactory) { + if ( graphText == null ) { + return null; + } + final var parser = new ModernGraphLanguageParser( new CommonTokenStream( new ModernGraphLanguageLexer( + CharStreams.fromString( graphText ) ) ) ); + final var graph = parser.graph(); + + @SuppressWarnings( + "unchecked") final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel() + .entity( entityName ); + return parse( entityType, graph.graphElementList(), sessionFactory ); + } + + @Override + public RootGraphImplementor parse(String graphText, SessionFactoryImplementor sessionFactory) { + throw new UnsupportedOperationException( + "Parsing of graph text is not supported with 'modern' graph parser mode" ); + } + + @Override + public void parseInto(GraphImplementor graph, String graphText, SessionFactoryImplementor sessionFactory) { + final var parser = new ModernGraphLanguageParser( new CommonTokenStream( new ModernGraphLanguageLexer( + CharStreams.fromString( graphText ) ) ) ); + final var graphCtx = parser.graph(); + final var visitor = new ModernGraphParser( sessionFactory ); + visitor.getGraphStack().push( graph ); + try { + visitor.visitGraphElementList( graphCtx.graphElementList() ); + } + finally { + visitor.getGraphStack().pop(); + assert visitor.getGraphStack().isEmpty(); + } + } + + public RootGraphImplementor parse( + EntityDomainType rootType, + ModernGraphLanguageParser.GraphElementListContext graphElementListContext, + SessionFactoryImplementor sessionFactory) { + return parse( null, rootType, graphElementListContext, new EntityNameResolverSessionFactory( sessionFactory ) ); + } + + + public RootGraphImplementor parse( + @Nullable String name, + EntityDomainType rootType, + ModernGraphLanguageParser.GraphElementListContext graphElementListContext, + EntityNameResolver entityNameResolver) { + final RootGraphImpl targetGraph = new RootGraphImpl<>( name, rootType ); + final var visitor = new ModernGraphParser( entityNameResolver ); + visitor.getGraphStack().push( targetGraph ); + try { + visitor.visitGraphElementList( graphElementListContext ); + } + finally { + visitor.getGraphStack().pop(); + assert visitor.getGraphStack().isEmpty(); + } + return targetGraph; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java index e097de473ad6..125244f8cc30 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java @@ -248,4 +248,12 @@ void recognizedObsoleteHibernateNamespace( value = "DEPRECATED: use [%s] instead with custom [%s] implementation" ) void deprecatedUuidGenerator(String name, String name2); + + @LogMessage(level = WARN) + @Message( + id = 90000041, + value = "Deprecated syntax when using @NamedEntityGraph: 'Type: attr1, attr2' is deprecated. " + + "Specify the root entity using the 'root' attribute instead of prefixing the graph with the entity type." + ) + void deprecatedNamedEntityGraphTextThatContainTypeIndicator(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index e412aa39bb02..0cb3b72c4284 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -502,7 +502,8 @@ private void applyNamedEntityGraphs(Collection named } } throw new IllegalArgumentException( "Cannot resolve entity name : " + jpaEntityName ); - } + }, + serviceRegistry ); entityGraphMap.put( definition.name(), graph ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractClassLevelTests.java similarity index 72% rename from hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractClassLevelTests.java index 2051af3f2f86..8a497a577acd 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractClassLevelTests.java @@ -6,14 +6,14 @@ import org.hibernate.DuplicateMappingException; import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.model.internal.InvalidNamedEntityGraphParameterException; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.graph.InvalidGraphException; +import org.hibernate.orm.test.entitygraph.named.parsed.entity.BadRootClassEntity; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Book; import org.hibernate.orm.test.entitygraph.named.parsed.entity.DomesticPublishingHouse; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Duplicator; import org.hibernate.orm.test.entitygraph.named.parsed.entity.ForeignPublishingHouse; -import org.hibernate.orm.test.entitygraph.named.parsed.entity.InvalidParsedGraphEntity; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Isbn; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Person; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Publisher; @@ -35,7 +35,7 @@ * @author Steve Ebersole */ @SuppressWarnings("JUnitMalformedDeclaration") -public class ClassLevelTests { +public abstract class AbstractClassLevelTests { @Test @DomainModel(annotatedClasses = { @@ -53,24 +53,38 @@ void testRegistrations(SessionFactoryScope factoryScope) { final SessionFactoryImplementor sessionFactory = factoryScope.getSessionFactory(); assertBasicAttributes( sessionFactory.findEntityGraphByName( "book-title-isbn" ), "title", "isbn" ); - assertBasicAttributes( sessionFactory.findEntityGraphByName( "book-title-isbn-author" ), "title", "isbn", "author" ); - assertBasicAttributes( sessionFactory.findEntityGraphByName( "book-title-isbn-editor" ), "title", "isbn", "editor" ); - assertBasicAttributes( sessionFactory.findEntityGraphByName( "publishing-house-bio" ), "name", "ceo", "boardMembers" ); + assertBasicAttributes( + sessionFactory.findEntityGraphByName( "book-title-isbn-author" ), + "title", + "isbn", + "author" + ); + + assertBasicAttributes( + sessionFactory.findEntityGraphByName( "book-title-isbn-editor" ), + "title", + "isbn", + "editor" + ); + + assertBasicAttributes( + sessionFactory.findEntityGraphByName( "publishing-house-bio" ), + "name", + "ceo", + "boardMembers" + ); } @Test - @DomainModel(annotatedClasses = InvalidParsedGraphEntity.class) - void testInvalidParsedGraph(DomainModelScope modelScope) { + @DomainModel(annotatedClasses = BadRootClassEntity.class) + void testRootEntityDifferentFromEntityMarkedWithAnnotation(DomainModelScope modelScope) { final MetadataImplementor domainModel = modelScope.getDomainModel(); - try { - try (org.hibernate.SessionFactory sessionFactory = domainModel.buildSessionFactory()) { - fail( "Expecting an exception" ); - } - catch (InvalidGraphException expected) { - } + + try (org.hibernate.SessionFactory sessionFactory = domainModel.buildSessionFactory()) { + fail( "Expecting an exception" ); } - catch (InvalidGraphException expected) { + catch (InvalidNamedEntityGraphParameterException expected) { } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractPackageLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractPackageLevelTests.java new file mode 100644 index 000000000000..39f34fd01327 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractPackageLevelTests.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed; + +import org.hibernate.DuplicateMappingException; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.orm.test.entitygraph.named.parsed.pckgwithgraphnameduplication.Duplicator; + +import org.hibernate.testing.orm.junit.ServiceRegistryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.hibernate.orm.test.entitygraph.parser.AssertionHelper.assertBasicAttributes; + + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +public abstract class AbstractPackageLevelTests { + protected static void assertBasicGraph(SessionFactoryImplementor sessionFactory, String name, String... names) { + RootGraphImplementor graph = sessionFactory.findEntityGraphByName( name ); + assertThat( graph.getName() ).isEqualTo( name ); + assertBasicAttributes( graph, names ); + } + + @Test + void testDuplication(ServiceRegistryScope registryScope) { + final StandardServiceRegistry serviceRegistry = registryScope.getRegistry(); + try { + new MetadataSources( serviceRegistry ) + .addAnnotatedClass( Duplicator.class ) + .addPackage( "org.hibernate.orm.test.entitygraph.named.parsed.pckgwithgraphnameduplication" ) + .buildMetadata(); + fail( "Expected an exception" ); + } + catch (DuplicateMappingException expected) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxClassLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxClassLevelTests.java new file mode 100644 index 000000000000..bdf0d6922138 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxClassLevelTests.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.graph.InvalidGraphException; +import org.hibernate.orm.test.entitygraph.named.parsed.entity.InvalidParsedGraphEntity; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.fail; + +@ServiceRegistry(settings = @Setting(name = AvailableSettings.GRAPH_PARSER_MODE, value = "legacy")) +public class LegacySyntaxClassLevelTests extends AbstractClassLevelTests { + @Test + @DomainModel(annotatedClasses = InvalidParsedGraphEntity.class) + void testInvalidParsedGraph(DomainModelScope modelScope) { + final MetadataImplementor domainModel = modelScope.getDomainModel(); + try { + try (org.hibernate.SessionFactory sessionFactory = domainModel.buildSessionFactory()) { + fail( "Expecting an exception" ); + } + catch (InvalidGraphException expected) { + } + } + catch (InvalidGraphException expected) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/PackageLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxPackageLevelTests.java similarity index 54% rename from hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/PackageLevelTests.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxPackageLevelTests.java index 97ef1080c219..8fe6ac35c750 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/PackageLevelTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxPackageLevelTests.java @@ -4,33 +4,26 @@ */ package org.hibernate.orm.test.entitygraph.named.parsed; -import org.hibernate.DuplicateMappingException; -import org.hibernate.boot.MetadataSources; -import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.model.internal.InvalidNamedEntityGraphParameterException; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.graph.InvalidGraphException; -import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.orm.test.entitygraph.named.parsed.pkg.Book; -import org.hibernate.orm.test.entitygraph.named.parsed.pkg.Duplicator; import org.hibernate.orm.test.entitygraph.named.parsed.pkg.Isbn; import org.hibernate.orm.test.entitygraph.named.parsed.pkg.Person; + import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModelScope; import org.hibernate.testing.orm.junit.ServiceRegistry; -import org.hibernate.testing.orm.junit.ServiceRegistryScope; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; import org.junit.jupiter.api.Test; -import static org.hibernate.orm.test.entitygraph.parser.AssertionHelper.assertBasicAttributes; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -/** - * @author Steve Ebersole - */ -@SuppressWarnings("JUnitMalformedDeclaration") -public class PackageLevelTests { +@ServiceRegistry(settings = @Setting(name = AvailableSettings.GRAPH_PARSER_MODE, value = "legacy")) +public class LegacySyntaxPackageLevelTests extends AbstractPackageLevelTests { + @Test @DomainModel( annotatedClasses = { Book.class, Isbn.class, Person.class }, @@ -43,27 +36,8 @@ void testDiscovery(SessionFactoryScope factoryScope) { assertBasicGraph( sessionFactory, "book-title-isbn", "title", "isbn" ); assertBasicGraph( sessionFactory, "book-title-isbn-author", "title", "isbn", "author" ); assertBasicGraph( sessionFactory, "book-title-isbn-editor", "title", "isbn", "editor" ); - } - - private static void assertBasicGraph(SessionFactoryImplementor sessionFactory, String name, String... names) { - RootGraphImplementor graph = sessionFactory.findEntityGraphByName( name ); - assertEquals( name, graph.getName() ); - assertBasicAttributes( graph, names ); - } - - @Test - @ServiceRegistry - void testDuplication(ServiceRegistryScope registryScope) { - final StandardServiceRegistry serviceRegistry = registryScope.getRegistry(); - try { - new MetadataSources( serviceRegistry ) - .addAnnotatedClass( Duplicator.class ) - .addPackage( "org.hibernate.orm.test.entitygraph.named.parsed.pkg" ) - .buildMetadata(); - fail( "Expected an exception" ); - } - catch (DuplicateMappingException expected) { - } + assertBasicGraph( sessionFactory, "book-title-with-root-attribute", "title" ); + assertBasicGraph( sessionFactory, "book-title-with-root-attribute-and-type-indicator", "title" ); } @Test @@ -75,7 +49,8 @@ void testInvalid(DomainModelScope modelScope) { try (org.hibernate.SessionFactory sessionFactory = modelScope.getDomainModel().buildSessionFactory()) { fail( "Expected an exception" ); } - catch (InvalidGraphException expected) { + catch (InvalidNamedEntityGraphParameterException expected) { } } + } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ModernSyntaxClassLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ModernSyntaxClassLevelTests.java new file mode 100644 index 000000000000..ee18f5fcf7d0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ModernSyntaxClassLevelTests.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.Setting; + +@ServiceRegistry(settings = @Setting(name = AvailableSettings.GRAPH_PARSER_MODE, value = "modern")) +public class ModernSyntaxClassLevelTests extends AbstractClassLevelTests { + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ModernSyntaxPackageLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ModernSyntaxPackageLevelTests.java new file mode 100644 index 000000000000..255e27336bfa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ModernSyntaxPackageLevelTests.java @@ -0,0 +1,50 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed; + +import org.hibernate.boot.model.internal.InvalidNamedEntityGraphParameterException; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.orm.test.entitygraph.named.parsed.pkg3.Book; +import org.hibernate.orm.test.entitygraph.named.parsed.pkg3.Person; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.fail; + +@ServiceRegistry(settings = @Setting(name = AvailableSettings.GRAPH_PARSER_MODE, value = "modern")) +public class ModernSyntaxPackageLevelTests extends AbstractPackageLevelTests { + + @Test + @DomainModel( + annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg2" + ) + void givenModernGraphParserModeShouldThrowExceptionWhenUsingAnnotationsWithoutRootAttribute(DomainModelScope modelScope) { + try (org.hibernate.SessionFactory sessionFactory = modelScope.getDomainModel().buildSessionFactory()) { + fail( "Expected an exception" ); + } + catch (InvalidNamedEntityGraphParameterException expected) { + } + } + + @Test + @DomainModel( + annotatedClasses = { Book.class, Person.class }, + annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg3" + ) + @SessionFactory(exportSchema = false) + void givenModernGraphParserModeShouldParseGraphCorrectlyWhenRootAttributeIsValid(SessionFactoryScope factoryScope) { + final SessionFactoryImplementor sessionFactory = factoryScope.getSessionFactory(); + + assertBasicGraph( sessionFactory, "book-title-with-root-attribute", "title" ); + assertBasicGraph( sessionFactory, "book-title-author-editor-with-root-attribute", "title", "author", "editor" ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/BadRootClassEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/BadRootClassEntity.java new file mode 100644 index 000000000000..5d0b1c2d7cd3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/BadRootClassEntity.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed.entity; + + +import org.hibernate.annotations.NamedEntityGraph; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity(name = "BadRootClassEntity") +@Table(name = "BadRootClassEntity") +@NamedEntityGraph(root = Book.class, name = "bad-root", graph = "name") +public class BadRootClassEntity { + @Id + private Integer id; + private String name; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/GraphWithRootClassEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/GraphWithRootClassEntity.java new file mode 100644 index 000000000000..8dcf720e7ac5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/GraphWithRootClassEntity.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed.entity; + +import org.hibernate.annotations.NamedEntityGraph; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + + +@Entity(name = "GraphWithRootClassEntity") +@Table(name = "GraphWithRootClassEntity") +@NamedEntityGraph(root = GraphWithRootClassEntity.class, name = "valid-root-on-annotation", graph = "name") +public class GraphWithRootClassEntity { + @Id + private Integer id; + private String name; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/Duplicator.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pckgwithgraphnameduplication/Duplicator.java similarity index 81% rename from hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/Duplicator.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pckgwithgraphnameduplication/Duplicator.java index 266c3388bd75..47d1b4386219 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/Duplicator.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pckgwithgraphnameduplication/Duplicator.java @@ -2,18 +2,19 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.orm.test.entitygraph.named.parsed.pkg; +package org.hibernate.orm.test.entitygraph.named.parsed.pckgwithgraphnameduplication; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Basic; + import org.hibernate.annotations.NamedEntityGraph; /** * @author Steve Ebersole */ @Entity -@NamedEntityGraph( name = "duplicated-name", graph = "name") +@NamedEntityGraph(name = "duplicated-name", graph = "name") public class Duplicator { @Id private Integer id; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pckgwithgraphnameduplication/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pckgwithgraphnameduplication/package-info.java new file mode 100644 index 000000000000..22f4097ad12d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pckgwithgraphnameduplication/package-info.java @@ -0,0 +1,9 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ + +@NamedEntityGraph(root = Duplicator.class, name = "duplicated-name", graph = "name") +package org.hibernate.orm.test.entitygraph.named.parsed.pckgwithgraphnameduplication; + +import org.hibernate.annotations.NamedEntityGraph; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java index c0dfa7ae3ed0..a5b0a69d6273 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java @@ -7,10 +7,12 @@ * @author Steve Ebersole */ -@NamedEntityGraph( name = "book-title-isbn", graph = "Book: title, isbn") -@NamedEntityGraph( name = "book-title-isbn-author", graph = "Book: title, isbn, author") -@NamedEntityGraph( name = "book-title-isbn-editor", graph = "Book: title, isbn, editor") -@NamedEntityGraph( name = "duplicated-name", graph = "Book: title") +@NamedEntityGraph(name = "book-title-isbn", graph = "Book: title, isbn") +@NamedEntityGraph(name = "book-title-isbn-author", graph = "Book: title, isbn, author") +@NamedEntityGraph(name = "book-title-isbn-editor", graph = "Book: title, isbn, editor") +@NamedEntityGraph(name = "duplicated-name", graph = "Book: title") +@NamedEntityGraph(root = Book.class, name = "book-title-with-root-attribute", graph = "title") +@NamedEntityGraph(root = Book.class, name = "book-title-with-root-attribute-and-type-indicator", graph = "Book: title") package org.hibernate.orm.test.entitygraph.named.parsed.pkg; import org.hibernate.annotations.NamedEntityGraph; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/Book.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/Book.java new file mode 100644 index 000000000000..a3ef9e8d1ef8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/Book.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed.pkg3; + + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +@Entity +public class Book { + @Id + private Integer id; + private String title; + + @ManyToOne + @JoinColumn(name = "author_fk") + Person author; + + @ManyToOne + @JoinColumn(name = "editor_fk") + Person editor; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/Person.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/Person.java new file mode 100644 index 000000000000..1d07faceabd9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/Person.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed.pkg3; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class Person { + @Id + private Integer id; + private String name; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/package-info.java new file mode 100644 index 000000000000..90c91a5787b2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg3/package-info.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ + + +@NamedEntityGraph(root = Book.class, name = "book-title-with-root-attribute", graph = "title") +@NamedEntityGraph(root = Book.class, name = "book-title-author-editor-with-root-attribute", graph = "title, author, editor") +package org.hibernate.orm.test.entitygraph.named.parsed.pkg3; + +import org.hibernate.annotations.NamedEntityGraph; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/EntityGraphParserTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/AbstractEntityGraphParserTest.java similarity index 91% rename from hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/EntityGraphParserTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/AbstractEntityGraphParserTest.java index 863ebcb4505f..54316908f7cf 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/EntityGraphParserTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/AbstractEntityGraphParserTest.java @@ -25,7 +25,7 @@ * * @author asusnjar */ -public class EntityGraphParserTest extends AbstractEntityGraphTest { +public abstract class AbstractEntityGraphParserTest extends AbstractEntityGraphTest { @Test public void testNullParsing(EntityManagerFactoryScope scope) { @@ -169,28 +169,6 @@ public void testMixParsingWithSimplifiedMaps(EntityManagerFactoryScope scope) { } } - @Test - public void testLinkSubtypeParsing(EntityManagerFactoryScope scope) { - RootGraphImplementor graph = parseGraph( - "linkToOne(name, description), linkToOne(GraphParsingTestSubEntity: sub)", scope ); - assertThat( graph ).isNotNull(); - - List> attrs = graph.getAttributeNodeList(); - assertThat( attrs ).isNotNull(); - assertThat( attrs.size() ).isEqualTo( 1 ); - - AttributeNodeImplementor linkToOneNode = attrs.get( 0 ); - assertThat( linkToOneNode ).isNotNull(); - assertThat( linkToOneNode.getAttributeName() ).isEqualTo( "linkToOne" ); - - AssertionHelper.assertNullOrEmpty( linkToOneNode.getKeySubgraphs() ); - - final SubGraphImplementor subgraph = linkToOneNode.getSubGraphs().get( GraphParsingTestSubEntity.class ); - assertThat( subgraph ).isNotNull(); - - AssertionHelper.assertBasicAttributes( subgraph, "sub" ); - } - @Test public void testHHH10378IsNotFixedYet(EntityManagerFactoryScope scope) { scope.inEntityManager( @@ -209,7 +187,6 @@ public void testHHH10378IsNotFixedYet(EntityManagerFactoryScope scope) { assert subTypeAttrNode != null; } ); - } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/LegacySyntaxEntityGraphParserTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/LegacySyntaxEntityGraphParserTest.java new file mode 100644 index 000000000000..5165b043baa5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/LegacySyntaxEntityGraphParserTest.java @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.parser; + +import java.util.List; + +import org.hibernate.cfg.GraphParserSettings; +import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.graph.spi.SubGraphImplementor; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Jpa( + annotatedClasses = { + GraphParsingTestEntity.class, + GraphParsingTestSubEntity.class + }, + integrationSettings = { + @Setting(name = GraphParserSettings.GRAPH_PARSER_MODE, value = "legacy") + } +) +public class LegacySyntaxEntityGraphParserTest extends AbstractEntityGraphParserTest { + + @Test + public void testLinkSubtypeParsing(EntityManagerFactoryScope scope) { + RootGraphImplementor graph = parseGraph( + "linkToOne(name, description), linkToOne(GraphParsingTestSubEntity: sub)", scope ); + assertThat( graph ).isNotNull(); + + List> attrs = graph.getAttributeNodeList(); + assertThat( attrs ).isNotNull(); + assertThat( attrs.size() ).isEqualTo( 1 ); + + AttributeNodeImplementor linkToOneNode = attrs.get( 0 ); + assertThat( linkToOneNode ).isNotNull(); + assertThat( linkToOneNode.getAttributeName() ).isEqualTo( "linkToOne" ); + + AssertionHelper.assertNullOrEmpty( linkToOneNode.getKeySubgraphs() ); + + final SubGraphImplementor subgraph = linkToOneNode.getSubGraphs().get( GraphParsingTestSubEntity.class ); + assertThat( subgraph ).isNotNull(); + + AssertionHelper.assertBasicAttributes( subgraph, "sub" ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/ModernSyntaxEntityGraphParserTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/ModernSyntaxEntityGraphParserTest.java new file mode 100644 index 000000000000..07aa683e70ef --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/ModernSyntaxEntityGraphParserTest.java @@ -0,0 +1,93 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.parser; + +import java.util.List; + +import org.hibernate.cfg.GraphParserSettings; +import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.graph.spi.SubGraphImplementor; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@Jpa( + annotatedClasses = { + GraphParsingTestEntity.class, + GraphParsingTestSubEntity.class + }, + integrationSettings = { + @Setting(name = GraphParserSettings.GRAPH_PARSER_MODE, value = "modern") + } +) +public class ModernSyntaxEntityGraphParserTest extends AbstractEntityGraphParserTest { + + + @Test + public void testLinkSubtypeParsingWithNewSyntax(EntityManagerFactoryScope scope) { + RootGraphImplementor graph = parseGraph( + "linkToOne(name, description), linkToOne:GraphParsingTestSubEntity(sub)", scope ); + assertThat( graph ).isNotNull(); + + List> attrs = graph.getAttributeNodeList(); + assertThat( attrs ).isNotNull(); + assertThat( attrs ).hasSize( 1 ); + + AttributeNodeImplementor linkToOneNode = attrs.get( 0 ); + assertThat( linkToOneNode ).isNotNull(); + assertThat( linkToOneNode.getAttributeName() ).isEqualTo( "linkToOne" ); + + AssertionHelper.assertNullOrEmpty( linkToOneNode.getKeySubgraphs() ); + + final SubGraphImplementor subgraph = linkToOneNode.getSubGraphs().get( GraphParsingTestSubEntity.class ); + assertThat( subgraph ).isNotNull(); + + AssertionHelper.assertBasicAttributes( subgraph, "sub" ); + } + + @Test + public void testSubtypeAndTwoBasicAttributesParsing(EntityManagerFactoryScope scope) { + var graph = parseGraph( "name, :GraphParsingTestSubEntity(sub), description", scope ); + assertThat( graph ).isNotNull(); + + AssertionHelper.assertBasicAttributes( graph, "name", "description" ); + + var treatedSubgraphs = graph.getTreatedSubgraphs(); + assertThat( treatedSubgraphs ).hasSize( 1 ); + + + var subEntityGraph = treatedSubgraphs.get( GraphParsingTestSubEntity.class ); + var subEntityGraphAttributes = subEntityGraph.getAttributeNodes(); + assertThat( subEntityGraphAttributes ).isNotNull(); + assertThat( subEntityGraphAttributes ).hasSize( 1 ); + + var subEntityGraphAttributeNode = subEntityGraphAttributes.get( 0 ); + assertThat( subEntityGraphAttributeNode ).isNotNull(); + assertThat( subEntityGraphAttributeNode.getAttributeName() ).isEqualTo( "sub" ); + } + + @Test + public void testSubtypeParsing(EntityManagerFactoryScope scope) { + var graph = parseGraph( ":GraphParsingTestSubEntity(sub)", scope ); + assertThat( graph ).isNotNull(); + + var treatedSubgraphs = graph.getTreatedSubgraphs(); + assertThat( treatedSubgraphs ).hasSize( 1 ); + + var subEntityGraph = treatedSubgraphs.get( GraphParsingTestSubEntity.class ); + var subEntityGraphAttributes = subEntityGraph.getAttributeNodes(); + + assertThat( subEntityGraphAttributes ).isNotNull(); + assertThat( subEntityGraphAttributes ).hasSize( 1 ); + + var attributeNode = subEntityGraphAttributes.get( 0 ); + assertThat( attributeNode ).isNotNull(); + assertThat( attributeNode.getAttributeName() ).isEqualTo( "sub" ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/AbstractEntityGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/AbstractEntityGraphTest.java index f25b1fe047e9..35f4fcab3750 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/AbstractEntityGraphTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/AbstractEntityGraphTest.java @@ -4,20 +4,20 @@ */ package org.hibernate.orm.test.graph; -import jakarta.persistence.AttributeNode; import jakarta.persistence.EntityGraph; -import jakarta.persistence.Subgraph; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.graph.EntityGraphs; import org.hibernate.graph.GraphParser; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.orm.test.graph.entity.GraphParsingTestEntity; +import org.hibernate.orm.test.graph.entity.GraphParsingTestSubentity; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.Jpa; - -import java.util.Collection; -import java.util.List; -import java.util.Map; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; @Jpa( @@ -38,69 +38,182 @@ protected RootGraphImplementor parseGraph(String gra return parseGraph( GraphParsingTestEntity.class, graphString, scope ); } - private > void assertNullOrEmpty(C collection) { - if ( collection != null ) { - assertThat( collection).hasSize( 0 ); - } + + private void checkMerge(EntityManagerFactoryScope scope, Class rootType, EntityGraph expected, EntityGraph... graphs) { + scope.inEntityManager( + entityManager -> { + EntityGraph actual = EntityGraphs.merge( entityManager, rootType, graphs ); + assertThat( EntityGraphs.areEqual( expected, actual ) ).isTrue(); + } + ); } - protected > void assertNullOrEmpty(M map) { - if ( map != null ) { - assertThat( map.size()).isEqualTo( 0 ); - } + @SafeVarargs + private void checkMerge(EntityManagerFactoryScope scope, EntityGraph expected, EntityGraph... graphs) { + checkMerge( scope, GraphParsingTestEntity.class, expected, graphs ); } - protected void assertBasicAttributes(EntityGraph graph, String... names) { - assertThat( graph ).isNotNull(); - assertBasicAttributes( graph.getAttributeNodes(), names ); + @Test + public void testSameBasicsEqual(EntityManagerFactoryScope scope) { + EntityGraph g = parseGraph( "name, description ", scope ); + assertThat( EntityGraphs.areEqual( g, g ) ).isTrue(); } - protected void assertBasicAttributes(Subgraph graph, String... names) { - assertThat( graph ).isNotNull(); - assertBasicAttributes( graph.getAttributeNodes(), names ); + @Test + public void testEqualBasicsEqual(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "name, description ", scope ); + EntityGraph b = parseGraph( "description, name ", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); } - private void assertBasicAttributes(List> attrs, String... names) { - if ( (names == null) || (names.length == 0) ) { - assertNullOrEmpty( attrs ); - } - else { - assertThat( attrs ).isNotNull(); - assertThat( names.length).isLessThanOrEqualTo( attrs.size() ); - - for ( String name : names ) { - AttributeNode node = null; - for ( AttributeNode candidate : attrs ) { - if ( candidate.getAttributeName().equals( name ) ) { - node = candidate; - break; - } - } - assertThat( node ).isNotNull(); - assertNullOrEmpty( node.getKeySubgraphs() ); - assertNullOrEmpty( node.getSubgraphs() ); - } - } + @Test + public void testDifferentBasicsEqual1(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "name, description ", scope ); + EntityGraph b = parseGraph( "description ", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); } - protected AttributeNode getAttributeNodeByName(EntityGraph graph, String name, boolean required) { - return getAttributeNodeByName( graph.getAttributeNodes(), name, required ); + @Test + public void testDifferentBasicsEqual2(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "name ", scope ); + EntityGraph b = parseGraph( "description ", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); } - protected AttributeNode getAttributeNodeByName(Subgraph graph, String name, boolean required) { - return getAttributeNodeByName( graph.getAttributeNodes(), name, required ); + @Test + public void testEqualLinksEqual1(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "linkToOne(name, description)", scope ); + EntityGraph b = parseGraph( "linkToOne(description, name)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); } - private AttributeNode getAttributeNodeByName(List> attrs, String name, boolean required) { - for ( AttributeNode attr : attrs ) { - if ( name.equals( attr.getAttributeName() ) ) { - return attr; - } - } - if ( required ) { - fail( "Required attribute not found." ); - } - return null; + @Test + public void testDifferentLinksEqual1(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "linkToOne(name, description)", scope ); + EntityGraph b = parseGraph( "linkToOne(description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + @Test + public void testDifferentLinksEqual2(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "linkToOne(name)", scope ); + EntityGraph b = parseGraph( "linkToOne(description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + @Test + public void testEqualMapKeysEqual(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "map.key(name, description)", scope ); + EntityGraph b = parseGraph( "map.key(description, name)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); + } + + @Test + public void testDifferentMapKeysEqual1(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "map.key(name, description)", scope ); + EntityGraph b = parseGraph( "map.key(description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + @Test + public void testDifferentMapKeysEqual2(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "map.key(name)", scope ); + EntityGraph b = parseGraph( "map.key(description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + @Test + public void testEqualMapValuesEqual(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "map.value(name, description)", scope ); + EntityGraph b = parseGraph( "map.value(description, name)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); + } + + @Test + public void testDifferentMapValuesEqual1(EntityManagerFactoryScope scop) { + EntityGraph a = parseGraph( "map.value(name, description)", scop ); + EntityGraph b = parseGraph( "map.value(description)", scop ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + @Test + public void testDifferentMapValuesEqual2(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( "map.value(name)", scope ); + EntityGraph b = parseGraph( "map.value(description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + @Test + public void testEqualComplexGraphsEqual(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( + "map.key(name, description), name, linkToOne(description), description", scope ); + EntityGraph b = parseGraph( + "description, map.key(description, name), name, linkToOne(description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); + } + + @Test + public void testDifferentComplexGraphsEqual(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( + "map.key(name, description), name, linkToOne(description), description", scope ); + EntityGraph b = parseGraph( + "description, map.value(description, name), name, linkToOne(description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + @Test + public void testNullsEqual(EntityManagerFactoryScope scope) { + assertThat( EntityGraphs.areEqual( null, (EntityGraph) null ) ).isTrue(); + } + + @Test + public void testNullAndNonNullEqual(EntityManagerFactoryScope scope) { + EntityGraph graph = parseGraph( "name ", scope ); + assertThat( EntityGraphs.areEqual( graph, null ) ).isFalse(); + assertThat( EntityGraphs.areEqual( null, graph ) ).isFalse(); + } + + @Test + public void testBasicMerge(EntityManagerFactoryScope scope) { + EntityGraph g1 = parseGraph( "name", scope ); + EntityGraph g2 = parseGraph( "description", scope ); + EntityGraph expected = parseGraph( "name, description ", scope ); + checkMerge( scope, expected, g1, g2 ); + } + + @Test + public void testLinkMerge(EntityManagerFactoryScope scope) { + EntityGraph g1 = parseGraph( "linkToOne(name)", scope ); + EntityGraph g2 = parseGraph( "linkToOne(description)", scope ); + EntityGraph expected = parseGraph( "linkToOne(name, description) ", scope ); + checkMerge( scope, expected, g1, g2 ); + } + + @Test + public void testMapKeyMerge(EntityManagerFactoryScope scope) { + EntityGraph g1 = parseGraph( "map.key(name)", scope ); + EntityGraph g2 = parseGraph( "map.key(description)", scope ); + EntityGraph expected = parseGraph( "map.key(name, description) ", scope ); + checkMerge( scope, expected, g1, g2 ); + } + + @Test + public void testMapValueMerge(EntityManagerFactoryScope scope) { + EntityGraph g1 = parseGraph( "map.value(name)", scope ); + EntityGraph g2 = parseGraph( "map.value(description)", scope ); + EntityGraph expected = parseGraph( "map.value(name, description) ", scope ); + checkMerge( scope, expected, g1, g2 ); + } + + @Test + @JiraKey(value = "HHH-14264") + public void testRootGraphAppliesToChildEntityClass(EntityManagerFactoryScope scope) { + RootGraphImplementor rootGraphImplementor = parseGraph( GraphParsingTestEntity.class, + "name, description", scope ); + EntityDomainType entity = scope.getEntityManagerFactory().unwrap( SessionFactoryImplementor.class ) + .getJpaMetamodel() + .entity( (Class) GraphParsingTestSubentity.class ); + assertThat( rootGraphImplementor.appliesTo( entity ) ).isTrue(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/EntityGraphsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/EntityGraphsTest.java deleted file mode 100644 index f4d919fae75c..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/EntityGraphsTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * Copyright Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.orm.test.graph; - -import jakarta.persistence.EntityGraph; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.graph.EntityGraphs; -import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; -import org.hibernate.testing.orm.junit.JiraKey; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class EntityGraphsTest extends AbstractEntityGraphTest { - - private void checkMerge(EntityManagerFactoryScope scope, Class rootType, EntityGraph expected, EntityGraph... graphs) { - scope.inEntityManager( - entityManager -> { - EntityGraph actual = EntityGraphs.merge( entityManager, rootType, graphs ); - assertThat( EntityGraphs.areEqual( expected, actual ) ).isTrue(); - } - ); - } - - @SafeVarargs - private void checkMerge(EntityManagerFactoryScope scope, EntityGraph expected, EntityGraph... graphs) { - checkMerge( scope, GraphParsingTestEntity.class, expected, graphs ); - } - - @Test - public void testSameBasicsEqual(EntityManagerFactoryScope scope) { - EntityGraph g = parseGraph( "name, description ", scope ); - assertThat( EntityGraphs.areEqual( g, g ) ).isTrue(); - } - - @Test - public void testEqualBasicsEqual(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "name, description ", scope ); - EntityGraph b = parseGraph( "description, name ", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); - } - - @Test - public void testDifferentBasicsEqual1(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "name, description ", scope ); - EntityGraph b = parseGraph( "description ", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testDifferentBasicsEqual2(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "name ", scope ); - EntityGraph b = parseGraph( "description ", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testEqualLinksEqual1(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "linkToOne(name, description)", scope ); - EntityGraph b = parseGraph( "linkToOne(description, name)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); - } - - @Test - public void testEqualLinksWithSubclassesEqual(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( - "linkToOne(name), linkToOne(GraphParsingTestSubentity:description)", scope ); - EntityGraph b = parseGraph( - "linkToOne(GraphParsingTestSubentity:description), linkToOne(name)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); - } - - @Test - public void testDifferentLinksEqual1(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "linkToOne(name, description)", scope ); - EntityGraph b = parseGraph( "linkToOne(description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testDifferentLinksEqual2(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "linkToOne(name)", scope ); - EntityGraph b = parseGraph( "linkToOne(description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testDifferentLinksEqual3(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( - "linkToOne(name), linkToOne(GraphParsingTestSubentity:description)", scope ); - EntityGraph b = parseGraph( "linkToOne(name, description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testEqualMapKeysEqual(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "map.key(name, description)", scope ); - EntityGraph b = parseGraph( "map.key(description, name)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); - } - - @Test - public void testDifferentMapKeysEqual1(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "map.key(name, description)", scope ); - EntityGraph b = parseGraph( "map.key(description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testDifferentMapKeysEqual2(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "map.key(name)", scope ); - EntityGraph b = parseGraph( "map.key(description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testEqualMapValuesEqual(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "map.value(name, description)", scope ); - EntityGraph b = parseGraph( "map.value(description, name)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); - } - - @Test - public void testDifferentMapValuesEqual1(EntityManagerFactoryScope scop) { - EntityGraph a = parseGraph( "map.value(name, description)", scop ); - EntityGraph b = parseGraph( "map.value(description)", scop ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testDifferentMapValuesEqual2(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( "map.value(name)", scope ); - EntityGraph b = parseGraph( "map.value(description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testEqualComplexGraphsEqual(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( - "map.key(name, description), name, linkToOne(description), description", scope ); - EntityGraph b = parseGraph( - "description, map.key(description, name), name, linkToOne(description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); - } - - @Test - public void testDifferentComplexGraphsEqual(EntityManagerFactoryScope scope) { - EntityGraph a = parseGraph( - "map.key(name, description), name, linkToOne(description), description", scope ); - EntityGraph b = parseGraph( - "description, map.value(description, name), name, linkToOne(description)", scope ); - assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); - } - - @Test - public void testNullsEqual(EntityManagerFactoryScope scope) { - assertThat( EntityGraphs.areEqual( null, (EntityGraph) null ) ).isTrue(); - } - - @Test - public void testNullAndNonNullEqual(EntityManagerFactoryScope scope) { - EntityGraph graph = parseGraph( "name ", scope ); - assertThat( EntityGraphs.areEqual( graph, null ) ).isFalse(); - assertThat( EntityGraphs.areEqual( null, graph ) ).isFalse(); - } - - @Test - public void testBasicMerge(EntityManagerFactoryScope scope) { - EntityGraph g1 = parseGraph( "name", scope ); - EntityGraph g2 = parseGraph( "description", scope ); - EntityGraph expected = parseGraph( "name, description ", scope ); - checkMerge( scope, expected, g1, g2 ); - } - - @Test - public void testLinkMerge(EntityManagerFactoryScope scope) { - EntityGraph g1 = parseGraph( "linkToOne(name)", scope ); - EntityGraph g2 = parseGraph( "linkToOne(description)", scope ); - EntityGraph expected = parseGraph( "linkToOne(name, description) ", scope ); - checkMerge( scope, expected, g1, g2 ); - } - - @Test - public void testMapKeyMerge(EntityManagerFactoryScope scope) { - EntityGraph g1 = parseGraph( "map.key(name)", scope ); - EntityGraph g2 = parseGraph( "map.key(description)", scope ); - EntityGraph expected = parseGraph( "map.key(name, description) ", scope ); - checkMerge( scope, expected, g1, g2 ); - } - - @Test - public void testMapValueMerge(EntityManagerFactoryScope scope) { - EntityGraph g1 = parseGraph( "map.value(name)", scope ); - EntityGraph g2 = parseGraph( "map.value(description)", scope ); - EntityGraph expected = parseGraph( "map.value(name, description) ", scope ); - checkMerge( scope, expected, g1, g2 ); - } - - @Test - @JiraKey(value = "HHH-14264") - public void testRootGraphAppliesToChildEntityClass(EntityManagerFactoryScope scope) { - RootGraphImplementor rootGraphImplementor = parseGraph( GraphParsingTestEntity.class, - "name, description", scope ); - EntityDomainType entity = scope.getEntityManagerFactory().unwrap( SessionFactoryImplementor.class ) - .getJpaMetamodel() - .entity( (Class) GraphParsingTestSubentity.class ); - assertThat( rootGraphImplementor.appliesTo( entity ) ).isTrue(); - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/LegacySyntaxEntityGraphsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/LegacySyntaxEntityGraphsTest.java new file mode 100644 index 000000000000..7d47068f896e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/LegacySyntaxEntityGraphsTest.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.graph; + + +import org.hibernate.cfg.GraphParserSettings; +import org.hibernate.graph.EntityGraphs; +import org.hibernate.orm.test.graph.entity.GraphParsingTestEntity; + +import org.hibernate.orm.test.graph.entity.GraphParsingTestSubentity; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; + + +import jakarta.persistence.EntityGraph; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@Jpa( + annotatedClasses = { + GraphParsingTestEntity.class, + GraphParsingTestSubentity.class + }, + integrationSettings = { + @Setting(name = GraphParserSettings.GRAPH_PARSER_MODE, value = "legacy") + } +) +public class LegacySyntaxEntityGraphsTest extends AbstractEntityGraphTest { + + + @Test + public void testEqualLinksWithSubclassesEqual(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( + "linkToOne(name), linkToOne(GraphParsingTestSubentity: description)", scope ); + EntityGraph b = parseGraph( + "linkToOne(GraphParsingTestSubentity: description), linkToOne(name)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); + } + + @Test + public void testDifferentLinksEqual3(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( + "linkToOne(name), linkToOne(GraphParsingTestSubentity: description)", scope ); + EntityGraph b = parseGraph( "linkToOne(name, description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/ModernSyntaxEntityGraphsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/ModernSyntaxEntityGraphsTest.java new file mode 100644 index 000000000000..aa86cc7a7fe9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/ModernSyntaxEntityGraphsTest.java @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.graph; + +import org.hibernate.cfg.GraphParserSettings; +import org.hibernate.graph.EntityGraphs; +import org.hibernate.orm.test.graph.entity.GraphParsingTestEntity; + +import org.hibernate.orm.test.graph.entity.GraphParsingTestSubentity; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; + +import jakarta.persistence.EntityGraph; +import org.hibernate.testing.orm.junit.Jpa; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +@Jpa( + annotatedClasses = { + GraphParsingTestEntity.class, + GraphParsingTestSubentity.class + }, + integrationSettings = { + @Setting(name = GraphParserSettings.GRAPH_PARSER_MODE, value = "modern") + } +) +public class ModernSyntaxEntityGraphsTest extends AbstractEntityGraphTest { + + @Test + public void testEqualLinksWithSubclassesEqual(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( + "linkToOne(name), linkToOne:GraphParsingTestSubentity(description)", scope ); + EntityGraph b = parseGraph( + "linkToOne:GraphParsingTestSubentity(description), linkToOne(name)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isTrue(); + } + + @Test + public void testDifferentLinksEqual3(EntityManagerFactoryScope scope) { + EntityGraph a = parseGraph( + "linkToOne(name), linkToOne:GraphParsingTestSubentity(description)", scope ); + EntityGraph b = parseGraph( "linkToOne(name, description)", scope ); + assertThat( EntityGraphs.areEqual( a, b ) ).isFalse(); + } + + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/GraphParsingTestEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/entity/GraphParsingTestEntity.java similarity index 97% rename from hibernate-core/src/test/java/org/hibernate/orm/test/graph/GraphParsingTestEntity.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/graph/entity/GraphParsingTestEntity.java index 1b0091a3746c..d5b85440a482 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/GraphParsingTestEntity.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/entity/GraphParsingTestEntity.java @@ -2,7 +2,7 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.orm.test.graph; +package org.hibernate.orm.test.graph.entity; import java.util.Map; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/GraphParsingTestSubentity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/entity/GraphParsingTestSubentity.java similarity index 89% rename from hibernate-core/src/test/java/org/hibernate/orm/test/graph/GraphParsingTestSubentity.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/graph/entity/GraphParsingTestSubentity.java index e84a55a4c092..e136a64f8968 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/GraphParsingTestSubentity.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/entity/GraphParsingTestSubentity.java @@ -2,7 +2,7 @@ * SPDX-License-Identifier: Apache-2.0 * Copyright Red Hat Inc. and Hibernate Authors */ -package org.hibernate.orm.test.graph; +package org.hibernate.orm.test.graph.entity; import jakarta.persistence.Basic; import jakarta.persistence.Entity; diff --git a/local-build-plugins/src/main/java/org/hibernate/orm/antlr/AntlrPlugin.java b/local-build-plugins/src/main/java/org/hibernate/orm/antlr/AntlrPlugin.java index fb135ec77e77..16964be46d70 100644 --- a/local-build-plugins/src/main/java/org/hibernate/orm/antlr/AntlrPlugin.java +++ b/local-build-plugins/src/main/java/org/hibernate/orm/antlr/AntlrPlugin.java @@ -27,6 +27,7 @@ public class AntlrPlugin implements Plugin { public static final String HQL_PKG = "org.hibernate.grammars.hql"; public static final String SQL_PKG = "org.hibernate.grammars.importsql"; + public static final String LEGACY_GRAPH_PKG = "org.hibernate.grammars.graph.legacy"; public static final String GRAPH_PKG = "org.hibernate.grammars.graph"; public static final String ORDER_PKG = "org.hibernate.grammars.ordering"; @@ -72,14 +73,23 @@ private void populateGrammars(AntlrSpec antlrSpec) { ); antlrSpec.getGrammarDescriptors().create( - "graph", + "deprecated-graph", (grammarDescriptor) -> { - grammarDescriptor.getPackageName().set( GRAPH_PKG ); + grammarDescriptor.getPackageName().set( LEGACY_GRAPH_PKG ); grammarDescriptor.getLexerFileName().set( "GraphLanguageLexer.g4" ); grammarDescriptor.getParserFileName().set( "GraphLanguageParser.g4" ); } ); + antlrSpec.getGrammarDescriptors().create( + "graph", + (grammarDescriptor) -> { + grammarDescriptor.getPackageName().set( GRAPH_PKG ); + grammarDescriptor.getLexerFileName().set( "ModernGraphLanguageLexer.g4" ); + grammarDescriptor.getParserFileName().set( "ModernGraphLanguageParser.g4" ); + } + ); + antlrSpec.getGrammarDescriptors().create( "sqlScript", (grammarDescriptor) -> {