diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc index e9ca78b19744..7e2d515895b4 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 `LegacyGraphParser` 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 `LegacyGraphParser` 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/GraphLanguageParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 index a0e6c33b9974..ad3fdb779aab 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 @@ -21,15 +21,32 @@ package org.hibernate.grammars.graph; */ } - graph - : typeIndicator? attributeList - ; + : 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)* ; @@ -44,9 +61,4 @@ attributePath attributeQualifier : DOT ATTR_NAME - ; - -subGraph - : LPAREN typeIndicator? attributeList RPAREN - ; - + ; \ No newline at end of file diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/LegacyGraphLanguageLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/LegacyGraphLanguageLexer.g4 new file mode 100644 index 000000000000..e32b19ae01da --- /dev/null +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/LegacyGraphLanguageLexer.g4 @@ -0,0 +1,66 @@ +lexer grammar LegacyGraphLanguageLexer; + +@header { +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.grammars.graph.legacy; +} + +@members { +/* + * Lexer for the Hibernate EntityGraph Language + * + * It is generated by Antlr via the lexer grammar file `LegacyGraphLanguageLexer.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/legacy/LegacyGraphLanguageParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/LegacyGraphLanguageParser.g4 new file mode 100644 index 000000000000..30e39c927d38 --- /dev/null +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/LegacyGraphLanguageParser.g4 @@ -0,0 +1,50 @@ +parser grammar LegacyGraphLanguageParser; + +options { + tokenVocab=LegacyGraphLanguageLexer; +} + +@header { +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.grammars.graph.legacy; +} + +@members { +/* + * Antlr grammar describing the Hibernate EntityGraph Language - for parsing a structured + * textual representation of an entity graph + * + * `LegacyGraphLanguageParser.g4` + */ +} + +graph + : typeIndicator? attributeList + ; + +typeIndicator + : TYPE_NAME COLON + ; + +attributeList + : attributeNode (COMMA attributeNode)* + ; + +attributeNode + : attributePath subGraph? + ; + +attributePath + : ATTR_NAME attributeQualifier? + ; + +attributeQualifier + : DOT ATTR_NAME + ; + +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 e3b397ff475d..44ecabf56658 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 9c37032008c9..f74c61218e3a 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; @@ -261,6 +263,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; @@ -275,7 +280,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() ); @@ -284,8 +289,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; } @@ -304,7 +311,7 @@ public BootstrapContext getBootstrapContext() { }; jsonFormatMapper = jsonFormatMapper( settings.get( JSON_FORMAT_MAPPER ), - !getBoolean( ORACLE_OSON_DISABLED, settings), + !getBoolean( ORACLE_OSON_DISABLED, settings ), strategySelector, formatMapperCreationContext ); @@ -336,17 +343,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; @@ -422,15 +433,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 = @@ -480,12 +498,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 ); } @@ -534,6 +552,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) @@ -579,14 +605,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 ); @@ -731,7 +759,7 @@ else if ( emptyConstructor != null ) { catch (Exception e) { throw new StrategySelectionException( "Could not instantiate named strategy class [" + - strategyClass.getName() + "]", + strategyClass.getName() + "]", e ); } @@ -792,7 +820,7 @@ private SqmTranslatorFactory resolveSqmTranslator( } private static Interceptor determineInterceptor( - Map configurationSettings, + Map configurationSettings, StrategySelector strategySelector) { return strategySelector.resolveStrategy( Interceptor.class, @@ -802,7 +830,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 ) { @@ -831,13 +859,17 @@ private static Supplier interceptorSupplier(Class configurationSettings, + Map configurationSettings, StandardServiceRegistry serviceRegistry) { final var specifiedHandlingMode = PhysicalConnectionHandlingMode.interpret( configurationSettings.get( CONNECTION_HANDLING ) ); @@ -847,7 +879,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, @@ -897,7 +932,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 = @@ -1026,15 +1064,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 ); } @@ -1043,15 +1085,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 ); } @@ -1076,7 +1122,7 @@ public StatementInspector getStatementInspector() { @Override public SessionFactoryObserver[] getSessionFactoryObservers() { - return sessionFactoryObserverList.toArray(new SessionFactoryObserver[0]); + return sessionFactoryObserverList.toArray( new SessionFactoryObserver[0] ); } @Override @@ -1099,12 +1145,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; } @@ -1209,7 +1257,8 @@ public boolean isAutoEvictCollectionCache() { return autoEvictCollectionCache; } - @Override @Deprecated + @Override + @Deprecated public SchemaAutoTooling getSchemaAutoTooling() { return schemaAutoTooling; } @@ -1261,7 +1310,7 @@ public CustomEntityDirtinessStrategy getCustomEntityDirtinessStrategy() { @Override public EntityNameResolver[] getEntityNameResolvers() { - return entityNameResolvers.toArray(new EntityNameResolver[0]); + return entityNameResolvers.toArray( new EntityNameResolver[0] ); } @Override @@ -1516,8 +1565,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 + ); } } ); @@ -1611,7 +1662,7 @@ public void enableNamedQueryCheckingOnStartup(boolean enabled) { } public void enableSecondLevelCacheSupport(boolean enabled) { - this.secondLevelCacheEnabled = enabled; + this.secondLevelCacheEnabled = enabled; } public void enableQueryCacheSupport(boolean enabled) { @@ -1801,8 +1852,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 ); @@ -1840,4 +1896,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 098fbbe844e2..fd0e743b23d3 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 @@ -7,6 +7,7 @@ import org.hibernate.graph.spi.GraphParserEntityClassResolver; import org.hibernate.graph.spi.GraphParserEntityNameResolver; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.service.ServiceRegistry; /** * @author Steve Ebersole @@ -15,5 +16,6 @@ public interface NamedGraphCreator { RootGraphImplementor createEntityGraph( GraphParserEntityClassResolver entityDomainClassResolver, - GraphParserEntityNameResolver entityDomainNameResolver); + GraphParserEntityNameResolver 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 852df030cc65..5eab9384daf2 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 @@ -17,6 +17,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 static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.internal.util.StringHelper.nullIfEmpty; @@ -39,7 +40,8 @@ class NamedGraphCreatorJpa implements NamedGraphCreator { @Override public RootGraphImplementor createEntityGraph( GraphParserEntityClassResolver entityDomainClassResolver, - GraphParserEntityNameResolver entityDomainNameResolver) { + GraphParserEntityNameResolver entityDomainNameResolver, + ServiceRegistry serviceRegistry) { return createGraph( (EntityDomainType) entityDomainNameResolver.resolveEntityName( 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 01b29907259b..a7aec1ea5a38 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 @@ -6,17 +6,23 @@ import org.checkerframework.checker.nullness.qual.NonNull; 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.GraphLanguageParser; +import org.hibernate.cfg.GraphParserSettings; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.graph.InvalidGraphException; import org.hibernate.graph.spi.GraphParserEntityClassResolver; import org.hibernate.graph.spi.GraphParserEntityNameResolver; 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 static org.hibernate.graph.internal.parse.GraphParsing.parseLegacyGraphText; import static org.hibernate.graph.internal.parse.GraphParsing.parseText; import static org.hibernate.internal.util.StringHelper.nullIfEmpty; @@ -41,35 +47,22 @@ class NamedGraphCreatorParsed implements NamedGraphCreator { @Override public RootGraphImplementor createEntityGraph( GraphParserEntityClassResolver entityDomainClassResolver, - GraphParserEntityNameResolver entityDomainNameResolver) { - final var graphContext = parseText( annotation.graph() ); - final var typeIndicator = graphContext.typeIndicator(); - final EntityDomainType entityDomainType; - final String jpaEntityName; - if ( entityType == null ) { - if ( typeIndicator == null ) { - throw new InvalidGraphException( "Expecting graph text to include an entity name: " + annotation.graph() ); - } - jpaEntityName = typeIndicator.TYPE_NAME().toString(); - entityDomainType = entityDomainNameResolver.resolveEntityName( jpaEntityName ); - } - else { - if ( typeIndicator != null ) { - throw new InvalidGraphException( "Expecting graph text to not include an entity name: " + annotation.graph() ); - } - entityDomainType = entityDomainClassResolver.resolveEntityClass( entityType ); - jpaEntityName = entityDomainType.getName(); + GraphParserEntityNameResolver entityDomainNameResolver, + ServiceRegistry serviceRegistry) { + + final ConfigurationService configurationService = serviceRegistry.getService( ConfigurationService.class ); + + final GraphParserMode graphParserMode = configurationService == null ? null : configurationService.getSetting( + GraphParserSettings.GRAPH_PARSER_MODE, + GraphParserMode::interpret, + GraphParserMode.LEGACY + ); + + if ( graphParserMode != null && graphParserMode.equals( GraphParserMode.MODERN ) ) { + return parseGraphForModernParsingMode( entityDomainClassResolver, entityDomainNameResolver ); } - return visit( name == null ? jpaEntityName : name, - entityDomainType, entityDomainNameResolver, graphContext ); - } - private static @NonNull RootGraphImplementor visit( - String name, - EntityDomainType entityDomainType, GraphParserEntityNameResolver entityDomainNameResolver, - GraphLanguageParser.GraphContext graphContext) { - return GraphParsing.visit( name, entityDomainType, graphContext.attributeList(), - entityName -> resolve( entityName, entityDomainNameResolver ) ); + return parseGraphForLegacyParsingMode( entityDomainClassResolver, entityDomainNameResolver ); } private static @NonNull EntityDomainType resolve( @@ -84,4 +77,87 @@ public RootGraphImplementor createEntityGraph( return entityDomainType; } } + + private RootGraphImplementor parseGraphForModernParsingMode( + GraphParserEntityClassResolver entityDomainClassResolver, + GraphParserEntityNameResolver entityDomainNameResolver) { + final EntityDomainType entityDomainType = resolveEntityDomainTypeFromAnnotation( + entityDomainClassResolver + ); + + final var graphContext = parseText( annotation.graph() ); + + final String graphName = this.name == null ? entityDomainType.getName() : this.name; + + return GraphParsing.visit( graphName, entityDomainType, graphContext.graphElementList(), + entityName -> resolve( entityName, entityDomainNameResolver ) ); + } + + + private RootGraphImplementor parseGraphForLegacyParsingMode( + GraphParserEntityClassResolver entityDomainClassResolver, + GraphParserEntityNameResolver entityDomainNameResolver) { + + final var graphContext = parseLegacyGraphText( annotation.graph() ); + + + EntityDomainType entityDomainType; + + 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(); + + entityDomainType = (EntityDomainType) resolve( typeIndicator.TYPE_NAME().toString(), entityDomainNameResolver ); + } + else { + entityDomainType = resolveEntityDomainTypeFromAnnotation( entityDomainClassResolver ); + } + + + final String graphName = this.name == null ? entityDomainType.getName() : this.name; + + return GraphParsing.visit( graphName, entityDomainType, graphContext.attributeList(), + entityName -> resolve( entityName, entityDomainNameResolver ) ); + } + + + private EntityDomainType resolveEntityDomainTypeFromAnnotation(GraphParserEntityClassResolver entityDomainClassResolver) { + final Class annotationRootAttribute = annotation.root(); + final boolean isAnnotationRootAttributeVoid = void.class.equals( annotationRootAttribute ); + + if ( entityType == null ) { + if ( isAnnotationRootAttributeVoid ) { + throw new InvalidNamedEntityGraphParameterException( + "The 'root' parameter of the @NamedEntityGraph should be passed. Graph : " + annotation.name() + ); + } + + //noinspection unchecked + return (EntityDomainType) entityDomainClassResolver.resolveEntityClass( annotationRootAttribute ); + } + + 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 + return (EntityDomainType) entityDomainClassResolver.resolveEntityClass( annotationRootAttribute ); + } + + //noinspection unchecked + return (EntityDomainType) entityDomainClassResolver.resolveEntityClass( 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/internal/parse/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java index 2f69953025c9..00caf8f1be11 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/GraphParser.java @@ -4,17 +4,19 @@ */ package org.hibernate.graph.internal.parse; +import org.hibernate.UnknownEntityTypeException; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.grammars.graph.GraphLanguageParser; import org.hibernate.grammars.graph.GraphLanguageParserBaseVisitor; import org.hibernate.graph.GraphNode; import org.hibernate.graph.InvalidGraphException; import org.hibernate.graph.spi.AttributeNodeImplementor; -import org.hibernate.graph.spi.GraphParserEntityNameResolver; import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; import org.hibernate.graph.spi.SubGraphImplementor; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; +import org.hibernate.metamodel.model.domain.EntityDomainType; import static org.hibernate.graph.internal.GraphParserLogging.PARSING_LOGGER; import static org.hibernate.internal.util.StringHelper.repeat; @@ -28,7 +30,7 @@ public class GraphParser extends GraphLanguageParserBaseVisitor> { private final GraphParserEntityNameResolver entityNameResolver; private final Stack> graphStack = new StandardStack<>(); - private final Stack> attributeNodeStack = new StandardStack<>(); + private final Stack> attributeNodeStack = new StandardStack<>(); private final Stack graphSourceStack = new StandardStack<>(); public GraphParser(GraphParserEntityNameResolver entityNameResolver) { @@ -38,7 +40,6 @@ public GraphParser(GraphParserEntityNameResolver 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(GraphParserEntityNameResolver) */ public GraphParser(SessionFactoryImplementor sessionFactory) { @@ -50,7 +51,7 @@ public Stack> getGraphStack() { } @Override - public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.AttributeNodeContext attributeNodeContext) { + public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.AttributeNodeContext attributeNodeContext) { final var attributePathContext = attributeNodeContext.attributePath(); final var attributeQualifierContext = attributePathContext.attributeQualifier(); @@ -110,7 +111,37 @@ private SubGraphGenerator subGraphCreator(GraphLanguageParser.AttributeQualifier } } - private AttributeNodeImplementor resolveAttributeNode(String attributeName) { + private SubGraphImplementor createSubGraph(AttributeNodeImplementor attributeNode, String subTypeName) { + + final var shouldCreateTreatedSubgraph = attributeNode == null && subTypeName != null; + + if ( shouldCreateTreatedSubgraph ) { + final var currentGraph = graphStack.getCurrent(); + + final EntityDomainType entityDomainType = entityNameResolver.resolveEntityName( subTypeName ); + + if ( entityDomainType == null ) { + throw new UnknownEntityTypeException( subTypeName ); + } + + return currentGraph.addTreatedSubgraph( + entityDomainType + ); + + } + + + final var subGraphCreator = graphSourceStack.getCurrent(); + + return subGraphCreator.createSubGraph( + attributeNode, + subTypeName, + entityNameResolver + ); + + } + + private AttributeNodeImplementor resolveAttributeNode(String attributeName) { final var currentGraph = graphStack.getCurrent(); assert currentGraph != null; @@ -135,8 +166,8 @@ private PathQualifierType resolvePathQualifier(String qualifier) { @Override public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext subGraphContext) { final String subTypeName = - subGraphContext.typeIndicator() == null ? null - : subGraphContext.typeIndicator().TYPE_NAME().getText(); + subGraphContext.subTypeIndicator() == null ? null + : subGraphContext.subTypeIndicator().TYPE_NAME().getText(); if ( PARSING_LOGGER.isTraceEnabled() ) { PARSING_LOGGER.tracef( @@ -147,13 +178,8 @@ public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext } final var attributeNode = attributeNodeStack.getCurrent(); - final var subGraphCreator = graphSourceStack.getCurrent(); - final var subGraph = subGraphCreator.createSubGraph( - attributeNode, - subTypeName, - entityNameResolver - ); + final var subGraph = createSubGraph( attributeNode, subTypeName ); graphStack.push( subGraph ); 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 514b6c3c7316..857630bd1ea8 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 @@ -8,12 +8,16 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.checkerframework.checker.nullness.qual.NonNull; 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.grammars.graph.GraphLanguageParser.GraphContext; -import org.hibernate.graph.InvalidGraphException; -import org.hibernate.graph.internal.RootGraphImpl; +import org.hibernate.grammars.graph.legacy.LegacyGraphLanguageLexer; +import org.hibernate.grammars.graph.legacy.LegacyGraphLanguageParser; +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.GraphParserEntityNameResolver; import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor; @@ -30,18 +34,11 @@ public static RootGraphImplementor parse( EntityDomainType entityDomainType, String graphText, SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - - final var graphContext = parseText( graphText ); - 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 visit( entityDomainType, graphContext.attributeList(), sessionFactory ); + return getGraphParsingStrategy( sessionFactory ).parse( + entityDomainType, + graphText, + sessionFactory + ); } public static RootGraphImplementor parse( @@ -60,38 +57,36 @@ public static RootGraphImplementor parse( graphText, sessionFactory ); } + @Deprecated(forRemoval = true) public static RootGraphImplementor parse( String graphText, SessionFactoryImplementor sessionFactory) { - if ( graphText == null ) { - return null; - } - final var graphContext = parseText( graphText ); - if ( graphContext.typeIndicator() == null ) { - throw new InvalidGraphException( "Expecting graph text to include an entity name: " + graphText ); - } - - final String entityName = graphContext.typeIndicator().TYPE_NAME().getText(); - final var entityType = sessionFactory.getJpaMetamodel().entity( entityName ); - return visit( entityType, graphContext.attributeList(), sessionFactory ); + return getGraphParsingStrategy( sessionFactory ).parse( graphText, sessionFactory ); } public static RootGraphImplementor visit( EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + LegacyGraphLanguageParser.AttributeListContext attributeListContext, SessionFactoryImplementor sessionFactory) { return visit( rootType, attributeListContext, sessionFactory.getJpaMetamodel()::findEntityType ); } public static RootGraphImplementor visit( EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + LegacyGraphLanguageParser.AttributeListContext attributeListContext, GraphParserEntityNameResolver entityNameResolver) { return visit( null, rootType, attributeListContext, entityNameResolver ); } - public static @NonNull GraphContext parseText(String graphText) { + @Deprecated(forRemoval = true) + public static LegacyGraphLanguageParser.@NonNull GraphContext parseLegacyGraphText(String graphText) { + final var lexer = new LegacyGraphLanguageLexer( CharStreams.fromString( graphText ) ); + final var parser = new LegacyGraphLanguageParser( new CommonTokenStream( lexer ) ); + return parser.graph(); + } + + public static GraphLanguageParser.@NonNull GraphContext parseText(String graphText) { final var lexer = new GraphLanguageLexer( CharStreams.fromString( graphText ) ); final var parser = new GraphLanguageParser( new CommonTokenStream( lexer ) ); return parser.graph(); @@ -100,11 +95,19 @@ public static RootGraphImplementor visit( public static RootGraphImplementor visit( @Nullable String name, EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + LegacyGraphLanguageParser.AttributeListContext graphElementListContext, + GraphParserEntityNameResolver entityNameResolver) { + + return LegacyGraphParsingStrategy.parse( name, rootType, graphElementListContext, entityNameResolver ); + } + + public static RootGraphImplementor visit( + @Nullable String name, + EntityDomainType rootType, + GraphLanguageParser.GraphElementListContext graphElementListContext, GraphParserEntityNameResolver entityNameResolver) { - final RootGraphImpl targetGraph = new RootGraphImpl<>( name, rootType ); - visitGraph( targetGraph, entityNameResolver, attributeListContext ); - return targetGraph; + + return ModernGraphParsingStrategy.parse( name, rootType, graphElementListContext, entityNameResolver ); } /** @@ -115,29 +118,18 @@ public static void parseInto( GraphImplementor targetGraph, CharSequence graphString, SessionFactoryImplementor sessionFactory) { - final var graphContext = parseText( graphString.toString() ); - if ( graphContext.typeIndicator() != null ) { - // todo : throw an exception? Log warning? Ignore? - // for now, ignore - } - visitGraph( targetGraph, - sessionFactory.getJpaMetamodel()::findEntityType, - graphContext.attributeList() ); + + getGraphParsingStrategy( sessionFactory ).parseInto( targetGraph, graphString.toString(), sessionFactory ); } - private static void visitGraph( - GraphImplementor targetGraph, - GraphParserEntityNameResolver entityNameResolver, - GraphLanguageParser.AttributeListContext attributeList) { - // Build an instance of this class as a visitor - final var visitor = new GraphParser( entityNameResolver ); - visitor.getGraphStack().push( targetGraph ); - try { - visitor.visitAttributeList( attributeList ); - } - finally { - visitor.getGraphStack().pop(); - assert visitor.getGraphStack().isEmpty(); + private static GraphParsingStrategy getGraphParsingStrategy(SessionFactoryImplementor sessionFactory) { + final GraphParserMode mode = sessionFactory.getSessionFactoryOptions().getGraphParserMode(); + + if ( mode == GraphParserMode.MODERN ) { + return new ModernGraphParsingStrategy(); } + + return new LegacyGraphParsingStrategy(); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/LegacyGraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/LegacyGraphParser.java new file mode 100644 index 000000000000..e5f95b56d194 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/LegacyGraphParser.java @@ -0,0 +1,177 @@ +/* + * 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.legacy.LegacyGraphLanguageParser; +import org.hibernate.grammars.graph.legacy.LegacyGraphLanguageParserBaseVisitor; +import org.hibernate.graph.GraphNode; +import org.hibernate.graph.InvalidGraphException; +import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.SubGraphImplementor; +import org.hibernate.internal.util.collections.Stack; +import org.hibernate.internal.util.collections.StandardStack; + +import static org.hibernate.graph.internal.GraphParserLogging.PARSING_LOGGER; +import static org.hibernate.internal.util.StringHelper.repeat; + +/** + * Unified access to the Antlr parser for Hibernate's "graph language" + * + * @author Steve Ebersole + */ +public class LegacyGraphParser extends LegacyGraphLanguageParserBaseVisitor> { + private final GraphParserEntityNameResolver entityNameResolver; + + private final Stack> graphStack = new StandardStack<>(); + private final Stack> attributeNodeStack = new StandardStack<>(); + private final Stack graphSourceStack = new StandardStack<>(); + + public LegacyGraphParser(GraphParserEntityNameResolver 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 LegacyGraphParser#LegacyGraphParser(GraphParserEntityNameResolver) + */ + public LegacyGraphParser(SessionFactoryImplementor sessionFactory) { + this( sessionFactory.getJpaMetamodel()::findEntityType ); + } + + public Stack> getGraphStack() { + return graphStack; + } + + @Override + public AttributeNodeImplementor visitAttributeNode(LegacyGraphLanguageParser.AttributeNodeContext attributeNodeContext) { + final var attributePathContext = attributeNodeContext.attributePath(); + final var attributeQualifierContext = attributePathContext.attributeQualifier(); + + final var attributeName = attributePathContext.ATTR_NAME().getText(); + + final var subGraphCreator = subGraphCreator( attributeQualifierContext, attributeName ); + + final var 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", + repeat( "<<", attributeNodeStack.depth() + 1 ), + attributeName + ); + } + + return attributeNode; + } + + private SubGraphGenerator subGraphCreator(LegacyGraphLanguageParser.AttributeQualifierContext attributeQualifierContext, String attributeName) { + if ( attributeQualifierContext == null ) { + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Start attribute : %s", + repeat( ">>", attributeNodeStack.depth() + 1 ), + attributeName + ); + } + return PathQualifierType.VALUE.getSubGraphCreator(); + } + else { + final var qualifierName = attributeQualifierContext.ATTR_NAME().getText(); + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Start qualified attribute : %s.%s", + repeat( ">>", attributeNodeStack.depth() + 1 ), + attributeName, + qualifierName + ); + } + return resolvePathQualifier( qualifierName ).getSubGraphCreator(); + } + } + + private AttributeNodeImplementor resolveAttributeNode(String attributeName) { + final var currentGraph = graphStack.getCurrent(); + assert currentGraph != null; + + final var 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'" ); + } + + @Override + public SubGraphImplementor visitSubGraph(LegacyGraphLanguageParser.SubGraphContext subGraphContext) { + final String subTypeName = + subGraphContext.typeIndicator() == null ? null + : subGraphContext.typeIndicator().TYPE_NAME().getText(); + + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Starting graph: %s", + repeat( ">>", attributeNodeStack.depth() + 2 ), + subTypeName + ); + } + + final var attributeNode = attributeNodeStack.getCurrent(); + final var subGraphCreator = graphSourceStack.getCurrent(); + + final var subGraph = subGraphCreator.createSubGraph( + attributeNode, + subTypeName, + entityNameResolver + ); + + graphStack.push( subGraph ); + + try { + subGraphContext.attributeList().accept( this ); + } + finally { + graphStack.pop(); + } + + if ( PARSING_LOGGER.isTraceEnabled() ) { + PARSING_LOGGER.tracef( + "%s Finished graph : %s", + repeat( "<<", attributeNodeStack.depth() + 2 ), + subGraph.getGraphedType().getTypeName() + ); + } + + return subGraph; + } +} 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..139e473be48b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/GraphParsingStrategy.java @@ -0,0 +1,23 @@ +/* + * 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( + EntityDomainType entityDomainType, + 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..29085764e108 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/LegacyGraphParsingStrategy.java @@ -0,0 +1,101 @@ +/* + * 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.LegacyGraphLanguageLexer; +import org.hibernate.grammars.graph.legacy.LegacyGraphLanguageParser; +import org.hibernate.graph.InvalidGraphException; +import org.hibernate.graph.internal.RootGraphImpl; +import org.hibernate.graph.internal.parse.LegacyGraphParser; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; +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(EntityDomainType entityDomainType, String graphText, SessionFactoryImplementor sessionFactory) { + if ( graphText == null ) { + return null; + } + final var parser = new LegacyGraphLanguageParser( + new CommonTokenStream( new LegacyGraphLanguageLexer( 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 + @Deprecated(forRemoval = true) + public RootGraphImplementor parse(String graphText, SessionFactoryImplementor sessionFactory) { + if ( graphText == null ) { + return null; + } + final var parser = new LegacyGraphLanguageParser( + new CommonTokenStream( new LegacyGraphLanguageLexer( 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 var lexer = new LegacyGraphLanguageLexer( CharStreams.fromString( graphText ) ); + final var parser = new LegacyGraphLanguageParser( new CommonTokenStream( lexer ) ); + final var 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 static RootGraphImplementor parse(EntityDomainType rootType, LegacyGraphLanguageParser.AttributeListContext graphElementListContext, SessionFactoryImplementor sessionFactory) { + return parse( null, rootType, graphElementListContext, sessionFactory.getJpaMetamodel()::findEntityType ); + } + + public static RootGraphImplementor parse(@Nullable String name, EntityDomainType rootType, LegacyGraphLanguageParser.AttributeListContext graphElementListContext, GraphParserEntityNameResolver 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..c048db8b18ee --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/strategy/ModernGraphParsingStrategy.java @@ -0,0 +1,85 @@ +/* + * 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.GraphLanguageLexer; +import org.hibernate.grammars.graph.GraphLanguageParser; +import org.hibernate.graph.internal.RootGraphImpl; +import org.hibernate.graph.internal.parse.GraphParser; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.graph.spi.GraphParserEntityNameResolver; +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( + 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(); + + return parse( entityDomainType, 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 GraphLanguageParser( new CommonTokenStream( new GraphLanguageLexer( + CharStreams.fromString( graphText ) ) ) ); + final var graphCtx = parser.graph(); + final var visitor = new GraphParser( sessionFactory ); + visitor.getGraphStack().push( graph ); + try { + visitor.visitGraphElementList( graphCtx.graphElementList() ); + } + finally { + visitor.getGraphStack().pop(); + assert visitor.getGraphStack().isEmpty(); + } + } + + public static RootGraphImplementor parse( + EntityDomainType rootType, + GraphLanguageParser.GraphElementListContext graphElementListContext, + SessionFactoryImplementor sessionFactory) { + return parse( null, rootType, graphElementListContext, sessionFactory.getJpaMetamodel()::findEntityType ); + } + + + public static RootGraphImplementor parse( + @Nullable String name, + EntityDomainType rootType, + GraphLanguageParser.GraphElementListContext graphElementListContext, + GraphParserEntityNameResolver entityNameResolver) { + final RootGraphImpl targetGraph = new RootGraphImpl<>( name, rootType ); + final var visitor = new GraphParser( 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 36a76ab9f4cc..488dec2247b8 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 @@ -505,7 +505,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/AbstractClassLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractClassLevelTests.java new file mode 100644 index 000000000000..8a497a577acd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/AbstractClassLevelTests.java @@ -0,0 +1,104 @@ +/* + * 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.model.internal.InvalidNamedEntityGraphParameterException; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.engine.spi.SessionFactoryImplementor; +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.Isbn; +import org.hibernate.orm.test.entitygraph.named.parsed.entity.Person; +import org.hibernate.orm.test.entitygraph.named.parsed.entity.Publisher; +import org.hibernate.orm.test.entitygraph.named.parsed.entity.PublishingHouse; +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.junit.jupiter.api.Test; + +import static org.hibernate.orm.test.entitygraph.parser.AssertionHelper.assertBasicAttributes; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * Test for Hibernate's {@link org.hibernate.annotations.NamedEntityGraph @NamedEntityGraph} + * + * @author Steve Ebersole + */ +@SuppressWarnings("JUnitMalformedDeclaration") +public abstract class AbstractClassLevelTests { + + @Test + @DomainModel(annotatedClasses = { + Book.class, + Person.class, + Publisher.class, + PublishingHouse.class, + DomesticPublishingHouse.class, + ForeignPublishingHouse.class, + Isbn.class + + }) + @SessionFactory(exportSchema = false) + 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" + ); + } + + @Test + @DomainModel(annotatedClasses = BadRootClassEntity.class) + void testRootEntityDifferentFromEntityMarkedWithAnnotation(DomainModelScope modelScope) { + final MetadataImplementor domainModel = modelScope.getDomainModel(); + + try (org.hibernate.SessionFactory sessionFactory = domainModel.buildSessionFactory()) { + fail( "Expecting an exception" ); + } + catch (InvalidNamedEntityGraphParameterException expected) { + } + } + + @Test + @ServiceRegistry + void testDuplicateNames(ServiceRegistryScope registryScope) { + final MetadataSources metadataSources = new MetadataSources( registryScope.getRegistry() ) + .addAnnotatedClasses( Duplicator.class ); + try { + metadataSources.buildMetadata(); + fail( "Expecting a failure" ); + } + catch (DuplicateMappingException 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/ClassLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java index 2051af3f2f86..d0ff442e3fa9 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/ClassLevelTests.java @@ -4,87 +4,12 @@ */ package org.hibernate.orm.test.entitygraph.named.parsed; -import org.hibernate.DuplicateMappingException; -import org.hibernate.boot.MetadataSources; -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.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; -import org.hibernate.orm.test.entitygraph.named.parsed.entity.PublishingHouse; -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.junit.jupiter.api.Test; - -import static org.hibernate.orm.test.entitygraph.parser.AssertionHelper.assertBasicAttributes; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * Test for Hibernate's {@link org.hibernate.annotations.NamedEntityGraph @NamedEntityGraph} - * - * @author Steve Ebersole - */ -@SuppressWarnings("JUnitMalformedDeclaration") -public class ClassLevelTests { - - @Test - @DomainModel(annotatedClasses = { - Book.class, - Person.class, - Publisher.class, - PublishingHouse.class, - DomesticPublishingHouse.class, - ForeignPublishingHouse.class, - Isbn.class +import org.hibernate.cfg.AvailableSettings; - }) - @SessionFactory(exportSchema = false) - 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" ); - } - - @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) { - } - } +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.Setting; - @Test - @ServiceRegistry - void testDuplicateNames(ServiceRegistryScope registryScope) { - final MetadataSources metadataSources = new MetadataSources( registryScope.getRegistry() ) - .addAnnotatedClasses( Duplicator.class ); - try { - metadataSources.buildMetadata(); - fail( "Expecting a failure" ); - } - catch (DuplicateMappingException expected) { - } - } +@ServiceRegistry(settings = @Setting(name = AvailableSettings.GRAPH_PARSER_MODE, value = "modern")) +public class ClassLevelTests extends AbstractClassLevelTests { } 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/LegacySyntaxPackageLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxPackageLevelTests.java new file mode 100644 index 000000000000..8fe6ac35c750 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/LegacySyntaxPackageLevelTests.java @@ -0,0 +1,56 @@ +/* + * 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.pkg.Book; +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.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 = "legacy")) +public class LegacySyntaxPackageLevelTests extends AbstractPackageLevelTests { + + @Test + @DomainModel( + annotatedClasses = { Book.class, Isbn.class, Person.class }, + annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg" + ) + @SessionFactory(exportSchema = false) + void testDiscovery(SessionFactoryScope factoryScope) { + final SessionFactoryImplementor sessionFactory = factoryScope.getSessionFactory(); + + 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" ); + assertBasicGraph( sessionFactory, "book-title-with-root-attribute", "title" ); + assertBasicGraph( sessionFactory, "book-title-with-root-attribute-and-type-indicator", "title" ); + } + + @Test + @DomainModel( + annotatedClasses = Person.class, + annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg2" + ) + void testInvalid(DomainModelScope modelScope) { + try (org.hibernate.SessionFactory sessionFactory = modelScope.getDomainModel().buildSessionFactory()) { + fail( "Expected an exception" ); + } + catch (InvalidNamedEntityGraphParameterException 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/PackageLevelTests.java index 97ef1080c219..fa9048f6a1c0 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/PackageLevelTests.java @@ -4,78 +4,47 @@ */ 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.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.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 = "modern")) +public class PackageLevelTests extends AbstractPackageLevelTests { + @Test @DomainModel( - annotatedClasses = { Book.class, Isbn.class, Person.class }, - annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg" + annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg2" ) - @SessionFactory(exportSchema = false) - void testDiscovery(SessionFactoryScope factoryScope) { - final SessionFactoryImplementor sessionFactory = factoryScope.getSessionFactory(); - - 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(); + void givenModernGraphParserModeShouldThrowExceptionWhenUsingAnnotationsWithoutRootAttribute(DomainModelScope modelScope) { + try (org.hibernate.SessionFactory sessionFactory = modelScope.getDomainModel().buildSessionFactory()) { fail( "Expected an exception" ); } - catch (DuplicateMappingException expected) { + catch (InvalidNamedEntityGraphParameterException expected) { } } @Test @DomainModel( - annotatedClasses = Person.class, - annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg2" + annotatedClasses = { Book.class, Person.class }, + annotatedPackageNames = "org.hibernate.orm.test.entitygraph.named.parsed.pkg3" ) - void testInvalid(DomainModelScope modelScope) { - try (org.hibernate.SessionFactory sessionFactory = modelScope.getDomainModel().buildSessionFactory()) { - fail( "Expected an exception" ); - } - catch (InvalidGraphException expected) { - } + @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/AbstractEntityGraphParserTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/AbstractEntityGraphParserTest.java new file mode 100644 index 000000000000..54316908f7cf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/AbstractEntityGraphParserTest.java @@ -0,0 +1,250 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.parser; + +import jakarta.persistence.AttributeNode; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.Subgraph; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.graph.GraphParser; +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.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +/** + * A unit test of {@link GraphParser}. + * + * @author asusnjar + */ +public abstract class AbstractEntityGraphParserTest extends AbstractEntityGraphTest { + + @Test + public void testNullParsing(EntityManagerFactoryScope scope) { + EntityGraph graph = parseGraph( null, scope ); + assertThat( graph ).isNull(); + } + + @Test + public void testOneBasicAttributeParsing(EntityManagerFactoryScope scope) { + EntityGraph graph = parseGraph( "name", scope ); + AssertionHelper.assertBasicAttributes( graph, "name" ); + } + + @Test + public void testTwoBasicAttributesParsing(EntityManagerFactoryScope scope) { + EntityGraph graph = parseGraph( "name, description", scope ); + AssertionHelper.assertBasicAttributes( graph, "name", "description" ); + } + + @Test + public void testLinkParsing(EntityManagerFactoryScope scope) { + EntityGraph graph = parseGraph( "linkToOne(name, description)", scope ); + assertThat( graph ).isNotNull(); + + List> attrs = graph.getAttributeNodes(); + assertThat( attrs ).isNotNull(); + + assertThat( attrs.size() ).isEqualTo( 1 ); + AttributeNode node = attrs.get( 0 ); + assertThat( node ).isNotNull(); + assertThat( node.getAttributeName() ).isEqualTo( "linkToOne" ); + AssertionHelper.assertNullOrEmpty( node.getKeySubgraphs() ); + @SuppressWarnings("rawtypes") + Map sub = node.getSubgraphs(); + AssertionHelper.assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" ); + } + + @Test + public void testMapKeyParsing(EntityManagerFactoryScope scope) { + EntityGraph graph = parseGraph( "map.key(name, description)", scope ); + assertThat( graph ).isNotNull(); + List> attrs = graph.getAttributeNodes(); + assertThat( attrs ).isNotNull(); + assertThat( attrs.size() ).isEqualTo( 1 ); + AttributeNode node = attrs.get( 0 ); + assertThat( node ).isNotNull(); + assertThat( node.getAttributeName() ).isEqualTo( "map" ); + AssertionHelper.assertNullOrEmpty( node.getSubgraphs() ); + @SuppressWarnings("rawtypes") + Map sub = node.getKeySubgraphs(); + AssertionHelper.assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" ); + } + + @Test + public void testMapValueParsing(EntityManagerFactoryScope scope) { + EntityGraph graph = parseGraph( "map.value(name, description)", scope ); + assertThat( graph ).isNotNull(); + List> attrs = graph.getAttributeNodes(); + assertThat( attrs ).isNotNull(); + assertThat( attrs.size() ).isEqualTo( 1 ); + AttributeNode node = attrs.get( 0 ); + assertThat( node ).isNotNull(); + assertThat( node.getAttributeName() ).isEqualTo( "map" ); + AssertionHelper.assertNullOrEmpty( node.getKeySubgraphs() ); + @SuppressWarnings("rawtypes") + Map sub = node.getSubgraphs(); + AssertionHelper.assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" ); + } + + @Test + public void testMixParsingWithMaps(EntityManagerFactoryScope scope) { + String g = " name , linkToOne ( description, map . key ( name ) , map . value ( description ) , name ) , description , map . key ( name , description ) , map . value ( description ) "; + g = g.replace( " ", " " ); + for ( int i = 1; i <= 2; i++, g = g.replace( " ", "" ) ) { + EntityGraph graph = parseGraph( g, scope ); + AssertionHelper.assertBasicAttributes( graph, "name", "description" ); + + AttributeNode linkToOne = AssertionHelper.getAttributeNodeByName( graph, "linkToOne", true ); + AssertionHelper.assertNullOrEmpty( linkToOne.getKeySubgraphs() ); + @SuppressWarnings("rawtypes") + Map linkToOneSubgraphs = linkToOne.getSubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph linkToOneRoot = linkToOneSubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( linkToOneRoot, "name", "description" ); + + AttributeNode linkToOneMap = AssertionHelper.getAttributeNodeByName( linkToOneRoot, "map", true ); + @SuppressWarnings("rawtypes") + Map linkToOneMapKeySubgraphs = linkToOneMap.getKeySubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph linkToOneMapKeyRoot = linkToOneMapKeySubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( linkToOneMapKeyRoot, "name" ); + @SuppressWarnings("rawtypes") + Map linkToOneMapSubgraphs = linkToOneMap.getSubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph linkToOneMapRoot = linkToOneMapSubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( linkToOneMapRoot, "description" ); + + AttributeNode map = AssertionHelper.getAttributeNodeByName( graph, "map", true ); + @SuppressWarnings("rawtypes") + Map mapKeySubgraphs = map.getKeySubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph mapKeyRoot = mapKeySubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( mapKeyRoot, "name", "description" ); + @SuppressWarnings("rawtypes") + Map mapSubgraphs = map.getSubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph mapRoot = mapSubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( mapRoot, "description" ); + } + } + + @Test + public void testMixParsingWithSimplifiedMaps(EntityManagerFactoryScope scope) { + String g = " name , linkToOne ( description, map . key ( name ) , name ) , description , map . value ( description, name ) "; + g = g.replace( " ", " " ); + for ( int i = 1; i <= 2; i++, g = g.replace( " ", "" ) ) { + EntityGraph graph = parseGraph( g, scope ); + AssertionHelper.assertBasicAttributes( graph, "name", "description" ); + + AttributeNode linkToOne = AssertionHelper.getAttributeNodeByName( graph, "linkToOne", true ); + AssertionHelper.assertNullOrEmpty( linkToOne.getKeySubgraphs() ); + @SuppressWarnings("rawtypes") + Map linkToOneSubgraphs = linkToOne.getSubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph linkToOneRoot = linkToOneSubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( linkToOneRoot, "name", "description" ); + + AttributeNode linkToOneMap = AssertionHelper.getAttributeNodeByName( linkToOneRoot, "map", true ); + @SuppressWarnings("rawtypes") + Map linkToOneMapKeySubgraphs = linkToOneMap.getKeySubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph linkToOneMapKeyRoot = linkToOneMapKeySubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( linkToOneMapKeyRoot, "name" ); + + AttributeNode map = AssertionHelper.getAttributeNodeByName( graph, "map", true ); + @SuppressWarnings("rawtypes") + Map mapSubgraphs = map.getSubgraphs(); + @SuppressWarnings("rawtypes") + Subgraph mapRoot = mapSubgraphs.get( GraphParsingTestEntity.class ); + AssertionHelper.assertBasicAttributes( mapRoot, "description", "name" ); + } + } + + @Test + public void testHHH10378IsNotFixedYet(EntityManagerFactoryScope scope) { + scope.inEntityManager( + entityManager -> { + RootGraphImplementor graph = ((SessionImplementor) entityManager) + .createEntityGraph( GraphParsingTestEntity.class ); + final SubGraphImplementor subGraph = graph.addSubGraph( + "linkToOne", + GraphParsingTestSubEntity.class + ); + + assertThat( GraphParsingTestSubEntity.class ).isEqualTo( subGraph.getGraphedType().getJavaType() ); + + final AttributeNodeImplementor subTypeAttrNode = subGraph.findOrCreateAttributeNode( + "sub" ); + assert subTypeAttrNode != null; + } + ); + } + + @Test + public void testHHH12696MapSubgraphsKeyFirst(EntityManagerFactoryScope scope) { + scope.inEntityManager( + entityManager -> { + EntityGraph graph = entityManager + .createEntityGraph( GraphParsingTestEntity.class ); + + final String mapAttributeName = "map"; + Subgraph keySubgraph = graph.addKeySubgraph( mapAttributeName ); + Subgraph valueSubgraph = graph.addSubgraph( mapAttributeName ); + + checkMapKeyAndValueSubgraphs( graph, mapAttributeName, keySubgraph, valueSubgraph ); + } + ); + } + + private void checkMapKeyAndValueSubgraphs( + EntityGraph graph, + final String mapAttributeName, + Subgraph keySubgraph, + Subgraph valueSubgraph) { + int count = 0; + for ( AttributeNode node : graph.getAttributeNodes() ) { + if ( mapAttributeName.equals( node.getAttributeName() ) ) { + count++; + @SuppressWarnings("rawtypes") + Map keySubgraphs = node.getKeySubgraphs(); + assertThat( !keySubgraphs.isEmpty() ) + .describedAs( "Missing the key subgraph" ) + .isTrue(); + assertThat( keySubgraphs.get( GraphParsingTestEntity.class ) ).isSameAs( keySubgraph ); + + @SuppressWarnings("rawtypes") + Map valueSubgraphs = node.getSubgraphs(); + assertThat( !valueSubgraphs.isEmpty() ) + .describedAs( "Missing the value subgraph" ) + .isTrue(); + assertThat( valueSubgraphs.get( GraphParsingTestEntity.class ) ).isSameAs( valueSubgraph ); + } + } + assertThat( count ).isEqualTo( 1 ); + } + + @Test + public void testHHH12696MapSubgraphsValueFirst(EntityManagerFactoryScope scope) { + scope.inEntityManager( + entityManager -> { + EntityGraph graph = entityManager + .createEntityGraph( GraphParsingTestEntity.class ); + + final String mapAttributeName = "map"; + Subgraph valueSubgraph = graph.addSubgraph( mapAttributeName ); + Subgraph keySubgraph = graph.addKeySubgraph( mapAttributeName ); + + checkMapKeyAndValueSubgraphs( graph, mapAttributeName, keySubgraph, valueSubgraph ); + } + ); + } +} 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/EntityGraphParserTest.java index 863ebcb4505f..62ebda376582 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/EntityGraphParserTest.java @@ -4,180 +4,42 @@ */ package org.hibernate.orm.test.entitygraph.parser; -import jakarta.persistence.AttributeNode; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.Subgraph; -import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.graph.GraphParser; +import java.util.List; + +import org.hibernate.UnknownEntityTypeException; +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 java.util.List; -import java.util.Map; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -/** - * A unit test of {@link GraphParser}. - * - * @author asusnjar - */ -public class EntityGraphParserTest extends AbstractEntityGraphTest { - - @Test - public void testNullParsing(EntityManagerFactoryScope scope) { - EntityGraph graph = parseGraph( null, scope ); - assertThat( graph ).isNull(); - } - - @Test - public void testOneBasicAttributeParsing(EntityManagerFactoryScope scope) { - EntityGraph graph = parseGraph( "name", scope ); - AssertionHelper.assertBasicAttributes( graph, "name" ); - } - - @Test - public void testTwoBasicAttributesParsing(EntityManagerFactoryScope scope) { - EntityGraph graph = parseGraph( "name, description", scope ); - AssertionHelper.assertBasicAttributes( graph, "name", "description" ); - } - - @Test - public void testLinkParsing(EntityManagerFactoryScope scope) { - EntityGraph graph = parseGraph( "linkToOne(name, description)", scope ); - assertThat( graph ).isNotNull(); - - List> attrs = graph.getAttributeNodes(); - assertThat( attrs ).isNotNull(); - - assertThat( attrs.size() ).isEqualTo( 1 ); - AttributeNode node = attrs.get( 0 ); - assertThat( node ).isNotNull(); - assertThat( node.getAttributeName() ).isEqualTo( "linkToOne" ); - AssertionHelper.assertNullOrEmpty( node.getKeySubgraphs() ); - @SuppressWarnings("rawtypes") - Map sub = node.getSubgraphs(); - AssertionHelper.assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" ); - } - - @Test - public void testMapKeyParsing(EntityManagerFactoryScope scope) { - EntityGraph graph = parseGraph( "map.key(name, description)", scope ); - assertThat( graph ).isNotNull(); - List> attrs = graph.getAttributeNodes(); - assertThat( attrs ).isNotNull(); - assertThat( attrs.size() ).isEqualTo( 1 ); - AttributeNode node = attrs.get( 0 ); - assertThat( node ).isNotNull(); - assertThat( node.getAttributeName() ).isEqualTo( "map" ); - AssertionHelper.assertNullOrEmpty( node.getSubgraphs() ); - @SuppressWarnings("rawtypes") - Map sub = node.getKeySubgraphs(); - AssertionHelper.assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" ); - } - - @Test - public void testMapValueParsing(EntityManagerFactoryScope scope) { - EntityGraph graph = parseGraph( "map.value(name, description)", scope ); - assertThat( graph ).isNotNull(); - List> attrs = graph.getAttributeNodes(); - assertThat( attrs ).isNotNull(); - assertThat( attrs.size() ).isEqualTo( 1 ); - AttributeNode node = attrs.get( 0 ); - assertThat( node ).isNotNull(); - assertThat( node.getAttributeName() ).isEqualTo( "map" ); - AssertionHelper.assertNullOrEmpty( node.getKeySubgraphs() ); - @SuppressWarnings("rawtypes") - Map sub = node.getSubgraphs(); - AssertionHelper.assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" ); - } - - @Test - public void testMixParsingWithMaps(EntityManagerFactoryScope scope) { - String g = " name , linkToOne ( description, map . key ( name ) , map . value ( description ) , name ) , description , map . key ( name , description ) , map . value ( description ) "; - g = g.replace( " ", " " ); - for ( int i = 1; i <= 2; i++, g = g.replace( " ", "" ) ) { - EntityGraph graph = parseGraph( g, scope ); - AssertionHelper.assertBasicAttributes( graph, "name", "description" ); - - AttributeNode linkToOne = AssertionHelper.getAttributeNodeByName( graph, "linkToOne", true ); - AssertionHelper.assertNullOrEmpty( linkToOne.getKeySubgraphs() ); - @SuppressWarnings("rawtypes") - Map linkToOneSubgraphs = linkToOne.getSubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph linkToOneRoot = linkToOneSubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( linkToOneRoot, "name", "description" ); - - AttributeNode linkToOneMap = AssertionHelper.getAttributeNodeByName( linkToOneRoot, "map", true ); - @SuppressWarnings("rawtypes") - Map linkToOneMapKeySubgraphs = linkToOneMap.getKeySubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph linkToOneMapKeyRoot = linkToOneMapKeySubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( linkToOneMapKeyRoot, "name" ); - @SuppressWarnings("rawtypes") - Map linkToOneMapSubgraphs = linkToOneMap.getSubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph linkToOneMapRoot = linkToOneMapSubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( linkToOneMapRoot, "description" ); - - AttributeNode map = AssertionHelper.getAttributeNodeByName( graph, "map", true ); - @SuppressWarnings("rawtypes") - Map mapKeySubgraphs = map.getKeySubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph mapKeyRoot = mapKeySubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( mapKeyRoot, "name", "description" ); - @SuppressWarnings("rawtypes") - Map mapSubgraphs = map.getSubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph mapRoot = mapSubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( mapRoot, "description" ); +@Jpa( + annotatedClasses = { + GraphParsingTestEntity.class, + GraphParsingTestSubEntity.class + }, + integrationSettings = { + @Setting(name = GraphParserSettings.GRAPH_PARSER_MODE, value = "modern") } - } - - @Test - public void testMixParsingWithSimplifiedMaps(EntityManagerFactoryScope scope) { - String g = " name , linkToOne ( description, map . key ( name ) , name ) , description , map . value ( description, name ) "; - g = g.replace( " ", " " ); - for ( int i = 1; i <= 2; i++, g = g.replace( " ", "" ) ) { - EntityGraph graph = parseGraph( g, scope ); - AssertionHelper.assertBasicAttributes( graph, "name", "description" ); +) +public class EntityGraphParserTest extends AbstractEntityGraphParserTest { - AttributeNode linkToOne = AssertionHelper.getAttributeNodeByName( graph, "linkToOne", true ); - AssertionHelper.assertNullOrEmpty( linkToOne.getKeySubgraphs() ); - @SuppressWarnings("rawtypes") - Map linkToOneSubgraphs = linkToOne.getSubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph linkToOneRoot = linkToOneSubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( linkToOneRoot, "name", "description" ); - - AttributeNode linkToOneMap = AssertionHelper.getAttributeNodeByName( linkToOneRoot, "map", true ); - @SuppressWarnings("rawtypes") - Map linkToOneMapKeySubgraphs = linkToOneMap.getKeySubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph linkToOneMapKeyRoot = linkToOneMapKeySubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( linkToOneMapKeyRoot, "name" ); - - AttributeNode map = AssertionHelper.getAttributeNodeByName( graph, "map", true ); - @SuppressWarnings("rawtypes") - Map mapSubgraphs = map.getSubgraphs(); - @SuppressWarnings("rawtypes") - Subgraph mapRoot = mapSubgraphs.get( GraphParsingTestEntity.class ); - AssertionHelper.assertBasicAttributes( mapRoot, "description", "name" ); - } - } @Test - public void testLinkSubtypeParsing(EntityManagerFactoryScope scope) { + public void testLinkSubtypeParsingWithNewSyntax(EntityManagerFactoryScope scope) { RootGraphImplementor graph = parseGraph( - "linkToOne(name, description), linkToOne(GraphParsingTestSubEntity: sub)", scope ); + "linkToOne(name, description), linkToOne:GraphParsingTestSubEntity(sub)", scope ); assertThat( graph ).isNotNull(); List> attrs = graph.getAttributeNodeList(); assertThat( attrs ).isNotNull(); - assertThat( attrs.size() ).isEqualTo( 1 ); + assertThat( attrs ).hasSize( 1 ); AttributeNodeImplementor linkToOneNode = attrs.get( 0 ); assertThat( linkToOneNode ).isNotNull(); @@ -192,82 +54,52 @@ public void testLinkSubtypeParsing(EntityManagerFactoryScope scope) { } @Test - public void testHHH10378IsNotFixedYet(EntityManagerFactoryScope scope) { - scope.inEntityManager( - entityManager -> { - RootGraphImplementor graph = ((SessionImplementor) entityManager) - .createEntityGraph( GraphParsingTestEntity.class ); - final SubGraphImplementor subGraph = graph.addSubGraph( - "linkToOne", - GraphParsingTestSubEntity.class - ); + public void testSubtypeAndTwoBasicAttributesParsing(EntityManagerFactoryScope scope) { + var graph = parseGraph( "name, :GraphParsingTestSubEntity(sub), description", scope ); + assertThat( graph ).isNotNull(); - assertThat( GraphParsingTestSubEntity.class ).isEqualTo( subGraph.getGraphedType().getJavaType() ); + AssertionHelper.assertBasicAttributes( graph, "name", "description" ); - final AttributeNodeImplementor subTypeAttrNode = subGraph.findOrCreateAttributeNode( - "sub" ); - assert subTypeAttrNode != null; - } - ); + 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 testHHH12696MapSubgraphsKeyFirst(EntityManagerFactoryScope scope) { - scope.inEntityManager( - entityManager -> { - EntityGraph graph = entityManager - .createEntityGraph( GraphParsingTestEntity.class ); + public void testSubtypeParsing(EntityManagerFactoryScope scope) { + var graph = parseGraph( ":GraphParsingTestSubEntity(sub)", scope ); + assertThat( graph ).isNotNull(); - final String mapAttributeName = "map"; - Subgraph keySubgraph = graph.addKeySubgraph( mapAttributeName ); - Subgraph valueSubgraph = graph.addSubgraph( mapAttributeName ); + var treatedSubgraphs = graph.getTreatedSubgraphs(); + assertThat( treatedSubgraphs ).hasSize( 1 ); - checkMapKeyAndValueSubgraphs( graph, mapAttributeName, keySubgraph, valueSubgraph ); - } - ); - } + var subEntityGraph = treatedSubgraphs.get( GraphParsingTestSubEntity.class ); + var subEntityGraphAttributes = subEntityGraph.getAttributeNodes(); - private void checkMapKeyAndValueSubgraphs( - EntityGraph graph, - final String mapAttributeName, - Subgraph keySubgraph, - Subgraph valueSubgraph) { - int count = 0; - for ( AttributeNode node : graph.getAttributeNodes() ) { - if ( mapAttributeName.equals( node.getAttributeName() ) ) { - count++; - @SuppressWarnings("rawtypes") - Map keySubgraphs = node.getKeySubgraphs(); - assertThat( !keySubgraphs.isEmpty() ) - .describedAs( "Missing the key subgraph" ) - .isTrue(); - assertThat( keySubgraphs.get( GraphParsingTestEntity.class ) ).isSameAs( keySubgraph ); + assertThat( subEntityGraphAttributes ).isNotNull(); + assertThat( subEntityGraphAttributes ).hasSize( 1 ); - @SuppressWarnings("rawtypes") - Map valueSubgraphs = node.getSubgraphs(); - assertThat( !valueSubgraphs.isEmpty() ) - .describedAs( "Missing the value subgraph" ) - .isTrue(); - assertThat( valueSubgraphs.get( GraphParsingTestEntity.class ) ).isSameAs( valueSubgraph ); - } - } - assertThat( count ).isEqualTo( 1 ); + var attributeNode = subEntityGraphAttributes.get( 0 ); + assertThat( attributeNode ).isNotNull(); + assertThat( attributeNode.getAttributeName() ).isEqualTo( "sub" ); } - @Test - public void testHHH12696MapSubgraphsValueFirst(EntityManagerFactoryScope scope) { - scope.inEntityManager( - entityManager -> { - EntityGraph graph = entityManager - .createEntityGraph( GraphParsingTestEntity.class ); - final String mapAttributeName = "map"; - Subgraph valueSubgraph = graph.addSubgraph( mapAttributeName ); - Subgraph keySubgraph = graph.addKeySubgraph( mapAttributeName ); - - checkMapKeyAndValueSubgraphs( graph, mapAttributeName, keySubgraph, valueSubgraph ); - } + @Test + public void testWithSubTypeThatNotExist(EntityManagerFactoryScope scope) { + assertThrows( + UnknownEntityTypeException.class, + () -> parseGraph( ":SubTypeThatNotExist(sub)", scope ) ); + } } 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/graph/AbstractEntityGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/AbstractEntityGraphTest.java index f25b1fe047e9..9cf0ff4ccd36 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() { + 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 index f4d919fae75c..6f0c18111ab5 100644 --- 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 @@ -4,210 +4,48 @@ */ package org.hibernate.orm.test.graph; -import jakarta.persistence.EntityGraph; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.cfg.GraphParserSettings; import org.hibernate.graph.EntityGraphs; -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 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; -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(); - } +@Jpa( + annotatedClasses = { + GraphParsingTestEntity.class, + GraphParsingTestSubentity.class + }, + integrationSettings = { + @Setting(name = GraphParserSettings.GRAPH_PARSER_MODE, value = "modern") + } +) +public class EntityGraphsTest extends AbstractEntityGraphTest { @Test public void testEqualLinksWithSubclassesEqual(EntityManagerFactoryScope scope) { EntityGraph a = parseGraph( - "linkToOne(name), linkToOne(GraphParsingTestSubentity:description)", scope ); + "linkToOne(name), linkToOne:GraphParsingTestSubentity(description)", scope ); EntityGraph b = parseGraph( - "linkToOne(GraphParsingTestSubentity:description), linkToOne(name)", scope ); + "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 ); + "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/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..a78a8f18691c 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"; @@ -71,6 +72,15 @@ private void populateGrammars(AntlrSpec antlrSpec) { } ); + antlrSpec.getGrammarDescriptors().create( + "deprecated-graph", + (grammarDescriptor) -> { + grammarDescriptor.getPackageName().set( LEGACY_GRAPH_PKG ); + grammarDescriptor.getLexerFileName().set( "LegacyGraphLanguageLexer.g4" ); + grammarDescriptor.getParserFileName().set( "LegacyGraphLanguageParser.g4" ); + } + ); + antlrSpec.getGrammarDescriptors().create( "graph", (grammarDescriptor) -> {