diff --git a/hibernate-core/src/main/java/org/hibernate/Internal.java b/hibernate-core/src/main/java/org/hibernate/Internal.java new file mode 100644 index 0000000000..ebb60ed8db --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/Internal.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PACKAGE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Annotation used to identify a package, class, interface or method + * as "internal", meaning that applications should expect no guarantees + * with regard to the binary stability from release to release. + * + * @author Steve Ebersole + */ +@Target({PACKAGE, TYPE, METHOD}) +@Retention(CLASS) +public @interface Internal { +} + diff --git a/hibernate-core/src/main/java/org/hibernate/boot/Metadata.java b/hibernate-core/src/main/java/org/hibernate/boot/Metadata.java index c73c8845bb..a2c3662ae6 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/Metadata.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/Metadata.java @@ -8,6 +8,7 @@ package org.hibernate.boot; import java.util.Map; import java.util.UUID; +import java.util.function.Consumer; import org.hibernate.SessionFactory; import org.hibernate.boot.model.IdentifierGeneratorDefinition; @@ -113,26 +114,31 @@ public interface Metadata extends Mapping { /** * Retrieve named query metadata by name. * - * @param name The query name - * * @return The named query metadata, or {@code null}. */ NamedHqlQueryDefinition getNamedHqlQueryMapping(String name); - java.util.Collection getNamedHqlQueryMappings(); + /** + * Visit all named HQL query definitions + */ + void visitNamedHqlQueryDefinitions(Consumer definitionConsumer); /** * Retrieve named SQL query metadata. * - * @param name The SQL query name. - * * @return The named query metadata, or {@code null} */ NamedNativeQueryDefinition getNamedNativeQueryMapping(String name); - java.util.Collection getNamedNativeQueryMappings(); + /** + * Visit all named native query definitions + */ + void visitNamedNativeQueryDefinitions(Consumer definitionConsumer); - java.util.Collection getNamedProcedureCallMappings(); + /** + * Visit all named callable query definitions + */ + void visitNamedProcedureCallDefinition(Consumer definitionConsumer); /** * Retrieve the metadata for a named SQL result set mapping. @@ -143,13 +149,14 @@ public interface Metadata extends Mapping { */ NamedResultSetMappingDefinition getResultSetMapping(String name); - Map getResultSetMappingDefinitions(); + /** + * Visit all named SQL result set mapping definitions + */ + void visitNamedResultSetMappingDefinition(Consumer definitionConsumer); /** * Retrieve a type definition by name. * - * @param typeName The name of the type definition to retrieve. - * * @return The named type definition, or {@code null} */ TypeDefinition getTypeDefinition(String typeName); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java index c6ec70cfae..5356ab66d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import javax.persistence.AttributeConverter; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -79,12 +80,12 @@ import org.hibernate.cfg.annotations.NamedEntityGraphDefinition; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.engine.spi.FilterDefinition; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; @@ -228,7 +229,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector } @Override - public NamedQueryRepository buildNamedQueryRepository(SessionFactoryImpl sessionFactory) { + public NamedQueryRepository buildNamedQueryRepository(SessionFactoryImplementor sessionFactory) { throw new UnsupportedOperationException( "#buildNamedQueryRepository should not be called on InFlightMetadataCollector" ); } @@ -534,8 +535,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector } @Override - public java.util.Collection getNamedHqlQueryMappings() { - return namedQueryMap.values(); + public void visitNamedHqlQueryDefinitions(Consumer definitionConsumer) { + namedQueryMap.values().forEach( definitionConsumer ); } @Override @@ -580,8 +581,8 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector } @Override - public java.util.Collection getNamedNativeQueryMappings() { - return namedNativeQueryMap.values(); + public void visitNamedNativeQueryDefinitions(Consumer definitionConsumer) { + namedNativeQueryMap.values().forEach( definitionConsumer ); } @Override @@ -615,9 +616,10 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Named stored-procedure handling + @Override - public java.util.Collection getNamedProcedureCallMappings() { - return namedProcedureCallMap.values(); + public void visitNamedProcedureCallDefinition(Consumer definitionConsumer) { + namedProcedureCallMap.values().forEach( definitionConsumer ); } @Override @@ -649,13 +651,13 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector // result-set mapping handling @Override - public Map getResultSetMappingDefinitions() { - return sqlResultSetMappingMap; + public NamedResultSetMappingDefinition getResultSetMapping(String name) { + return sqlResultSetMappingMap.get( name ); } @Override - public NamedResultSetMappingDefinition getResultSetMapping(String name) { - return sqlResultSetMappingMap.get( name ); + public void visitNamedResultSetMappingDefinition(Consumer definitionConsumer) { + sqlResultSetMappingMap.values().forEach( definitionConsumer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java index cdba765bac..4a1725a27b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/MetadataImpl.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -38,7 +39,6 @@ import org.hibernate.dialect.function.SQLFunction; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; -import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.mapping.Collection; import org.hibernate.mapping.FetchProfile; import org.hibernate.mapping.MappedSuperclass; @@ -85,6 +85,7 @@ public class MetadataImpl implements MetadataImplementor, Serializable { private final Map sqlFunctionMap; private final Database database; + @SuppressWarnings("WeakerAccess") public MetadataImpl( UUID uuid, MetadataBuildingOptions metadataBuildingOptions, @@ -234,8 +235,8 @@ public class MetadataImpl implements MetadataImplementor, Serializable { } @Override - public java.util.Collection getNamedHqlQueryMappings() { - return namedQueryMap.values(); + public void visitNamedHqlQueryDefinitions(Consumer definitionConsumer) { + namedQueryMap.values().forEach( definitionConsumer ); } @Override @@ -244,13 +245,13 @@ public class MetadataImpl implements MetadataImplementor, Serializable { } @Override - public java.util.Collection getNamedNativeQueryMappings() { - return namedNativeQueryMap.values(); + public void visitNamedNativeQueryDefinitions(Consumer definitionConsumer) { + namedNativeQueryMap.values().forEach( definitionConsumer ); } @Override - public java.util.Collection getNamedProcedureCallMappings() { - return namedProcedureCallMap.values(); + public void visitNamedProcedureCallDefinition(Consumer definitionConsumer) { + namedProcedureCallMap.values().forEach( definitionConsumer ); } @Override @@ -259,8 +260,8 @@ public class MetadataImpl implements MetadataImplementor, Serializable { } @Override - public Map getResultSetMappingDefinitions() { - return sqlResultSetMappingMap; + public void visitNamedResultSetMappingDefinition(Consumer definitionConsumer) { + sqlResultSetMappingMap.values().forEach( definitionConsumer ); } @Override @@ -318,7 +319,7 @@ public class MetadataImpl implements MetadataImplementor, Serializable { } @Override - public NamedQueryRepository buildNamedQueryRepository(SessionFactoryImpl sessionFactory) { + public NamedQueryRepository buildNamedQueryRepository(SessionFactoryImplementor sessionFactory) { return new NamedQueryRepositoryImpl( buildNamedHqlMementos( sessionFactory ), buildNamedNativeMementos( sessionFactory ), @@ -335,7 +336,7 @@ public class MetadataImpl implements MetadataImplementor, Serializable { return map; } - private Map buildNamedNativeMementos(SessionFactoryImpl sessionFactory) { + private Map buildNamedNativeMementos(SessionFactoryImplementor sessionFactory) { final HashMap map = new HashMap<>(); if ( namedNativeQueryMap != null ) { namedNativeQueryMap.forEach( (key, value) -> map.put( key, value.resolve( sessionFactory ) ) ); @@ -351,7 +352,7 @@ public class MetadataImpl implements MetadataImplementor, Serializable { return map; } - private Map buildResultSetMappingMementos(SessionFactoryImpl sessionFactory) { + private Map buildResultSetMappingMementos(SessionFactoryImplementor sessionFactory) { final HashMap map = new HashMap<>(); if ( sqlResultSetMappingMap != null ) { sqlResultSetMappingMap.forEach( (key, value) -> map.put( key, value.resolve( sessionFactory ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java index dd484e5065..d107dad2e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingMetadata.java @@ -10,19 +10,17 @@ import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; import org.hibernate.MappingException; import org.hibernate.SessionFactory; import org.hibernate.boot.SessionFactoryBuilder; -import org.hibernate.boot.internal.NamedProcedureCallDefinitionImpl; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.relational.Database; import org.hibernate.cfg.annotations.NamedEntityGraphDefinition; import org.hibernate.dialect.function.SQLFunction; -import org.hibernate.query.sql.spi.ResultSetMappingDescriptor; import org.hibernate.engine.spi.FilterDefinition; -import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl; import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.mapping.FetchProfile; @@ -118,13 +116,13 @@ public abstract class AbstractDelegatingMetadata implements MetadataImplementor } @Override - public NamedHqlQueryMementoImpl getNamedHqlQueryMapping(String name) { + public NamedHqlQueryDefinition getNamedHqlQueryMapping(String name) { return delegate.getNamedHqlQueryMapping( name ); } @Override - public Collection getNamedHqlQueryMappings() { - return delegate.getNamedHqlQueryMappings(); + public void visitNamedHqlQueryDefinitions(Consumer definitionConsumer) { + delegate.visitNamedHqlQueryDefinitions( definitionConsumer ); } @Override @@ -133,23 +131,23 @@ public abstract class AbstractDelegatingMetadata implements MetadataImplementor } @Override - public Collection getNamedNativeQueryMappings() { - return delegate.getNamedNativeQueryMappings(); + public void visitNamedNativeQueryDefinitions(Consumer definitionConsumer) { + delegate.visitNamedNativeQueryDefinitions( definitionConsumer ); } @Override - public Collection getNamedProcedureCallMappings() { - return delegate.getNamedProcedureCallMappings(); + public void visitNamedProcedureCallDefinition(Consumer definitionConsumer) { + delegate.visitNamedProcedureCallDefinition( definitionConsumer ); } @Override - public ResultSetMappingDescriptor getResultSetMapping(String name) { + public NamedResultSetMappingDefinition getResultSetMapping(String name) { return delegate.getResultSetMapping( name ); } @Override - public Map getResultSetMappingDefinitions() { - return delegate.getResultSetMappingDefinitions(); + public void visitNamedResultSetMappingDefinition(Consumer definitionConsumer) { + delegate.visitNamedResultSetMappingDefinition( definitionConsumer ); } @Override @@ -224,11 +222,6 @@ public abstract class AbstractDelegatingMetadata implements MetadataImplementor return delegate.getTypeResolver(); } - @Override - public NamedQueryRepository buildNamedQueryRepository(SessionFactoryImpl sessionFactory) { - return delegate.buildNamedQueryRepository( sessionFactory ); - } - @Override public void validate() throws MappingException { delegate.validate(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java index 0bd1a400c3..67dc546a7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/MetadataImplementor.java @@ -6,14 +6,12 @@ */ package org.hibernate.boot.spi; -import java.util.Collection; import java.util.Set; import org.hibernate.MappingException; import org.hibernate.boot.Metadata; -import org.hibernate.cache.cfg.internal.DomainDataRegionConfigImpl; import org.hibernate.engine.spi.Mapping; -import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.mapping.MappedSuperclass; import org.hibernate.query.spi.NamedQueryRepository; import org.hibernate.type.Type; @@ -23,8 +21,6 @@ import org.hibernate.type.spi.TypeConfiguration; /** * The SPI-level Metadata contract. * - * @todo Should Mapping be implemented here, or on InFlightMetadataCollector instead? - * * @author Steve Ebersole * * @since 5.0 @@ -32,8 +28,6 @@ import org.hibernate.type.spi.TypeConfiguration; public interface MetadataImplementor extends Metadata, Mapping { /** * Access to the options used to build this Metadata - * - * @return */ MetadataBuildingOptions getMetadataBuildingOptions(); @@ -54,7 +48,7 @@ public interface MetadataImplementor extends Metadata, Mapping { @Deprecated TypeResolver getTypeResolver(); - NamedQueryRepository buildNamedQueryRepository(SessionFactoryImpl sessionFactory); + NamedQueryRepository buildNamedQueryRepository(SessionFactoryImplementor sessionFactory); void validate() throws MappingException; 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 be055237b3..626cc6450c 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 @@ -33,6 +33,7 @@ import org.hibernate.loader.BatchFetchStyle; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; import org.hibernate.query.criteria.LiteralHandlingMode; +import org.hibernate.query.sqm.produce.function.SqmFunctionRegistry; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.stat.Statistics; @@ -315,5 +316,8 @@ public interface SessionFactoryOptions { return false; } + SqmFunctionRegistry getSqmFunctionRegistry(); + boolean isOmitJoinOfSuperclassTablesEnabled(); + } 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 280dd19e6e..67e1537d60 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -18,7 +18,7 @@ import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode; import org.hibernate.query.internal.ParameterMetadataImpl; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.resource.beans.container.spi.ExtendedBeanManager; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; @@ -1218,14 +1218,14 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { String QUERY_PLAN_CACHE_ENABLED = "hibernate.query.plan_cache_enabled"; /** - * The maximum number of strong references maintained by {@link QueryPlanCache}. Default is 128. + * The maximum number of strong references maintained by {@link QueryInterpretationCache}. Default is 128. * @deprecated in favor of {@link #QUERY_PLAN_CACHE_PARAMETER_METADATA_MAX_SIZE} */ @Deprecated String QUERY_PLAN_CACHE_MAX_STRONG_REFERENCES = "hibernate.query.plan_cache_max_strong_references"; /** - * The maximum number of soft references maintained by {@link QueryPlanCache}. Default is 2048. + * The maximum number of soft references maintained by {@link QueryInterpretationCache}. Default is 2048. * @deprecated in favor of {@link #QUERY_PLAN_CACHE_MAX_SIZE} */ @Deprecated @@ -1239,13 +1239,13 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { *
  • {@link org.hibernate.engine.query.spi.NativeSQLQueryPlan}
  • * * - * maintained by {@link QueryPlanCache}. Default is 2048. + * maintained by {@link QueryInterpretationCache}. Default is 2048. */ String QUERY_PLAN_CACHE_MAX_SIZE = "hibernate.query.plan_cache_max_size"; /** * The maximum number of {@link ParameterMetadataImpl} maintained - * by {@link QueryPlanCache}. Default is 128. + * by {@link QueryInterpretationCache}. Default is 128. */ String QUERY_PLAN_CACHE_PARAMETER_METADATA_MAX_SIZE = "hibernate.query.plan_parameter_metadata_max_size"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 385647a858..c67f1fc8b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -97,6 +97,8 @@ import org.hibernate.mapping.Table; import org.hibernate.persister.entity.Lockable; import org.hibernate.procedure.internal.StandardCallableStatementSupport; import org.hibernate.procedure.spi.CallableStatementSupport; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.spi.QueryOptions; import org.hibernate.service.ServiceRegistry; import org.hibernate.sql.ANSICaseFragment; import org.hibernate.sql.ANSIJoinFragment; @@ -261,6 +263,10 @@ public abstract class Dialect implements ConversionContext { uniqueDelegate = new DefaultUniqueDelegate( this ); } + public void initializeFunctionRegistry(QueryEngine queryEngine) { + + } + /** * Get an instance of the dialect specified by the current System properties. * @deprecated this static method will be removed. @@ -2737,6 +2743,20 @@ public abstract class Dialect implements ConversionContext { return useFollowOnLocking( null ); } + /** + * Some dialects have trouble applying pessimistic locking depending upon what other query options are + * specified (paging, ordering, etc). This method allows these dialects to request that locking be applied + * by subsequent selects. + * + * @return {@code true} indicates that the dialect requests that locking be applied by subsequent select; + * {@code false} (the default) indicates that locking should be applied to the main SQL statement.. + * + * @since 5.2 + */ + public boolean useFollowOnLocking(String sql, QueryOptions queryOptions) { + return false; + } + /** * Some dialects have trouble applying pessimistic locking depending upon what other query options are * specified (paging, ordering, etc). This method allows these dialects to request that locking be applied diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java index 930238bf51..a8ed79cc78 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryDelegatingImpl.java @@ -17,12 +17,10 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceUnitUtil; import javax.persistence.Query; import javax.persistence.SynchronizationType; -import javax.persistence.criteria.CriteriaBuilder; import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; -import org.hibernate.Interceptor; import org.hibernate.MappingException; import org.hibernate.Session; import org.hibernate.SessionFactory; @@ -36,27 +34,28 @@ import org.hibernate.cfg.Settings; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.SQLFunctionRegistry; -import org.hibernate.query.sql.spi.ResultSetMappingDescriptor; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.profile.FetchProfile; -import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl; -import org.hibernate.query.spi.QueryPlanCache; import org.hibernate.exception.spi.SQLExceptionConverter; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.id.IdentifierGenerator; import org.hibernate.id.factory.IdentifierGeneratorFactory; import org.hibernate.metadata.ClassMetadata; import org.hibernate.metadata.CollectionMetadata; +import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.EntityNotFoundDelegate; -import org.hibernate.query.spi.NamedQueryRepository; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; +import org.hibernate.type.spi.TypeConfiguration; /** * Base delegating implementation of the SessionFactory and SessionFactoryImplementor @@ -208,6 +207,11 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, return delegate.getTypeResolver(); } + @Override + public IdentifierGenerator getIdentifierGenerator(String rootEntityName) { + return delegate.getIdentifierGenerator( rootEntityName ); + } + @Override public Map getProperties() { return delegate.getProperties(); @@ -243,26 +247,6 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, return delegate.getDialect(); } - @Override - public Interceptor getInterceptor() { - return delegate.getInterceptor(); - } - - @Override - public QueryPlanCache getQueryPlanCache() { - return delegate.getQueryPlanCache(); - } - - @Override - public Type[] getReturnTypes(String queryString) throws HibernateException { - return delegate.getReturnTypes( queryString ); - } - - @Override - public String[] getReturnAliases(String queryString) throws HibernateException { - return delegate.getReturnAliases( queryString ); - } - @Override public String[] getImplementors(String className) throws MappingException { return delegate.getImplementors( className ); @@ -283,36 +267,6 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, return delegate.getStatistics(); } - @Override - public NamedHqlQueryMementoImpl getNamedQuery(String queryName) { - return delegate.getNamedQuery( queryName ); - } - - @Override - public void registerNamedQueryDefinition(String name, NamedHqlQueryMementoImpl memento) { - delegate.registerNamedQueryDefinition( name, memento ); - } - - @Override - public NamedSQLQueryDefinition getNamedSQLQuery(String queryName) { - return delegate.getNamedSQLQuery( queryName ); - } - - @Override - public void registerNamedSQLQueryDefinition(String name, NamedSQLQueryDefinition definition) { - delegate.registerNamedSQLQueryDefinition( name, definition ); - } - - @Override - public ResultSetMappingDescriptor getResultSetMapping(String name) { - return delegate.getResultSetMapping( name ); - } - - @Override - public IdentifierGenerator getIdentifierGenerator(String rootEntityName) { - return delegate.getIdentifierGenerator( rootEntityName ); - } - @Override public SQLExceptionConverter getSQLExceptionConverter() { return delegate.getSQLExceptionConverter(); @@ -353,11 +307,21 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, return delegate.getFetchProfile( name ); } + @Override + public JpaMetamodel getJpaMetamodel() { + return delegate.getJpaMetamodel(); + } + @Override public ServiceRegistryImplementor getServiceRegistry() { return delegate.getServiceRegistry(); } + @Override + public Integer getMaximumFetchDepth() { + return delegate.getMaximumFetchDepth(); + } + @Override public void addObserver(SessionFactoryObserver observer) { delegate.addObserver( observer ); @@ -373,11 +337,6 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, return delegate.getCurrentTenantIdentifierResolver(); } - @Override - public NamedQueryRepository getNamedQueryRepository() { - return delegate.getNamedQueryRepository(); - } - @Override public Iterable iterateEntityNameResolvers() { return delegate.iterateEntityNameResolvers(); @@ -428,6 +387,16 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, return delegate.getName(); } + @Override + public TypeConfiguration getTypeConfiguration() { + return delegate.getTypeConfiguration(); + } + + @Override + public QueryEngine getQueryEngine() { + return delegate.getQueryEngine(); + } + @Override public Reference getReference() throws NamingException { return delegate.getReference(); @@ -459,7 +428,7 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, } @Override - public CriteriaBuilder getCriteriaBuilder() { + public NodeBuilder getCriteriaBuilder() { return delegate.getCriteriaBuilder(); } @@ -474,12 +443,12 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor, } @Override - public Type resolveParameterBindType(Object bindValue) { + public AllowableParameterType resolveParameterBindType(Object bindValue) { return delegate.resolveParameterBindType( bindValue ); } @Override - public Type resolveParameterBindType(Class clazz) { + public AllowableParameterType resolveParameterBindType(Class clazz) { return delegate.resolveParameterBindType( clazz ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index 1ebabee9a0..ff75727bcd 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -15,13 +15,11 @@ import javax.persistence.EntityGraph; import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; -import org.hibernate.Interceptor; import org.hibernate.MappingException; import org.hibernate.Metamodel; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; -import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.cfg.Settings; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; @@ -38,18 +36,12 @@ import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.EntityNotFoundDelegate; -import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl; -import org.hibernate.query.hql.spi.NamedHqlQueryMemento; -import org.hibernate.query.spi.NamedQueryRepository; -import org.hibernate.query.spi.NamedResultSetMappingMemento; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryParameterBindingTypeResolver; -import org.hibernate.query.spi.QueryPlanCache; -import org.hibernate.query.sql.spi.NamedNativeQueryMemento; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.produce.spi.SqmCreationContext; import org.hibernate.service.spi.ServiceRegistryImplementor; -import org.hibernate.sql.ast.produce.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; @@ -117,38 +109,6 @@ public interface SessionFactoryImplementor */ ServiceRegistryImplementor getServiceRegistry(); - /** - * Get the factory scoped interceptor for this factory. - * - * @return The factory scope interceptor, or null if none. - * - * @deprecated (since 5.2) if access to the SessionFactory-scoped Interceptor is needed, use - * {@link SessionFactoryOptions#getInterceptor()} instead. However, generally speaking this access - * is not needed. - */ - @Deprecated - Interceptor getInterceptor(); - - /** - * Access to the cachres of HQL/JPQL and native query plans. - * - * @return The query plan cache - * - * @deprecated (since 5.2) it will be replaced with the new QueryEngine concept introduced in 6.0 - */ - @Deprecated - QueryPlanCache getQueryPlanCache(); - - /** - * Provides access to the named query repository - * - * @return The repository for named query definitions - * - * @deprecated (since 5.2) it will be replaced with the new QueryEngine concept introduced in 6.0 - */ - @Deprecated - NamedQueryRepository getNamedQueryRepository(); - /** * Retrieve fetch profile by name. * @@ -212,28 +172,6 @@ public interface SessionFactoryImplementor // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Deprecations - /** - * Get the return types of a query - * - * @deprecated No replacement. - */ - @Deprecated - default Type[] getReturnTypes(String queryString) { - throw new UnsupportedOperationException( "Concept of query return org.hibernate.type.Types is no longer supported" ); - } - - /** - * Get the return aliases of a query - * - * @deprecated No replacement. - */ - @Deprecated - default String[] getReturnAliases(String queryString) { - throw new UnsupportedOperationException( "Access to of query return aliases via Sessionfactory is no longer supported" ); - } - - - /** * @deprecated (since 5.2) Just use {@link #getStatistics} (with covariant return here as {@link StatisticsImplementor}). */ @@ -242,51 +180,6 @@ public interface SessionFactoryImplementor return getStatistics(); } - - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // NamedQueryRepository - - /** - * @deprecated (since 5.2) Use {@link NamedQueryRepository#getHqlQueryMemento} instead. - */ - @Deprecated - default NamedHqlQueryMemento getNamedQuery(String queryName) { - return getNamedQueryRepository().getHqlQueryMemento( queryName ); - } - - /** - * @deprecated (since 5.2) Use {@link NamedQueryRepository#registerHqlQueryMemento} instead. - */ - @Deprecated - default void registerNamedQueryDefinition(String name, NamedHqlQueryMementoImpl memento) { - getNamedQueryRepository().registerHqlQueryMemento( name, memento ); - } - - /** - * @deprecated (since 5.2) Use {@link NamedQueryRepository#getNativeQueryMemento} instead. - */ - @Deprecated - default NamedNativeQueryMemento getNamedSQLQuery(String queryName) { - return getNamedQueryRepository().getNativeQueryMemento( queryName ); - } - - /** - * @deprecated (since 5.2) Use {@link NamedQueryRepository#registerNativeQueryMemento} instead. - */ - @Deprecated - default void registerNamedSQLQueryDefinition(String name, NamedNativeQueryMemento memento) { - getNamedQueryRepository().registerNativeQueryMemento( name, memento ); - } - - /** - * @deprecated (since 5.2) Use {@link NamedQueryRepository#getResultSetMappingMemento} instead. - */ - @Deprecated - default NamedResultSetMappingMemento getResultSetMapping(String name) { - return getNamedQueryRepository().getResultSetMappingMemento( name ); - } - /** * Get the JdbcServices. * diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index a18b11f857..943423518b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -606,11 +606,11 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont } protected HQLQueryPlan getQueryPlan(String query, boolean shallow) throws HibernateException { - return getFactory().getQueryPlanCache().getHQLQueryPlan( query, shallow, getLoadQueryInfluencers().getEnabledFilters() ); + return getFactory().getQueryInterpretationCache().getHQLQueryPlan( query, shallow, getLoadQueryInfluencers().getEnabledFilters() ); } protected NativeSQLQueryPlan getNativeQueryPlan(NativeSQLQuerySpecification spec) throws HibernateException { - return getFactory().getQueryPlanCache().getNativeSQLQueryPlan( spec ); + return getFactory().getQueryInterpretationCache().getNativeSQLQueryPlan( spec ); } @Override @@ -640,7 +640,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont } private NativeQueryImplementor createNativeQuery(NamedNativeQueryMemento queryDefinition, boolean isOrdinalParameterZeroBased) { - final ParameterMetadata parameterMetadata = factory.getQueryPlanCache().getSQLParameterMetadata( + final ParameterMetadata parameterMetadata = factory.getQueryInterpretationCache().getSQLParameterMetadata( queryDefinition.getQueryString(), isOrdinalParameterZeroBased ); @@ -733,7 +733,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @SuppressWarnings({"unchecked", "WeakerAccess", "StatementWithEmptyBody"}) protected void resultClassChecking(Class resultClass, Query hqlQuery) { // make sure the query is a select -> HHH-7192 - final HQLQueryPlan queryPlan = getFactory().getQueryPlanCache().getHQLQueryPlan( + final HQLQueryPlan queryPlan = getFactory().getQueryInterpretationCache().getHQLQueryPlan( hqlQuery.getQueryString(), false, getLoadQueryInfluencers().getEnabledFilters() @@ -829,7 +829,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont final NativeQueryImpl query = new NativeQueryImpl( queryDefinition, this, - factory.getQueryPlanCache().getSQLParameterMetadata( queryDefinition.getQueryString(), false ) + factory.getQueryInterpretationCache().getSQLParameterMetadata( queryDefinition.getQueryString(), false ) ); if ( Tuple.class.equals( resultType ) ) { query.setResultTransformer( new NativeQueryTupleTransformer() ); @@ -982,7 +982,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont queryString, false, this, - getFactory().getQueryPlanCache().getSQLParameterMetadata( queryString, isOrdinalParameterZeroBased ) + getFactory().getQueryInterpretationCache().getSQLParameterMetadata( queryString, isOrdinalParameterZeroBased ) ); query.setComment( "dynamic native SQL query" ); return query; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 93be6489bf..da195a98b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -73,7 +73,6 @@ import org.hibernate.engine.jndi.spi.JndiService; import org.hibernate.engine.profile.Association; import org.hibernate.engine.profile.Fetch; import org.hibernate.engine.profile.FetchProfile; -import org.hibernate.engine.query.spi.ReturnMetadata; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.SessionBuilderImplementor; import org.hibernate.engine.spi.SessionEventListenerManager; @@ -97,15 +96,18 @@ import org.hibernate.jpa.internal.PersistenceUnitUtilImpl; import org.hibernate.mapping.RootClass; import org.hibernate.metadata.ClassMetadata; import org.hibernate.metadata.CollectionMetadata; +import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.metamodel.model.domain.JpaMetamodel; import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; -import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.proxy.HibernateProxyHelper; -import org.hibernate.query.hql.internal.NamedHqlQueryMementoImpl; -import org.hibernate.query.hql.spi.NamedHqlQueryMemento; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.hql.spi.HqlQueryImplementor; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; import org.hibernate.query.NativeQuery; @@ -125,9 +127,9 @@ import org.hibernate.service.spi.SessionFactoryServiceRegistryFactory; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.tool.schema.spi.DelayedDropAction; import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; -import org.hibernate.type.SerializableType; import org.hibernate.type.Type; import org.hibernate.type.TypeResolver; +import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; @@ -180,8 +182,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { private final transient NodeBuilder criteriaBuilder; private final PersistenceUnitUtil jpaPersistenceUnitUtil; private final transient CacheImplementor cacheAccess; - private final transient NamedQueryRepository namedQueryRepository; - private final transient QueryPlanCache queryPlanCache; + private final transient QueryEngine queryEngine; private final transient CurrentSessionContext currentSessionContext; @@ -213,6 +214,8 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { prepareEventListeners( metadata ); + this.queryEngine = QueryEngine.from( this, metadata ); + final CfgXmlAccessService cfgXmlAccessService = serviceRegistry.getService( CfgXmlAccessService.class ); String sfName = settings.getSessionFactoryName(); @@ -244,7 +247,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { this.sqlFunctionRegistry = new SQLFunctionRegistry( jdbcServices.getJdbcEnvironment().getDialect(), options.getCustomSqlFunctionMap() ); this.cacheAccess = this.serviceRegistry.getService( CacheImplementor.class ); - this.criteriaBuilder = new SqmCriteriaNodeBuilder.create( this ); + this.criteriaBuilder = SqmCriteriaNodeBuilder.create( this ); this.jpaPersistenceUnitUtil = new PersistenceUnitUtilImpl( this ); for ( SessionFactoryObserver sessionFactoryObserver : options.getSessionFactoryObservers() ) { @@ -259,8 +262,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { LOG.debugf( "Session factory constructed with filter configurations : %s", filters ); LOG.debugf( "Instantiating session factory with properties: %s", properties ); - this.queryPlanCache = new QueryPlanCache( this ); - class IntegratorObserver implements SessionFactoryObserver { private ArrayList integrators = new ArrayList<>(); @@ -301,9 +302,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { this.metamodel = (MetamodelImplementor) metadata.getTypeConfiguration().scope( this ) .create( metadata, determineJpaMetaModelPopulationSetting( properties ) ); - //Named Queries: - this.namedQueryRepository = metadata.buildNamedQueryRepository( this ); - settings.getMultiTableBulkIdStrategy().prepare( jdbcServices, buildLocalConnectionAccess(), @@ -320,26 +318,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { currentSessionContext = buildCurrentSessionContext(); - //checking for named queries - if ( settings.isNamedQueryStartupCheckingEnabled() ) { - final Map errors = checkNamedQueries(); - if ( !errors.isEmpty() ) { - StringBuilder failingQueries = new StringBuilder( "Errors in named queries: " ); - String separator = System.lineSeparator(); - - for ( Map.Entry entry : errors.entrySet() ) { - LOG.namedQueryError( entry.getKey(), entry.getValue() ); - - failingQueries - .append( separator) - .append( entry.getKey() ) - .append( " failed because of: " ) - .append( entry.getValue() ); - } - throw new HibernateException( failingQueries.toString() ); - } - } - // this needs to happen after persisters are all ready to go... this.fetchProfiles = new HashMap<>(); for ( org.hibernate.mapping.FetchProfile mappingProfile : metadata.getFetchProfiles() ) { @@ -408,7 +386,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { eventListenerRegistry.prepare( metadata ); for ( Map.Entry entry : ( (Map) cfgService.getSettings() ).entrySet() ) { - if ( !String.class.isInstance( entry.getKey() ) ) { + if ( !(entry.getKey() instanceof String) ) { continue; } final String propertyName = (String) entry.getKey(); @@ -421,6 +399,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { final EventType eventType = EventType.resolveEventTypeByName( eventTypeName ); final EventListenerGroup eventListenerGroup = eventListenerRegistry.getEventListenerGroup( eventType ); for ( String listenerImpl : LISTENER_SEPARATION_PATTERN.split( ( (String) entry.getValue() ) ) ) { + //noinspection unchecked eventListenerGroup.appendListener( instantiate( listenerImpl, classLoaderService ) ); } } @@ -534,6 +513,16 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { return name; } + @Override + public TypeConfiguration getTypeConfiguration() { + return getMetamodel().getTypeConfiguration(); + } + + @Override + public QueryEngine getQueryEngine() { + return null; + } + @Override public JdbcServices getJdbcServices() { return jdbcServices; @@ -555,25 +544,12 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { return metamodel.getTypeConfiguration().getTypeResolver(); } - public QueryPlanCache getQueryPlanCache() { - return queryPlanCache; - } - - private Map checkNamedQueries() throws HibernateException { - return namedQueryRepository.checkNamedQueries( queryPlanCache ); - } - @Override public DeserializationResolver getDeserializationResolver() { - return new DeserializationResolver() { - @Override - public SessionFactoryImplementor resolve() { - return (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory( - uuid, - name - ); - } - }; + return (DeserializationResolver) () -> (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory( + uuid, + name + ); } @SuppressWarnings("deprecation") @@ -702,12 +678,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { ); } - @Override - public NamedQueryRepository getNamedQueryRepository() { - return namedQueryRepository; - } - - public Type getIdentifierType(String className) throws MappingException { return getMetamodel().entityPersister( className ).getIdentifierType(); } @@ -715,18 +685,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { return getMetamodel().entityPersister( className ).getIdentifierPropertyName(); } - public Type[] getReturnTypes(String queryString) throws HibernateException { - final ReturnMetadata metadata = queryPlanCache.getHQLQueryPlan( queryString, false, Collections.EMPTY_MAP ) - .getReturnMetadata(); - return metadata == null ? null : metadata.getReturnTypes(); - } - - public String[] getReturnAliases(String queryString) throws HibernateException { - final ReturnMetadata metadata = queryPlanCache.getHQLQueryPlan( queryString, false, Collections.EMPTY_MAP ) - .getReturnMetadata(); - return metadata == null ? null : metadata.getReturnAliases(); - } - public ClassMetadata getClassMetadata(Class persistentClass) throws HibernateException { return getClassMetadata( persistentClass.getName() ); } @@ -800,9 +758,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { metamodel.close(); } - if ( queryPlanCache != null ) { - queryPlanCache.cleanup(); - } + queryEngine.close(); if ( delayedDropAction != null ) { delayedDropAction.perform( serviceRegistry ); @@ -839,31 +795,34 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { // first, handle StoredProcedureQuery try { - final ProcedureCall unwrapped = query.unwrap( ProcedureCall.class ); + final ProcedureCallImplementor unwrapped = query.unwrap( ProcedureCallImplementor.class ); if ( unwrapped != null ) { - addNamedStoredProcedureQuery( name, unwrapped ); + getQueryEngine().getNamedQueryRepository().registerCallableQueryMemento( + name, + unwrapped.toMemento( name ) + ); return; } } catch ( PersistenceException ignore ) { - // this means 'query' is not a StoredProcedureQueryImpl + // this means 'query' is not a ProcedureCallImplementor } // then try as a native-SQL or JPQL query try { - org.hibernate.query.Query hibernateQuery = query.unwrap( org.hibernate.query.Query.class ); + QueryImplementor hibernateQuery = query.unwrap( QueryImplementor.class ); if ( hibernateQuery != null ) { // create and register the proper NamedQueryDefinition... - if ( NativeQuery.class.isInstance( hibernateQuery ) ) { - getNamedQueryRepository().registerNamedSQLQueryDefinition( + if ( hibernateQuery instanceof NativeQueryImplementor ) { + getQueryEngine().getNamedQueryRepository().registerNativeQueryMemento( name, - extractSqlQueryDefinition( (NativeQuery) hibernateQuery, name ) + ( (NativeQueryImplementor) hibernateQuery ).toMemento( name ) ); } else { - getNamedQueryRepository().registerNamedQueryDefinition( + getQueryEngine().getNamedQueryRepository().registerHqlQueryMemento( name, - extractHqlQueryDefinition( hibernateQuery, name ) + ( ( HqlQueryImplementor ) hibernateQuery ).toMemento( name ) ); } return; @@ -882,56 +841,6 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { ); } - private void addNamedStoredProcedureQuery(String name, ProcedureCall procedureCall) { - getNamedQueryRepository().registerNamedProcedureCallMemento( - name, - procedureCall.extractMemento( procedureCall.getHints() ) - ); - } - - private NamedSQLQueryDefinition extractSqlQueryDefinition(org.hibernate.query.NativeQuery nativeSqlQuery, String name) { - final NamedNativeQueryMementoBuilder builder = new NamedNativeQueryMementoBuilder( name ); - fillInNamedQueryBuilder( builder, nativeSqlQuery ); - builder.setCallable( nativeSqlQuery.isCallable() ) - .setQuerySpaces( nativeSqlQuery.getSynchronizedQuerySpaces() ) - .setQueryReturns( nativeSqlQuery.getQueryReturns() ); - return builder.createNamedQueryDefinition(); - } - - private NamedHqlQueryMementoImpl extractHqlQueryDefinition(org.hibernate.query.Query hqlQuery, String name) { - final NamedHqlQueryMemento.Builder builder = new NamedHqlQueryMemento.Builder( name ); - fillInNamedQueryBuilder( builder, hqlQuery ); - // LockOptions only valid for HQL/JPQL queries... - builder.setLockOptions( hqlQuery.getLockOptions().makeCopy() ); - return builder.createNamedQueryDefinition(); - } - - private void fillInNamedQueryBuilder(NamedHqlQueryMemento.Builder builder, org.hibernate.query.Query query) { - builder.setQuery( query.getQueryString() ) - .setComment( query.getComment() ) - .setCacheable( query.isCacheable() ) - .setCacheRegion( query.getCacheRegion() ) - .setCacheMode( query.getCacheMode() ) - .setReadOnly( query.isReadOnly() ) - .setFlushMode( query.getHibernateFlushMode() ); - - if ( query.getQueryOptions().getFirstRow() != null ) { - builder.setFirstResult( query.getQueryOptions().getFirstRow() ); - } - - if ( query.getQueryOptions().getMaxRows() != null ) { - builder.setMaxResults( query.getQueryOptions().getMaxRows() ); - } - - if ( query.getQueryOptions().getTimeout() != null ) { - builder.setTimeout( query.getQueryOptions().getTimeout() ); - } - - if ( query.getQueryOptions().getFetchSize() != null ) { - builder.setFetchSize( query.getQueryOptions().getFetchSize() ); - } - } - @Override public T unwrap(Class type) { if ( type.isAssignableFrom( SessionFactory.class ) ) { @@ -1038,6 +947,16 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { } } + @Override + public JpaMetamodel getJpaMetamodel() { + return getMetamodel().getJpaMetamodel(); + } + + @Override + public Integer getMaximumFetchDepth() { + return getSessionFactoryOptions().getMaximumFetchDepth(); + } + @Override public ServiceRegistryImplementor getServiceRegistry() { return serviceRegistry; @@ -1061,7 +980,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { } @Override - public Type resolveParameterBindType(Object bindValue) { + public AllowableParameterType resolveParameterBindType(Object bindValue) { if ( bindValue == null ) { // we can't guess return null; @@ -1071,27 +990,8 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { } @Override - public Type resolveParameterBindType(Class clazz){ - String typename = clazz.getName(); - Type type = getTypeResolver().heuristicType( typename ); - boolean serializable = type != null && type instanceof SerializableType; - if ( type == null || serializable ) { - try { - getMetamodel().entityPersister( clazz.getName() ); - } - catch (MappingException me) { - if ( serializable ) { - return type; - } - else { - throw new HibernateException( "Could not determine a type for class: " + typename ); - } - } - return getTypeHelper().entity( clazz ); - } - else { - return type; - } + public AllowableParameterType resolveParameterBindType(Class javaType) { + return getMetamodel().resolveQueryParameterType( javaType ); } public static Interceptor configuredInterceptor(Interceptor interceptor, SessionFactoryOptions options) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java index ec94c1ceda..1c12259e85 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java @@ -41,12 +41,12 @@ public class SessionFactoryRegistry { /** * A map for mapping the UUID of a SessionFactory to the corresponding SessionFactory instance */ - private final ConcurrentHashMap sessionFactoryMap = new ConcurrentHashMap(); + private final ConcurrentHashMap sessionFactoryMap = new ConcurrentHashMap<>(); /** * A cross-reference for mapping a SessionFactory name to its UUID. Not all SessionFactories get named, */ - private final ConcurrentHashMap nameUuidXref = new ConcurrentHashMap(); + private final ConcurrentHashMap nameUuidXref = new ConcurrentHashMap<>(); private SessionFactoryRegistry() { LOG.debugf( "Initializing SessionFactoryRegistry : %s", this ); @@ -65,7 +65,7 @@ public class SessionFactoryRegistry { String uuid, String name, boolean isNameAlsoJndiName, - SessionFactory instance, + SessionFactoryImplementor instance, JndiService jndiService) { if ( uuid == null ) { throw new IllegalArgumentException( "SessionFactory UUID cannot be null" ); @@ -143,16 +143,16 @@ public class SessionFactoryRegistry { * * @return The SessionFactory */ - public SessionFactory getNamedSessionFactory(String name) { + public SessionFactoryImplementor getNamedSessionFactory(String name) { LOG.debugf( "Lookup: name=%s", name ); final String uuid = nameUuidXref.get( name ); // protect against NPE -- see HHH-8428 return uuid == null ? null : getSessionFactory( uuid ); } - public SessionFactory getSessionFactory(String uuid) { + public SessionFactoryImplementor getSessionFactory(String uuid) { LOG.debugf( "Lookup: uid=%s", uuid ); - final SessionFactory sessionFactory = sessionFactoryMap.get( uuid ); + final SessionFactoryImplementor sessionFactory = sessionFactoryMap.get( uuid ); if ( sessionFactory == null && LOG.isDebugEnabled() ) { LOG.debugf( "Not found: %s", uuid ); LOG.debug( sessionFactoryMap.toString() ); @@ -160,8 +160,8 @@ public class SessionFactoryRegistry { return sessionFactory; } - public SessionFactory findSessionFactory(String uuid, String name) { - SessionFactory sessionFactory = getSessionFactory( uuid ); + public SessionFactoryImplementor findSessionFactory(String uuid, String name) { + SessionFactoryImplementor sessionFactory = getSessionFactory( uuid ); if ( sessionFactory == null && StringHelper.isNotEmpty( name ) ) { sessionFactory = getNamedSessionFactory( name ); } @@ -205,10 +205,13 @@ public class SessionFactoryRegistry { LOG.factoryUnboundFromName( jndiName ); final String uuid = nameUuidXref.remove( jndiName ); + //noinspection StatementWithEmptyBody if ( uuid == null ) { // serious problem... but not sure what to do yet } - sessionFactoryMap.remove( uuid ); + else { + sessionFactoryMap.remove( uuid ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 52f0faeafd..813f9a49d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1691,7 +1691,7 @@ public final class SessionImpl if ( roleAfterFlush == null ) { throw new QueryException( "The collection was unreferenced" ); } - plan = factory.getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryInterpretationCache().getFilterQueryPlan( filter, roleAfterFlush.getRole(), shallow, @@ -1701,7 +1701,7 @@ public final class SessionImpl else { // otherwise, we only need to flush if there are in-memory changes // to the queried tables - plan = factory.getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryInterpretationCache().getFilterQueryPlan( filter, roleBeforeFlush.getRole(), shallow, @@ -1716,7 +1716,7 @@ public final class SessionImpl if ( roleAfterFlush == null ) { throw new QueryException( "The collection was dereferenced" ); } - plan = factory.getQueryPlanCache().getFilterQueryPlan( + plan = factory.getQueryInterpretationCache().getFilterQueryPlan( filter, roleAfterFlush.getRole(), shallow, @@ -2106,7 +2106,7 @@ public final class SessionImpl log.tracev( "Scroll SQL query: {0}", customQuery.getSQL() ); } - CustomLoader loader = getFactory().getQueryPlanCache().getNativeQueryInterpreter().createCustomLoader( customQuery, getFactory() ); + CustomLoader loader = getFactory().getQueryInterpretationCache().getNativeQueryInterpreter().createCustomLoader( customQuery, getFactory() ); autoFlushIfRequired( loader.getQuerySpaces() ); @@ -2130,7 +2130,7 @@ public final class SessionImpl log.tracev( "SQL query: {0}", customQuery.getSQL() ); } - CustomLoader loader = getFactory().getQueryPlanCache().getNativeQueryInterpreter().createCustomLoader( customQuery, getFactory() ); + CustomLoader loader = getFactory().getQueryInterpretationCache().getNativeQueryInterpreter().createCustomLoader( customQuery, getFactory() ); autoFlushIfRequired( loader.getQuerySpaces() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AllowableOutputParameterType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AllowableOutputParameterType.java index 948e430bd6..a8089783cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AllowableOutputParameterType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AllowableOutputParameterType.java @@ -34,6 +34,8 @@ public interface AllowableOutputParameterType extends AllowableParameterType< */ SqlTypeDescriptor getSqlTypeDescriptor(); + + /** * Perform the extraction * diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java index b29e89d37a..1aa868fc92 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/FunctionReturnImpl.java @@ -14,8 +14,8 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.model.domain.AllowableOutputParameterType; import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.procedure.spi.FunctionReturnImplementor; +import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ProcedureCallImplementor; -import org.hibernate.query.QueryParameter; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.type.spi.TypeConfiguration; @@ -108,17 +108,13 @@ public class FunctionReturnImpl implements FunctionReturnImplementor { } @Override - public ParameterMemento toMemento() { - // todo (6.0) : do we need a FunctionReturnMemento? - return new ParameterMemento() { - @Override - public QueryParameter toQueryParameter(SharedSessionContractImplementor session) { - if ( ormType != null ) { - return new FunctionReturnImpl( procedureCall, ormType ); - } - else { - return new FunctionReturnImpl( procedureCall, jdbcTypeCode ); - } + public NamedCallableQueryMemento.ParameterMemento toMemento() { + return session -> { + if ( ormType != null ) { + return new FunctionReturnImpl( procedureCall, ormType ); + } + else { + return new FunctionReturnImpl( procedureCall, jdbcTypeCode ); } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java index edb979213f..1fbdcc9823 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedCallableQueryMementoImpl.java @@ -19,7 +19,7 @@ import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.procedure.ProcedureCall; import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.AbstractNamedQueryMemento; import org.hibernate.query.spi.NamedQueryMemento; @@ -32,7 +32,7 @@ public class NamedCallableQueryMementoImpl extends AbstractNamedQueryMemento imp private final String callableName; private final ParameterStrategy parameterStrategy; - private final List parameterMementos; + private final List parameterMementos; private final String[] resultSetMappingNames; private final Class[] resultSetMappingClasses; @@ -47,7 +47,7 @@ public class NamedCallableQueryMementoImpl extends AbstractNamedQueryMemento imp String name, String callableName, ParameterStrategy parameterStrategy, - List parameterMementos, + List parameterMementos, String[] resultSetMappingNames, Class[] resultSetMappingClasses, Set querySpaces, @@ -86,7 +86,7 @@ public class NamedCallableQueryMementoImpl extends AbstractNamedQueryMemento imp } @Override - public List getParameterMementos() { + public List getParameterMementos() { return parameterMementos; } @@ -140,7 +140,7 @@ public class NamedCallableQueryMementoImpl extends AbstractNamedQueryMemento imp /** * A "disconnected" copy of the metadata for a parameter, that can be used in ProcedureCallMementoImpl. */ - public static class ParameterMementoImpl implements ParameterMemento { + public static class ParameterMementoImpl implements NamedCallableQueryMemento.ParameterMemento { private final Integer position; private final String name; private final ParameterMode mode; diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java deleted file mode 100644 index e5536efd32..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterBindImpl.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.procedure.internal; - -import javax.persistence.ParameterMode; -import javax.persistence.TemporalType; - -import org.hibernate.procedure.ParameterBind; -import org.hibernate.query.internal.BindingTypeHelper; -import org.hibernate.query.procedure.internal.ProcedureParamBindings; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; -import org.hibernate.type.Type; - -import org.jboss.logging.Logger; - -/** - * Implementation of the {@link ParameterBind} contract. - * - * @author Steve Ebersole - */ -public class ParameterBindImpl implements ParameterBind { - private static final Logger log = Logger.getLogger( ParameterBindImpl.class ); - - private final ProcedureParameterImplementor procedureParameter; - private final ProcedureParamBindings procedureParamBindings; - - private boolean isBound; - - private T value; - private Type hibernateType; - - private TemporalType explicitTemporalType; - - public ParameterBindImpl( - ProcedureParameterImplementor procedureParameter, - ProcedureParamBindings procedureParamBindings) { - this.procedureParameter = procedureParameter; - this.procedureParamBindings = procedureParamBindings; - - this.hibernateType = procedureParameter.getHibernateType(); - } - - @Override - public T getValue() { - return value; - } - - @Override - public TemporalType getExplicitTemporalType() { - return explicitTemporalType; - } - - @Override - public boolean isBound() { - return isBound; - } - - @Override - public void setBindValue(T value) { - internalSetValue( value ); - - if ( value != null && hibernateType == null ) { - hibernateType = procedureParamBindings.getProcedureCall() - .getSession() - .getFactory() - .getTypeResolver() - .heuristicType( value.getClass().getName() ); - log.debugf( "Using heuristic type [%s] based on bind value [%s] as `bindType`", hibernateType, value ); - } - } - - private void internalSetValue(T value) { - if ( procedureParameter.getMode() != ParameterMode.IN && procedureParameter.getMode() != ParameterMode.INOUT ) { - throw new IllegalStateException( "Can only bind values for IN/INOUT parameters : " + procedureParameter ); - } - - if ( procedureParameter.getParameterType() != null ) { - if ( value == null ) { - if ( !procedureParameter.isPassNullsEnabled() ) { - throw new IllegalArgumentException( "The parameter " + - ( procedureParameter.getName() != null - ? "named [" + procedureParameter.getName() + "]" - : "at position [" + procedureParameter.getPosition() + "]" ) - + " was null. You need to call ParameterRegistration#enablePassingNulls(true) in order to pass null parameters." ); - } - } - else if ( !procedureParameter.getParameterType().isInstance( value ) && - !procedureParameter.getHibernateType().getReturnedClass().isInstance( value ) ) { - throw new IllegalArgumentException( "Bind value [" + value + "] was not of specified type [" + procedureParameter - .getParameterType() ); - } - } - - this.value = value; - this.isBound = true; - } - - @Override - public void setBindValue(T value, Type clarifiedType) { - internalSetValue( value ); - this.hibernateType = clarifiedType; - log.debugf( "Using explicit type [%s] as `bindType`", hibernateType, value ); - } - - @Override - public void setBindValue(T value, TemporalType clarifiedTemporalType) { - internalSetValue( value ); - this.hibernateType = BindingTypeHelper.INSTANCE.determineTypeForTemporalType( clarifiedTemporalType, hibernateType, value ); - this.explicitTemporalType = clarifiedTemporalType; - log.debugf( "Using type [%s] (based on TemporalType [%s] as `bindType`", hibernateType, clarifiedTemporalType ); - } - - @Override - public T getBindValue() { - if ( !isBound ) { - throw new IllegalStateException( "Value not yet bound" ); - } - return value; - } - - @Override - public Type getBindType() { - return hibernateType; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index b6e8b46c94..92e8c012b1 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -45,14 +45,11 @@ import org.hibernate.procedure.ProcedureOutputs; import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.procedure.spi.ProcedureCallImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.Query; import org.hibernate.query.QueryParameter; import org.hibernate.query.internal.QueryOptionsImpl; import org.hibernate.query.procedure.ProcedureParameter; -import org.hibernate.query.procedure.internal.ProcedureParamBindings; -import org.hibernate.query.procedure.internal.ProcedureParameterImpl; -import org.hibernate.query.procedure.internal.ProcedureParameterMetadataImpl; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.AbstractQuery; import org.hibernate.query.spi.MutableQueryOptions; import org.hibernate.query.spi.QueryParameterBindings; @@ -108,8 +105,8 @@ public class ProcedureCallImpl super( session ); this.procedureName = procedureName; - this.parameterMetadata = new ProcedureParameterMetadataImpl( this ); - this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); + this.parameterMetadata = new ProcedureParameterMetadataImpl(); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, getSessionFactory() ); this.synchronizedQuerySpaces = null; this.domainResultProducers = null; @@ -128,8 +125,8 @@ public class ProcedureCallImpl this.procedureName = procedureName; - this.parameterMetadata = new ProcedureParameterMetadataImpl( this ); - this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); + this.parameterMetadata = new ProcedureParameterMetadataImpl(); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, getSessionFactory() ); this.domainResultProducers = CollectionHelper.arrayList( resultClasses.length ); this.synchronizedQuerySpaces = new HashSet<>(); @@ -159,8 +156,8 @@ public class ProcedureCallImpl this.procedureName = procedureName; - this.parameterMetadata = new ProcedureParameterMetadataImpl( this ); - this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); + this.parameterMetadata = new ProcedureParameterMetadataImpl(); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, getSessionFactory() ); this.domainResultProducers = CollectionHelper.arrayList( resultSetMappingNames.length ); this.synchronizedQuerySpaces = new HashSet<>(); @@ -183,8 +180,8 @@ public class ProcedureCallImpl super( session ); this.procedureName = memento.getCallableName(); - this.parameterMetadata = new ProcedureParameterMetadataImpl( this, memento ); - this.paramBindings = new ProcedureParamBindings( parameterMetadata, this ); + this.parameterMetadata = new ProcedureParameterMetadataImpl( memento, session ); + this.paramBindings = new ProcedureParamBindings( parameterMetadata, getSessionFactory() ); this.domainResultProducers = new ArrayList<>(); this.synchronizedQuerySpaces = CollectionHelper.makeCopy( memento.getQuerySpaces() ); @@ -240,6 +237,11 @@ public class ProcedureCallImpl return this; } + @Override + public QueryParameterBindings getParameterBindings() { + return paramBindings; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // DomainParameterBindingContext @@ -299,13 +301,11 @@ public class ProcedureCallImpl @SuppressWarnings("unchecked") public ProcedureParameter registerParameter(int position, Class javaType, ParameterMode mode) { final ProcedureParameterImpl procedureParameter = new ProcedureParameterImpl( - this, position, mode, javaType, - getSession().getFactory().getMetamodel().resolveQueryParameterType( javaType ) + getSessionFactory().getDomainModel().resolveQueryParameterType( javaType ) ); - registerParameter( procedureParameter ); return procedureParameter; } @@ -328,13 +328,12 @@ public class ProcedureCallImpl @Override @SuppressWarnings("unchecked") - public ProcedureParameterImplementor registerParameter(String name, Class type, ParameterMode mode) { + public ProcedureParameterImplementor registerParameter(String name, Class javaType, ParameterMode mode) { final ProcedureParameterImpl parameter = new ProcedureParameterImpl( - this, name, mode, - type, - getSession().getFactory().getMetamodel().resolveQueryParameterType( type ) + javaType, + getSessionFactory().getDomainModel().resolveQueryParameterType( javaType ) ); registerParameter( parameter ); @@ -408,7 +407,7 @@ public class ProcedureCallImpl public void accept(QueryParameter queryParameter) { try { final ProcedureParameterImplementor registration = (ProcedureParameterImplementor) queryParameter; - registration.prepare( statement, i ); + registration.prepare( statement, i, ProcedureCallImpl.this ); if ( registration.getMode() == ParameterMode.REF_CURSOR ) { i++; } @@ -525,14 +524,14 @@ public class ProcedureCallImpl ); } - private static List toParameterMementos( + private static List toParameterMementos( ProcedureParameterMetadataImpl parameterMetadata) { if ( parameterMetadata.getParameterStrategy() == ParameterStrategy.UNKNOWN ) { // none... return Collections.emptyList(); } - final List mementos = new ArrayList<>(); + final List mementos = new ArrayList<>(); parameterMetadata.visitRegistrations( queryParameter -> { @@ -922,5 +921,4 @@ public class ProcedureCallImpl public ProcedureCallImplementor setParameter(int position, Date value, TemporalType temporalPrecision) { return (ProcedureCallImplementor) super.setParameter( position, value, temporalPrecision ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java index 5552e1c66c..6c0484eda9 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java @@ -8,15 +8,18 @@ package org.hibernate.procedure.internal; import java.sql.CallableStatement; import java.sql.ResultSet; -import java.util.List; -import java.util.function.Supplier; +import java.sql.SQLException; import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; -import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.metamodel.model.domain.AllowableOutputParameterType; +import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.procedure.ParameterMisuseException; import org.hibernate.procedure.ProcedureOutputs; -import org.hibernate.procedure.spi.ParameterRegistrationImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.query.procedure.ProcedureParameter; import org.hibernate.result.Output; import org.hibernate.result.internal.OutputsImpl; +import org.hibernate.sql.exec.ExecutionException; /** * Implementation of ProcedureResult. Defines centralized access to all of the results of a procedure call. @@ -27,7 +30,7 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput private final ProcedureCallImpl procedureCall; private final CallableStatement callableStatement; - private final ParameterRegistrationImplementor[] refCursorParameters; + private final ProcedureParameterImplementor[] refCursorParameters; private int refCursorParamIndex; ProcedureOutputsImpl(ProcedureCallImpl procedureCall, CallableStatement callableStatement) { @@ -39,18 +42,34 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput } @Override - public T getOutputParameterValue(ParameterRegistration parameterRegistration) { - return ( (ParameterRegistrationImplementor) parameterRegistration ).extract( callableStatement ); + public T getOutputParameterValue(ProcedureParameter parameter) { + final AllowableParameterType hibernateType = parameter.getHibernateType(); + if ( hibernateType instanceof AllowableOutputParameterType ) { + try { + //noinspection unchecked + return (T) ( (AllowableOutputParameterType) hibernateType ).extract( + callableStatement, + parameter.getPosition(), + procedureCall.getSession() + ); + } + catch (SQLException e) { + throw new ExecutionException( "Error extracting procedure output parameter value [" + parameter + "]", e ); + } + } + else { + throw new ParameterMisuseException( "Parameter type cannot extract procedure output parameters" ); + } } @Override public Object getOutputParameterValue(String name) { - return procedureCall.getParameterRegistration( name ).extract( callableStatement ); + return getOutputParameterValue( procedureCall.getParameterMetadata().getQueryParameter( name ) ); } @Override public Object getOutputParameterValue(int position) { - return procedureCall.getParameterRegistration( position ).extract( callableStatement ); + return getOutputParameterValue( procedureCall.getParameterMetadata().getQueryParameter( position ) ); } @Override @@ -80,7 +99,7 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput @Override protected Output buildExtendedReturn() { ProcedureOutputsImpl.this.refCursorParamIndex++; - final ParameterRegistrationImplementor refCursorParam = ProcedureOutputsImpl.this.refCursorParameters[refCursorParamIndex]; + final ProcedureParameterImplementor refCursorParam = ProcedureOutputsImpl.this.refCursorParameters[refCursorParamIndex]; ResultSet resultSet; if ( refCursorParam.getName() != null ) { resultSet = ProcedureOutputsImpl.this.procedureCall.getSession().getFactory().getServiceRegistry() diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java similarity index 63% rename from hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java rename to hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java index 49f17d5025..b3da6a98d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParamBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParamBindings.java @@ -1,20 +1,21 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.query.procedure.internal; +package org.hibernate.procedure.internal; import java.util.HashMap; import java.util.Map; + import javax.persistence.ParameterMode; -import org.hibernate.procedure.internal.ParameterBindImpl; -import org.hibernate.procedure.internal.ProcedureCallImpl; import org.hibernate.query.procedure.ProcedureParameterBinding; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.procedure.spi.ProcedureParameterBindingImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.query.spi.QueryParameterBindingTypeResolver; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterImplementor; @@ -23,23 +24,25 @@ import org.hibernate.query.spi.QueryParameterImplementor; */ public class ProcedureParamBindings implements QueryParameterBindings { private final ProcedureParameterMetadataImpl parameterMetadata; - private final ProcedureCallImpl procedureCall; + private final QueryParameterBindingTypeResolver typeResolver; - private final Map, ProcedureParameterBinding> bindingMap = new HashMap<>(); + private final Map, ProcedureParameterBindingImplementor> bindingMap = new HashMap<>(); public ProcedureParamBindings( ProcedureParameterMetadataImpl parameterMetadata, - ProcedureCallImpl procedureCall) { + QueryParameterBindingTypeResolver typeResolver) { this.parameterMetadata = parameterMetadata; - this.procedureCall = procedureCall; + this.typeResolver = typeResolver; } public ProcedureParameterMetadataImpl getParameterMetadata() { return parameterMetadata; } - public ProcedureCallImpl getProcedureCall() { - return procedureCall; + @Override + public boolean isBound(QueryParameterImplementor parameter) { + //noinspection SuspiciousMethodCalls + return bindingMap.containsKey( parameter ); } @Override @@ -47,16 +50,17 @@ public class ProcedureParamBindings implements QueryParameterBindings { return getBinding( parameterMetadata.resolve( parameter ) ); } - public QueryParameterBinding getBinding(ProcedureParameterImplementor parameter) { + public ProcedureParameterBindingImplementor getBinding(ProcedureParameterImplementor parameter) { final ProcedureParameterImplementor procParam = parameterMetadata.resolve( parameter ); - ProcedureParameterBinding binding = bindingMap.get( procParam ); + ProcedureParameterBindingImplementor binding = bindingMap.get( procParam ); if ( binding == null ) { if ( ! parameterMetadata.containsReference( parameter ) ) { throw new IllegalArgumentException( "Passed parameter is not registered with this query" ); } - binding = new ParameterBindImpl( procParam, this ); + //noinspection unchecked + binding = new ProcedureParameterBindingImpl( procParam, typeResolver ); bindingMap.put( procParam, binding ); } @@ -65,7 +69,7 @@ public class ProcedureParamBindings implements QueryParameterBindings { @Override public ProcedureParameterBinding getBinding(String name) { - return getBinding( parameterMetadata.getQueryParameter( name ) ); + return (ProcedureParameterBinding) getBinding( parameterMetadata.getQueryParameter( name ) ); } @Override @@ -74,7 +78,7 @@ public class ProcedureParamBindings implements QueryParameterBindings { } @Override - public void verifyParametersBound(boolean callable) { + public void validate() { parameterMetadata.visitRegistrations( queryParameter -> { final ProcedureParameterImplementor procParam = (ProcedureParameterImplementor) queryParameter; @@ -89,4 +93,10 @@ public class ProcedureParamBindings implements QueryParameterBindings { ); } + @Override + public boolean hasAnyMultiValuedBindings() { + return false; + } + + } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java index 616b36fc03..38c3336792 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterBindingImpl.java @@ -9,8 +9,8 @@ package org.hibernate.procedure.internal; import org.hibernate.metamodel.model.domain.AllowableParameterType; import org.hibernate.query.internal.QueryParameterBindingImpl; import org.hibernate.query.procedure.ProcedureParameterBinding; -import org.hibernate.query.procedure.spi.ProcedureParameterBindingImplementor; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.procedure.spi.ProcedureParameterBindingImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.QueryParameterBindingTypeResolver; /** diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java index d96ae7f7dd..9143b4bd29 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterImpl.java @@ -6,13 +6,22 @@ */ package org.hibernate.procedure.internal; +import java.sql.CallableStatement; +import java.sql.SQLException; import java.util.Objects; import javax.persistence.ParameterMode; +import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.metamodel.model.domain.AllowableTemporalParameterType; import org.hibernate.procedure.spi.NamedCallableQueryMemento; +import org.hibernate.procedure.spi.ParameterStrategy; +import org.hibernate.procedure.spi.ProcedureCallImplementor; import org.hibernate.query.AbstractQueryParameter; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.query.spi.QueryParameterBinding; +import org.hibernate.type.ProcedureParameterExtractionAware; +import org.hibernate.type.ProcedureParameterNamedBinder; /** * @author Steve Ebersole @@ -111,4 +120,124 @@ public class ProcedureParameterImpl extends AbstractQueryParameter impleme public int hashCode() { return Objects.hash( name, position, mode ); } + + + @Override + public void prepare( + CallableStatement statement, + int startIndex, + ProcedureCallImplementor procedureCall) throws SQLException { + final QueryParameterBinding binding = procedureCall.getParameterBindings().getBinding( this ); + + // initially set up the Type we will use for binding as the explicit type. + AllowableParameterType typeToUse = getHibernateType(); + + // however, for Calendar binding with an explicit TemporalType we may need to adjust this... + if ( binding != null && binding.getExplicitTemporalPrecision() != null ) { + typeToUse = ( (AllowableTemporalParameterType) typeToUse ).resolveTemporalPrecision( + binding.getExplicitTemporalPrecision(), + procedureCall.getSession().getFactory().getTypeConfiguration() + ); + } + + this.startIndex = startIndex; + if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( sqlTypesToUse.length > 1 ) { + // there is more than one column involved; see if the Hibernate Type can handle + // multi-param extraction... + final boolean canHandleMultiParamExtraction = + ProcedureParameterExtractionAware.class.isInstance( typeToUse ) + && ( (ProcedureParameterExtractionAware) typeToUse ).canDoExtraction(); + if ( ! canHandleMultiParamExtraction ) { + // it cannot... + throw new UnsupportedOperationException( + "Type [" + typeToUse + "] does support multi-parameter value extraction" + ); + } + } + // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). + // The idea is that an embeddable/custom type can have more than one column values + // that correspond with embeddable/custom attribute value. This does not seem to + // be working yet. For now, if sqlTypesToUse.length > 1, then register + // the out parameters by position (since we only have one name). + // This will cause a failure if there are other parameters bound by + // name and the dialect does not support "mixed" named/positional parameters; + // e.g., Oracle. + if ( sqlTypesToUse.length == 1 && + procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && + canDoNameParameterBinding( typeToUse ) ) { + statement.registerOutParameter( getName(), sqlTypesToUse[0] ); + } + else { + for ( int i = 0; i < sqlTypesToUse.length; i++ ) { + statement.registerOutParameter( startIndex + i, sqlTypesToUse[i] ); + } + } + } + + if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { + if ( binding == null || binding.getBindValue() == null ) { + // the user did not binding a value to the parameter being processed. This is the condition + // defined by `passNulls` and that value controls what happens here. If `passNulls` is + // {@code true} we will binding the NULL value into the statement; if `passNulls` is + // {@code false} we will not. + // + // Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure + // parameter defines a default value. Deferring to that information would be the best option + if ( isPassNullsEnabled() ) { + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", + procedureCall.getProcedureName(), + this + ); + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { + ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( + statement, + null, + this.getName(), + procedureCall.getSession() + ); + } + else { + typeToUse.nullSafeSet( statement, null, startIndex, procedureCall.getSession() ); + } + } + else { + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value", + procedureCall.getProcedureName(), + this + ); + } + } + else { + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { + ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( + statement, + binding.getBindValue(), + this.getName(), + procedureCall.getSession() + ); + } + else { + typeToUse.nullSafeSet( statement, binding.getBindValue(), startIndex, procedureCall.getSession() ); + } + } + } + } + else { + // we have a REF_CURSOR type param + if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { + procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, getName() ); + } + else { + procedureCall.getSession().getFactory().getServiceRegistry() + .getService( RefCursorSupport.class ) + .registerRefCursorParameter( statement, startIndex ); + } + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterMetadataImpl.java similarity index 83% rename from hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadataImpl.java rename to hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterMetadataImpl.java index d71733736f..28831c017e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterMetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureParameterMetadataImpl.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.query.procedure.internal; +package org.hibernate.procedure.internal; import java.util.ArrayList; import java.util.Collections; @@ -13,35 +13,35 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; - import javax.persistence.Parameter; -import org.hibernate.procedure.internal.ProcedureCallImpl; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; import org.hibernate.query.QueryParameter; import org.hibernate.query.procedure.ProcedureParameter; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.ParameterMetadataImplementor; import org.hibernate.query.spi.QueryParameterImplementor; /** + * Specialized ParameterMetadataImplementor for callable queries implementing + * expandable parameter registrations + * * @author Steve Ebersole */ public class ProcedureParameterMetadataImpl implements ParameterMetadataImplementor { - private final ProcedureCallImpl procedureCall; - private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN; - private List> parameters = new ArrayList<>(); + private List> parameters; - public ProcedureParameterMetadataImpl(ProcedureCallImpl procedureCall) { - this.procedureCall = procedureCall; + @SuppressWarnings("WeakerAccess") + public ProcedureParameterMetadataImpl() { } - public ProcedureParameterMetadataImpl(ProcedureCallImpl procedureCall, NamedCallableQueryMemento memento) { - this( procedureCall ); + @SuppressWarnings("WeakerAccess") + public ProcedureParameterMetadataImpl(NamedCallableQueryMemento memento, SharedSessionContractImplementor session) { memento.getParameterMementos().forEach( - parameterMemento -> registerParameter( parameterMemento.resolve( procedureCall.getSession() ) ) + parameterMemento -> registerParameter( parameterMemento.resolve( session ) ) ); } @@ -69,6 +69,13 @@ public class ProcedureParameterMetadataImpl implements ParameterMetadataImplemen parameters.add( parameter ); } + @Override + public void visitParameters(Consumer> consumer) { + if ( parameters != null ) { + parameters.forEach( consumer ); + } + } + @Override public boolean hasNamedParameters() { return parameterStrategy == ParameterStrategy.NAMED; @@ -105,15 +112,11 @@ public class ProcedureParameterMetadataImpl implements ParameterMetadataImplemen return parameters.contains( parameter ); } + @SuppressWarnings("WeakerAccess") public ParameterStrategy getParameterStrategy() { return parameterStrategy; } - @Override - public void collectAllParameters(ParameterCollector collector) { - parameters.forEach( collector::collect ); - } - @Override public boolean hasAnyMatching(Predicate> filter) { if ( parameters.isEmpty() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java index 39e44d7b6b..8aa8bd35d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/CallableStatementSupport.java @@ -10,8 +10,9 @@ import java.sql.CallableStatement; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.internal.FunctionReturnImpl; -import org.hibernate.query.procedure.internal.ProcedureParamBindings; +import org.hibernate.procedure.internal.ProcedureParamBindings; import org.hibernate.query.spi.ParameterMetadataImplementor; +import org.hibernate.sql.exec.spi.JdbcCall; /** * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java index 2e83be6562..b31e9f2112 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/FunctionReturnImplementor.java @@ -7,7 +7,6 @@ package org.hibernate.procedure.spi; import org.hibernate.procedure.FunctionReturn; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; /** * @author Steve Ebersole diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/NamedCallableQueryMemento.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/NamedCallableQueryMemento.java index ccc0a57240..ee76eabad8 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/NamedCallableQueryMemento.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/NamedCallableQueryMemento.java @@ -14,7 +14,6 @@ import org.hibernate.Session; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.procedure.ProcedureCall; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; import org.hibernate.query.spi.NamedQueryMemento; /** @@ -70,7 +69,7 @@ public interface NamedCallableQueryMemento extends NamedQueryMemento { */ ProcedureCall makeProcedureCall(SharedSessionContractImplementor session); - interface ParameterMemento { + interface ParameterMemento extends NamedQueryMemento.ParameterMemento { ProcedureParameterImplementor resolve(SharedSessionContractImplementor session); } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java index b6da120565..5882773460 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureCallImplementor.java @@ -26,6 +26,8 @@ public interface ProcedureCallImplementor extends ProcedureCall, QueryImpleme return list(); } + ParameterStrategy getParameterStrategy(); + @Override default R getSingleResult() { return uniqueResult(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterBindingImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterBindingImplementor.java similarity index 68% rename from hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterBindingImplementor.java rename to hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterBindingImplementor.java index 9461ebf5ae..e433624b72 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterBindingImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterBindingImplementor.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.query.procedure.spi; +package org.hibernate.procedure.spi; import org.hibernate.query.procedure.ProcedureParameterBinding; diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterImplementor.java new file mode 100644 index 0000000000..0087bd6ad6 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/spi/ProcedureParameterImplementor.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.procedure.spi; + +import java.sql.CallableStatement; +import java.sql.SQLException; + +import org.hibernate.Incubating; +import org.hibernate.query.procedure.ProcedureParameter; +import org.hibernate.query.spi.QueryParameterImplementor; + +/** + * SPI extension for ProcedureParameter + * + * @author Steve Ebersole + */ +@Incubating +public interface ProcedureParameterImplementor extends ProcedureParameter, QueryParameterImplementor { + /** + * Allow the parameter to register itself with the JDBC CallableStatement, + * if necessary, as well as perform any other needed preparation for exeuction + * + * @throws SQLException Indicates a problem with any underlying JDBC calls + */ + void prepare( + CallableStatement statement, + int startIndex, + ProcedureCallImplementor callImplementor) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/TupleTransformer.java b/hibernate-core/src/main/java/org/hibernate/query/TupleTransformer.java index ca48431ba4..3af01b773e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/TupleTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/TupleTransformer.java @@ -6,11 +6,13 @@ */ package org.hibernate.query; +import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; + /** * User hook for applying transformations of the query result tuples (the result "row"). * * Ultimately, gets wrapped in a - * {@link org.hibernate.sql.exec.internal.RowTransformerTupleTransformerAdapter} + * {@link RowTransformerTupleTransformerAdapter} * to adapt the TupleTransformer to the {@link org.hibernate.sql.exec.spi.RowTransformer} * contract, which is the thing actually used to process the results internally. * diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index 76dd6f77be..bee9f8b6c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -1666,7 +1666,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { } private boolean isSelect() { - return getProducer().getFactory().getQueryPlanCache() + return getProducer().getFactory().getQueryInterpretationCache() .getHQLQueryPlan( getQueryString(), false, Collections.emptyMap() ) .isSelect(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/NamedQueryRepositoryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/NamedQueryRepositoryImpl.java index 38020e01db..617a50ac52 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/NamedQueryRepositoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/NamedQueryRepositoryImpl.java @@ -19,7 +19,7 @@ import org.hibernate.query.hql.spi.NamedHqlQueryMemento; import org.hibernate.query.spi.NamedQueryRepository; import org.hibernate.query.spi.NamedResultSetMappingMemento; import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.sql.spi.NamedNativeQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; @@ -131,8 +131,8 @@ public class NamedQueryRepositoryImpl implements NamedQueryRepository { Map errors = new HashMap<>(); final SemanticQueryProducer sqmProducer = queryEngine.getSemanticQueryProducer(); - final QueryPlanCache queryPlanCache = queryEngine.getQueryPlanCache(); - final boolean cachingEnabled = queryPlanCache.isEnabled(); + final QueryInterpretationCache interpretationCache = queryEngine.getInterpretationCache(); + final boolean cachingEnabled = interpretationCache.isEnabled(); // Check named HQL queries log.debugf( "Checking %s named HQL queries", hqlMementoMap.size() ); @@ -144,7 +144,7 @@ public class NamedQueryRepositoryImpl implements NamedQueryRepository { if ( cachingEnabled ) { // todo (6.0) : need to cache these; however atm that requires producing a SqmQueryImpl - // queryEngine.getQueryPlanCache().getHQLQueryPlan( hqlMemento.getQueryString(), false, Collections.EMPTY_MAP ); + // queryEngine.getQueryInterpretationCache().getHQLQueryPlan( hqlMemento.getQueryString(), false, Collections.EMPTY_MAP ); } } catch ( HibernateException e ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java index e8b7b1408f..ac550bed3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java @@ -160,14 +160,8 @@ public class ParameterMetadataImpl implements ParameterMetadataImplementor { } @Override - public void visitRegistrations(Consumer> action) { - //noinspection unchecked - queryParameters.forEach( (Consumer) action ); - } - - @Override - public void collectAllParameters(ParameterCollector collector) { - queryParameters.forEach( collector::collect ); + public void visitParameters(Consumer> consumer) { + queryParameters.forEach( consumer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryPlanCacheDisabledImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java similarity index 83% rename from hibernate-core/src/main/java/org/hibernate/query/internal/QueryPlanCacheDisabledImpl.java rename to hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java index d1505b35ef..8a6a003a68 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryPlanCacheDisabledImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java @@ -9,18 +9,18 @@ package org.hibernate.query.internal; import java.util.function.Function; import org.hibernate.query.spi.NonSelectQueryPlan; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.tree.SqmStatement; /** * @author Steve Ebersole */ -public class QueryPlanCacheDisabledImpl implements QueryPlanCache { +public class QueryInterpretationCacheDisabledImpl implements QueryInterpretationCache { /** * Singleton access */ - public static final QueryPlanCacheDisabledImpl INSTANCE = new QueryPlanCacheDisabledImpl(); + public static final QueryInterpretationCacheDisabledImpl INSTANCE = new QueryInterpretationCacheDisabledImpl(); @Override public SelectQueryPlan getSelectQueryPlan(Key key) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryPlanCacheStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java similarity index 90% rename from hibernate-core/src/main/java/org/hibernate/query/internal/QueryPlanCacheStandardImpl.java rename to hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java index 91403fce9a..2ff4841ff3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryPlanCacheStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java @@ -12,7 +12,7 @@ import org.hibernate.internal.util.collections.BoundedConcurrentHashMap; import org.hibernate.query.QueryLogger; import org.hibernate.query.spi.NonSelectQueryPlan; import org.hibernate.query.spi.QueryPlan; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.tree.SqmStatement; @@ -23,7 +23,7 @@ import org.jboss.logging.Logger; * * @author Steve Ebersole */ -public class QueryPlanCacheStandardImpl implements QueryPlanCache { +public class QueryInterpretationCacheStandardImpl implements QueryInterpretationCache { private static final Logger log = QueryLogger.subLogger( "plan.cache" ); /** @@ -41,7 +41,7 @@ public class QueryPlanCacheStandardImpl implements QueryPlanCache { private final BoundedConcurrentHashMap queryPlanCache; private final BoundedConcurrentHashMap> sqmStatementCache; - public QueryPlanCacheStandardImpl(int maxQueryPlanCount) { + public QueryInterpretationCacheStandardImpl(int maxQueryPlanCount) { log.debugf( "Starting QueryPlanCache(%s)", maxQueryPlanCount ); queryPlanCache = new BoundedConcurrentHashMap<>( maxQueryPlanCount, 20, BoundedConcurrentHashMap.Eviction.LIRS ); @@ -88,13 +88,13 @@ public class QueryPlanCacheStandardImpl implements QueryPlanCache { @Override public SqmStatement getSqmStatement(String queryString) { - log.debugf( "Creating and caching SqmStatement - %s", queryString ); + log.tracef( "#getSqmStatement( %s )", queryString ); return sqmStatementCache.get( queryString ); } @Override public void cacheSqmStatement(String queryString, SqmStatement sqmStatement) { - log.debugf( "Creating and caching SqmStatement - %s", queryString ); + log.tracef( "#cacheSqmStatement( %s )", queryString ); // todo (6.0) : Log and stats, see HHH-12855 sqmStatementCache.putIfAbsent( queryString, sqmStatement ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java index 47751a9ecf..8115b3e649 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingImpl.java @@ -22,7 +22,6 @@ import org.hibernate.type.spi.TypeConfiguration; * @author Steve Ebersole */ public class QueryParameterBindingImpl implements QueryParameterBinding { - private final QueryParameter queryParameter; private final QueryParameterBindingTypeResolver typeResolver; private final boolean isBindingValidationRequired; @@ -30,6 +29,7 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { private boolean isMultiValued; private AllowableParameterType bindType; + private TemporalType explicitTemporalPrecision; private T bindValue; private Collection bindValues; @@ -40,7 +40,6 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { QueryParameter queryParameter, QueryParameterBindingTypeResolver typeResolver, boolean isBindingValidationRequired) { - this.queryParameter = queryParameter; this.typeResolver = typeResolver; this.isBindingValidationRequired = isBindingValidationRequired; this.bindType = queryParameter.getHibernateType(); @@ -51,7 +50,6 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { QueryParameterBindingTypeResolver typeResolver, AllowableParameterType bindType, boolean isBindingValidationRequired) { - this.queryParameter = queryParameter; this.typeResolver = typeResolver; this.isBindingValidationRequired = isBindingValidationRequired; this.bindType = bindType; @@ -62,6 +60,11 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { return bindType; } + @Override + public TemporalType getExplicitTemporalPrecision() { + return explicitTemporalPrecision; + } + @Override public boolean isBound() { return isBound; @@ -99,7 +102,8 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { this.bindValue = value; if ( bindType == null ) { - this.bindType = typeResolver.resolveParameterBindType( value ); + //noinspection unchecked + this.bindType = (AllowableParameterType) typeResolver.resolveParameterBindType( value ); } } @@ -129,6 +133,8 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { getBindType().getJavaType(), getBindType() ); + + this.explicitTemporalPrecision = temporalTypePrecision; } @@ -153,7 +159,8 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { this.bindValues = values; if ( bindType == null && !values.isEmpty() ) { - this.bindType = typeResolver.resolveParameterBindType( values.iterator().next() ); + //noinspection unchecked + this.bindType = (AllowableParameterType) typeResolver.resolveParameterBindType( values.iterator().next() ); } } @@ -173,12 +180,13 @@ public class QueryParameterBindingImpl implements QueryParameterBinding { TypeConfiguration typeConfiguration) { setBindValues( values ); - final Object exampleValue = values.isEmpty() ? null : values.iterator().next(); this.bindType = BindingTypeHelper.INSTANCE.resolveTemporalPrecision( temporalTypePrecision, bindType, typeConfiguration ); + + this.explicitTemporalPrecision = temporalTypePrecision; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java index aeddfd2fea..36dc95e8ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterNamedImpl.java @@ -1,32 +1,60 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ + package org.hibernate.query.internal; -import org.hibernate.query.QueryParameter; -import org.hibernate.type.Type; +import java.util.Objects; + +import org.hibernate.metamodel.model.mapping.spi.AllowableParameterType; +import org.hibernate.query.named.spi.ParameterMemento; +import org.hibernate.query.sqm.tree.expression.SqmParameter; /** - * Models a named query parameter - * - * NOTE: Unfortunately we need to model named and positional parameters separately still until 6.0 - * - * NOTE : Also, notice that this still treats JPA "positional" parameters as named. This will change in - * 6.0 as well after we remove support for legacy positional parameters (the JPA model is better there). + * QueryParameter impl for named-parameters in HQL, JPQL or Criteria queries. * * @author Steve Ebersole */ -public class QueryParameterNamedImpl extends AbstractQueryParameterImpl implements QueryParameter { - private final String name; - private final int[] sourceLocations; +public class QueryParameterNamedImpl extends AbstractQueryParameter { + /** + * Create a named parameter descriptor from the SQM parameter + * + * @param parameter The source parameter info + * + * @return The parameter descriptor + */ + public static QueryParameterNamedImpl fromSqm(SqmParameter parameter) { + assert parameter.getName() != null; + assert parameter.getPosition() == null; - public QueryParameterNamedImpl(String name, int[] sourceLocations, Type expectedType) { - super( expectedType ); + return new QueryParameterNamedImpl( + parameter.getName(), + parameter.allowMultiValuedBinding(), + parameter.getAnticipatedType() != null ? + (AllowableParameterType) parameter.getAnticipatedType() : + null + ); + } + + public static QueryParameterNamedImpl fromNativeQuery(String name) { + return new QueryParameterNamedImpl( + name, + false, + null + ); + } + + private final String name; + + private QueryParameterNamedImpl( + String name, + boolean allowMultiValuedBinding, + AllowableParameterType anticipatedType) { + super( allowMultiValuedBinding, anticipatedType ); this.name = name; - this.sourceLocations = sourceLocations; } @Override @@ -35,13 +63,8 @@ public class QueryParameterNamedImpl extends AbstractQueryParameterImpl im } @Override - public Integer getPosition() { - return null; - } - - @Override - public int[] getSourceLocations() { - return sourceLocations; + public ParameterMemento toMemento() { + return session -> new QueryParameterNamedImpl( getName(), allowsMultiValuedBinding(), getHibernateType() ); } @Override @@ -52,13 +75,12 @@ public class QueryParameterNamedImpl extends AbstractQueryParameterImpl im if ( o == null || getClass() != o.getClass() ) { return false; } - QueryParameterNamedImpl that = (QueryParameterNamedImpl) o; - return getName().equals( that.getName() ); + return Objects.equals( name, that.name ); } @Override public int hashCode() { - return getName().hashCode(); + return Objects.hash( name ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterPositionalImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterPositionalImpl.java new file mode 100644 index 0000000000..b7d809c0ce --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterPositionalImpl.java @@ -0,0 +1,86 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +package org.hibernate.query.internal; + +import java.util.Objects; + +import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.query.AbstractQueryParameter; +import org.hibernate.query.spi.NamedQueryMemento; +import org.hibernate.query.sqm.tree.expression.SqmParameter; + +/** + * QueryParameter impl for positional-parameters in HQL, JPQL or Criteria queries. + * + * @author Steve Ebersole + */ +public class QueryParameterPositionalImpl extends AbstractQueryParameter { + + private final int position; + + /** + * Create a positional parameter descriptor from the SQM parameter + * + * @param parameter The source parameter info + * + * @return The parameter descriptor + */ + public static QueryParameterPositionalImpl fromSqm(SqmParameter parameter) { + assert parameter.getPosition() != null; + assert parameter.getName() == null; + + return new QueryParameterPositionalImpl( + parameter.getPosition(), + parameter.allowMultiValuedBinding(), + parameter.getAnticipatedType() + ); + } + + public static QueryParameterPositionalImpl fromNativeQuery(int position) { + return new QueryParameterPositionalImpl( + position, + false, + null + ); + } + + public QueryParameterPositionalImpl( + Integer position, + boolean allowMultiValuedBinding, + AllowableParameterType anticipatedType) { + super( allowMultiValuedBinding, anticipatedType ); + this.position = position; + } + + @Override + public Integer getPosition() { + return position; + } + + @Override + public NamedQueryMemento.ParameterMemento toMemento() { + return session -> new QueryParameterPositionalImpl( getPosition(), allowsMultiValuedBinding(), getHibernateType() ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + QueryParameterPositionalImpl that = (QueryParameterPositionalImpl) o; + return position == that.position; + } + + @Override + public int hashCode() { + return Objects.hash( position ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java deleted file mode 100644 index 6635c361ec..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/internal/ProcedureParameterImpl.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.query.procedure.internal; - -import java.sql.CallableStatement; -import java.sql.SQLException; -import javax.persistence.ParameterMode; - -import org.hibernate.engine.jdbc.cursor.spi.RefCursorSupport; -import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; -import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; -import org.hibernate.metamodel.model.domain.AllowableOutputParameterType; -import org.hibernate.metamodel.model.domain.AllowableParameterType; -import org.hibernate.metamodel.model.domain.AllowableTemporalParameterType; -import org.hibernate.procedure.ParameterMisuseException; -import org.hibernate.procedure.internal.ProcedureCallImpl; -import org.hibernate.procedure.spi.ParameterStrategy; -import org.hibernate.query.internal.AbstractQueryParameterImpl; -import org.hibernate.query.procedure.spi.ProcedureParameterImplementor; -import org.hibernate.query.spi.QueryParameterBinding; -import org.hibernate.type.ProcedureParameterExtractionAware; -import org.hibernate.type.ProcedureParameterNamedBinder; -import org.hibernate.type.Type; - -import org.jboss.logging.Logger; - -/** - * @author Steve Ebersole - */ -public class ProcedureParameterImpl - extends AbstractQueryParameterImpl - implements ProcedureParameterImplementor { - private static final Logger log = Logger.getLogger( ProcedureParameterImpl.class ); - - private final ProcedureCallImpl procedureCall; - private final String name; - private final Integer position; - private final ParameterMode mode; - private final Class javaType; - - // in-flight state needed between prepare and extract - private int startIndex; - - public ProcedureParameterImpl( - ProcedureCallImpl procedureCall, - String name, - ParameterMode mode, - Class javaType, - AllowableParameterType hibernateType) { - super( hibernateType ); - this.procedureCall = procedureCall; - this.name = name; - this.position = null; - this.mode = mode; - this.javaType = javaType; - - setHibernateType( hibernateType ); - } - - public ProcedureParameterImpl( - ProcedureCallImpl procedureCall, - Integer position, - ParameterMode mode, - Class javaType, - AllowableParameterType hibernateType) { - super( hibernateType ); - this.procedureCall = procedureCall; - this.name = null; - this.position = position; - this.mode = mode; - this.javaType = javaType; - - setHibernateType( hibernateType ); - } - - @Override - public ParameterMode getMode() { - return mode; - } - - @Override - public String getName() { - return name; - } - - @Override - public Integer getPosition() { - return position; - } - - @Override - public void setHibernateType(AllowableParameterType expectedType) { - super.setHibernateType( expectedType ); - - if ( mode == ParameterMode.REF_CURSOR || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - if ( ! ( expectedType instanceof AllowableOutputParameterType ) ) { - throw new IllegalArgumentException( "Passed type must implement AllowableOutputParameterType" ); - } - } - } - - @Override - public void prepare(CallableStatement statement, int startIndex) throws SQLException { - final QueryParameterBinding binding = procedureCall.getQueryParameterBindings().getBinding( this ); - - // initially set up the Type we will use for binding as the explicit type. - AllowableParameterType typeToUse = getHibernateType(); - - // however, for Calendar binding with an explicit TemporalType we may need to adjust this... - if ( binding != null && binding.getExplicitTemporalPrecision() != null ) { - typeToUse = ( (AllowableTemporalParameterType) typeToUse ).resolveTemporalPrecision( - binding.getExplicitTemporalPrecision(), - procedureCall.getSession().getFactory().getTypeConfiguration() - ); - } - - this.startIndex = startIndex; - if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { - if ( sqlTypesToUse.length > 1 ) { - // there is more than one column involved; see if the Hibernate Type can handle - // multi-param extraction... - final boolean canHandleMultiParamExtraction = - ProcedureParameterExtractionAware.class.isInstance( typeToUse ) - && ( (ProcedureParameterExtractionAware) typeToUse ).canDoExtraction(); - if ( ! canHandleMultiParamExtraction ) { - // it cannot... - throw new UnsupportedOperationException( - "Type [" + typeToUse + "] does support multi-parameter value extraction" - ); - } - } - // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). - // The idea is that an embeddable/custom type can have more than one column values - // that correspond with embeddable/custom attribute value. This does not seem to - // be working yet. For now, if sqlTypesToUse.length > 1, then register - // the out parameters by position (since we only have one name). - // This will cause a failure if there are other parameters bound by - // name and the dialect does not support "mixed" named/positional parameters; - // e.g., Oracle. - if ( sqlTypesToUse.length == 1 && - procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && - canDoNameParameterBinding( typeToUse ) ) { - statement.registerOutParameter( getName(), sqlTypesToUse[0] ); - } - else { - for ( int i = 0; i < sqlTypesToUse.length; i++ ) { - statement.registerOutParameter( startIndex + i, sqlTypesToUse[i] ); - } - } - } - - if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { - if ( binding == null || binding.getValue() == null ) { - // the user did not binding a value to the parameter being processed. This is the condition - // defined by `passNulls` and that value controls what happens here. If `passNulls` is - // {@code true} we will binding the NULL value into the statement; if `passNulls` is - // {@code false} we will not. - // - // Unfortunately there is not a way to reliably know through JDBC metadata whether a procedure - // parameter defines a default value. Deferring to that information would be the best option - if ( isPassNullsEnabled() ) { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to true; binding NULL", - procedureCall.getProcedureName(), - this - ); - if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { - ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( - statement, - null, - this.getName(), - procedureCall.getSession() - ); - } - else { - typeToUse.nullSafeSet( statement, null, startIndex, procedureCall.getSession() ); - } - } - else { - log.debugf( - "Stored procedure [%s] IN/INOUT parameter [%s] not bound and `passNulls` was set to false; assuming procedure defines default value", - procedureCall.getProcedureName(), - this - ); - } - } - else { - if ( this.procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && canDoNameParameterBinding( typeToUse ) ) { - ((ProcedureParameterNamedBinder) typeToUse).nullSafeSet( - statement, - binding.getValue(), - this.getName(), - procedureCall.getSession() - ); - } - else { - typeToUse.nullSafeSet( statement, binding.getValue(), startIndex, procedureCall.getSession() ); - } - } - } - } - else { - // we have a REF_CURSOR type param - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { - procedureCall.getSession().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, getName() ); - } - else { - procedureCall.getSession().getFactory().getServiceRegistry() - .getService( RefCursorSupport.class ) - .registerRefCursorParameter( statement, startIndex ); - } - } - } - - private boolean canDoNameParameterBinding(Type hibernateType) { - final ExtractedDatabaseMetaData databaseMetaData = procedureCall.getSession() - .getJdbcCoordinator() - .getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry().getService( JdbcEnvironment.class ) - .getExtractedDatabaseMetaData(); - return - databaseMetaData.supportsNamedParameters() - && ProcedureParameterNamedBinder.class.isInstance( hibernateType ) - && ((ProcedureParameterNamedBinder) hibernateType).canDoSetting(); - } - - private Type determineHibernateType() { - final ParameterBind bind = getBind(); - - // if the bind defines a type, that should be the most specific... - final Type bindType = bind.getBindType(); - if ( bindType != null ) { - return bindType; - } - - // Next, see if the parameter itself has an expected type, and if so use that... - final Type paramType = getHibernateType(); - if ( paramType != null ) { - return paramType; - } - - // here we just have guessing games - if ( bind.getValue() != null ) { - return procedureCall.getSession() - .getFactory() - .getTypeResolver() - .heuristicType( bind.getValue().getClass().getName() ); - } - - throw new IllegalStateException( "Unable to determine SQL type(s) - Hibernate Type not known" ); - } - - @Override - @SuppressWarnings("unchecked") - public T extract(CallableStatement statement) { - if ( mode == ParameterMode.IN ) { - throw new ParameterMisuseException( "IN parameter not valid for output extraction" ); - } - try { - if ( mode == ParameterMode.REF_CURSOR ) { - if ( procedureCall.getParameterStrategy() == ParameterStrategy.NAMED ) { - return (T) statement.getObject( name ); - } - else { - return (T) statement.getObject( startIndex ); - } - } - else { - final Type hibernateType = determineHibernateType(); - final int[] sqlTypes = hibernateType.sqlTypes( procedureCall.getSession().getFactory() ); - - // TODO: sqlTypesToUse.length > 1 does not seem to have a working use case (HHH-10769). - // For now, if sqlTypes.length > 1 with a named parameter, then extract - // parameter values by position (since we only have one name). - final boolean useNamed = sqlTypes.length == 1 && - procedureCall.getParameterStrategy() == ParameterStrategy.NAMED && - canDoNameParameterBinding( hibernateType ); - - - if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { - if ( useNamed ) { - return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( - statement, - new String[] { getName() }, - procedureCall.getSession() - ); - } - else { - return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( - statement, - startIndex, - procedureCall.getSession() - ); - } - } - else { - if ( useNamed ) { - return (T) statement.getObject( name ); - } - else { - return (T) statement.getObject( startIndex ); - } - } - } - } - catch (SQLException e) { - throw procedureCall.getSession().getFactory().getSQLExceptionHelper().convert( - e, - "Unable to extract OUT/INOUT parameter value" - ); - } - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java deleted file mode 100644 index b3ee034931..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/procedure/spi/ProcedureParameterImplementor.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ -package org.hibernate.query.procedure.spi; - -import java.sql.CallableStatement; -import java.sql.SQLException; - -import org.hibernate.Incubating; -import org.hibernate.query.procedure.ProcedureParameter; -import org.hibernate.query.spi.QueryParameterImplementor; - -/** - * NOTE: Consider this contract (and its sub-contracts) as incubating as we transition to 6.0 and SQM - * - * @author Steve Ebersole - */ -@Incubating -public interface ProcedureParameterImplementor extends ProcedureParameter, QueryParameterImplementor { - void prepare(CallableStatement statement, int startIndex) throws SQLException; - - @Override - default boolean allowsMultiValuedBinding() { - return false; - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryMemento.java b/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryMemento.java index 0dbb45db90..c261e2d6e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryMemento.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/NamedQueryMemento.java @@ -10,6 +10,7 @@ import java.util.Map; import org.hibernate.CacheMode; import org.hibernate.FlushMode; +import org.hibernate.engine.spi.SharedSessionContractImplementor; /** * Named Query mementos are stored in the QueryEngine's @@ -46,4 +47,8 @@ public interface NamedQueryMemento { String getComment(); Map getHints(); + + interface ParameterMemento { + QueryParameterImplementor resolve(SharedSessionContractImplementor session); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/ParameterMetadataImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/spi/ParameterMetadataImplementor.java index a837761cdb..ce1f92f418 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/ParameterMetadataImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/ParameterMetadataImplementor.java @@ -6,30 +6,38 @@ */ package org.hibernate.query.spi; +import java.util.function.Consumer; import java.util.function.Predicate; import javax.persistence.Parameter; import org.hibernate.query.ParameterMetadata; +import org.hibernate.query.QueryParameter; +import org.hibernate.procedure.spi.ProcedureParameterImplementor; /** * @author Steve Ebersole */ public interface ParameterMetadataImplementor extends ParameterMetadata { - @FunctionalInterface - interface ParameterCollector { -

    > void collect(P queryParameter); + void visitParameters(Consumer> consumer); + + default void collectAllParameters(Consumer> collector) { + visitParameters( collector ); } - void collectAllParameters(ParameterCollector collector); + @Override + default void visitRegistrations(Consumer> action) { + //noinspection unchecked + visitParameters( (Consumer) action ); + } boolean hasAnyMatching(Predicate> filter); @Override - QueryParameterImplementor getQueryParameter(String name); + ProcedureParameterImplementor getQueryParameter(String name); @Override - QueryParameterImplementor getQueryParameter(int positionLabel); + ProcedureParameterImplementor getQueryParameter(int positionLabel); @Override - QueryParameterImplementor resolve(Parameter param); + ProcedureParameterImplementor resolve(Parameter param); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java index a388ce57be..169116f2f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java @@ -10,6 +10,7 @@ import java.util.Map; import org.hibernate.HibernateException; import org.hibernate.Incubating; +import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -17,8 +18,8 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.query.QueryLogger; -import org.hibernate.query.internal.QueryPlanCacheDisabledImpl; -import org.hibernate.query.internal.QueryPlanCacheStandardImpl; +import org.hibernate.query.internal.QueryInterpretationCacheDisabledImpl; +import org.hibernate.query.internal.QueryInterpretationCacheStandardImpl; import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; import org.hibernate.query.hql.SemanticQueryProducer; import org.hibernate.query.sqm.produce.function.SqmFunctionRegistry; @@ -38,7 +39,7 @@ import org.hibernate.service.ServiceRegistry; public class QueryEngine { public static QueryEngine from( SessionFactoryImplementor sessionFactory, - NamedQueryRepository namedQueryRepository) { + MetadataImplementor metadata) { return new QueryEngine( sessionFactory.getMetamodel(), sessionFactory.getServiceRegistry(), @@ -46,14 +47,14 @@ public class QueryEngine { sessionFactory, new SqmCreationOptionsStandard( sessionFactory ), sessionFactory.getProperties(), - namedQueryRepository + metadata.buildNamedQueryRepository( sessionFactory ) ); } private final NamedQueryRepository namedQueryRepository; private final SqmCriteriaNodeBuilder criteriaBuilder; private final SemanticQueryProducer semanticQueryProducer; - private final QueryPlanCache queryPlanCache; + private final QueryInterpretationCache interpretationCache; private final SqmFunctionRegistry sqmFunctionRegistry; public QueryEngine( @@ -72,7 +73,7 @@ public class QueryEngine { serviceRegistry ); - this.queryPlanCache = buildQueryPlanCache( properties ); + this.interpretationCache = buildQueryPlanCache( properties ); this.sqmFunctionRegistry = new SqmFunctionRegistry(); serviceRegistry.getService( JdbcServices.class ) @@ -80,9 +81,11 @@ public class QueryEngine { .getDialect() .initializeFunctionRegistry( this ); runtimeOptions.getSqmFunctionRegistry().overlay( sqmFunctionRegistry ); + + getNamedQueryRepository().checkNamedQueries( this ); } - private static QueryPlanCache buildQueryPlanCache(Map properties) { + private static QueryInterpretationCache buildQueryPlanCache(Map properties) { final boolean explicitUseCache = ConfigurationHelper.getBoolean( AvailableSettings.QUERY_PLAN_CACHE_ENABLED, properties, @@ -95,15 +98,15 @@ public class QueryEngine { ); if ( explicitUseCache || ( explicitMaxPlanCount != null && explicitMaxPlanCount > 0 ) ) { - return new QueryPlanCacheStandardImpl( + return new QueryInterpretationCacheStandardImpl( explicitMaxPlanCount != null ? explicitMaxPlanCount - : QueryPlanCacheStandardImpl.DEFAULT_QUERY_PLAN_MAX_COUNT + : QueryInterpretationCacheStandardImpl.DEFAULT_QUERY_PLAN_MAX_COUNT ); } else { // disabled - return QueryPlanCacheDisabledImpl.INSTANCE; + return QueryInterpretationCacheDisabledImpl.INSTANCE; } } @@ -137,8 +140,8 @@ public class QueryEngine { return semanticQueryProducer; } - public QueryPlanCache getQueryPlanCache() { - return queryPlanCache; + public QueryInterpretationCache getInterpretationCache() { + return interpretationCache; } public SqmFunctionRegistry getSqmFunctionRegistry() { @@ -158,8 +161,8 @@ public class QueryEngine { semanticQueryProducer.close(); } - if ( queryPlanCache != null ) { - queryPlanCache.close(); + if ( interpretationCache != null ) { + interpretationCache.close(); } if ( sqmFunctionRegistry != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryImplementor.java index c9cfa7de09..2bcd0d70e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryImplementor.java @@ -25,4 +25,6 @@ public interface QueryImplementor extends Query { void setOptionalEntityName(String entityName); void setOptionalObject(Object optionalObject); + + QueryParameterBindings getParameterBindings(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryPlanCache.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryInterpretationCache.java similarity index 97% rename from hibernate-core/src/main/java/org/hibernate/query/spi/QueryPlanCache.java rename to hibernate-core/src/main/java/org/hibernate/query/spi/QueryInterpretationCache.java index d3a96e64d7..bce42dc62e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryPlanCache.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryInterpretationCache.java @@ -18,7 +18,7 @@ import org.hibernate.query.sqm.tree.SqmStatement; * @author Steve Ebersole */ @Incubating -public interface QueryPlanCache { +public interface QueryInterpretationCache { interface Key { } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingTypeResolver.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingTypeResolver.java index 2cea5d8f9e..d3ad73530e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingTypeResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterBindingTypeResolver.java @@ -15,6 +15,6 @@ import org.hibernate.metamodel.model.domain.AllowableParameterType; * @author Steve Ebersole */ public interface QueryParameterBindingTypeResolver { - AllowableParameterType resolveParameterBindType(Object bindValue); - AllowableParameterType resolveParameterBindType(Class clazz); + AllowableParameterType resolveParameterBindType(Object bindValue); + AllowableParameterType resolveParameterBindType(Class clazz); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterImplementor.java index e864d2057a..006681384b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryParameterImplementor.java @@ -16,4 +16,6 @@ public interface QueryParameterImplementor extends QueryParameter { void disallowMultiValuedBinding(); void applyAnticipatedType(AllowableParameterType type); + + NamedQueryMemento.ParameterMemento toMemento(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java index 9f3ba5947b..9bd6e81f32 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NativeQueryImplementor.java @@ -31,6 +31,7 @@ import org.hibernate.query.NativeQuery; import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; +import org.hibernate.query.spi.NameableQuery; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.type.Type; @@ -38,12 +39,16 @@ import org.hibernate.type.Type; * @author Steve Ebersole */ @Incubating -public interface NativeQueryImplementor extends QueryImplementor, NativeQuery { +public interface NativeQueryImplementor extends QueryImplementor, NativeQuery, NameableQuery { NativeQueryImplementor setCollectionKey(Serializable key); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // covariant overrides - NativeQuery + + @Override + NamedNativeQueryMemento toMemento(String name); + @Override NativeQueryImplementor addScalar(String columnAlias); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/consume/spi/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/consume/spi/BaseSqmToSqlAstConverter.java index f129511986..0ab5c6da41 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/consume/spi/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/consume/spi/BaseSqmToSqlAstConverter.java @@ -86,7 +86,7 @@ import org.hibernate.sql.ast.produce.metamodel.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.produce.spi.FromClauseAccess; import org.hibernate.sql.ast.produce.spi.FromClauseIndex; import org.hibernate.sql.ast.produce.spi.SqlAliasBaseManager; -import org.hibernate.sql.ast.produce.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.query.sqm.tree.expression.function.SqmFunction; import org.hibernate.sql.ast.produce.spi.SqlAstProcessingState; import org.hibernate.sql.ast.produce.spi.SqlAstQuerySpecProcessingState; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/consume/spi/SqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/consume/spi/SqmToSqlAstConverter.java new file mode 100644 index 0000000000..97c8c39d39 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/consume/spi/SqmToSqlAstConverter.java @@ -0,0 +1,16 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.sqm.consume.spi; + +/** + * Specialized SemanticQueryWalker (SQM visitor) for producing + * SQL AST + * + * @author Steve Ebersole + */ +public interface SqmToSqlAstConverter extends SemanticQueryWalker, SqlAstCreationState { +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index aa041456bd..545f15bd9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -31,10 +31,10 @@ import org.hibernate.sql.ast.produce.spi.SqlAstSelectDescriptor; import org.hibernate.sql.ast.produce.sqm.spi.SqmSelectInterpretation; import org.hibernate.sql.ast.produce.sqm.spi.SqmSelectToSqlAstConverter; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; -import org.hibernate.sql.exec.internal.RowTransformerJpaTupleImpl; -import org.hibernate.sql.exec.internal.RowTransformerPassThruImpl; -import org.hibernate.sql.exec.internal.RowTransformerSingularReturnImpl; -import org.hibernate.sql.exec.internal.RowTransformerTupleTransformerAdapter; +import org.hibernate.sql.results.internal.RowTransformerJpaTupleImpl; +import org.hibernate.sql.results.internal.RowTransformerPassThruImpl; +import org.hibernate.sql.results.internal.RowTransformerSingularReturnImpl; +import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; import org.hibernate.sql.exec.internal.TupleElementImpl; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcParameter; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index 9992da228b..ef9be13a7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -39,7 +39,7 @@ import org.hibernate.query.spi.ParameterMetadataImplementor; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterImplementor; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.query.spi.SelectQueryPlan; import org.hibernate.query.sqm.mutation.spi.DeleteHandler; @@ -317,15 +317,15 @@ public class QuerySqmImpl SelectQueryPlan queryPlan = null; - final QueryPlanCache.Key cacheKey = SqmInterpretationsKey.generateFrom( this ); + final QueryInterpretationCache.Key cacheKey = SqmInterpretationsKey.generateFrom( this ); if ( cacheKey != null ) { - queryPlan = getSession().getFactory().getQueryEngine().getQueryPlanCache().getSelectQueryPlan( cacheKey ); + queryPlan = getSession().getFactory().getQueryEngine().getInterpretationCache().getSelectQueryPlan( cacheKey ); } if ( queryPlan == null ) { queryPlan = buildSelectQueryPlan(); if ( cacheKey != null ) { - getSession().getFactory().getQueryEngine().getQueryPlanCache().cacheSelectQueryPlan( cacheKey, queryPlan ); + getSession().getFactory().getQueryEngine().getInterpretationCache().cacheSelectQueryPlan( cacheKey, queryPlan ); } } @@ -400,15 +400,15 @@ public class QuerySqmImpl NonSelectQueryPlan queryPlan = null; - final QueryPlanCache.Key cacheKey = SqmInterpretationsKey.generateNonSelectKey( this ); + final QueryInterpretationCache.Key cacheKey = SqmInterpretationsKey.generateNonSelectKey( this ); if ( cacheKey != null ) { - queryPlan = getSession().getFactory().getQueryEngine().getQueryPlanCache().getNonSelectQueryPlan( cacheKey ); + queryPlan = getSession().getFactory().getQueryEngine().getInterpretationCache().getNonSelectQueryPlan( cacheKey ); } if ( queryPlan == null ) { queryPlan = buildNonSelectQueryPlan(); if ( cacheKey != null ) { - getSession().getFactory().getQueryEngine().getQueryPlanCache().cacheNonSelectQueryPlan( cacheKey, queryPlan ); + getSession().getFactory().getQueryEngine().getInterpretationCache().cacheNonSelectQueryPlan( cacheKey, queryPlan ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java index a287f92e9a..e05b9d31ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmInterpretationsKey.java @@ -13,12 +13,12 @@ import org.hibernate.query.QueryParameter; import org.hibernate.query.ResultListTransformer; import org.hibernate.query.TupleTransformer; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; /** * @author Steve Ebersole */ -public class SqmInterpretationsKey implements QueryPlanCache.Key { +public class SqmInterpretationsKey implements QueryInterpretationCache.Key { @SuppressWarnings("WeakerAccess") public static SqmInterpretationsKey generateFrom(QuerySqmImpl query) { if ( !isCacheable( query ) ) { @@ -33,7 +33,7 @@ public class SqmInterpretationsKey implements QueryPlanCache.Key { } @SuppressWarnings("WeakerAccess") - public static QueryPlanCache.Key generateNonSelectKey(QuerySqmImpl query) { + public static QueryInterpretationCache.Key generateNonSelectKey(QuerySqmImpl query) { // todo (6.0) : do we want to cache non-select plans? If so, what requirements? // - very minimum is that it be a "simple" (non-multi-table) statement // diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/cte/CteTableGroup.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/cte/CteTableGroup.java index f6bfe2eaf7..f2d38de27b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/cte/CteTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/cte/CteTableGroup.java @@ -15,7 +15,7 @@ import org.hibernate.metamodel.model.mapping.EntityTypeDescriptor; import org.hibernate.metamodel.model.relational.spi.Column; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.from.AbstractTableGroup; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/idtable/IdTableGroup.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/idtable/IdTableGroup.java index 9a7d30b464..94754c244a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/idtable/IdTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/spi/idtable/IdTableGroup.java @@ -15,7 +15,7 @@ import org.hibernate.metamodel.model.mapping.EntityTypeDescriptor; import org.hibernate.metamodel.model.relational.spi.Column; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.from.AbstractTableGroup; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/SqmFunctionRegistry.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/SqmFunctionRegistry.java index 706b8edf32..efc8e3d274 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/SqmFunctionRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/SqmFunctionRegistry.java @@ -198,7 +198,7 @@ public class SqmFunctionRegistry { } /** - * Overlay (put on top) the functions registered here on top of the + * Overlay the functions registered here on top of the * incoming registry, potentially overriding its registrations */ public void overlay(SqmFunctionRegistry registryToOverly) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/PatternRenderer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/PatternRenderer.java index 480cfbf89f..e2919995cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/PatternRenderer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/PatternRenderer.java @@ -15,7 +15,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/SelfRenderingFunctionSqlAstExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/SelfRenderingFunctionSqlAstExpression.java index 90f5c84e34..090d946fea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/SelfRenderingFunctionSqlAstExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/internal/SelfRenderingFunctionSqlAstExpression.java @@ -15,7 +15,7 @@ import org.hibernate.query.sqm.tree.SqmVisitableNode; import org.hibernate.sql.SqlExpressableType; import org.hibernate.sql.ast.consume.spi.SelfRenderingExpression; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.produce.spi.SqlExpressable; import org.hibernate.sql.ast.produce.sqm.spi.SqmExpressionInterpretation; import org.hibernate.sql.ast.produce.sqm.spi.SqmToSqlAstConverter; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/FunctionAsExpressionTemplate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/FunctionAsExpressionTemplate.java index 79be655c6a..2c44be2cda 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/FunctionAsExpressionTemplate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/FunctionAsExpressionTemplate.java @@ -15,7 +15,7 @@ import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; import org.jboss.logging.Logger; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/NamedSqmFunctionTemplate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/NamedSqmFunctionTemplate.java index 11585c983f..2f59dee153 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/NamedSqmFunctionTemplate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/NamedSqmFunctionTemplate.java @@ -13,7 +13,7 @@ import org.hibernate.query.sqm.produce.function.ArgumentsValidator; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; import java.util.List; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/PatternBasedSqmFunctionTemplate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/PatternBasedSqmFunctionTemplate.java index e9ec0d7a00..b9413c6bdb 100755 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/PatternBasedSqmFunctionTemplate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/PatternBasedSqmFunctionTemplate.java @@ -15,7 +15,7 @@ import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.internal.PatternRenderer; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; import java.util.List; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/SelfRenderingFunctionSupport.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/SelfRenderingFunctionSupport.java index c28c762f37..3980380cca 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/SelfRenderingFunctionSupport.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/produce/function/spi/SelfRenderingFunctionSupport.java @@ -8,7 +8,7 @@ package org.hibernate.query.sqm.produce.function.spi; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.sql.ast.consume.spi.SqlAppender; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; import java.util.List; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCriteriaParameter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCriteriaParameter.java index 2f54d74063..a8fb88ffd9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCriteriaParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmCriteriaParameter.java @@ -7,6 +7,7 @@ package org.hibernate.query.sqm.tree.expression; import org.hibernate.metamodel.model.domain.AllowableParameterType; +import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.query.ParameterMetadata; import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.NodeBuilder; @@ -110,7 +111,7 @@ public class SqmCriteriaParameter } @Override - public ParameterMemento toMemento() { + public NamedCallableQueryMemento.ParameterMemento toMemento() { throw new UnsupportedOperationException( "ParameterMemento cannot be extracted from Criteria query parameter" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/produce/spi/SqlAstCreationContext.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationContext.java similarity index 93% rename from hibernate-core/src/main/java/org/hibernate/sql/ast/produce/spi/SqlAstCreationContext.java rename to hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationContext.java index d25233f848..c1bce080ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/produce/spi/SqlAstCreationContext.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationContext.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.sql.ast.produce.spi; +package org.hibernate.sql.ast.spi; import org.hibernate.metamodel.spi.DomainMetamodel; import org.hibernate.service.ServiceRegistry; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationState.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationState.java new file mode 100644 index 0000000000..54343f713e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstCreationState.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.spi; + +import java.util.List; + +import org.hibernate.LockMode; +import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.graph.spi.GraphImplementor; + +/** + * @author Steve Ebersole + */ +public interface SqlAstCreationState { + SqlAstCreationContext getCreationContext(); + + SqlAstProcessingState getCurrentProcessingState(); + + SqlExpressionResolver getSqlExpressionResolver(); + + FromClauseAccess getFromClauseAccess(); + + SqlAliasBaseGenerator getSqlAliasBaseGenerator(); + + default GraphImplementor getCurrentResultGraphNode() { + throw new NotYetImplementedFor6Exception( getClass() ); + } + + LockMode determineLockMode(String identificationVariable); + + /** + * Visit fetches for the given parent. + * + * We walk fetches via the SqlAstCreationContext because each "context" + * will define differently what should be fetched (HQL versus load) + */ + List visitFetches(FetchParent fetchParent); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstProcessingState.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstProcessingState.java new file mode 100644 index 0000000000..b07bb0bca0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstProcessingState.java @@ -0,0 +1,21 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.spi; + +/** + * Generalized access to state information relative to the "current process" of + * creating a SQL AST. + * + * @author Steve Ebersole + */ +public interface SqlAstProcessingState { + SqlAstProcessingState getParentState(); + + SqlExpressionResolver getSqlExpressionResolver(); + + SqlAstCreationState getSqlAstCreationState(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstWalker.java new file mode 100644 index 0000000000..def96a77cf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstWalker.java @@ -0,0 +1,150 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.ast.spi; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.produce.spi.SqlSelectionExpression; +import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression; +import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression; +import org.hibernate.sql.ast.tree.expression.CaseSimpleExpression; +import org.hibernate.sql.ast.tree.expression.CastTarget; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Distinct; +import org.hibernate.sql.ast.tree.expression.ExtractUnit; +import org.hibernate.sql.ast.tree.expression.GenericParameter; +import org.hibernate.sql.ast.tree.expression.NamedParameter; +import org.hibernate.sql.ast.tree.expression.PositionalParameter; +import org.hibernate.sql.ast.tree.expression.QueryLiteral; +import org.hibernate.sql.ast.tree.expression.SqlTuple; +import org.hibernate.sql.ast.tree.expression.Star; +import org.hibernate.sql.ast.tree.expression.TrimSpecification; +import org.hibernate.sql.ast.tree.expression.UnaryOperation; +import org.hibernate.sql.ast.tree.expression.domain.EntityTypeLiteral; +import org.hibernate.sql.ast.tree.from.FromClause; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupJoin; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.from.TableReferenceJoin; +import org.hibernate.sql.ast.tree.predicate.BetweenPredicate; +import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.FilterPredicate; +import org.hibernate.sql.ast.tree.predicate.GroupedPredicate; +import org.hibernate.sql.ast.tree.predicate.InListPredicate; +import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; +import org.hibernate.sql.ast.tree.predicate.Junction; +import org.hibernate.sql.ast.tree.predicate.LikePredicate; +import org.hibernate.sql.ast.tree.predicate.NegatedPredicate; +import org.hibernate.sql.ast.tree.predicate.NullnessPredicate; +import org.hibernate.sql.ast.tree.predicate.SelfRenderingPredicate; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; +import org.hibernate.sql.ast.tree.sort.SortSpecification; +import org.hibernate.sql.ast.tree.update.Assignment; +import org.hibernate.sql.results.spi.SqlSelection; + +/** + * @author Steve Ebersole + * @author Andrea Boriero + */ +public interface SqlAstWalker { + + SessionFactoryImplementor getSessionFactory(); + + void visitAssignment(Assignment assignment); + + void visitQuerySpec(QuerySpec querySpec); + + void visitSortSpecification(SortSpecification sortSpecification); + + void visitLimitOffsetClause(QuerySpec querySpec); + + void visitSelectClause(SelectClause selectClause); + + void visitSqlSelection(SqlSelection sqlSelection); + + void visitFromClause(FromClause fromClause); + + void visitTableGroup(TableGroup tableGroup); + + void visitTableGroupJoin(TableGroupJoin tableGroupJoin); + + void visitTableReference(TableReference tableReference); + + void visitTableReferenceJoin(TableReferenceJoin tableReferenceJoin); + +// void visitEntityExpression(EntityReference entityExpression); +// +// void visitSingularAttributeReference(SingularAttributeReference attributeExpression); +// +// void visitPluralAttribute(PluralAttributeReference pluralAttributeReference); +// +// void visitPluralAttributeElement(PluralAttributeElementReference elementExpression); +// +// void visitPluralAttributeIndex(PluralAttributeIndexReference indexExpression); + + void visitColumnReference(ColumnReference columnReference); + + void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeticExpression); + + void visitCaseSearchedExpression(CaseSearchedExpression caseSearchedExpression); + + void visitCaseSimpleExpression(CaseSimpleExpression caseSimpleExpression); + + void visitNamedParameter(NamedParameter namedParameter); + + void visitGenericParameter(GenericParameter parameter); + + void visitPositionalParameter(PositionalParameter positionalParameter); + + void visitQueryLiteral(QueryLiteral queryLiteral); + + void visitUnaryOperationExpression(UnaryOperation unaryOperationExpression); + + void visitBetweenPredicate(BetweenPredicate betweenPredicate); + + void visitFilterPredicate(FilterPredicate filterPredicate); + + void visitGroupedPredicate(GroupedPredicate groupedPredicate); + + void visitInListPredicate(InListPredicate inListPredicate); + + void visitInSubQueryPredicate(InSubQueryPredicate inSubQueryPredicate); + + void visitJunction(Junction junction); + + void visitLikePredicate(LikePredicate likePredicate); + + void visitNegatedPredicate(NegatedPredicate negatedPredicate); + + void visitNullnessPredicate(NullnessPredicate nullnessPredicate); + + void visitRelationalPredicate(ComparisonPredicate comparisonPredicate); + + void visitSelfRenderingPredicate(SelfRenderingPredicate selfRenderingPredicate); + + void visitSelfRenderingExpression(SelfRenderingExpression expression); + + void visitSqlSelectionExpression(SqlSelectionExpression expression); + + void visitEntityTypeLiteral(EntityTypeLiteral expression); + + void visitTuple(SqlTuple tuple); + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // functions + + void visitExtractUnit(ExtractUnit unit); + + void visitCastTarget(CastTarget castTarget); + + void visitTrimSpecification(TrimSpecification trimSpecification); + + void visitStar(Star star); + + void visitDistinct(Distinct distinct); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/package-info.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/package-info.java new file mode 100644 index 0000000000..d4f4ab6748 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/package-info.java @@ -0,0 +1,12 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +/** + * Package defining an AST for describing a SQL Statement, as well as + * for creating and consuming then + */ +package org.hibernate.sql.ast.spi; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcAnonBlock.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcAnonBlock.java new file mode 100644 index 0000000000..fa0fd6985e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcAnonBlock.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.exec.spi; + +import java.util.List; + +import org.hibernate.sql.results.spi.JdbcValuesMappingDescriptor; + +/** + * An anonymous call block (sometimes called an anonymous procedure) to be executed + * on the database. The format of this various by database, but it is essentially an + * unnamed procedure without OUT, INOUT or REF_CURSOR type parameters + * + * @author Steve Ebersole + */ +public interface JdbcAnonBlock extends JdbcOperation { + /** + * Retrieve the "result set mappings" for processing any ResultSets returned from + * the JDBC call. We expose multiple because JPA allows for an application to + * define multiple such mappings which are (unclearly) intended to describe the mapping + * for each ResultSet (in order) returned from the call. + */ + List getResultSetMappings(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcCall.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcCall.java new file mode 100644 index 0000000000..31c8beee4f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcCall.java @@ -0,0 +1,38 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.exec.spi; + +import java.util.List; + +/** + * @author Steve Ebersole + */ +public interface JdbcCall extends JdbcAnonBlock { + /** + * If the call is a function, returns the function return descriptor + */ + JdbcCallFunctionReturn getFunctionReturn(); + + /** + * Get the list of any parameter registrations we need to register + * against the generated CallableStatement + */ + List getParameterRegistrations(); + + /** + * Extractors for reading back any OUT/INOUT parameters. + * + * @apiNote Note that REF_CURSOR parameters should be handled via + * {@link #getCallRefCursorExtractors()} + */ + List getParameterExtractors(); + + /** + * Extractors for REF_CURSOR (ResultSet) parameters + */ + List getCallRefCursorExtractors(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelect.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelect.java new file mode 100644 index 0000000000..68390ab046 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelect.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.exec.spi; + +import org.hibernate.sql.results.spi.JdbcValuesMappingDescriptor; + +/** + * Executable JDBC command + * + * @author Steve Ebersole + */ +public interface JdbcSelect extends JdbcOperation { + /** + * Retrieve the descriptor for performing the mapping + * of the JDBC ResultSet back to object query results. + */ + JdbcValuesMappingDescriptor getResultSetMapping(); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelectExecutor.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelectExecutor.java new file mode 100644 index 0000000000..abc8417dc5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelectExecutor.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.exec.spi; + +import java.util.List; +import java.util.stream.Stream; + +import org.hibernate.Incubating; +import org.hibernate.ScrollMode; +import org.hibernate.query.spi.ScrollableResultsImplementor; +import org.hibernate.sql.results.spi.RowTransformer; + +/** + * An executor for JdbcSelect operations. + * + * @author Steve Ebersole + */ +@Incubating +public interface JdbcSelectExecutor { + // todo (6.0) : Ideally we'd have a singular place (JdbcServices? ServiceRegistry?) to obtain these executors + + List list( + JdbcSelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer); + + ScrollableResultsImplementor scroll( + JdbcSelect jdbcSelect, + ScrollMode scrollMode, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer); + + Stream stream( + JdbcSelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcValueBinder.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcValueBinder.java new file mode 100644 index 0000000000..755b68ba55 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcValueBinder.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.exec.spi; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * The low-level contract for binding (writing) values to JDBC. + * + * @apiNote At the JDBC-level we always deal with simple/basic values; never + * composites, entities, collections, etc + * + * @author Steve Ebersole + * + * @see JdbcValueExtractor + */ +public interface JdbcValueBinder { + /** + * Bind a value to a prepared statement. + */ + void bind(PreparedStatement statement, int parameterPosition, J value, ExecutionContext executionContext) throws SQLException; + + /** + * Bind a value to a CallableStatement. + * + * @apiNote Binding to a CallableStatement by position is done via {@link #bind(PreparedStatement, int, Object, ExecutionContext)} - + * CallableStatement extends PreparedStatement + */ + void bind(CallableStatement statement, String parameterName, J value, ExecutionContext executionContext) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcValueExtractor.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcValueExtractor.java new file mode 100644 index 0000000000..ffb5b64555 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcValueExtractor.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.exec.spi; + +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * The low-level contract for extracting (reading) values to JDBC. + * + * @apiNote At the JDBC-level we always deal with simple/basic values; never + * composites, entities, collections, etc + * + * @author Steve Ebersole + * + * @see JdbcValueBinder + * @see SqlExpressableType + */ +public interface JdbcValueExtractor { + /** + * Extract value from result set + */ + J extract(ResultSet resultSet, int jdbcParameterPosition, ExecutionContext executionContext) throws SQLException; + + /** + * Extract value from CallableStatement + */ + J extract( + CallableStatement statement, + int jdbcParameterPosition, + ExecutionContext executionContext) throws SQLException; + + /** + * Extract value from CallableStatement, by name + */ + J extract(CallableStatement statement, String jdbcParameterName, ExecutionContext executionContext) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/JdbcValuesMappingProducerUndefined.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/JdbcValuesMappingProducerUndefined.java index 740cfb0174..90b7730aad 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/JdbcValuesMappingProducerUndefined.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/JdbcValuesMappingProducerUndefined.java @@ -12,7 +12,7 @@ import java.util.List; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.sql.JdbcValueExtractor; -import org.hibernate.sql.ast.consume.spi.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstWalker; import org.hibernate.sql.results.internal.StandardResultSetMapping; import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.JdbcValuesMapping; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerJpaTupleImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerJpaTupleImpl.java new file mode 100644 index 0000000000..4de41c4124 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerJpaTupleImpl.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +package org.hibernate.sql.results.internal; + +import java.util.List; +import javax.persistence.Tuple; +import javax.persistence.TupleElement; + +import org.hibernate.sql.exec.spi.RowTransformer; + +/** + * RowTransformer generating a JPA {@link Tuple} + * + * @author Steve Ebersole + */ +public class RowTransformerJpaTupleImpl implements RowTransformer { + private final List> tupleElements; + + public RowTransformerJpaTupleImpl(List> tupleElements) { + this.tupleElements = tupleElements; + } + + @Override + public Tuple transformRow(Object[] row) { + return new TupleImpl( tupleElements, row ); + } + + @Override + public int determineNumberOfResultElements(int rawElementCount) { + return 1; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerPassThruImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerPassThruImpl.java new file mode 100644 index 0000000000..de1f5bb46f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerPassThruImpl.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal; + +import org.hibernate.Incubating; +import org.hibernate.sql.exec.spi.RowTransformer; + +/** + * Essentially a no-op transformer - simply passes the result through + * + * @author Steve Ebersole + */ +@Incubating +public class RowTransformerPassThruImpl implements RowTransformer { + /** + * Singleton access + */ + public static final RowTransformerPassThruImpl INSTANCE = new RowTransformerPassThruImpl(); + + @SuppressWarnings("unchecked") + public static RowTransformerPassThruImpl instance() { + return INSTANCE; + } + + private RowTransformerPassThruImpl() { + } + + @Override + @SuppressWarnings("unchecked") + public T transformRow(Object[] row) { + return row.length == 1 ? (T) row[0] : (T) row; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerSingularReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerSingularReturnImpl.java new file mode 100644 index 0000000000..a1af160760 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerSingularReturnImpl.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +package org.hibernate.sql.results.internal; + +import org.hibernate.sql.exec.spi.RowTransformer; + +/** + * @author Steve Ebersole + */ +public class RowTransformerSingularReturnImpl implements RowTransformer { + /** + * Singleton access + */ + public static final RowTransformerSingularReturnImpl INSTANCE = new RowTransformerSingularReturnImpl(); + + @SuppressWarnings("unchecked") + public static RowTransformerSingularReturnImpl instance() { + return INSTANCE; + } + + @Override + @SuppressWarnings("unchecked") + public R transformRow(Object[] row) { + return (R) row[0]; + } + + @Override + public int determineNumberOfResultElements(int rawElementCount) { + return 1; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerTupleTransformerAdapter.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerTupleTransformerAdapter.java new file mode 100644 index 0000000000..0eab3be183 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/RowTransformerTupleTransformerAdapter.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +package org.hibernate.sql.results.internal; + +import org.hibernate.query.TupleTransformer; +import org.hibernate.sql.results.spi.RowTransformer; + +/** + * An adapter for treating a {@link TupleTransformer} as a {@link RowTransformer} + * + * @author Steve Ebersole + */ +public class RowTransformerTupleTransformerAdapter implements RowTransformer { + private final String[] aliases; + private final TupleTransformer tupleTransformer; + + public RowTransformerTupleTransformerAdapter(String[] aliases, TupleTransformer tupleTransformer) { + this.aliases = aliases; + this.tupleTransformer = tupleTransformer; + } + + @Override + public T transformRow(Object[] row) { + assert aliases == null || row.length == aliases.length; + return tupleTransformer.transformTuple( row, aliases ); + } + + @Override + public int determineNumberOfResultElements(int rawElementCount) { + return tupleTransformer.determineNumberOfResultElements( rawElementCount ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/JdbcValuesMapping.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/JdbcValuesMapping.java index 3ee7bbbbcc..2148603466 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/JdbcValuesMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/JdbcValuesMapping.java @@ -9,14 +9,13 @@ package org.hibernate.sql.results.spi; import java.util.List; import java.util.Set; import java.util.function.Consumer; -import java.util.stream.Collectors; /** - * The "resolved" form of {@link JdbcValuesMappingProducer} providing access + * The "resolved" form of {@link JdbcValuesMappingDescriptor} providing access * to resolved JDBC results ({@link SqlSelection}) descriptors and resolved * domain results ({@link DomainResult}) descriptors. * - * @see JdbcValuesMappingProducer#resolve + * @see JdbcValuesMappingDescriptor#resolve * * @author Steve Ebersole */ @@ -27,13 +26,9 @@ public interface JdbcValuesMapping { */ Set getSqlSelections(); - List> getDomainResults(); + List getDomainResults(); - default List> resolveAssemblers( + List resolveAssemblers( Consumer initializerConsumer, - AssemblerCreationState creationState) { - return getDomainResults().stream() - .map( domainResult -> domainResult.createResultAssembler( initializerConsumer, creationState ) ) - .collect( Collectors.toList() ); - } + AssemblerCreationState creationState); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/JdbcValuesMappingDescriptor.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/JdbcValuesMappingDescriptor.java new file mode 100644 index 0000000000..12b3854fd2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/JdbcValuesMappingDescriptor.java @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.spi; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +/** + * Descriptor for the mapping of a JDBC ResultSet providing + * support for delayed resolution if needed (mainly in the + * case of {@link org.hibernate.query.NativeQuery}). + * + * @author Steve Ebersole + */ +public interface JdbcValuesMappingDescriptor { + + // todo (6.0) : ? have a specific impl for "consuming" JPA SqlResultSetMapping? + // there are a few different cases for defining result mappings: + // 1) JPA SqlResultSetMapping + // 2) JPA Class-based mapping + // 3) Hibernate's legacy XML-defined mapping + // 4) Hibernate's legacy Query-specific mapping (`NativeQuery#addScalar`, etc). + // 5) + // + // (1), (2) and (3) can really all be handled by the same impl - they are all + // known/resolved up-front. These cases can all be represented as + // `ResultSetMappingDescriptorDefined` + // + // (4) is unique though in that it is not know up + // front and needs to wait until there is a ResultSet available to complete + // its "resolution". This case is represented as `ResultSetMappingDescriptorUndefined` + // + // Both `ResultSetMappingDescriptorDefined` and `ResultSetMappingDescriptorUndefined` could + // definitely use better names + + + /** + * Access to information about the underlying JDBC values + * such as type, position, column name, etc + */ + interface JdbcValuesMetadata { + /** + * Number of values in the underlying result + */ + int getColumnCount(); + + /** + * Position of a particular result value by name + */ + int resolveColumnPosition(String columnName); + + /** + * Name of a particular result value by position + */ + String resolveColumnName(int position); + + /** + * Descriptor of the JDBC/SQL type of a particular result value by + * position + */ + SqlTypeDescriptor resolveSqlTypeDescriptor(int position); + + } + + /** + * Resolve the selections (both at the JDBC and object level) for this + * mapping. Acts as delayed access to this resolution process to support + * "auto discovery" as needed for "undefined scalar" results as defined by + * JPA. + * + * @param jdbcResultsMetadata Access to information about the underlying results + * @param sessionFactory + * @return The resolved result references + */ + JdbcValuesMapping resolve(JdbcValuesMetadata jdbcResultsMetadata, SessionFactoryImplementor sessionFactory); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowProcessingState.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowProcessingState.java index 3c6e9761d0..626f34b410 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowProcessingState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowProcessingState.java @@ -10,18 +10,45 @@ import org.hibernate.query.NavigablePath; import org.hibernate.sql.exec.spi.ExecutionContext; /** - * State pertaining to the processing of a single row of a JdbcValuesSource + * State pertaining to the processing of a single "row" of a JdbcValuesSource * * @author Steve Ebersole */ public interface RowProcessingState extends ExecutionContext { /** - * Access to the "parent state" related to the overall processing - * of the results. + * Access to the state related to the overall processing of the results. */ JdbcValuesSourceProcessingState getJdbcValuesSourceProcessingState(); + /** + * Retrieve the value corresponding to the given SqlSelection as part + * of the "current JDBC row". + * + * @see SqlSelection#getValuesArrayPosition() + * @see #getJdbcValue(int) + */ + default Object getJdbcValue(SqlSelection sqlSelection) { + return getJdbcValue( sqlSelection.getValuesArrayPosition() ); + } + + /** + * Retrieve the value corresponding to the given index as part + * of the "current JDBC row". + * + * We read all the ResultSet values for the given row one time + * and store them into an array internally based on the principle that multiple + * accesses to this array will be significantly faster than accessing them + * from the ResultSet potentially multiple times. + */ + Object getJdbcValue(int position); + + /** + * Callback at the end of processing the current "row" + */ void finishRowProcessing(); + /** + * Locate the Initalizer registered for the given path + */ Initializer resolveInitializer(NavigablePath path); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowTransformer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowTransformer.java new file mode 100644 index 0000000000..f0921a4611 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/RowTransformer.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.spi; + +import org.hibernate.Incubating; + +/** + * Defines transformation of a raw row in the domain query result row. + * + * E.g. a query might select multiple + * + * @see org.hibernate.query.TupleTransformer + * + * @author Steve Ebersole + */ +@Incubating +public interface RowTransformer { + /** + * Transform the "raw" row values into the ultimate query result (for a row) + */ + T transformRow(Object[] row); + + /** + * How many result elements will this transformation produce? + */ + default int determineNumberOfResultElements(int rawElementCount) { + return rawElementCount; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/SqlSelection.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/SqlSelection.java new file mode 100644 index 0000000000..1c06130475 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/SqlSelection.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.spi; + +import java.util.function.Consumer; + +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.hibernate.sql.ast.spi.SqlAstWalker; +import org.hibernate.sql.exec.spi.JdbcValueExtractor; + +/** + * Represents a selection at the SQL/JDBC level. Essentially made up of: + * + * {@link #getJdbcValueExtractor}:: How to read a value from JDBC (conceptually similar to a method reference) + * {@link #getValuesArrayPosition}:: The position for this selection in relation to the "JDBC values array" (see {@link RowProcessingState#getJdbcValue}) + * {@link #getJdbcResultSetIndex()}:: The position for this selection in relation to the JDBC object (ResultSet, etc) + * + * @author Steve Ebersole + */ +public interface SqlSelection extends SqlSelectionGroupNode { + /** + * Get the extractor that can be used to extract JDBC values for this selection + */ + JdbcValueExtractor getJdbcValueExtractor(); + + // todo (6.0) : add proper support for "virtual" selections + // - things like selecting a literal or a parameter + // - would require the ability to define the "value + // arrays" position and the ResultSet position + // independently on the impls side. + // - would require understanding whether the selection + // occurs in the root query or a subquery. If used + // in a subquery we need to render the parameter rather + // than just manage it locally. + + /** + * Get the position within the "JDBC values" array (0-based). Negative indicates this is + * not a "real" selection + */ + int getValuesArrayPosition(); + + /** + * Get the JDBC position (1-based) + */ + default int getJdbcResultSetIndex() { + return getValuesArrayPosition() + 1; + } + + default void prepare( + JdbcValuesMappingDescriptor.JdbcValuesMetadata jdbcResultsMetadata, + SessionFactoryImplementor sessionFactory) { + // By default we have nothing to do. Here as a hook for NativeQuery mapping resolutions + } + + @Override + default Object hydrateStateArray(RowProcessingState currentRowState) { + return currentRowState.getJdbcValue( getValuesArrayPosition() ); + } + + @Override + default void visitSqlSelections(Consumer action) { + action.accept( this ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // todo (6.0) : remove methods below + + /** + * todo (6.0) : why removing this one? + */ + void accept(SqlAstWalker interpreter); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/SqlSelectionGroupNode.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/SqlSelectionGroupNode.java new file mode 100644 index 0000000000..6c894a40cb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/SqlSelectionGroupNode.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.spi; + +import java.util.function.Consumer; + +/** + * @author Steve Ebersole + */ +public interface SqlSelectionGroupNode { + Object hydrateStateArray(RowProcessingState currentRowState); + void visitSqlSelections(Consumer action); +} diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java index f6abe610c7..385bfb89d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/internal/Helper.java @@ -14,7 +14,9 @@ import java.sql.SQLException; import java.util.Map; import org.hibernate.boot.model.relational.Namespace; +import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.internal.CoreLogging; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueBinder.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueBinder.java index 579750a733..b2cbb7d571 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueBinder.java @@ -16,26 +16,19 @@ import java.sql.SQLException; */ public interface ValueBinder { /** - * Bind a value to a prepared statement. + * Bind a value to a prepared statement by index * - * @param st The prepared statement to which to bind the value. - * @param value The value to bind. - * @param index The position at which to bind the value within the prepared statement - * @param options The options. + * @apiNote Also works for callables since {@link CallableStatement} extends + * {@link PreparedStatement} * * @throws SQLException Indicates a JDBC error occurred. */ - public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException; + void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException; /** - * Bind a value to a CallableStatement. - * - * @param st The prepared statement to which to bind the value. - * @param value The value to bind. - * @param name The name to bind the value within the prepared statement - * @param options The options. + * Bind a value to a callable statement by name * * @throws SQLException Indicates a JDBC error occurred. */ - public void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException; + void bind(CallableStatement st, X value, String name, WrapperOptions options) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java index aaf8ecf057..24eb85a1d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java @@ -11,7 +11,8 @@ import java.sql.ResultSet; import java.sql.SQLException; /** - * Contract for extracting value via JDBC (from {@link ResultSet} or as output param from {@link CallableStatement}). + * Contract for extracting value via JDBC from {@link ResultSet} or as output + * param from {@link CallableStatement}. * * @author Steve Ebersole */ @@ -19,17 +20,21 @@ public interface ValueExtractor { /** * Extract value from result set * - * @param rs The result set from which to extract the value - * @param name The name by which to extract the value from the result set - * @param options The options - * - * @return The extracted value + * @throws SQLException Indicates a JDBC error occurred. + */ + X extract(ResultSet rs, String name, WrapperOptions options) throws SQLException; + + /** + * Extract value from a callable output parameter by index * * @throws SQLException Indicates a JDBC error occurred. */ - public X extract(ResultSet rs, String name, WrapperOptions options) throws SQLException; + X extract(CallableStatement statement, int index, WrapperOptions options) throws SQLException; - public X extract(CallableStatement statement, int index, WrapperOptions options) throws SQLException; - - public X extract(CallableStatement statement, String[] paramNames, WrapperOptions options) throws SQLException; + /** + * Extract value from a callable output parameter by name + * + * @throws SQLException Indicates a JDBC error occurred. + */ + X extract(CallableStatement statement, String[] paramNames, WrapperOptions options) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java index a296a28530..04ba4399d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/WrapperOptions.java @@ -16,8 +16,6 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** * Gives binding (nullSafeSet) and extracting (nullSafeGet) code access to options. * - * @todo Definitely could use a better name - * * @author Steve Ebersole */ public interface WrapperOptions { @@ -26,14 +24,14 @@ public interface WrapperOptions { * * @return {@code true}/{@code false} */ - public boolean useStreamForLobBinding(); + boolean useStreamForLobBinding(); /** * Obtain access to the {@link LobCreator} * * @return The LOB creator */ - public LobCreator getLobCreator(); + LobCreator getLobCreator(); /** * Allow remapping of descriptors for dealing with sql type. @@ -42,7 +40,7 @@ public interface WrapperOptions { * * @return The remapped descriptor. May be the same as the known descriptor indicating no remapping. */ - public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor); + SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor); /** * The JDBC {@link TimeZone} used when persisting Timestamp and DateTime properties into the database. @@ -52,5 +50,5 @@ public interface WrapperOptions { * * @return JDBC {@link TimeZone} */ - public TimeZone getJdbcTimeZone(); + TimeZone getJdbcTimeZone(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java b/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java index 02ba8aff9c..9f9a96191f 100755 --- a/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/cid/CompositeIdTest.java @@ -44,7 +44,7 @@ public class CompositeIdTest extends BaseCoreFunctionalTestCase { @Test public void testNonDistinctCountOfEntityWithCompositeId() { // the check here is all based on whether we had commas in the expressions inside the count - final HQLQueryPlan plan = sessionFactory().getQueryPlanCache().getHQLQueryPlan( + final HQLQueryPlan plan = sessionFactory().getQueryInterpretationCache().getHQLQueryPlan( "select count(o) from Order o", false, Collections.EMPTY_MAP @@ -70,7 +70,7 @@ public class CompositeIdTest extends BaseCoreFunctionalTestCase { public void testDistinctCountOfEntityWithCompositeId() { // today we do not account for Dialects supportsTupleDistinctCounts() is false. though really the only // "option" there is to throw an error. - final HQLQueryPlan plan = sessionFactory().getQueryPlanCache().getHQLQueryPlan( + final HQLQueryPlan plan = sessionFactory().getQueryInterpretationCache().getHQLQueryPlan( "select count(distinct o) from Order o", false, Collections.EMPTY_MAP diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java index 7efff1e585..716fd01078 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java @@ -157,7 +157,7 @@ public class EntityJoinTest extends BaseNonConfigCoreFunctionalTestCase { @TestForIssue(jiraKey = "HHH-11538") public void testNoImpliedJoinGeneratedForEqualityComparison() { doInHibernate( this::sessionFactory, session -> { - final HQLQueryPlan plan = sessionFactory().getQueryPlanCache().getHQLQueryPlan( + final HQLQueryPlan plan = sessionFactory().getQueryInterpretationCache().getHQLQueryPlan( "select r.id, cust.name " + "from FinancialRecord r " + " join Customer cust on r.customer = cust" + diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/TupleSupportTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/TupleSupportTest.java index 5f5d867e06..e091367502 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/TupleSupportTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/TupleSupportTest.java @@ -14,7 +14,6 @@ import javax.persistence.Id; import java.util.Collections; import org.hibernate.Filter; -import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; @@ -23,13 +22,10 @@ import org.hibernate.engine.query.spi.HQLQueryPlan; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.junit.After; -import org.junit.AfterClass; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseUnitTestCase; import static org.junit.Assert.assertEquals; @@ -81,7 +77,7 @@ public class TupleSupportTest extends BaseUnitTestCase { @Test public void testImplicitTupleNotEquals() { final String hql = "from TheEntity e where e.compositeValue <> :p1"; - HQLQueryPlan queryPlan = ( (SessionFactoryImplementor) sessionFactory ).getQueryPlanCache() + HQLQueryPlan queryPlan = ( (SessionFactoryImplementor) sessionFactory ).getQueryInterpretationCache() .getHQLQueryPlan( hql, false, Collections.emptyMap() ); assertEquals( 1, queryPlan.getSqlStrings().length ); @@ -92,7 +88,7 @@ public class TupleSupportTest extends BaseUnitTestCase { @Test public void testImplicitTupleNotInList() { final String hql = "from TheEntity e where e.compositeValue not in (:p1,:p2)"; - HQLQueryPlan queryPlan = ( (SessionFactoryImplementor) sessionFactory ).getQueryPlanCache() + HQLQueryPlan queryPlan = ( (SessionFactoryImplementor) sessionFactory ).getQueryInterpretationCache() .getHQLQueryPlan( hql, false, Collections.emptyMap() ); assertEquals( 1, queryPlan.getSqlStrings().length ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java b/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java index 652e0b490a..2d309fbace 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/inheritance/discriminator/MultiInheritanceImplicitDowncastTest.java @@ -52,7 +52,6 @@ import javax.persistence.MappedSuperclass; import javax.persistence.OneToMany; import javax.persistence.OrderColumn; -import org.hibernate.Session; import org.hibernate.engine.query.spi.HQLQueryPlan; import org.hibernate.hql.spi.QueryTranslator; @@ -118,7 +117,7 @@ public class MultiInheritanceImplicitDowncastTest extends BaseCoreFunctionalTest } private void testMultiJoinAddition(String hql) { - final HQLQueryPlan plan = sessionFactory().getQueryPlanCache().getHQLQueryPlan( + final HQLQueryPlan plan = sessionFactory().getQueryInterpretationCache().getHQLQueryPlan( hql, false, Collections.EMPTY_MAP diff --git a/hibernate-core/src/test/java/org/hibernate/test/queryplan/GetHqlQueryPlanTest.java b/hibernate-core/src/test/java/org/hibernate/test/queryplan/GetHqlQueryPlanTest.java index 7adab39a34..0ded247165 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/queryplan/GetHqlQueryPlanTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/queryplan/GetHqlQueryPlanTest.java @@ -11,7 +11,7 @@ import java.util.Map; import org.hibernate.Session; import org.hibernate.engine.query.spi.HQLQueryPlan; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; @@ -45,7 +45,7 @@ public class GetHqlQueryPlanTest extends BaseCoreFunctionalTestCase { @Test public void testHqlQueryPlan() { Session s = openSession(); - QueryPlanCache cache = ( ( SessionImplementor ) s ).getFactory().getQueryPlanCache(); + QueryInterpretationCache cache = ( ( SessionImplementor ) s ).getFactory().getQueryInterpretationCache(); assertTrue( getEnabledFilters( s ).isEmpty() ); HQLQueryPlan plan1 = cache.getHQLQueryPlan( "from Person", false, getEnabledFilters( s ) ); @@ -72,7 +72,7 @@ public class GetHqlQueryPlanTest extends BaseCoreFunctionalTestCase { @TestForIssue(jiraKey = "HHH-12413") public void testExpandingQueryStringMultipleTimesWorks() { doInHibernate( this::sessionFactory, session -> { - QueryPlanCache cache = ( ( SessionImplementor ) session ).getFactory().getQueryPlanCache(); + QueryInterpretationCache cache = ( ( SessionImplementor ) session ).getFactory().getQueryInterpretationCache(); String queryString = "from Person where name in :names"; HQLQueryPlan plan = cache.getHQLQueryPlan( queryString, false, getEnabledFilters( session ) ); @@ -105,7 +105,7 @@ public class GetHqlQueryPlanTest extends BaseCoreFunctionalTestCase { @Test public void testHqlQueryPlanWithEnabledFilter() { Session s = openSession(); - QueryPlanCache cache = ( (SessionImplementor) s ).getFactory().getQueryPlanCache(); + QueryInterpretationCache cache = ( (SessionImplementor) s ).getFactory().getQueryInterpretationCache(); HQLQueryPlan plan1A = cache.getHQLQueryPlan( "from Person", true, getEnabledFilters( s ) ); HQLQueryPlan plan1B = cache.getHQLQueryPlan( "from Person", false, getEnabledFilters( s ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java b/hibernate-core/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java index d8f43b41e0..4160857232 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java @@ -9,7 +9,7 @@ package org.hibernate.test.queryplan; import org.junit.Test; import org.hibernate.engine.query.spi.NativeSQLQueryPlan; -import org.hibernate.query.spi.QueryPlanCache; +import org.hibernate.query.spi.QueryInterpretationCache; import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; @@ -29,7 +29,7 @@ public class NativeSQLQueryPlanEqualsTest extends BaseCoreFunctionalTestCase { @Test public void testNativeSQLQuerySpecEquals() { - QueryPlanCache cache = new QueryPlanCache( sessionFactory() ); + QueryInterpretationCache cache = new QueryInterpretationCache( sessionFactory() ); NativeSQLQuerySpecification firstSpec = createSpec(); NativeSQLQuerySpecification secondSpec = createSpec(); diff --git a/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversEntityManagerFactoryProducer.java b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversEntityManagerFactoryProducer.java new file mode 100644 index 0000000000..dbfff5ab72 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversEntityManagerFactoryProducer.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.envers; + +import javax.persistence.EntityManagerFactory; + +/** + * Envers contract for something that can build an EntityManagerFactory based on an audit strategy. + * + * @author Chris Cranford + */ +public interface EnversEntityManagerFactoryProducer { + EntityManagerFactory produceEntityManagerFactory(String auditStrategyName); +} diff --git a/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversEntityManagerFactoryScope.java b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversEntityManagerFactoryScope.java new file mode 100644 index 0000000000..da86d513a4 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversEntityManagerFactoryScope.java @@ -0,0 +1,228 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.envers; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.hibernate.testing.junit5.EntityManagerFactoryAccess; + +/** + * @author Chris Cranford + */ +public class EnversEntityManagerFactoryScope implements EntityManagerFactoryAccess { + private final EnversEntityManagerFactoryProducer entityManagerFactoryProducer; + private final Strategy auditStrategy; + + private EntityManagerFactory entityManagerFactory; + + public EnversEntityManagerFactoryScope(EnversEntityManagerFactoryProducer producer, Strategy auditStrategy) { + this.auditStrategy = auditStrategy; + this.entityManagerFactoryProducer = producer; + } + + public void releaseEntityManagerFactory() { + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + entityManagerFactory = null; + } + } + + @Override + public EntityManagerFactory getEntityManagerFactory() { + if ( entityManagerFactory == null || !entityManagerFactory.isOpen() ) { + final String strategy = auditStrategy.getSettingValue(); + entityManagerFactory = entityManagerFactoryProducer.produceEntityManagerFactory( strategy ); + } + return entityManagerFactory; + } + + public void inJtaTransaction(Consumer action) throws Exception { + inJtaTransaction( getEntityManagerFactory(), action ); + } + + public void inJtaTransaction(EntityManagerFactory factory, Consumer action) throws Exception { + TestingJtaPlatformImpl.INSTANCE.getTransactionManager().begin(); + EntityManager entityManager = factory.createEntityManager(); + try { + action.accept( entityManager ); + } + finally { + entityManager.close(); + TestingJtaPlatformImpl.tryCommit(); + } + } + + public void inTransaction(Consumer action) { + inTransaction( getEntityManagerFactory(), action ); + } + + public void inTransaction(EntityManagerFactory factory, Consumer action) { + EntityManager entityManager = factory.createEntityManager(); + try { + entityManager.getTransaction().begin(); + action.accept( entityManager ); + entityManager.getTransaction().commit(); + } + catch ( Exception e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + finally { + entityManager.close(); + } + } + + public void inTransaction(EntityManager entityManager, Consumer action) { + try { + entityManager.getTransaction().begin(); + action.accept( entityManager ); + entityManager.getTransaction().commit(); + } + catch ( Exception e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + } + + @SafeVarargs + public final void inTransactions(Consumer... actions) { + EntityManager entityManager = getEntityManagerFactory().createEntityManager(); + try { + for ( Consumer action : actions ) { + try { + entityManager.getTransaction().begin(); + action.accept( entityManager ); + entityManager.getTransaction().commit(); + } + catch ( Exception e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + } + } + finally { + entityManager.close(); + } + } + + @SafeVarargs + public final List inTransactionsWithTimeouts(int timeout, Consumer... actions) { + EntityManager entityManager = getEntityManagerFactory().createEntityManager(); + try { + final List timestamps = new ArrayList<>(); + + timestamps.add( System.currentTimeMillis() ); + for ( Consumer action : actions ) { + try { + Thread.sleep( 100 ); + entityManager.getTransaction().begin(); + action.accept( entityManager ); + entityManager.getTransaction().commit(); + timestamps.add( System.currentTimeMillis() ); + } + catch ( InterruptedException e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw new RuntimeException( "Failed to wait on timeout", e ); + } + catch ( Exception e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + } + + return timestamps; + } + finally { + entityManager.close(); + } + } + + public R inTransaction(Function action) { + return inTransaction( getEntityManagerFactory(), action ); + } + + public R inTransaction(EntityManagerFactory factory, Function action) { + EntityManager entityManager = factory.createEntityManager(); + try { + return inTransaction( entityManager, action ); + } + finally { + entityManager.close(); + } + } + + public R inTransaction(EntityManager entityManager, Function action) { + try { + entityManager.getTransaction().begin(); + R result = action.apply( entityManager ); + entityManager.getTransaction().commit(); + return result; + } + catch ( Exception e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + } + + public void inJPA(Consumer action) { + inJPA( getEntityManagerFactory(), action ); + } + + public R inJPA(Function action) { + return inJPA( getEntityManagerFactory(), action ); + } + + public void inJPA(EntityManagerFactory factory, Consumer action) { + EntityManager entityManager = factory.createEntityManager(); + try { + action.accept( entityManager ); + } + catch ( Exception e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + finally { + entityManager.close(); + } + } + + public R inJPA(EntityManagerFactory factory, Function action) { + EntityManager entityManager = factory.createEntityManager(); + try { + return action.apply( entityManager ); + } + catch ( Exception e ) { + if ( entityManager.getTransaction().isActive() ) { + entityManager.getTransaction().rollback(); + } + throw e; + } + finally { + entityManager.close(); + } + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversSessionFactoryProducer.java b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversSessionFactoryProducer.java new file mode 100644 index 0000000000..57856b61d5 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversSessionFactoryProducer.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.envers; + +import org.hibernate.SessionFactory; + +/** + * Envers contract for something that can build a SessionFactory based on an audit strategy. + * + * @author Chris Cranford + */ +public interface EnversSessionFactoryProducer { + SessionFactory produceSessionFactory(String auditStrategyName); +} diff --git a/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversSessionFactoryScope.java b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversSessionFactoryScope.java new file mode 100644 index 0000000000..0b99908de0 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/testing/envers/EnversSessionFactoryScope.java @@ -0,0 +1,211 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.envers; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.SessionFactory; +import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.envers.AuditReader; +import org.hibernate.envers.AuditReaderFactory; + +import org.hibernate.testing.junit5.SessionFactoryAccess; + +import org.jboss.logging.Logger; + +/** + * A scope or holder for the SessionFactory instance associated with a given test class. + * Used to: + *

      + *
    • Provide lifecycle management related to the SessionFactory
    • + *
    + * + * @author Chris Cranford + */ +public class EnversSessionFactoryScope implements SessionFactoryAccess { + private static final Logger log = Logger.getLogger( EnversSessionFactoryScope.class ); + + private final EnversSessionFactoryProducer sessionFactoryProducer; + private final Strategy auditStrategy; + + private SessionFactory sessionFactory; + + public EnversSessionFactoryScope(EnversSessionFactoryProducer producer, Strategy auditStrategy) { + log.debugf( "# - %s", auditStrategy.getDisplayName() ); + this.auditStrategy = auditStrategy; + this.sessionFactoryProducer = producer; + } + + public void releaseSessionFactory() { + log.debugf( "#releaseSessionFactory - %s", auditStrategy.getDisplayName() ); + if ( sessionFactory != null ) { + log.infof( "Closing SessionFactory %s (%s)", sessionFactory, auditStrategy.getDisplayName() ); + sessionFactory.close(); + sessionFactory = null; + } + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + log.debugf( "#getSessionFactory - %s", auditStrategy.getDisplayName() ); + if ( sessionFactory == null || sessionFactory.isClosed() ) { + sessionFactory = sessionFactoryProducer.produceSessionFactory( auditStrategy.getSettingValue() ); + } + return (SessionFactoryImplementor) sessionFactory; + } + + /** + * Invoke a lambda action inside an open session without a transaction-scope. + * + * The session will be closed after the action completes, regardless of failure. + * + * @param action The lambda action to invoke. + */ + public void inSession(Consumer action) { + inSession( getSessionFactory(), action ); + } + + /** + * Invoke a lambda action with an open session without a transaction-scope. + * + * The session will be closed after the action completes, regardless of failure. + * + * @param sfi The session factory. + * @param action The lambda action to invoke. + */ + public void inSession(SessionFactoryImplementor sfi, Consumer action) { + try ( SessionImplementor session = (SessionImplementor) sfi.openSession() ) { + action.accept( session ); + } + } + + public R inSession(Function action) { + return inSession( getSessionFactory(), action ); + } + + public R inSession(SessionFactoryImplementor sfi, Function action) { + try ( SessionImplementor session = (SessionImplementor) sfi.openSession() ) { + return action.apply( session ); + } + } + + /** + * Invoke a lambda action with an open session inside a new transaction. + * + * The session will be closed after the action completes, regardless of failure. + * The transaction will be committed if successful; otherwise it will be rolled back. + * + * @param action The lambda action to invoke. + */ + public void inTransaction(Consumer action) { + inTransaction( getSessionFactory(), action ); + } + + /** + * Invoke a lambda action with an open session inside a new transaction. + * + * The session will be closed after the action completes, regardless of failure. + * The transaction will be committed if successful; otherwise it will be rolled back. + * + * @param sfi The session factory. + * @param action The lambda action to invoke. + */ + public void inTransaction(SessionFactoryImplementor sfi, Consumer action) { + try ( SessionImplementor session = (SessionImplementor) sfi.openSession() ) { + inTransaction( session, action ); + } + } + + /** + * Invoke a lambda action inside a new transaction but bound to an existing open session. + * + * The session will not be closed after the action completes, it will be left as-is. + * The transaction will be committed if successful; otherwise it will be rolled back. + * + * @param session The session to bind the transaction against. + * @param action The lambda action to invoke. + */ + public void inTransaction(SessionImplementor session, Consumer action) { + final Transaction trx = session.beginTransaction(); + try { + action.accept( session ); + trx.commit(); + } + catch ( Exception e ) { + try { + trx.rollback(); + } + catch ( Exception ignored ) { + } + throw e; + } + } + + public R inTransaction(Function action) { + return inTransaction( getSessionFactory(), action ); + } + + public R inTransaction(SessionFactoryImplementor sfi, Function action) { + try ( SessionImplementor session = (SessionImplementor) sfi.openSession() ) { + return inTransaction( session, action ); + } + } + + public R inTransaction(SessionImplementor session, Function action) { + final Transaction trx = session.beginTransaction(); + try { + R result = action.apply( session ); + trx.commit(); + return result; + } + catch ( Exception e ) { + try { + trx.rollback(); + } + catch ( Exception ignored ) { + + } + throw e; + } + } + + /** + * Invoke a series of lambda actions bound inside their own transaction-scope but bound to the same session. + * + * The session will be closed after the actions complete, regardless of failure. + * The transaction associated with lambda will be committed if successful; otherwise it will be rolled back. + * Should a lambda action fail, the remaining actions will be skipped. + * + * @param actions The lambda actions to invoke. + */ + @SafeVarargs + public final void inTransactions(Consumer... actions) { + try( SessionImplementor session = (SessionImplementor) getSessionFactory().openSession() ) { + for ( Consumer action : actions ) { + inTransaction( session, action ); + } + } + } + + /** + * Invoke a lambda action against an {@link AuditReader} bound to a newly allocated session. + * + * The audit reader instance will be automatically allocated and provided to the lambda. + * The underlying session will be automatically opened and closed. + * + * @param action The lambda action to invoke. + */ + public void inAuditReader(Consumer action) { + try ( SessionImplementor session = (SessionImplementor) getSessionFactory().openSession() ) { + AuditReader auditReader = AuditReaderFactory.get( session ); + action.accept( auditReader ); + } + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/testing/envers/ExcludeAuditStrategy.java b/hibernate-envers/src/test/java/org/hibernate/testing/envers/ExcludeAuditStrategy.java new file mode 100644 index 0000000000..cd181249e5 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/testing/envers/ExcludeAuditStrategy.java @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.envers; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.hibernate.envers.strategy.spi.AuditStrategy; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation used to indicate that a test should be excluded for a specific audit strategy. + * + * @author Chris Cranford + * @since 6.0 + */ +@Retention(RUNTIME) +@Target({METHOD, TYPE}) +public @interface ExcludeAuditStrategy { + /** + * The strategies against which to exclude the test. + * + * @return The strategies. + */ + Class[] value(); + + /** + * Comment describing the reason why the audit strategy is excluded. + * + * @return The comment. + */ + String comment() default ""; + + /** + * The key of a JIRA issue hwich relates to this restriction. + * + * @return The jira issue key. + */ + String jiraKey() default ""; +} diff --git a/hibernate-envers/src/test/java/org/hibernate/testing/envers/RequiresAuditStrategy.java b/hibernate-envers/src/test/java/org/hibernate/testing/envers/RequiresAuditStrategy.java new file mode 100644 index 0000000000..44aae8ab60 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/testing/envers/RequiresAuditStrategy.java @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.envers; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.hibernate.envers.strategy.spi.AuditStrategy; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation used to indicate that a test should be run only for specific audit strategies. + * + * @author Chris Cranford + * @since 6.0 + */ +@Retention(RUNTIME) +@Target({ METHOD, TYPE }) +public @interface RequiresAuditStrategy { + /** + * The strategies against which to run the test + * + * @return The strategies + */ + Class[] value(); + + /** + * Comment describing the reason why the audit strategy is required. + * + * @return The comment + */ + String comment() default ""; + + /** + * The key of a JIRA issue which relates to this restriction. + * + * @return The jira issue key. + */ + String jiraKey() default ""; +} diff --git a/hibernate-envers/src/test/java/org/hibernate/testing/envers/Strategy.java b/hibernate-envers/src/test/java/org/hibernate/testing/envers/Strategy.java new file mode 100644 index 0000000000..9af8c31a97 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/testing/envers/Strategy.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.envers; + +import org.hibernate.envers.strategy.internal.DefaultAuditStrategy; +import org.hibernate.envers.strategy.internal.ValidityAuditStrategy; + +/** + * @author Chris Cranford + */ +public enum Strategy { + DEFAULT( "", null, DefaultAuditStrategy.class ), + VALIDITY( "", ValidityAuditStrategy.class.getName(), ValidityAuditStrategy.class ); + + private final String displayName; + private final String settingValue; + private final Class strategyClass; + + Strategy(String displayName, String settingValue, Class strategyClass) { + this.displayName = displayName; + this.settingValue = settingValue; + this.strategyClass = strategyClass; + } + + public String getDisplayName() { + return displayName; + } + + public String getSettingValue() { + return settingValue; + } + + public boolean isStrategy(Class strategyClass) { + return strategyClass.isAssignableFrom( this.strategyClass ); + } +} diff --git a/hibernate-testing/hibernate-testing.gradle b/hibernate-testing/hibernate-testing.gradle index ed371b88fd..82b5d62095 100644 --- a/hibernate-testing/hibernate-testing.gradle +++ b/hibernate-testing/hibernate-testing.gradle @@ -11,16 +11,31 @@ description = 'Support for testing Hibernate ORM functionality' dependencies { compile project( ':hibernate-core' ) - compile( libraries.jta ) + compile( libraries.junit ) + compile( libraries.junit5_api ) + compile( libraries.junit5_jupiter ) + compile( libraries.junit5_params ) + compile( 'org.hamcrest:hamcrest-all:1.3' ) + + runtime( libraries.junit5_vintage ) + runtime( libraries.junit5_jupiter ) + compile( libraries.byteman ) compile( libraries.byteman_install ) compile( libraries.byteman_bmunit ) compile( libraries.xapool ) compile( libraries.log4j ) - compile ( libraries.jboss_jta ) { - transitive=false; + compile( libraries.jboss_jta ) { + transitive = false; } + compile( 'javax.money:money-api:1.0.1' ) + compile( 'org.javamoney:moneta:1.1' ) + + annotationProcessor( project( ':hibernate-jpamodelgen' ) ) + + testRuntime( libraries.h2 ) + testRuntime( libraries.log4j ) } // resources inherently exclude sources diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/BaseUnitTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/BaseUnitTest.java new file mode 100644 index 0000000000..a05211ac3d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/BaseUnitTest.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import org.hibernate.testing.orm.junit.ExpectedExceptionExtension; +import org.hibernate.testing.orm.junit.FailureExpectedExtension; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ExpectedExceptionExtension.class ) +public abstract class BaseUnitTest { + + @SuppressWarnings("unchecked") + protected T cast(Object thing, Class type) { + assertThat( thing, instanceOf( type ) ); + return type.cast( thing ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DatabaseAgnostic.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DatabaseAgnostic.java new file mode 100644 index 0000000000..3daa4726a0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DatabaseAgnostic.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Meant to mark tests that should only be run on the default + * testing database. Generally indicates the test does not + * use JDBC at all. + * + * todo (6.0) : add JUnit extension to skip these on database runs other than the default one? + * + * @author Steve Ebersole + */ +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface DatabaseAgnostic { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectAccess.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectAccess.java new file mode 100644 index 0000000000..5ac9c0cec5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectAccess.java @@ -0,0 +1,22 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Contract for things that expose a Dialect + * + * @author Steve Ebersole + */ +public interface DialectAccess { + ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create( DialectAccess.class.getName() ); + + Dialect getDialect(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectFilterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectFilterExtension.java new file mode 100644 index 0000000000..0d638790a8 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectFilterExtension.java @@ -0,0 +1,155 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import org.hibernate.dialect.Dialect; + +import org.hibernate.testing.orm.junit.DialectFeatureCheck; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.RequiresDialectFeature; +import org.hibernate.testing.orm.junit.RequiresDialectFeatureGroup; +import org.hibernate.testing.orm.junit.RequiresDialects; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.hibernate.testing.orm.junit.SkipForDialectGroup; +import org.hibernate.testing.orm.junit.TestingUtil; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +/** + * JUnit 5 extension used to add {@link RequiresDialect} and {@link SkipForDialect} + * handling + * + * @author Steve Ebersole + */ +public class DialectFilterExtension implements ExecutionCondition { + private static final Logger log = Logger.getLogger( DialectFilterExtension.class ); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if ( !context.getTestInstance().isPresent() ) { + assert !context.getTestMethod().isPresent(); + + return ConditionEvaluationResult.enabled( + "No test-instance was present - " + + "likely that test was not defined with a per-class test lifecycle; " + + "skipping Dialect checks for this context [" + context.getDisplayName() + "]" + ); + } + + final Dialect dialect = getDialect( context ); + if ( dialect == null ) { + throw new RuntimeException( "#getDialect returned null" ); + } + + log.debugf( "Checking Dialect [%s] - context = %s", dialect, context.getDisplayName() ); + + final List effectiveRequiresDialects = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialect.class, + RequiresDialects.class + ); + + if ( !effectiveRequiresDialects.isEmpty() ) { + StringBuilder requiredDialects = new StringBuilder( ); + for ( RequiresDialect requiresDialect : effectiveRequiresDialects ) { + requiredDialects.append(requiresDialect.value() ); + requiredDialects.append( " " ); + if ( requiresDialect.matchSubTypes() ) { + if ( requiresDialect.value().isInstance( dialect ) ) { + return ConditionEvaluationResult.enabled( "Matched @RequiresDialect" ); + } + } + else { + if ( requiresDialect.value().equals( dialect.getClass() ) ) { + return ConditionEvaluationResult.enabled( "Matched @RequiresDialect" ); + } + } + } + + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialect(dialect=%s) check - found %s]", + requiredDialects.toString(), + dialect.getClass().getName() + ) + ); + } + + final List effectiveSkips = TestingUtil.findEffectiveRepeatingAnnotation( + context, + SkipForDialect.class, + SkipForDialectGroup.class + ); + + for ( SkipForDialect effectiveSkipForDialect : effectiveSkips ) { + if ( effectiveSkipForDialect.matchSubTypes() ) { + if ( effectiveSkipForDialect.dialectClass().isInstance( dialect ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect(group)" ); + } + } + else { + if ( effectiveSkipForDialect.dialectClass().equals( dialect.getClass() ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect" ); + } + } + } + + List effectiveRequiresDialectFeatures = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialectFeature.class, + RequiresDialectFeatureGroup.class + ); + + for ( RequiresDialectFeature effectiveRequiresDialectFeature : effectiveRequiresDialectFeatures ) { + try { + final DialectFeatureCheck dialectFeatureCheck = effectiveRequiresDialectFeature.feature() + .newInstance(); + if ( !dialectFeatureCheck.apply( getDialect( context ) ) ) { + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialectFeature [%s]", + effectiveRequiresDialectFeature.feature() + ) ); + } + } + catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException( "Unable to instantiate DialectFeatureCheck class", e ); + } + } + + return ConditionEvaluationResult.enabled( "Passed all @SkipForDialects" ); + } + + private Dialect getDialect(ExtensionContext context) { + final Optional sfScope = SessionFactoryScopeExtension.findSessionFactoryScope( context ); + if ( !sfScope.isPresent() ) { + final Optional emScope = EntityManagerFactoryScopeExtension.findEntityManagerFactoryScope( context ); + if ( !emScope.isPresent() ) { + final Optional dialectAccess = Optional.ofNullable( + (DialectAccess) context.getStore( DialectAccess.NAMESPACE ) + .get( context.getRequiredTestInstance() ) ); + if ( !dialectAccess.isPresent() ) { + throw new RuntimeException( + "Could not locate any DialectAccess implementation in JUnit ExtensionContext" ); + } + return dialectAccess.get().getDialect(); + } + return emScope.get().getDialect(); + } + + return sfScope.get().getDialect(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryAccess.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryAccess.java new file mode 100644 index 0000000000..e5be5a74cb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryAccess.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import javax.persistence.EntityManagerFactory; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * Contract for things that expose an EntityManagerFactory + * + * @author Chris Cranford + */ +public interface EntityManagerFactoryAccess extends DialectAccess { + EntityManagerFactory getEntityManagerFactory(); + + @Override + default Dialect getDialect() { + return getEntityManagerFactory().unwrap( SessionFactoryImplementor.class ).getJdbcServices().getDialect(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryBasedFunctionalTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryBasedFunctionalTest.java new file mode 100644 index 0000000000..be11534342 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryBasedFunctionalTest.java @@ -0,0 +1,287 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.function.Consumer; +import java.util.function.Function; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.PersistenceUnitTransactionType; + +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; + +import org.junit.jupiter.api.AfterEach; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Chris Cranford + */ +@FunctionalEntityManagerFactoryTesting +public class EntityManagerFactoryBasedFunctionalTest + extends BaseUnitTest + implements EntityManagerFactoryProducer, EntityManagerFactoryScopeContainer { + private static final Logger log = Logger.getLogger( EntityManagerFactoryBasedFunctionalTest.class ); + + private EntityManagerFactoryScope entityManagerFactoryScope; + + @Override + public EntityManagerFactory produceEntityManagerFactory() { + final EntityManagerFactory entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder( + buildPersistenceUnitDescriptor(), + buildSettings() + ).build(); + + entityManagerFactoryBuilt( entityManagerFactory ); + + return entityManagerFactory; + } + + @Override + public void injectEntityManagerFactoryScope(EntityManagerFactoryScope scope) { + entityManagerFactoryScope = scope; + } + + @Override + public EntityManagerFactoryProducer getEntityManagerFactoryProducer() { + return this; + } + + protected EntityManagerFactoryScope entityManagerFactoryScope() { + return entityManagerFactoryScope; + } + + protected EntityManagerFactory entityManagerFactory() { + return entityManagerFactoryScope.getEntityManagerFactory(); + } + + protected void entityManagerFactoryBuilt(EntityManagerFactory factory) { + } + + protected boolean strictJpaCompliance() { + return false; + } + + protected boolean exportSchema() { + return true; + } + + protected static final String[] NO_MAPPINGS = new String[0]; + + protected String[] getMappings() { + return NO_MAPPINGS; + } + + protected void addConfigOptions(Map options) { + } + + protected static final Class[] NO_CLASSES = new Class[0]; + + protected Class[] getAnnotatedClasses() { + return NO_CLASSES; + } + + protected Map buildSettings() { + Map settings = getConfig(); + applySettings( settings ); + + if ( exportSchema() ) { + settings.put( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); + } + + settings.put( AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" ); + + return settings; + } + + protected Map getConfig() { + Map config = Environment.getProperties(); + ArrayList> classes = new ArrayList<>(); + + classes.addAll( Arrays.asList( getAnnotatedClasses() ) ); + config.put( org.hibernate.jpa.AvailableSettings.LOADED_CLASSES, classes ); + for ( Map.Entry entry : getCachedClasses().entrySet() ) { + config.put( + org.hibernate.jpa.AvailableSettings.CLASS_CACHE_PREFIX + "." + entry.getKey().getName(), + entry.getValue() + ); + } + for ( Map.Entry entry : getCachedCollections().entrySet() ) { + config.put( + org.hibernate.jpa.AvailableSettings.COLLECTION_CACHE_PREFIX + "." + entry.getKey(), + entry.getValue() + ); + } + if ( getEjb3DD().length > 0 ) { + ArrayList dds = new ArrayList<>(); + dds.addAll( Arrays.asList( getEjb3DD() ) ); + config.put( org.hibernate.jpa.AvailableSettings.XML_FILE_NAMES, dds ); + } + + addConfigOptions( config ); + return config; + } + + protected void applySettings(Map settings) { + String[] mappings = getMappings(); + if ( mappings != null ) { + settings.put( org.hibernate.jpa.AvailableSettings.HBXML_FILES, String.join( ",", mappings ) ); + } + } + + public Map getCachedClasses() { + return new HashMap<>(); + } + + public Map getCachedCollections() { + return new HashMap<>(); + } + + public String[] getEjb3DD() { + return new String[] {}; + } + + protected PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { + return new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); + } + + public static class TestingPersistenceUnitDescriptorImpl implements PersistenceUnitDescriptor { + private final String name; + + public TestingPersistenceUnitDescriptorImpl(String name) { + this.name = name; + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public boolean isUseQuotedIdentifiers() { + return false; + } + + @Override + public boolean isExcludeUnlistedClasses() { + return false; + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + @Override + public ValidationMode getValidationMode() { + return null; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return null; + } + + @Override + public List getManagedClassNames() { + return null; + } + + @Override + public List getMappingFileNames() { + return null; + } + + @Override + public List getJarFileUrls() { + return null; + } + + @Override + public Object getNonJtaDataSource() { + return null; + } + + @Override + public Object getJtaDataSource() { + return null; + } + + @Override + public Properties getProperties() { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return null; + } + + @Override + public ClassLoader getTempClassLoader() { + return null; + } + + @Override + public void pushClassTransformer(EnhancementContext enhancementContext) { + } + } + + @AfterEach + public final void afterTest() { + if ( isCleanupTestDataRequired() ) { + cleanupTestData(); + } + } + + protected boolean isCleanupTestDataRequired() { + return false; + } + + protected void cleanupTestData() { + doInJPA( this::entityManagerFactory, entityManager -> { + Arrays.stream( getAnnotatedClasses() ).forEach( annotatedClass -> + entityManager.createQuery( "delete from " + annotatedClass + .getSimpleName() ).executeUpdate() + ); + } ); + } + + protected void inTransaction(Consumer action) { + entityManagerFactoryScope().inTransaction( action ); + } + + protected T inTransaction(Function action) { + return entityManagerFactoryScope().inTransaction( action ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryProducer.java new file mode 100644 index 0000000000..9eb69e362d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryProducer.java @@ -0,0 +1,29 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import javax.persistence.EntityManagerFactory; + +/** + * Contract for something that can build an EntityManagerFactory. + * + * Used by EntityManagerFactoryScopeExtension to create the EntityManagerFactoryScope. + * + * Generally speaking, a test class would implement EntityManagerFactoryScopeContainer + * and return the EntityManagerFactoryProducer to be used for those tests. The + * EntityManagerFactoryProducer is then used to build the EntityManagerFactoryScope + * which is injected back into the EntityManagerFactoryScopeContainer. + * + * @see EntityManagerFactoryScopeExtension + * @see EntityManagerFactoryScope + * @see EntityManagerFactoryScopeContainer + * + * @author Chris Cranford + */ +public interface EntityManagerFactoryProducer { + EntityManagerFactory produceEntityManagerFactory(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScope.java new file mode 100644 index 0000000000..cc1e2bac72 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScope.java @@ -0,0 +1,137 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.util.function.Consumer; +import java.util.function.Function; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; + +import org.jboss.logging.Logger; + +/** + * A scope or holder for the EntityManagerFactory instance associated with a given test class. + * Used to: + *

    + * * provide lifecycle management related to the EntityManagerFactory + * * access to functional programming using an EntityManager generated + * from the EntityManagerFactory. + * + * @author Chris Cranford + */ +public class EntityManagerFactoryScope implements EntityManagerFactoryAccess { + private static final Logger log = Logger.getLogger( EntityManagerFactoryScope.class ); + + private final EntityManagerFactoryProducer producer; + + private EntityManagerFactory entityManagerFactory; + + public EntityManagerFactoryScope(EntityManagerFactoryProducer producer) { + log.trace( "EntityManagerFactoryScope#" ); + this.producer = producer; + } + + public void rebuild() { + log.trace( "EntityManagerFactoryScope#rebuild" ); + releaseEntityManagerFactory(); + + entityManagerFactory = producer.produceEntityManagerFactory(); + } + + public void releaseEntityManagerFactory() { + log.trace( "EntityManagerFactoryScope#releaseEntityManagerFactory" ); + if ( entityManagerFactory != null ) { + entityManagerFactory.close(); + } + } + + @Override + public EntityManagerFactory getEntityManagerFactory() { + log.trace( "EntityManagerFactoryScope#getEntityManagerFactory" ); + if ( entityManagerFactory == null || !entityManagerFactory.isOpen() ) { + entityManagerFactory = producer.produceEntityManagerFactory(); + } + return entityManagerFactory; + } + + public void inTransaction(Consumer action) { + log.trace( "#inTransaction(action)" ); + inTransaction( getEntityManagerFactory(), action ); + } + + public T inTransaction(Function action) { + return inTransaction( getEntityManagerFactory().createEntityManager(), action ); + } + + public void inTransaction(EntityManagerFactory factory, Consumer action) { + log.trace( "#inTransaction(factory, action)" ); + final EntityManager entityManager = factory.createEntityManager(); + try { + log.trace( "EntityManager opened, calling action" ); + inTransaction( entityManager, action ); + log.trace( "called action" ); + } + finally { + log.trace( "EntityManager close" ); + entityManager.close(); + } + } + + public T inTransaction(EntityManager entityManager, Function action) { + log.trace( "inTransaction(entityManager, action)" ); + final EntityTransaction trx = entityManager.getTransaction(); + final T result; + try { + trx.begin(); + log.trace( "Calling action in trx" ); + result = action.apply( entityManager ); + log.trace( "Called trx in action" ); + + log.trace( "Committing transaction" ); + trx.commit(); + log.trace( "Committed transaction" ); + } + catch (Exception e) { + log.tracef( "Error calling action: %s (%s) - rollingback", e.getClass().getName(), e.getMessage() ); + try { + trx.rollback(); + } + catch (Exception ignored) { + log.trace( "Was unable to roll back transaction" ); + } + throw e; + } + return result; + } + + public void inTransaction(EntityManager entityManager, Consumer action) { + log.trace( "inTransaction(entityManager, action)" ); + + final EntityTransaction trx = entityManager.getTransaction(); + try { + trx.begin(); + log.trace( "Calling action in trx" ); + action.accept( entityManager ); + log.trace( "Called trx in action" ); + + log.trace( "Committing transaction" ); + trx.commit(); + log.trace( "Committed transaction" ); + } + catch (Exception e) { + log.tracef( "Error calling action: %s (%s) - rollingback", e.getClass().getName(), e.getMessage() ); + try { + trx.rollback(); + } + catch (Exception ignored) { + log.trace( "Was unable to roll back transaction" ); + } + throw e; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScopeContainer.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScopeContainer.java new file mode 100644 index 0000000000..84afbbe9c4 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScopeContainer.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +/** + * The keystone in EntityManagerFactoryScopeExtension support. + * + * This is how the extensions know how to build an EntityManagerFactory (scope) + * and how to inject that EntityManagerFactory (scope) back into the test. + * + * @author Chris Cranford + */ +public interface EntityManagerFactoryScopeContainer { + /** + * Callback to inject the EntityManagerFactoryScope into the container. + */ + void injectEntityManagerFactoryScope(EntityManagerFactoryScope scope); + + /** + * Obtain the {@link EntityManagerFactoryProducer}. Quite often this is also + * implemented by the container itself. + */ + EntityManagerFactoryProducer getEntityManagerFactoryProducer(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScopeExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScopeExtension.java new file mode 100644 index 0000000000..906e7d83b1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryScopeExtension.java @@ -0,0 +1,98 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +import org.jboss.logging.Logger; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * The thing that actually manages lifecycle of the EntityManagerFactory related to a test class. + * Work in conjunction with EntityManagerFactoryScope and EntityManagerFactoryScopeContainer. + * + * @see EntityManagerFactoryScope + * @see EntityManagerFactoryScopeContainer + * @see EntityManagerFactoryProducer + * + * @author Chris Cranford + */ +public class EntityManagerFactoryScopeExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( EntityManagerFactoryScopeExtension.class ); + + public static ExtensionContext.Namespace namespace(Object testInstance) { + return create( EntityManagerFactoryScopeExtension.class.getName(), testInstance ); + } + + public static Optional findEntityManagerFactoryScope(ExtensionContext context) { + final Optional entityManagerFactoryScope = Optional.ofNullable( + context.getStore( namespace( context.getRequiredTestInstance() ) ) + .get( ENTITYMANAGER_FACTORY_KEY ) + ); + return entityManagerFactoryScope; + } + + public static final Object ENTITYMANAGER_FACTORY_KEY = "ENTITYMANAGER_FACTORY"; + + public EntityManagerFactoryScopeExtension() { + log.trace( "EntityManagerFactoryScopeExtension#" ); + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestInstancePostProcessor + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.trace( "EntityManagerFactoryScopeExtension#postProcessTestInstance" ); + if ( EntityManagerFactoryScopeContainer.class.isInstance( testInstance ) ) { + final EntityManagerFactoryScopeContainer scopeContainer = EntityManagerFactoryScopeContainer.class.cast( + testInstance ); + final EntityManagerFactoryScope scope = new EntityManagerFactoryScope( + scopeContainer.getEntityManagerFactoryProducer() + ); + context.getStore( namespace( testInstance ) ).put( ENTITYMANAGER_FACTORY_KEY, scope ); + + scopeContainer.injectEntityManagerFactoryScope( scope ); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AfterAllCallback + + @Override + public void afterAll(ExtensionContext context) { + final EntityManagerFactoryScope scope = (EntityManagerFactoryScope) + context.getStore( namespace( context.getRequiredTestInstance() ) ).remove( ENTITYMANAGER_FACTORY_KEY ); + if ( scope != null ) { + scope.releaseEntityManagerFactory(); + } + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestExecutionExceptionHandler + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + final Optional scopeOptional = findEntityManagerFactoryScope( context ); + if ( ! scopeOptional.isPresent() ) { + log.debug( "Could not locate EntityManagerFactoryScope on exception" ); + } + else { + scopeOptional.get().releaseEntityManagerFactory(); + } + + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/FunctionalEntityManagerFactoryTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/FunctionalEntityManagerFactoryTesting.java new file mode 100644 index 0000000000..dce9d2ca25 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/FunctionalEntityManagerFactoryTesting.java @@ -0,0 +1,43 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.testing.orm.junit.FailureExpectedExtension; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for functional tests that require + * a functioning EntityManagerFactory. + * + * @apiNote Logically this should also include + * `@TestInstance( TestInstance.Lifecycle.PER_CLASS )` + * but that annotation is not conveyed (is that the + * right word? its not applied to the thing using this annotation). + * Test classes should apply that themselves. + * + * @see EntityManagerFactoryScopeExtension + * @see DialectFilterExtension + * @see FailureExpectedExtension + * + * @author Chris Cranford + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@TestInstance(TestInstance.Lifecycle.PER_CLASS ) +@ExtendWith(EntityManagerFactoryScopeExtension.class) +@ExtendWith(DialectFilterExtension.class) +@ExtendWith(FailureExpectedExtension.class) +public @interface FunctionalEntityManagerFactoryTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/FunctionalSessionFactoryTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/FunctionalSessionFactoryTesting.java new file mode 100644 index 0000000000..efbc88befa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/FunctionalSessionFactoryTesting.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.testing.orm.junit.FailureExpectedExtension; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for functional tests that + * require a functioning SessionFactory. + * + * @apiNote Logically this should also include + * `@TestInstance( TestInstance.Lifecycle.PER_CLASS )` + * but that annotation is not conveyed (is that the + * right word? its not applied to the thing using this annotation). + * Test classes should apply that themselves. + * + * @see SessionFactoryScopeExtension + * @see DialectFilterExtension + * @see FailureExpectedExtension + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target(ElementType.TYPE) +@Inherited +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) +@ExtendWith( SessionFactoryScopeExtension.class ) +@ExtendWith( DialectFilterExtension.class ) +@ExtendWith( FailureExpectedExtension.class ) +@SuppressWarnings("WeakerAccess") +public @interface FunctionalSessionFactoryTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryAccess.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryAccess.java new file mode 100644 index 0000000000..1fb7c7435f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryAccess.java @@ -0,0 +1,24 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * Contract for things that expose a SessionFactory + * + * @author Steve Ebersole + */ +public interface SessionFactoryAccess extends DialectAccess { + SessionFactoryImplementor getSessionFactory(); + + @Override + default Dialect getDialect() { + return getSessionFactory().getJdbcServices().getDialect(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryBasedFunctionalTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryBasedFunctionalTest.java new file mode 100644 index 0000000000..aebbc82069 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryBasedFunctionalTest.java @@ -0,0 +1,216 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.util.function.Consumer; +import java.util.function.Function; +import javax.persistence.SharedCacheMode; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.mapping.RootClass; +import org.hibernate.tool.schema.Action; +import org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator; + +import org.junit.jupiter.api.AfterEach; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +@FunctionalSessionFactoryTesting +public abstract class SessionFactoryBasedFunctionalTest + extends BaseUnitTest + implements SessionFactoryProducer, SessionFactoryScopeContainer { + protected static final Class[] NO_CLASSES = new Class[0]; + protected static final String[] NO_MAPPINGS = new String[0]; + + protected static final Logger log = Logger.getLogger( SessionFactoryBasedFunctionalTest.class ); + + private SessionFactoryScope sessionFactoryScope; + + private Metadata metadata; + + protected SessionFactoryScope sessionFactoryScope() { + return sessionFactoryScope; + } + + protected SessionFactoryImplementor sessionFactory() { + return sessionFactoryScope.getSessionFactory(); + } + + @Override + public SessionFactoryImplementor produceSessionFactory() { + log.trace( "Producing SessionFactory" ); + + final StandardServiceRegistryBuilder ssrBuilder = new StandardServiceRegistryBuilder() + .applySetting( AvailableSettings.HBM2DDL_AUTO, exportSchema() ? "create-drop" : "none" ); + applySettings( ssrBuilder ); + applyCacheSettings( ssrBuilder ); + final StandardServiceRegistry ssr = ssrBuilder.build(); + try { + metadata = buildMetadata( ssr ); + final SessionFactoryBuilder sfBuilder = metadata.getSessionFactoryBuilder(); + configure( sfBuilder ); + final SessionFactoryImplementor factory = (SessionFactoryImplementor) metadata.buildSessionFactory(); + sessionFactoryBuilt( factory ); + return factory; + } + catch (Exception e) { + StandardServiceRegistryBuilder.destroy( ssr ); + SchemaManagementToolCoordinator.ActionGrouping actions = SchemaManagementToolCoordinator.ActionGrouping.interpret( + ssrBuilder.getSettings() ); + if ( ( exportSchema() || actions.getDatabaseAction() != Action.NONE ) && metadata != null ) { + dropDatabase( ); + } + throw e; + } + } + + private MetadataImplementor buildMetadata(StandardServiceRegistry ssr) { + MetadataSources metadataSources = new MetadataSources( ssr ); + applyMetadataSources( metadataSources ); + final MetadataBuilder metadataBuilder = metadataSources.getMetadataBuilder(); + configureMetadataBuilder( metadataBuilder ); + return (MetadataImplementor) metadataBuilder.build(); + } + + private void dropDatabase() { +// final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); +// try { +// final DatabaseModel databaseModel = Helper.buildDatabaseModel( ssr, buildMetadata( ssr ) ); +// new SchemaExport( databaseModel, ssr ).drop( EnumSet.of( TargetType.DATABASE ) ); +// } +// finally { +// StandardServiceRegistryBuilder.destroy( ssr ); +// } + } + + protected void configureMetadataBuilder(MetadataBuilder metadataBuilder) { + } + + protected void applySettings(StandardServiceRegistryBuilder builer) { + } + + protected void configure(SessionFactoryBuilder builder) { + } + + protected void sessionFactoryBuilt(SessionFactoryImplementor factory) { + } + + protected boolean strictJpaCompliance() { + return false; + } + + protected boolean exportSchema() { + return true; + } + + protected void applyMetadataSources(MetadataSources metadataSources) { + for ( Class annotatedClass : getAnnotatedClasses() ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + for ( String mapping : getHbmMappingFiles() ) { + metadataSources.addResource( + getBaseForMappings() + mapping + ); + } + } + + protected Class[] getAnnotatedClasses() { + return NO_CLASSES; + } + + protected String[] getHbmMappingFiles() { + return NO_MAPPINGS; + } + + protected String getBaseForMappings() { + return "org/hibernate/orm/test/"; + } + + @Override + public void injectSessionFactoryScope(SessionFactoryScope scope) { + sessionFactoryScope = scope; + } + + @Override + public SessionFactoryProducer getSessionFactoryProducer() { + return this; + } + + protected Metadata getMetadata(){ + return metadata; + } + + protected String getCacheConcurrencyStrategy() { + return null; + } + + protected void applyCacheSettings(StandardServiceRegistryBuilder builer) { + if ( getCacheConcurrencyStrategy() != null ) { + builer.applySetting( AvailableSettings.DEFAULT_CACHE_CONCURRENCY_STRATEGY, getCacheConcurrencyStrategy() ); + builer.applySetting( AvailableSettings.JPA_SHARED_CACHE_MODE, SharedCacheMode.ALL.name() ); + } + } + + @AfterEach + public final void afterTest() { + if ( isCleanupTestDataRequired() ) { + cleanupTestData(); + } + } + + protected boolean isCleanupTestDataRequired() { + return false; + } + + protected void cleanupTestData() { + inTransaction( + session -> { + getMetadata().getEntityBindings().forEach( + persistentClass -> { + if ( persistentClass instanceof RootClass ) { + final String entityName = persistentClass.getEntityName(); + session.createQuery( "delete from " + entityName ).executeUpdate(); + } + } + ); + } + ); + } + + protected void inTransaction(Consumer action) { + sessionFactoryScope().inTransaction( action ); + } + + protected R inTransaction(Function action) { + return sessionFactoryScope().inTransaction( action ); + } + + protected R inSession(Function action) { + return sessionFactoryScope.inSession( action ); + } + + protected void inSession(Consumer action) { + sessionFactoryScope().inSession( action ); + } + + protected Dialect getDialect(){ + return sessionFactoryScope.getDialect(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryProducer.java new file mode 100644 index 0000000000..30c0b4f76e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryProducer.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * Contract for something that can build a SessionFactory. + * + * Used by SessionFactoryScopeExtension to create the + * SessionFactoryScope. + * + * Generally speaking, a test class would implement SessionFactoryScopeContainer + * and return the SessionFactoryProducer to be used for those tests. + * The SessionFactoryProducer is then used to build the SessionFactoryScope + * which is injected back into the SessionFactoryScopeContainer + * + * @see SessionFactoryScopeExtension + * @see SessionFactoryScope + * @see SessionFactoryScopeContainer + * + * @author Steve Ebersole + */ +public interface SessionFactoryProducer { + SessionFactoryImplementor produceSessionFactory(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScope.java new file mode 100644 index 0000000000..198a586343 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScope.java @@ -0,0 +1,208 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.Transaction; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; + +import org.jboss.logging.Logger; + +/** + * A scope or holder fot the SessionFactory instance associated with a + * given test class. Used to: + * + * * provide lifecycle management related to the SessionFactory + * * access to functional programming using a Session generated + * from that SessionFactory + * + * @author Steve Ebersole + */ +public class SessionFactoryScope implements SessionFactoryAccess { + private static final Logger log = Logger.getLogger( SessionFactoryScope.class ); + + private final SessionFactoryProducer producer; + + private SessionFactoryImplementor sessionFactory; + + public SessionFactoryScope(SessionFactoryProducer producer) { + log.trace( "SessionFactoryScope#" ); + this.producer = producer; + } + + public void rebuild() { + log.trace( "SessionFactoryScope#rebuild" ); + releaseSessionFactory(); + + sessionFactory = producer.produceSessionFactory(); + } + + public void releaseSessionFactory() { + log.trace( "SessionFactoryScope#releaseSessionFactory" ); + if ( sessionFactory != null ) { + sessionFactory.close(); + } + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + log.trace( "SessionFactoryScope#getSessionFactory" ); + if ( sessionFactory == null || sessionFactory.isClosed() ) { + sessionFactory = producer.produceSessionFactory(); + } + return sessionFactory; + } + + public void inSession(Consumer action) { + log.trace( "#inSession(action)" ); + inSession( getSessionFactory(), action ); + } + + public void inTransaction(Consumer action) { + log.trace( "#inTransaction(action)" ); + inTransaction( getSessionFactory(), action ); + } + + protected R inSession(Function action) { + return inSession( getSessionFactory(), action ); + } + + private R inSession(SessionFactoryImplementor sfi, Function action) { + log.trace( "##inSession(SF,action)" ); + + try (SessionImplementor session = (SessionImplementor) sfi.openSession()) { + log.trace( "Session opened, calling action" ); + R result = action.apply( session ); + log.trace( "called action" ); + return result; + } + finally { + log.trace( "Session close - auto-close lock" ); + } + } + + private void inSession(SessionFactoryImplementor sfi, Consumer action) { + log.trace( "##inSession(SF,action)" ); + + try (SessionImplementor session = (SessionImplementor) sfi.openSession()) { + log.trace( "Session opened, calling action" ); + action.accept( session ); + log.trace( "called action" ); + } + finally { + log.trace( "Session close - auto-close lock" ); + } + } + + private void inTransaction(SessionFactoryImplementor factory, Consumer action) { + log.trace( "#inTransaction(factory, action)"); + + + try (SessionImplementor session = (SessionImplementor) factory.openSession()) { + log.trace( "Session opened, calling action" ); + inTransaction( session, action ); + log.trace( "called action" ); + } + finally { + log.trace( "Session close - auto-close lock" ); + } + } + + private void inTransaction(SessionImplementor session, Consumer action) { + log.trace( "inTransaction(session,action)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + try { + log.trace( "Calling action in txn" ); + action.accept( session ); + log.trace( "Called action - in txn" ); + + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + } + + public R inTransaction(Function action) { + return inTransaction( getSessionFactory(), action ); + } + + private R inTransaction(SessionFactoryImplementor factory, Function action) { + log.trace( "#inTransaction(factory, action)" ); + + R result; + try (SessionImplementor session = (SessionImplementor) factory.openSession()) { + log.trace( "Session opened, calling action" ); + result = inTransaction( session, action ); + log.trace( "called action" ); + } + finally { + log.trace( "Session close - auto-close lock" ); + } + return result; + } + + private R inTransaction(SessionImplementor session, Function action) { + log.trace( "inTransaction(session,action)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + R result; + try { + log.trace( "Calling action in txn" ); + result = action.apply( session ); + log.trace( "Called action - in txn" ); + + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + return result; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScopeContainer.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScopeContainer.java new file mode 100644 index 0000000000..e265f33abc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScopeContainer.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +/** + * The keystone in SessionFactoryScopeExtension support. + * + * This is how the extensions know how to build a SessionFactory (scope) + * and how to inject that SessionFactory (scope) back into the test + * + * @author Steve Ebersole + */ +public interface SessionFactoryScopeContainer { + /** + * Callback to inject the SessionFactoryScope into the container + */ + void injectSessionFactoryScope(SessionFactoryScope scope); + + /** + * Obtain the {@link SessionFactoryProducer}. Quite often this + * is als implemented by the container itself. + */ + SessionFactoryProducer getSessionFactoryProducer(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScopeExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScopeExtension.java new file mode 100644 index 0000000000..388ea7b6e5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryScopeExtension.java @@ -0,0 +1,133 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +import org.jboss.logging.Logger; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * @asciidoc + * + * The thing that actually manages lifecycle of the SessionFactory related to a + * test class. Work in conjunction with SessionFactoryScope and SessionFactoryScopeContainer + * + * todo (6.0) ?? - allow annotation-driven definition of the test mappings and settings + * + * [code] + * .Annotation-driven example + * ```` + * + * @FunctionalSessionFactoryTesting( + * model = @ModelToTest( + * true, // export + * standardModels = { + * @DomainModel( AvailableDomainModel.CONTACTS ) + * }, + * annotatedClasses = { + * SomeAdditionalClass.class + * }, + * mappings = { + * "some-orm-mappings.xml" + * }, + * ... + * ), + * config = @PersistenceUnit( + * sessionFactoryName = "a-special-name", + * ... + * ) + * ) + * public class MyTest { + * + * } + * + * ```` + * + * + * @see SessionFactoryScope + * @see SessionFactoryScopeContainer + * @see SessionFactoryProducer + * + * @author Steve Ebersole + */ +public class SessionFactoryScopeExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( SessionFactoryScopeExtension.class ); + + public static final Object SESSION_FACTORY_KEY = "SESSION_FACTORY"; + + public static ExtensionContext.Namespace namespace(Object testInstance) { + return create( SessionFactoryScopeExtension.class.getName(), testInstance ); + } + + public static Optional findSessionFactoryScope(ExtensionContext context) { + final ExtensionContext.Namespace namespace = namespace( context.getRequiredTestInstance() ); + final SessionFactoryScope storedScope = (SessionFactoryScope) context.getStore( namespace ).get( SESSION_FACTORY_KEY ); + + return Optional.ofNullable( storedScope ); + } + + + public SessionFactoryScopeExtension() { + log.trace( "SessionFactoryScopeExtension#" ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestInstancePostProcessor + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.trace( "SessionFactoryScopeExtension#postProcessTestInstance" ); + if ( SessionFactoryScopeContainer.class.isInstance( testInstance ) ) { + final SessionFactoryScopeContainer scopeContainer = SessionFactoryScopeContainer.class.cast( + testInstance ); + final SessionFactoryScope scope = new SessionFactoryScope( scopeContainer.getSessionFactoryProducer() ); + context.getStore( namespace( testInstance ) ).put( SESSION_FACTORY_KEY, scope ); + + scopeContainer.injectSessionFactoryScope( scope ); + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AfterAllCallback + + @Override + public void afterAll(ExtensionContext context) { + final SessionFactoryScope scope = (SessionFactoryScope) context.getStore( namespace( context.getRequiredTestInstance() ) ) + .remove( SESSION_FACTORY_KEY ); + if ( scope != null ) { + scope.releaseSessionFactory(); + } + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // TestExecutionExceptionHandler + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + final Optional scopeOptional = findSessionFactoryScope( context ); + if ( ! scopeOptional.isPresent() ) { + log.debug( "Could not locate SessionFactoryScope on exception" ); + } + else { + scopeOptional.get().releaseSessionFactory(); + } + + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/StandardTags.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/StandardTags.java new file mode 100644 index 0000000000..2c242ec5bd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/StandardTags.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +public final class StandardTags { + public static final String FAILURE_EXPECTED = "failure-expected"; + public static final String PERF = "perf"; + public static final String QUERY = "query"; + public static final String SQM = "sqm"; + public static final String UNIT = "unit"; + public static final String ENVERS = "envers"; + + private StandardTags() { + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/AbstractDynamicTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/AbstractDynamicTest.java new file mode 100644 index 0000000000..be425bbfdd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/AbstractDynamicTest.java @@ -0,0 +1,285 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.hibernate.testing.orm.junit.FailureExpected; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DynamicNode; +import org.junit.jupiter.api.TestFactory; + +import org.opentest4j.TestAbortedException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.junit.platform.commons.util.ReflectionUtils.findMethods; + +/** + * Abstract base class that can be used to generate dynamic test-lets. + * + * @see DynamicBeforeAll + * @see DynamicAfterAll + * @see DynamicBeforeEach + * @see DynamicAfterEach + * @see DynamicTest + * + * @author Chris Cranford + */ +public abstract class AbstractDynamicTest { + @TestFactory + @SuppressWarnings("unchecked") + public List generateDynamicTestNodes() throws Exception { + final Class testClass = getClass(); + final List dynamicNodes = new ArrayList<>(); + + final List beforeAllMethods = findMethods( + testClass, + method -> method.isAnnotationPresent( DynamicBeforeAll.class ) + ); + + final List beforeEachMethods = findMethods( + testClass, + method -> method.isAnnotationPresent( DynamicBeforeEach.class ) + ); + + final List afterAllMethods = findMethods( + testClass, + method -> method.isAnnotationPresent( DynamicAfterAll.class ) + ); + + final List afterEachMethods = findMethods( + testClass, + method -> method.isAnnotationPresent( DynamicAfterEach.class ) + ); + + final List testMethods = resolveTestMethods( testClass ); + + for ( final T context : getExecutionContexts() ) { + if ( testClass.isAnnotationPresent( Disabled.class ) || !context.isExecutionAllowed( testClass ) ) { + continue; + } + + final AbstractDynamicTest testInstance = testClass.newInstance(); + final List tests = new ArrayList<>(); + + // First invoke all @DynamicBeforeAll annotated methods + beforeAllMethods.forEach( method -> { + if ( !method.isAnnotationPresent( Disabled.class ) && context.isExecutionAllowed( method ) ) { + final DynamicBeforeAll dynamicBeforeAllAnn = method.getAnnotation( DynamicBeforeAll.class ); + final Class expectedException = dynamicBeforeAllAnn.expected(); + tests.add( + dynamicTest( + method.getName(), + () -> { + Throwable exception = null; + try { + method.invoke( testInstance ); + + assertEquals( + DynamicBeforeAll.None.class, + expectedException, + "Expected: " + expectedException.getName() + ); + } + catch( InvocationTargetException t ) { + // only throw if the exception was not expected + if ( !expectedException.isInstance( t.getTargetException() ) ) { + if ( t.getTargetException() != null ) { + exception = t.getTargetException(); + throw t.getTargetException(); + } + else { + exception = t; + throw t; + } + } + } + finally { + // guarantee that if any resources are allocated by the @DynamicBeforeAll + // that those resources are cleaned up by @DynamicAfterEach. + try { + for ( Method afterEachMethod : afterEachMethods ) { + afterEachMethod.invoke( testInstance ); + } + } + catch ( Throwable t ) { + if ( exception == null ) { + throw t; + } + } + } + } + ) + ); + } + } ); + + // Iterate @DynamicTest methods and invoke them, if applicable. + // + // The before/after methods aren't tested as they're ran paired with the actual + // @DynamicTest method. So to control whether the test runs, only the @DynamicTest + // method is checked. + testMethods.forEach( method -> { + if ( !method.isAnnotationPresent( Disabled.class ) && context.isExecutionAllowed( method ) ) { + final DynamicTest dynamicTestAnnotation = method.getAnnotation( DynamicTest.class ); + final Class expectedException = dynamicTestAnnotation.expected(); + tests.add( + dynamicTest( + method.getName(), + () -> { + // invoke @DynamicBeforeEach + for ( Method beforeEachMethod : beforeEachMethods ) { + beforeEachMethod.invoke( testInstance ); + } + + Throwable exception = null; + try { + method.invoke( testInstance ); + + // If the @DynamicTest annotation specifies an expected exception + // and it wasn't thrown during the method invocation, we want to + // assert here and fail the test node accordingly. + assertEquals( + DynamicTest.None.class, + expectedException, + "Expected: " + expectedException.getName() + ); + } + catch ( InvocationTargetException t ) { + // Check if FailureExpected annotation is present. + if ( method.isAnnotationPresent( FailureExpected.class ) ) { + // We do nothing + } + else { + // only throw if the exception was not expected. + if ( !expectedException.isInstance( t.getTargetException() ) ) { + if ( t.getTargetException() != null ) { + // in this use case, we only really care about the cause + // we can safely ignore the wrapper exception here. + exception = t.getTargetException(); + throw t.getTargetException(); + } + else { + exception = t; + throw t; + } + } + } + } + catch ( Throwable t ) { + exception = t; + throw t; + } + finally { + try { + for ( Method afterEachMethod : afterEachMethods ) { + afterEachMethod.invoke( testInstance ); + } + } + catch( Throwable t ) { + if ( exception == null ) { + throw t; + } + } + } + } + ) + ); + } + // todo (6.0) - Be able to mark DynamicTest as skipped. + // + // After discussing with smoyer64 with junit5 team, we determined that it would be nice + // to mark DynamicTest instances as skipped. The proposal is to throw a black-listesd + // exception that Junit will catch and handle internally. + // + // See https://github.com/junit-team/junit5/issues/1816 + else { + tests.add( + dynamicTest( + method.getName(), + () -> { + final Disabled annotation = method.getAnnotation( Disabled.class ); + if ( annotation != null && annotation.value().length() > 0 ) { + throw new TestAbortedException( annotation.value() ); + } + } + ) + ); + } + } ); + + // Lastly invoke all @DynamicAfterAll annotated methods + afterAllMethods.forEach( method -> { + if ( context.isExecutionAllowed( method ) ) { + tests.add( dynamicTest( method.getName(), () -> method.invoke( testInstance ) ) ); + } + } ); + + // Only if the tests are not empty do we construct a container and inject the scope + if ( !tests.isEmpty() ) { + testInstance.injectExecutionContext( context ); + dynamicNodes.add( dynamicContainer( context.getTestContainerName( testClass ), tests ) ); + } + } + + return dynamicNodes; + } + + protected void injectExecutionContext(T context) { + + } + + @SuppressWarnings("unchecked") + protected Collection getExecutionContexts() { + return Collections.singletonList( (T) new DynamicExecutionContext() {} ); + } + + private List resolveTestMethods(Class testClass) { + final List testMethods = new ArrayList<>( + findMethods( + testClass, + method -> method.isAnnotationPresent( DynamicTest.class ) + ) + ); + testMethods.sort( new DynamicOrderAnnotationComparator() ); + return testMethods; + } + + private class DynamicOrderAnnotationComparator implements Comparator { + @Override + public int compare(Method method1, Method method2) { + final DynamicOrder order1 = method1.getAnnotation( DynamicOrder.class ); + final DynamicOrder order2 = method2.getAnnotation( DynamicOrder.class ); + if ( order1 != null && order2 != null ) { + if ( order1.value() < order2.value() ) { + return -1; + } + else if ( order1.value() > order2.value() ) { + return 1; + } + else { + return 0; + } + } + else if ( order1 != null ) { + return -1; + } + else if ( order2 != null ) { + return 1; + } + return 0; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicAfterAll.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicAfterAll.java new file mode 100644 index 0000000000..50d0aa5b61 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicAfterAll.java @@ -0,0 +1,25 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * {@link org.junit.jupiter.api.AfterAll} equilvalent. + * + * @author Chris Cranford + */ +@Target( METHOD ) +@Retention( RUNTIME ) +@Inherited +public @interface DynamicAfterAll { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicAfterEach.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicAfterEach.java new file mode 100644 index 0000000000..fb43c45da5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicAfterEach.java @@ -0,0 +1,25 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * {@link org.junit.jupiter.api.AfterEach} equivalent. + * + * @author Chris Cranford + */ +@Retention( RUNTIME ) +@Target( METHOD ) +@Inherited +public @interface DynamicAfterEach { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicBeforeAll.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicBeforeAll.java new file mode 100644 index 0000000000..e8c28a3015 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicBeforeAll.java @@ -0,0 +1,38 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * {@link org.junit.jupiter.api.BeforeAll} equivalent. + * + * @author Chris Cranford + */ +@Target( METHOD ) +@Retention( RUNTIME ) +@Inherited +public @interface DynamicBeforeAll { + /** + * Default empty exception. + */ + class None extends Throwable { + private None() { + } + } + + /** + * An expected {@link Throwable} to cause a test method to succeed, but only if an exception + * of the expected type is thrown. + */ + Class expected() default None.class; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicBeforeEach.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicBeforeEach.java new file mode 100644 index 0000000000..df21e13ff8 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicBeforeEach.java @@ -0,0 +1,25 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * {@link org.junit.jupiter.api.BeforeEach} equivalent. + * + * @author Chris Cranford + */ +@Retention( RUNTIME ) +@Target( METHOD ) +@Inherited +public @interface DynamicBeforeEach { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicExecutionContext.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicExecutionContext.java new file mode 100644 index 0000000000..935d701925 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicExecutionContext.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.reflect.Method; + +/** + * Simple contract for a sepcific execution group of dynamic test nodes that allows + * the {@link org.junit.jupiter.api.TestFactory} to check whether given test nodes + * should be generated or not. + * + * @author Chris Cranford + */ +public interface DynamicExecutionContext { + /** + * Allows applying filter criteria against the test class. + * + * @param testClass The test class. + * @return boolean true if the test class should generate nodes, false otherwise. + */ + default boolean isExecutionAllowed(Class testClass) { + return true; + } + + /** + * Allows applying filter criteria against the dynamic test method. + * + * @param method The test method. + * @return boolean true if the test method should generate a node, false otherwise. + */ + default boolean isExecutionAllowed(Method method) { + return true; + } + + /** + * Return the name of the dynamic node container associated with this execution context. + * + * @param testClass The test class. + * @return The name of the dynamic node container. + */ + default String getTestContainerName(Class testClass) { + return testClass.getName(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicOrder.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicOrder.java new file mode 100644 index 0000000000..0254859240 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicOrder.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Specifies the order in which tests will be sorted and executed. + *

    + * This is based on Junit 5.4's {@code @Order} annotation. + * + * @author Chris Cranford + */ +@Target(METHOD) +@Retention(RUNTIME) +@Inherited +public @interface DynamicOrder { + /** + * The order value for annotated element. + *

    + * The elements are ordered based on priority where a lower value has greater priority than a higher value. + * For example, {@link Integer#MAX_VALUE} has the lowest priority. + */ + int value() default Integer.MAX_VALUE; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicTest.java new file mode 100644 index 0000000000..913a0e145b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/dynamictests/DynamicTest.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.dynamictests; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * A {@link org.junit.jupiter.api.Test} equivalent. + * + * A marker annotation that identifies methods that should be collected and executed like a normal test + * but under the control of of our custom test factory implementation. + * + * A test which uses this annotation should extend {@link AbstractDynamicTest} for these marker methods + * to be injected into the Jupiter test framework via a {@link org.junit.jupiter.api.TestFactory}. + * + * @author Chris Cranford + */ +@Target( METHOD ) +@Retention( RUNTIME ) +public @interface DynamicTest { + /** + * Default empty exception. + */ + class None extends Throwable { + private None() { + } + } + + /** + * An expected {@link Throwable} to cause a test method to succeed, but only if an exception + * of the expected type is thrown. + */ + Class expected() default None.class; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/package-info.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/package-info.java new file mode 100644 index 0000000000..811a230ead --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/package-info.java @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ + +/** + * Extensions based on the extension mechanism of JUnit 5 to support things like + * `@FailureExcepted`, `@RequiresDialect`, etc. Used in writing tests + * + * todo (6.0) : add the proposed Docker support - DomainModel, multi-threading etc + */ +package org.hibernate.testing.junit5; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/DialectTestInstancePostProcessor.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/DialectTestInstancePostProcessor.java new file mode 100644 index 0000000000..07da462d15 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/DialectTestInstancePostProcessor.java @@ -0,0 +1,24 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.schema; + + +import org.hibernate.testing.junit5.DialectAccess; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; + +/** + * @author Andrea Boriero + */ +public class DialectTestInstancePostProcessor implements TestInstancePostProcessor { + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { + if ( testInstance instanceof DialectAccess ) { + context.getStore( DialectAccess.NAMESPACE ).put( testInstance, testInstance ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/FunctionalMetaModelTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/FunctionalMetaModelTesting.java new file mode 100644 index 0000000000..45d9217a5b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/FunctionalMetaModelTesting.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.schema; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.testing.junit5.DialectFilterExtension; +import org.hibernate.testing.orm.junit.FailureExpectedExtension; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Andrea Boriero + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(DialectFilterExtension.class) +@ExtendWith(FailureExpectedExtension.class) +@ExtendWith(DialectTestInstancePostProcessor.class) +@Inherited +public @interface FunctionalMetaModelTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaScope.java new file mode 100644 index 0000000000..f8125d33fd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaScope.java @@ -0,0 +1,38 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.schema; + +import java.util.function.Consumer; + +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.tool.hbm2ddl.SchemaUpdate; +import org.hibernate.tool.hbm2ddl.SchemaValidator; +import org.hibernate.tool.schema.internal.SchemaCreatorImpl; +import org.hibernate.tool.schema.internal.SchemaDropperImpl; +import org.hibernate.tool.schema.spi.SchemaFilter; +import org.hibernate.tool.schema.spi.SchemaMigrator; + +import org.hibernate.testing.junit5.template.TestScope; + + +/** + * @author Andrea Boriero + */ +public interface SchemaScope extends TestScope { + + void withSchemaUpdate(Consumer counsumer); + + void withSchemaValidator(Consumer counsumer); + + void withSchemaMigrator(Consumer counsumer); + + void withSchemaExport(Consumer counsumer); + + void withSchemaCreator(SchemaFilter filter, Consumer consumer); + + void withSchemaDropper(SchemaFilter filter, Consumer consumer); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaScopeProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaScopeProducer.java new file mode 100644 index 0000000000..91c7680b5a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaScopeProducer.java @@ -0,0 +1,19 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.schema; + + +import org.hibernate.testing.junit5.template.TestParameter; +import org.hibernate.testing.junit5.template.TestScopeProducer; + +/** + * @author Andrea Boriero + */ +public interface SchemaScopeProducer extends TestScopeProducer { + @Override + SchemaScope produceTestScope(TestParameter metadataExtractionStrategy); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaTest.java new file mode 100644 index 0000000000..dfd173e85a --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaTest.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.schema; + +/** + * @author Andrea Boriero + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; + +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@TestTemplate +@ExtendWith(SchemaTestExtension.class) +public @interface SchemaTest { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaTestExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaTestExtension.java new file mode 100644 index 0000000000..71e1d3617c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/schema/SchemaTestExtension.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.schema; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import org.hibernate.tool.schema.JdbcMetadaAccessStrategy; + +import org.hibernate.testing.junit5.template.TestParameter; +import org.hibernate.testing.junit5.template.TestTemplateExtension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; + +/** + * @author Andrea Boriero + */ + +public class SchemaTestExtension + extends TestTemplateExtension { + + public static final List METADATA_ACCESS_STRATEGIES = Arrays.asList( JdbcMetadaAccessStrategy.INDIVIDUALLY.toString(), JdbcMetadaAccessStrategy.GROUPED.toString()); + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts( + ExtensionContext context) { + return METADATA_ACCESS_STRATEGIES.stream().map( metadataAccessStrategy -> invocationContext( new SchemaTestParameter( (metadataAccessStrategy))) ); + } + + private TestTemplateInvocationContext invocationContext(SchemaTestParameter parameter) { + return new CustomTestTemplateInvocationContext( parameter, SchemaScope.class ); + } + + public class SchemaTestParameter + implements TestParameter { + private final String metadataExtractionStartegy; + + public SchemaTestParameter(String metadataExtractionStartegy) { + this.metadataExtractionStartegy = metadataExtractionStartegy; + } + + @Override + public String getValue() { + return metadataExtractionStartegy; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/serviceregistry/ServiceRegistryAccess.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/serviceregistry/ServiceRegistryAccess.java new file mode 100644 index 0000000000..ed005eea50 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/serviceregistry/ServiceRegistryAccess.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.serviceregistry; + +import org.hibernate.boot.registry.StandardServiceRegistry; + +import org.hibernate.testing.junit5.DialectAccess; + +/** + * @author Andrea Boriero + */ +public interface ServiceRegistryAccess extends DialectAccess { + StandardServiceRegistry getStandardServiceRegistry(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/serviceregistry/ServiceRegistryContainer.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/serviceregistry/ServiceRegistryContainer.java new file mode 100644 index 0000000000..ee8a86ff31 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/serviceregistry/ServiceRegistryContainer.java @@ -0,0 +1,16 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.serviceregistry; + +import org.hibernate.boot.registry.StandardServiceRegistry; + +/** + * @author Andrea Boriero + */ +public interface ServiceRegistryContainer { + void setStandardServiceRegistry(StandardServiceRegistry standardServiceRegistry); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestParameter.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestParameter.java new file mode 100644 index 0000000000..cad900cc34 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestParameter.java @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.template; + +/** + * @author Andrea Boriero + */ +public interface TestParameter { + T getValue(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestScope.java new file mode 100644 index 0000000000..a134392fdd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestScope.java @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.testing.junit5.template; + +/** + * @author Andrea Boriero + */ +public interface TestScope { + void clearScope(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestScopeProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestScopeProducer.java new file mode 100644 index 0000000000..a1ea1b5509 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestScopeProducer.java @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.template; + +/** + * @author Andrea Boriero + */ +public interface TestScopeProducer { + T produceTestScope(TestParameter parameter); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestTemplateExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestTemplateExtension.java new file mode 100644 index 0000000000..08086d5cac --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/template/TestTemplateExtension.java @@ -0,0 +1,79 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.junit5.template; + +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; + +/** + * @author Andrea Boriero + */ +public abstract class TestTemplateExtension + implements TestTemplateInvocationContextProvider { + + public class CustomTestTemplateInvocationContext + implements TestTemplateInvocationContext { + private final String displayName; + private final ParameterResolver parameterResolver; + + public CustomTestTemplateInvocationContext(TestParameter parameter, Class parameterClass) { + this.displayName = parameter.getValue().toString(); + this.parameterResolver = new TestScopeParameterResolver( parameter, parameterClass ); + } + + @Override + public String getDisplayName(int invocationIndex) { + return displayName; + } + + @Override + public List getAdditionalExtensions() { + return Collections.singletonList( parameterResolver ); + } + } + + public class TestScopeParameterResolver implements ParameterResolver { + private final Class parameterClass; + private final TestParameter parameter; + private TestScope testScope; + + public TestScopeParameterResolver(TestParameter parameter, Class parameterClass) { + this.parameter = parameter; + this.parameterClass = parameterClass; + } + + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) + throws ParameterResolutionException { + return parameterContext.getParameter().getType().equals( parameterClass ); + } + + @Override + public TestScope resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) { + final Object testInstance = extensionContext.getRequiredTestInstance(); + if ( !TestScopeProducer.class.isInstance( testInstance ) ) { + throw new RuntimeException( "Test instance does not implement TestScopeProducer" ); + } + if ( testScope == null ) { + testScope = ( (TestScopeProducer) testInstance ).produceTestScope( parameter ); + } + return testScope; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java new file mode 100644 index 0000000000..15f9146090 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/AbstractDomainModelDescriptor.java @@ -0,0 +1,29 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain; + +import org.hibernate.boot.MetadataSources; + +/** + * Convenience base class test domain models based on annotated classes + * + * @author Steve Ebersole + */ +public abstract class AbstractDomainModelDescriptor implements DomainModelDescriptor { + private final Class[] annotatedClasses; + + protected AbstractDomainModelDescriptor(Class... annotatedClasses) { + this.annotatedClasses = annotatedClasses; + } + + @Override + public void applyDomainModel(MetadataSources sources) { + for ( Class annotatedClass : annotatedClasses ) { + sources.addAnnotatedClass( annotatedClass ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java new file mode 100644 index 0000000000..b62b9ffe44 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/DomainModelDescriptor.java @@ -0,0 +1,60 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain; + +import java.util.EnumSet; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.dialect.Dialect; + +/** + * Describes a standard domain model + * + * @see StandardDomainModel + * @see org.hibernate.testing.orm.junit.DomainModel + * @see org.hibernate.testing.orm.junit.DomainModelFunctionalTesting + * @see org.hibernate.testing.orm.junit.DomainModelExtension + * + * @author Steve Ebersole + */ +public interface DomainModelDescriptor { + + /** + * Apply the model classes to the given MetadataSources + */ + void applyDomainModel(MetadataSources sources); + + /** + * The namespace to apply the model to. This is interpreted as a catalog + * name or a schema name depending on the capability of the underlying database + * via {@link Dialect}. Would require a new Dialect method I think, though + * we could also leverage the driver's db-metadata to ascertain which interpretation + * to use which would not need any (more) test-specific Dialect feature. + * + * Note however that this might be a useful feature as well for users instead of + * JPA's {@link javax.persistence.Table#catalog} / {@link javax.persistence.Table#schema}. + * AKA, something like `@org.hibernate.annotations.Namespace("a_name")` or + * `@org.hibernate.annotations.Table( namespace="a_name", ... )`. + * + * This may be {@code null} indicating that the default namespace should be used. + * + * Note that domain models can use the same namespace so long as they do not share + * db-object (tables, etc) names + */ + default String getNamespace() { + return null; + } + + /** + * Identifies the specific mapping features this domain model uses. + */ + default EnumSet getMappingFeaturesUsed() { + // for now just return none. this is is simply informative, not used to + // drive any functionality - so maybe its not important to add + return EnumSet.noneOf( MappingFeature.class ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java new file mode 100644 index 0000000000..536992fa6c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MappingFeature.java @@ -0,0 +1,55 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain; + +import java.util.EnumSet; + +/** + * Identifies specific mapping features used by a {@link DomainModelDescriptor}. + * + * The intent is to help categorize which models use specific mapping features + * to help facilitate testing various outcomes based on those features. + * + * For example, when writing a test that depends on JPA's {@link javax.persistence.AttributeConverter}, + * we could just see which DomainModel reports using {@link #CONVERTER} and re-use that + * model. + * + * @author Steve Ebersole + */ +public enum MappingFeature { + CONVERTER, + ENUMERATED, + DYNAMIC_MODEL, + + DISCRIMINATOR_INHERIT, + JOINED_INHERIT, + UNION_INHERIT, + + SECONDARY_TABLE, + + AGG_COMP_ID, + NON_AGG_COMP_ID, + ID_CLASS, + + EMBEDDABLE, + MANY_ONE, + ONE_ONE, + ONE_MANY, + MANY_MANY, + ANY, + MANY_ANY, + + COLLECTION_TABLE, + JOIN_TABLE, + JOIN_COLUMN, + + ; + + public static EnumSet all() { + return EnumSet.allOf( MappingFeature.class ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java new file mode 100644 index 0000000000..e310f1444b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/MonetaryAmountConverter.java @@ -0,0 +1,32 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain; + +import javax.money.Monetary; +import javax.money.MonetaryAmount; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** + * @author Steve Ebersole + */ +@Converter( autoApply = true ) +public class MonetaryAmountConverter implements AttributeConverter { + @Override + public Double convertToDatabaseColumn(MonetaryAmount attribute) { + return attribute.getNumber().numberValueExact( Double.class ); + } + + @Override + public MonetaryAmount convertToEntityAttribute(Double dbData) { + if ( dbData == null ) { + return null; + } + + return Monetary.getDefaultAmountFactory().setNumber( dbData ).create(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java new file mode 100644 index 0000000000..9122ef9f22 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/StandardDomainModel.java @@ -0,0 +1,32 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain; + +import org.hibernate.testing.orm.domain.contacts.ContactsDomainModel; +import org.hibernate.testing.orm.domain.gambit.GambitDomainModel; +import org.hibernate.testing.orm.domain.helpdesk.HelpDeskDomainModel; +import org.hibernate.testing.orm.domain.retail.RetailDomainModel; + +/** + * @author Steve Ebersole + */ +public enum StandardDomainModel { + CONTACTS( ContactsDomainModel.INSTANCE ), + GAMBIT( GambitDomainModel.INSTANCE ), + HELPDESK( HelpDeskDomainModel.INSTANCE ), + RETAIL( RetailDomainModel.INSTANCE ); + + private final DomainModelDescriptor domainModel; + + StandardDomainModel(DomainModelDescriptor domainModel) { + this.domainModel = domainModel; + } + + public DomainModelDescriptor getDescriptor() { + return domainModel; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java new file mode 100644 index 0000000000..229619abaf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Address.java @@ -0,0 +1,83 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.contacts; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class Address { + private Classification classification; + private String line1; + private String line2; + private PostalCode postalCode; + + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + + public PostalCode getPostalCode() { + return postalCode; + } + + public void setPostalCode(PostalCode postalCode) { + this.postalCode = postalCode; + } + + + + public enum Classification { + HOME, + WORK, + MAIN, + OTHER + } + + @Embeddable + public static class PostalCode { + private int zipCode; + private int plus4; + + public int getZipCode() { + return zipCode; + } + + public void setZipCode(int zipCode) { + this.zipCode = zipCode; + } + + public int getPlus4() { + return plus4; + } + + public void setPlus4(int plus4) { + this.plus4 = plus4; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java new file mode 100644 index 0000000000..b4a49c7ad5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/Contact.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.contacts; + +import java.time.LocalDate; +import java.util.List; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Embeddable; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OrderColumn; +import javax.persistence.SecondaryTable; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Steve Ebersole + */ +@Entity +@SecondaryTable( name="contact_supp" ) +public class Contact { + private Integer id; + private Name name; + private Gender gender; + + private LocalDate birthDay; + + // NOTE : because of the @OrderColumn `addresses` is a List, while `phoneNumbers` is a BAG + // which is a List with no persisted order + @OrderColumn + private List

    addresses; + private List phoneNumbers; + + public Contact() { + } + + public Contact(Integer id, Name name, Gender gender, LocalDate birthDay) { + this.id = id; + this.name = name; + this.gender = gender; + this.birthDay = birthDay; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } + + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @Temporal( TemporalType.DATE ) + @Column( table = "contact_supp" ) + public LocalDate getBirthDay() { + return birthDay; + } + + public void setBirthDay(LocalDate birthDay) { + this.birthDay = birthDay; + } + + @ElementCollection + @CollectionTable( name = "contact_addresses" ) + public List
    getAddresses() { + return addresses; + } + + public void setAddresses(List
    addresses) { + this.addresses = addresses; + } + + @ElementCollection + @CollectionTable( name = "contact_phones" ) + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + @Embeddable + public static class Name { + private String first; + private String last; + + public Name() { + } + + public Name(String first, String last) { + this.first = first; + this.last = last; + } + + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + public String getLast() { + return last; + } + + public void setLast(String last) { + this.last = last; + } + } + + public enum Gender { + MALE, + FEMALE, + OTHER + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java new file mode 100644 index 0000000000..1c151a6503 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/ContactsDomainModel.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.contacts; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class ContactsDomainModel extends AbstractDomainModelDescriptor { + public static ContactsDomainModel INSTANCE = new ContactsDomainModel(); + + public static void applyContactsModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + private ContactsDomainModel() { + super( + Address.class, + PhoneNumber.class, + Contact.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java new file mode 100644 index 0000000000..7c81368319 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/contacts/PhoneNumber.java @@ -0,0 +1,62 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.contacts; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +public class PhoneNumber { + private int areaCode; + private int prefix; + private int lineNumber; + + private Classification classification; + + public int getAreaCode() { + return areaCode; + } + + public void setAreaCode(int areaCode) { + this.areaCode = areaCode; + } + + public int getPrefix() { + return prefix; + } + + public void setPrefix(int prefix) { + this.prefix = prefix; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public Classification getClassification() { + return classification; + } + + public void setClassification(Classification classification) { + this.classification = classification; + } + + public enum Classification { + HOME, + WORK, + MOBILE, + MAIN, + FAX, + OTHER + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java new file mode 100644 index 0000000000..e6a48211aa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/BasicEntity.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class BasicEntity { + @Id + private Integer id; + private String data; + + public BasicEntity() { + + } + + public BasicEntity(Integer id, String data) { + this.id = id; + this.data = data; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + BasicEntity that = (BasicEntity) o; + return Objects.equals( id, that.id ) && + Objects.equals( data, that.data ); + } + + @Override + public int hashCode() { + return Objects.hash( id, data ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java new file mode 100644 index 0000000000..6dd38eb9d8 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Component.java @@ -0,0 +1,129 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +@SuppressWarnings("unused") +public class Component { + + // alphabetical + private Integer basicInteger; + private Long basicLong; + private int basicPrimitiveInt; + private String basicString; + private Nested nested; + + @Embeddable + public static class Nested { + + // alphabetical + private String nestedValue; + private String secondNestedValue; + + public Nested() { + } + + public Nested(String nestedValue) { + this.nestedValue = nestedValue; + } + + public Nested(String nestedValue, String secondNestedValue) { + this.nestedValue = nestedValue; + this.secondNestedValue = secondNestedValue; + } + + public String getNestedValue() { + return nestedValue; + } + + public void setNestedValue(String nestedValue) { + this.nestedValue = nestedValue; + } + + public String getSecondNestedValue() { + return secondNestedValue; + } + + public void setSecondNestedValue(String secondNestedValue) { + this.secondNestedValue = secondNestedValue; + } + } + + public Component() { + } + + public Component( + String basicString, + Integer basicInteger, + Long basicLong, + int basicPrimitiveInt, + Nested nested) { + this.basicString = basicString; + this.basicInteger = basicInteger; + this.basicLong = basicLong; + this.basicPrimitiveInt = basicPrimitiveInt; + this.nested = nested; + } + + public Component( + Integer basicInteger, + Long basicLong, + int basicPrimitiveInt, + String basicString, + Nested nested) { + this.basicInteger = basicInteger; + this.basicLong = basicLong; + this.basicPrimitiveInt = basicPrimitiveInt; + this.basicString = basicString; + this.nested = nested; + } + + public String getBasicString() { + return basicString; + } + + public void setBasicString(String basicString) { + this.basicString = basicString; + } + + public Integer getBasicInteger() { + return basicInteger; + } + + public void setBasicInteger(Integer basicInteger) { + this.basicInteger = basicInteger; + } + + public Long getBasicLong() { + return basicLong; + } + + public void setBasicLong(Long basicLong) { + this.basicLong = basicLong; + } + + public int getBasicPrimitiveInt() { + return basicPrimitiveInt; + } + + public void setBasicPrimitiveInt(int basicPrimitiveInt) { + this.basicPrimitiveInt = basicPrimitiveInt; + } + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java new file mode 100644 index 0000000000..33cad28062 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EmbeddedIdEntity.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.io.Serializable; +import java.util.Objects; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +/** + * @author Chris Cranford + */ +@Entity +public class EmbeddedIdEntity { + @EmbeddedId + private EmbeddedIdEntityId id; + private String data; + + public EmbeddedIdEntityId getId() { + return id; + } + + public void setId(EmbeddedIdEntityId id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + @Embeddable + public static class EmbeddedIdEntityId implements Serializable { + private Integer value1; + private String value2; + + EmbeddedIdEntityId() { + + } + + public EmbeddedIdEntityId(Integer value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public Integer getValue1() { + return value1; + } + + public void setValue1(Integer value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + EmbeddedIdEntityId that = (EmbeddedIdEntityId) o; + return Objects.equals( value1, that.value1 ) && + Objects.equals( value2, that.value2 ); + } + + @Override + public int hashCode() { + return Objects.hash( value1, value2 ); + } + + @Override + public String toString() { + return "EmbeddedIdEntityId{" + + "value1=" + value1 + + ", value2='" + value2 + '\'' + + '}'; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java new file mode 100644 index 0000000000..22ff344911 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java @@ -0,0 +1,198 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.net.URL; +import java.sql.Clob; +import java.time.Instant; +import java.util.Date; +import javax.persistence.AttributeConverter; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Steve Ebersole + */ +@Entity +public class EntityOfBasics { + + public enum Gender { + MALE, + FEMALE + } + + private Integer id; + private String theString; + private Integer theInteger; + private int theInt; + private double theDouble; + private URL theUrl; + private Clob theClob; + private Date theDate; + private Date theTime; + private Date theTimestamp; + private Instant theInstant; + private Gender gender; + private Gender convertedGender; + private Gender ordinalGender; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTheString() { + return theString; + } + + public void setTheString(String theString) { + this.theString = theString; + } + + public Integer getTheInteger() { + return theInteger; + } + + public void setTheInteger(Integer theInteger) { + this.theInteger = theInteger; + } + + public int getTheInt() { + return theInt; + } + + public void setTheInt(int theInt) { + this.theInt = theInt; + } + + public double getTheDouble() { + return theDouble; + } + + public void setTheDouble(double theDouble) { + this.theDouble = theDouble; + } + + public URL getTheUrl() { + return theUrl; + } + + public void setTheUrl(URL theUrl) { + this.theUrl = theUrl; + } + + public Clob getTheClob() { + return theClob; + } + + public void setTheClob(Clob theClob) { + this.theClob = theClob; + } + + @Enumerated( EnumType.STRING ) + public Gender getGender() { + return gender; + } + + public void setGender(Gender gender) { + this.gender = gender; + } + + @Convert( converter = GenderConverter.class ) + @Column(name = "converted_gender") + public Gender getConvertedGender() { + return convertedGender; + } + + public void setConvertedGender(Gender convertedGender) { + this.convertedGender = convertedGender; + } + + @Column(name = "ordinal_gender") + public Gender getOrdinalGender() { + return ordinalGender; + } + + public void setOrdinalGender(Gender ordinalGender) { + this.ordinalGender = ordinalGender; + } + + @Temporal( TemporalType.DATE ) + public Date getTheDate() { + return theDate; + } + + public void setTheDate(Date theDate) { + this.theDate = theDate; + } + + @Temporal( TemporalType.TIME ) + public Date getTheTime() { + return theTime; + } + + public void setTheTime(Date theTime) { + this.theTime = theTime; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Date getTheTimestamp() { + return theTimestamp; + } + + public void setTheTimestamp(Date theTimestamp) { + this.theTimestamp = theTimestamp; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Instant getTheInstant() { + return theInstant; + } + + public void setTheInstant(Instant theInstant) { + this.theInstant = theInstant; + } + + public static class GenderConverter implements AttributeConverter { + @Override + public Character convertToDatabaseColumn(Gender attribute) { + if ( attribute == null ) { + return null; + } + + if ( attribute == Gender.MALE ) { + return 'M'; + } + + return 'F'; + } + + @Override + public Gender convertToEntityAttribute(Character dbData) { + if ( dbData == null ) { + return null; + } + + if ( 'M' == dbData ) { + return Gender.MALE; + } + + return Gender.FEMALE; + } + } +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java new file mode 100644 index 0000000000..03eb5221d9 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfComposites.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfComposites { + private Integer id; + private String name; + private Component component; + + public EntityOfComposites() { + } + + public EntityOfComposites(Integer id) { + this.id = id; + } + + public EntityOfComposites(Integer id, Component component) { + this.id = id; + this.component = component; + } + + public EntityOfComposites(Integer id, String name, Component component) { + this.id = id; + this.name = name; + this.component = component; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Embedded + public Component getComponent() { + return component; + } + + public void setComponent(Component component) { + this.component = component; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java new file mode 100644 index 0000000000..6aa5038c70 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfDynamicComponent.java @@ -0,0 +1,62 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Chris Cranford + */ +public class EntityOfDynamicComponent { + private Long id; + private String note; + private Map values = new HashMap<>(); + private Map valuesWithProperties = new HashMap<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } + + public Map getValuesWithProperties() { + return valuesWithProperties; + } + + public void setValuesWithProperties(Map valuesWithProperties) { + this.valuesWithProperties = valuesWithProperties; + } + + @Override + public String toString() { + return "EntityOfDynamicComponent{" + + "id=" + id + + ", note='" + note + '\'' + + ", values=" + values + + ", valuesWithProperties=" + valuesWithProperties + + '}'; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java new file mode 100644 index 0000000000..93306daac6 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfLists.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.List; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfLists { + private Integer id; + private List listOfBasics; + private List listOfComponents; + private List listOfOneToMany; + private List listOfManyToMany; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ElementCollection + @OrderColumn + public List getListOfBasics() { + return listOfBasics; + } + + public void setListOfBasics(List listOfBasics) { + this.listOfBasics = listOfBasics; + } + + @ElementCollection + @OrderColumn + public List getListOfComponents() { + return listOfComponents; + } + + public void setListOfComponents(List listOfComponents) { + this.listOfComponents = listOfComponents; + } + + @OneToMany + @OrderColumn + public List getListOfOneToMany() { + return listOfOneToMany; + } + + public void setListOfOneToMany(List listOfOneToMany) { + this.listOfOneToMany = listOfOneToMany; + } + + @ManyToMany + @OrderColumn + public List getListOfManyToMany() { + return listOfManyToMany; + } + + public void setListOfManyToMany(List listOfManyToMany) { + this.listOfManyToMany = listOfManyToMany; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java new file mode 100644 index 0000000000..f6f7fac3fe --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfMaps.java @@ -0,0 +1,85 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.Map; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfMaps { + private Integer id; + private Map basicToBasicMap; + private Map basicToComponentMap; + private Map componentToBasicMap; + private Map basicToOneToMany; + private Map basicToManyToMany; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ElementCollection + public Map getBasicToBasicMap() { + return basicToBasicMap; + } + + public void setBasicToBasicMap(Map basicToBasicMap) { + this.basicToBasicMap = basicToBasicMap; + } + + @ElementCollection + public Map getBasicToComponentMap() { + return basicToComponentMap; + } + + public void setBasicToComponentMap(Map basicToComponentMap) { + this.basicToComponentMap = basicToComponentMap; + } + + @ElementCollection + public Map getComponentToBasicMap() { + return componentToBasicMap; + } + + public void setComponentToBasicMap(Map componentToBasicMap) { + this.componentToBasicMap = componentToBasicMap; + } + + @OneToMany + @JoinColumn + public Map getBasicToOneToMany() { + return basicToOneToMany; + } + + public void setBasicToOneToMany(Map basicToOneToMany) { + this.basicToOneToMany = basicToOneToMany; + } + + @ManyToMany + public Map getBasicToManyToMany() { + return basicToManyToMany; + } + + public void setBasicToManyToMany(Map basicToManyToMany) { + this.basicToManyToMany = basicToManyToMany; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java new file mode 100644 index 0000000000..c11a1b7592 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfSets.java @@ -0,0 +1,106 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("unused") +@Entity +public class EntityOfSets { + private Integer id; + private Set setOfBasics; + private Set setOfComponents; + private Set setOfExtraLazyComponents; + private Set setOfOneToMany; + private Set setOfManyToMany; + + public EntityOfSets() { + } + + public EntityOfSets(Integer id) { + this.id = id; + this.setOfBasics = new HashSet<>(); + this.setOfComponents = new HashSet<>(); + this.setOfOneToMany = new HashSet<>(); + this.setOfManyToMany = new HashSet<>(); + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ElementCollection() +// @ElementCollection( fetch = FetchType.EAGER ) + @CollectionTable( name = "EntityOfSet_basics") + public Set getSetOfBasics() { + return setOfBasics; + } + + public void setSetOfBasics(Set setOfBasics) { + this.setOfBasics = setOfBasics; + } + + @ElementCollection + @CollectionTable( name = "EntityOfSet_components") + public Set getSetOfComponents() { + return setOfComponents; + } + + public void setSetOfComponents(Set setOfComponents) { + this.setOfComponents = setOfComponents; + } + + @ElementCollection + @LazyCollection( LazyCollectionOption.EXTRA ) + @CollectionTable( name = "EntityOfSet_extraLazyComponents") + public Set getSetOfExtraLazyComponents() { + return setOfExtraLazyComponents; + } + + public void setSetOfExtraLazyComponents(Set setOfExtraLazyComponents) { + this.setOfExtraLazyComponents = setOfExtraLazyComponents; + } + + @OneToMany + @CollectionTable( name = "EntityOfSet_oneToMany") + public Set getSetOfOneToMany() { + return setOfOneToMany; + } + + public void setSetOfOneToMany(Set setOfOneToMany) { + this.setOfOneToMany = setOfOneToMany; + } + + @ManyToMany + @CollectionTable( name = "EntityOfSet_manyToMany") + public Set getSetOfManyToMany() { + return setOfManyToMany; + } + + public void setSetOfManyToMany(Set setOfManyToMany) { + this.setOfManyToMany = setOfManyToMany; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java new file mode 100644 index 0000000000..32da858c6f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyManyToOneSelfReference.java @@ -0,0 +1,93 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithLazyManyToOneSelfReference { + private Integer id; + + // alphabetical + private String name; + private EntityWithLazyManyToOneSelfReference other; + private Integer someInteger; + + EntityWithLazyManyToOneSelfReference() { + } + + public EntityWithLazyManyToOneSelfReference(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + public EntityWithLazyManyToOneSelfReference( + Integer id, + String name, + Integer someInteger, + EntityWithLazyManyToOneSelfReference other) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + this.other = other; + } + + public EntityWithLazyManyToOneSelfReference( + Integer id, + String name, + EntityWithLazyManyToOneSelfReference other, + Integer someInteger) { + this.id = id; + this.name = name; + this.other = other; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn + public EntityWithLazyManyToOneSelfReference getOther() { + return other; + } + + public void setOther(EntityWithLazyManyToOneSelfReference other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java new file mode 100644 index 0000000000..cd5464ebc2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithLazyOneToOne.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithLazyOneToOne { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithLazyOneToOne() { + } + + public EntityWithLazyOneToOne(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne(fetch = FetchType.LAZY) + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java new file mode 100644 index 0000000000..da6f4f7a9f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneJoinTable.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinTable; +import javax.persistence.ManyToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithManyToOneJoinTable { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithManyToOneJoinTable() { + } + + public EntityWithManyToOneJoinTable(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne + @JoinTable(name = "ENTITY_OTHER") + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java new file mode 100644 index 0000000000..22a68415a1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneSelfReference.java @@ -0,0 +1,92 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +@SuppressWarnings("unused") +public class EntityWithManyToOneSelfReference { + private Integer id; + + // alphabetical + private String name; + private EntityWithManyToOneSelfReference other; + private Integer someInteger; + + EntityWithManyToOneSelfReference() { + } + + public EntityWithManyToOneSelfReference(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + public EntityWithManyToOneSelfReference( + Integer id, + String name, + Integer someInteger, + EntityWithManyToOneSelfReference other) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + this.other = other; + } + + public EntityWithManyToOneSelfReference( + Integer id, + String name, + EntityWithManyToOneSelfReference other, + Integer someInteger) { + this.id = id; + this.name = name; + this.other = other; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne + @JoinColumn + public EntityWithManyToOneSelfReference getOther() { + return other; + } + + public void setOther(EntityWithManyToOneSelfReference other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java new file mode 100644 index 0000000000..075c43a97f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithManyToOneWithoutJoinTable.java @@ -0,0 +1,55 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Chris Cranford + */ +@Entity +public class EntityWithManyToOneWithoutJoinTable { + private Integer id; + private Integer someInteger; + private EntityWithOneToManyNotOwned owner; + + EntityWithManyToOneWithoutJoinTable() { + } + + public EntityWithManyToOneWithoutJoinTable(Integer id, Integer someInteger) { + this.id = id; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + @ManyToOne + public EntityWithOneToManyNotOwned getOwner() { + return owner; + } + + public void setOwner(EntityWithOneToManyNotOwned owner) { + this.owner = owner; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java new file mode 100644 index 0000000000..88dd98761d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithNonIdAttributeNamedId.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +@SuppressWarnings("unused") +public class EntityWithNonIdAttributeNamedId { + private Integer pk; + private String id; + + @Id + public Integer getPk() { + return pk; + } + + public void setPk(Integer pk) { + this.pk = pk; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java new file mode 100644 index 0000000000..e0e30f7593 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToMany.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.GenericGenerator; + +/** + * @author Andrea Boriero + */ +@Entity +@GenericGenerator(name="increment", strategy = "increment") +public class EntityWithOneToMany { + private Integer id; + + // alphabetical + private String name; + private Set others = new HashSet<>( ); + private List othersIdentifierBag = new ArrayList<>( ); + private Integer someInteger; + + public EntityWithOneToMany() { + } + + public EntityWithOneToMany(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToMany(fetch = FetchType.LAZY) + public Set getOthers() { + return others; + } + + public void setOthers(Set others) { + this.others = others; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + public void addOther(SimpleEntity other) { + others.add( other ); + } + + @OneToMany + @CollectionTable(name = "idbag") + @org.hibernate.annotations.CollectionId( + columns = @Column(name = "BAG_ID"), + type = @org.hibernate.annotations.Type(type = "long"), + generator = "increment") + public List getOthersIdentifierBag() { + return othersIdentifierBag; + } + + public void setOthersIdentifierBag(List othersIdentifierBag) { + this.othersIdentifierBag = othersIdentifierBag; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java new file mode 100644 index 0000000000..8edeff3466 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToManyNotOwned.java @@ -0,0 +1,45 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Chris Cranford + */ +@Entity +public class EntityWithOneToManyNotOwned { + private Integer id; + private List children = new ArrayList<>(); + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany(mappedBy = "owner") + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(EntityWithManyToOneWithoutJoinTable child) { + child.setOwner( this ); + getChildren().add( child ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java new file mode 100644 index 0000000000..e017ba092d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOne.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithOneToOne { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOne() { + } + + public EntityWithOneToOne(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java new file mode 100644 index 0000000000..52959f3bca --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneJoinTable.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinTable; +import javax.persistence.OneToOne; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithOneToOneJoinTable { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOneJoinTable() { + } + + public EntityWithOneToOneJoinTable(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne + @JoinTable(name = "Entity_SimpleEntity") + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java new file mode 100644 index 0000000000..0c1a304509 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityWithOneToOneSharingPrimaryKey.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; + +/** + * @author Andrea Boriero + */ +@Entity +public class EntityWithOneToOneSharingPrimaryKey { + private Integer id; + + // alphabetical + private String name; + private SimpleEntity other; + private Integer someInteger; + + public EntityWithOneToOneSharingPrimaryKey() { + } + + public EntityWithOneToOneSharingPrimaryKey(Integer id, String name, Integer someInteger) { + this.id = id; + this.name = name; + this.someInteger = someInteger; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @OneToOne + @PrimaryKeyJoinColumn + public SimpleEntity getOther() { + return other; + } + + public void setOther(SimpleEntity other) { + this.other = other; + } + + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java new file mode 100644 index 0000000000..de575d628c --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/GambitDomainModel.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class GambitDomainModel extends AbstractDomainModelDescriptor { + public static final GambitDomainModel INSTANCE = new GambitDomainModel(); + + public GambitDomainModel() { + super( + BasicEntity.class, + Component.class, + EmbeddedIdEntity.class, + EntityOfBasics.class, + EntityOfComposites.class, + EntityOfDynamicComponent.class, + EntityOfLists.class, + EntityOfMaps.class, + EntityOfSets.class, + EntityWithLazyManyToOneSelfReference.class, + EntityWithLazyOneToOne.class, + EntityWithManyToOneJoinTable.class, + EntityWithManyToOneSelfReference.class, + EntityWithNonIdAttributeNamedId.class, + EntityWithOneToMany.class, + EntityWithOneToOne.class, + EntityWithOneToOneJoinTable.class, + EntityWithOneToOneSharingPrimaryKey.class, + Shirt.class, + SimpleEntity.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java new file mode 100644 index 0000000000..660a1be642 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java @@ -0,0 +1,107 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; + +/** + * @author Chris Cranford + */ +@Entity +public class Shirt { + @Id + private Integer id; + + @Convert(converter = ShirtStringToIntegerConverter.class) + private String data; + + @Enumerated + private Size size; + + @Enumerated(EnumType.STRING) + private Color color; + + public enum Size { + SMALL, + MEDIUM, + LARGE, + XLARGE + } + + public enum Color { + WHITE, + GREY, + BLACK, + BLUE, + TAN + } + + public static class ShirtStringToIntegerConverter implements AttributeConverter { + @Override + public Integer convertToDatabaseColumn(String attribute) { + if ( attribute != null ) { + if ( attribute.equalsIgnoreCase( "X" ) ) { + return 1; + } + else if ( attribute.equalsIgnoreCase( "Y" ) ) { + return 2; + } + } + return null; + } + + @Override + public String convertToEntityAttribute(Integer dbData) { + if ( dbData != null ) { + switch ( Integer.valueOf( dbData ) ) { + case 1: + return "X"; + case 2: + return "Y"; + } + } + return null; + } + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java new file mode 100644 index 0000000000..e1cc49742f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/SimpleEntity.java @@ -0,0 +1,100 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.gambit; + +import java.time.Instant; +import java.util.Date; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.hibernate.annotations.NaturalId; + +/** + * @author Steve Ebersole + */ +@Entity +public class SimpleEntity { + private Integer id; + + // NOTE : alphabetical + private Date someDate; + private Instant someInstant; + private Integer someInteger; + private Long someLong; + private String someString; + + public SimpleEntity() { + } + + public SimpleEntity( + Integer id, + Date someDate, + Instant someInstant, + Integer someInteger, + Long someLong, + String someString) { + this.id = id; + this.someDate = someDate; + this.someInstant = someInstant; + this.someInteger = someInteger; + this.someLong = someLong; + this.someString = someString; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getSomeString() { + return someString; + } + + public void setSomeString(String someString) { + this.someString = someString; + } + + @NaturalId + public Integer getSomeInteger() { + return someInteger; + } + + public void setSomeInteger(Integer someInteger) { + this.someInteger = someInteger; + } + + public Long getSomeLong() { + return someLong; + } + + public void setSomeLong(Long someLong) { + this.someLong = someLong; + } + + @Temporal( TemporalType.TIMESTAMP ) + public Date getSomeDate() { + return someDate; + } + + public void setSomeDate(Date someDate) { + this.someDate = someDate; + } + + public Instant getSomeInstant() { + return someInstant; + } + + public void setSomeInstant(Instant someInstant) { + this.someInstant = someInstant; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java new file mode 100644 index 0000000000..1cc8b9d5ff --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Account.java @@ -0,0 +1,99 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import javax.persistence.AttributeConverter; +import javax.persistence.Convert; +import javax.persistence.Converter; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Account { + private Integer id; + + private Status loginStatus; + private Status systemAccessStatus; + private Status serviceStatus; + + public Account() { + } + + public Account( + Integer id, + Status loginStatus, + Status systemAccessStatus, + Status serviceStatus) { + this.id = id; + this.loginStatus = loginStatus; + this.systemAccessStatus = systemAccessStatus; + this.serviceStatus = serviceStatus; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @Enumerated( EnumType.ORDINAL ) + public Status getLoginStatus() { + return loginStatus; + } + + public void setLoginStatus(Status loginStatus) { + this.loginStatus = loginStatus; + } + + @Enumerated( EnumType.STRING ) + public Status getSystemAccessStatus() { + return systemAccessStatus; + } + + public void setSystemAccessStatus(Status systemAccessStatus) { + this.systemAccessStatus = systemAccessStatus; + } + + @Convert( converter = ServiceStatusConverter.class ) + public Status getServiceStatus() { + return serviceStatus; + } + + public void setServiceStatus(Status serviceStatus) { + this.serviceStatus = serviceStatus; + } + + @Converter( autoApply = false ) + private static class ServiceStatusConverter implements AttributeConverter { + + @Override + public Integer convertToDatabaseColumn(Status attribute) { + if ( attribute == null ) { + return null; + } + + return attribute.getCode(); + } + + @Override + public Status convertToEntityAttribute(Integer dbData) { + if ( dbData == null ) { + return null; + } + + return Status.fromCode( dbData ); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java new file mode 100644 index 0000000000..afb3570ab7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/HelpDeskDomainModel.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; + +/** + * @author Steve Ebersole + */ +public class HelpDeskDomainModel extends AbstractDomainModelDescriptor { + public static final HelpDeskDomainModel INSTANCE = new HelpDeskDomainModel(); + + public HelpDeskDomainModel() { + super( + Status.class, + Account.class + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java new file mode 100644 index 0000000000..873fde41a9 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Incident.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import java.time.Instant; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Incident { + private Integer id; + + private Instant reported; + + private Instant effectiveStart; + private Instant effectiveEnd; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Instant getReported() { + return reported; + } + + public void setReported(Instant reported) { + this.reported = reported; + } + + public Instant getEffectiveStart() { + return effectiveStart; + } + + public void setEffectiveStart(Instant effectiveStart) { + this.effectiveStart = effectiveStart; + } + + public Instant getEffectiveEnd() { + return effectiveEnd; + } + + public void setEffectiveEnd(Instant effectiveEnd) { + this.effectiveEnd = effectiveEnd; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java new file mode 100644 index 0000000000..484a4c7aed --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Status.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.helpdesk; + +public enum Status { + CREATED, + INITIALIZING, + ACTIVE, + INACTIVE; + + private final int code; + + Status() { + this.code = this.ordinal() + 1000; + } + + public int getCode() { + return code; + } + + public static Status fromCode(Integer code) { + if ( code == null ) { + return null; + } + return values()[ code - 1000 ]; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java new file mode 100644 index 0000000000..2f929b4331 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/helpdesk/Ticket.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.helpdesk; + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Steve Ebersole + */ +@Entity +public class Ticket { + private Integer id; + + private String key; + + private String subject; + private String details; + + private Incident asssociatedIncident; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java new file mode 100644 index 0000000000..941bdcf992 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CardPayment.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class CardPayment extends Payment { + private Integer transactionId; + + public CardPayment() { + } + + public CardPayment(Integer id, Integer transactionId, MonetaryAmount amount) { + super( id,amount ); + this.transactionId = transactionId; + } + + public Integer getTransactionId() { + return transactionId; + } + + public void setTransactionId(Integer transactionId) { + this.transactionId = transactionId; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java new file mode 100644 index 0000000000..f5fcc5d917 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/CashPayment.java @@ -0,0 +1,23 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +public class CashPayment extends Payment { + public CashPayment() { + } + + public CashPayment(Integer id, MonetaryAmount amount) { + super( id, amount ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java new file mode 100644 index 0000000000..e0be8c01cb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/DomesticVendor.java @@ -0,0 +1,24 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +@DiscriminatorValue( "domestic" ) +public class DomesticVendor extends Vendor { + public DomesticVendor() { + } + + public DomesticVendor(Integer id, String name, String billingEntity) { + super( id, name, billingEntity ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java new file mode 100644 index 0000000000..1639bfaf41 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/ForeignVendor.java @@ -0,0 +1,24 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; + +/** + * @author Steve Ebersole + */ +@Entity +@DiscriminatorValue( "foreign" ) +public class ForeignVendor extends Vendor { + public ForeignVendor() { + } + + public ForeignVendor(Integer id, String name, String billingEntity) { + super( id, name, billingEntity ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java new file mode 100644 index 0000000000..f738151a1d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/LineItem.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +public class LineItem { + private Integer id; + private Product product; + + private int quantity; + private MonetaryAmount subTotal; + + private Order order; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne + @JoinColumn( name = "product_id" ) + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public int getQuantity() { + return quantity; + } + + public void setQuantity(int quantity) { + this.quantity = quantity; + } + + public MonetaryAmount getSubTotal() { + return subTotal; + } + + public void setSubTotal(MonetaryAmount subTotal) { + this.subTotal = subTotal; + } + + @ManyToOne + @JoinColumn( name = "order_id" ) + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java new file mode 100644 index 0000000000..da5f4eff9f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Name.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.Embeddable; + +/** + * @author Steve Ebersole + */ +@Embeddable +@SuppressWarnings("unused") +public class Name { + private String familyName; + private String familiarName; + + private String prefix; + private String suffix; + + public Name() { + } + + public Name(String familyName, String familiarName) { + this.familyName = familyName; + this.familiarName = familiarName; + } + + public Name(String familyName, String familiarName, String suffix) { + this.familyName = familyName; + this.familiarName = familiarName; + this.suffix = suffix; + } + + public Name(String familyName, String familiarName, String prefix, String suffix) { + this.familyName = familyName; + this.familiarName = familiarName; + this.prefix = prefix; + this.suffix = suffix; + } + + public String getFamilyName() { + return familyName; + } + + public void setFamilyName(String familyName) { + this.familyName = familyName; + } + + public String getFamiliarName() { + return familiarName; + } + + public void setFamiliarName(String familiarName) { + this.familiarName = familiarName; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSuffix() { + return suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java new file mode 100644 index 0000000000..c0a1ac0fa2 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Order.java @@ -0,0 +1,65 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import java.time.Instant; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "orders") +public class Order { + private Integer id; + private Instant transacted; + + private Payment payment; + private SalesAssociate salesAssociate; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Instant getTransacted() { + return transacted; + } + + public void setTransacted(Instant transacted) { + this.transacted = transacted; + } + + @ManyToOne + @JoinColumn(name = "payment_id") + public Payment getPayment() { + return payment; + } + + public void setPayment(Payment payment) { + this.payment = payment; + } + + @ManyToOne + @JoinColumn(name = "associate_id") + public SalesAssociate getSalesAssociate() { + return salesAssociate; + } + + public void setSalesAssociate(SalesAssociate salesAssociate) { + this.salesAssociate = salesAssociate; + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java new file mode 100644 index 0000000000..21a6b50410 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Payment.java @@ -0,0 +1,50 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.money.MonetaryAmount; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@Table( name = "payments" ) +public abstract class Payment { + private Integer id; + private MonetaryAmount amount; + + public Payment() { + } + + public Payment(Integer id, MonetaryAmount amount) { + this.id = id; + this.amount = amount; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public MonetaryAmount getAmount() { + return amount; + } + + public void setAmount(MonetaryAmount amount) { + this.amount = amount; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java new file mode 100644 index 0000000000..253204d4db --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Product.java @@ -0,0 +1,62 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import java.util.UUID; +import javax.money.MonetaryAmount; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * @author Steve Ebersole + */ +@Entity +public class Product { + private Integer id; + private UUID sku; + + private Vendor vendor; + + private MonetaryAmount currentSellPrice; + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ManyToOne + @JoinColumn + public Vendor getVendor() { + return vendor; + } + + public void setVendor(Vendor vendor) { + this.vendor = vendor; + } + + public UUID getSku() { + return sku; + } + + public void setSku(UUID sku) { + this.sku = sku; + } + + public MonetaryAmount getCurrentSellPrice() { + return currentSellPrice; + } + + public void setCurrentSellPrice(MonetaryAmount currentSellPrice) { + this.currentSellPrice = currentSellPrice; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java new file mode 100644 index 0000000000..255f650ef0 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/RetailDomainModel.java @@ -0,0 +1,61 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import java.util.EnumSet; + +import org.hibernate.boot.MetadataSources; + +import org.hibernate.testing.orm.domain.AbstractDomainModelDescriptor; +import org.hibernate.testing.orm.domain.MappingFeature; +import org.hibernate.testing.orm.domain.MonetaryAmountConverter; + +import static org.hibernate.testing.orm.domain.MappingFeature.CONVERTER; +import static org.hibernate.testing.orm.domain.MappingFeature.EMBEDDABLE; +import static org.hibernate.testing.orm.domain.MappingFeature.JOINED_INHERIT; +import static org.hibernate.testing.orm.domain.MappingFeature.JOIN_COLUMN; +import static org.hibernate.testing.orm.domain.MappingFeature.MANY_ONE; +import static org.hibernate.testing.orm.domain.MappingFeature.SECONDARY_TABLE; + +/** + * @author Steve Ebersole + */ +public class RetailDomainModel extends AbstractDomainModelDescriptor { + public static final RetailDomainModel INSTANCE = new RetailDomainModel(); + + public RetailDomainModel() { + super( + MonetaryAmountConverter.class, + SalesAssociate.class, + Vendor.class, + DomesticVendor.class, + ForeignVendor.class, + Product.class, + Order.class, + LineItem.class, + Payment.class, + CashPayment.class, + CardPayment.class + ); + } + + public static void applyRetailModel(MetadataSources sources) { + INSTANCE.applyDomainModel( sources ); + } + + @Override + public EnumSet getMappingFeaturesUsed() { + return EnumSet.of( + CONVERTER, + EMBEDDABLE, + MANY_ONE, + JOIN_COLUMN, + SECONDARY_TABLE, + JOINED_INHERIT + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java new file mode 100644 index 0000000000..6e22d4f0a7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/SalesAssociate.java @@ -0,0 +1,47 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Steve Ebersole + */ +@Entity +@Table( name = "ASSOCIATE") +public class SalesAssociate { + private Integer id; + + private Name name; + + public SalesAssociate() { + } + + public SalesAssociate(Integer id, Name name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Name getName() { + return name; + } + + public void setName(Name name) { + this.name = name; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java new file mode 100644 index 0000000000..632ab8a926 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/retail/Vendor.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.domain.retail; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.SecondaryTable; + +/** + * @author Steve Ebersole + */ +@Entity +@Inheritance( strategy = InheritanceType.SINGLE_TABLE ) +@DiscriminatorColumn( name = "vendor_type" ) +@SecondaryTable(name = "vendor_supp") +public class Vendor { + private Integer id; + private String name; + private String billingEntity; + + public Vendor() { + } + + public Vendor(Integer id, String name, String billingEntity) { + this.id = id; + this.name = name; + this.billingEntity = billingEntity; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java new file mode 100644 index 0000000000..929f6bf2ee --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/BaseSessionFactoryFunctionalTest.java @@ -0,0 +1,162 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; + +import org.junit.jupiter.api.AfterEach; + +import org.jboss.logging.Logger; + +/** + * Template (GoF pattern) based abstract class for tests bridging the legacy + * approach of SessionFactory building as a test fixture + * + * @author Steve Ebersole + */ +@SessionFactoryFunctionalTesting +public abstract class BaseSessionFactoryFunctionalTest + implements ServiceRegistryProducer, ServiceRegistryScopeAware, + DomainModelProducer, DomainModelScopeAware, + SessionFactoryProducer, SessionFactoryScopeAware { + + protected static final Class[] NO_CLASSES = new Class[0]; + private static final Logger log = Logger.getLogger( BaseSessionFactoryFunctionalTest.class ); + + private ServiceRegistryScope registryScope; + private DomainModelScope modelScope; + private SessionFactoryScope sessionFactoryScope; + + protected SessionFactoryScope sessionFactoryScope() { + return sessionFactoryScope; + } + + protected SessionFactoryImplementor sessionFactory() { + return sessionFactoryScope.getSessionFactory(); + } + + protected MetadataImplementor getMetadata(){ + return modelScope.getDomainModel(); + } + + @Override + public StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder ssrBuilder) { + ssrBuilder.applySetting( AvailableSettings.HBM2DDL_AUTO, exportSchema() ? "create-drop" : "none" ); + applySettings( ssrBuilder ); + return ssrBuilder.build(); + } + + protected boolean exportSchema() { + return true; + } + + protected void applySettings(StandardServiceRegistryBuilder builer) { + } + + @Override + public void injectServiceRegistryScope(ServiceRegistryScope registryScope) { + this.registryScope = registryScope; + } + + @Override + public MetadataImplementor produceModel(StandardServiceRegistry serviceRegistry) { + MetadataSources metadataSources = new MetadataSources( serviceRegistry ); + applyMetadataSources( metadataSources ); + return (MetadataImplementor) metadataSources.buildMetadata(); + } + + protected void applyMetadataSources(MetadataSources metadataSources) { + for ( Class annotatedClass : getAnnotatedClasses() ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + } + + protected Class[] getAnnotatedClasses() { + return NO_CLASSES; + } + + @Override + public void injectTestModelScope(DomainModelScope modelScope) { + this.modelScope = modelScope; + } + + @Override + public SessionFactoryImplementor produceSessionFactory(MetadataImplementor model) { + log.trace( "Producing SessionFactory" ); + final SessionFactoryBuilder sfBuilder = model.getSessionFactoryBuilder(); + configure( sfBuilder ); + final SessionFactoryImplementor factory = (SessionFactoryImplementor) model.buildSessionFactory(); + sessionFactoryBuilt( factory ); + return factory; + } + + protected void configure(SessionFactoryBuilder builder) { + } + + protected void sessionFactoryBuilt(SessionFactoryImplementor factory) { + } + + @Override + public void injectSessionFactoryScope(SessionFactoryScope scope) { + sessionFactoryScope = scope; + } + + // there is a chicken-egg problem here where the +// @AfterAll +// public void dropDatabase() { +// final SchemaManagementToolCoordinator.ActionGrouping actions = SchemaManagementToolCoordinator.ActionGrouping.interpret( +// registry.getService( ConfigurationService.class ).getSettings() +// ); +// +// final boolean needsDropped = this.model != null && ( exportSchema() || actions.getDatabaseAction() != Action.NONE ); +// +// if ( needsDropped ) { +// // atm we do not expose the (runtime) DatabaseModel from the SessionFactory so we +// // need to recreate it from the boot model. +// // +// // perhaps we should expose it from SF? +// final DatabaseModel databaseModel = Helper.buildDatabaseModel( registry, model ); +// new SchemaExport( databaseModel, registry ).drop( EnumSet.of( TargetType.DATABASE ) ); +// } +// } + + @AfterEach + public final void afterTest() { + if ( isCleanupTestDataRequired() ) { + cleanupTestData(); + } + } + + protected boolean isCleanupTestDataRequired() { + return false; + } + + protected void cleanupTestData() { + inTransaction( + session -> + getMetadata().getEntityBindings().forEach( + entityType -> session.createQuery( "delete from " + entityType.getEntityName() ).executeUpdate() + ) + + ); + } + + protected void inTransaction(Consumer action) { + sessionFactoryScope().inTransaction( action ); + } + + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java new file mode 100644 index 0000000000..20e961e6c3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureCheck.java @@ -0,0 +1,18 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.dialect.Dialect; + + +/** + * @author Andrea Boriero + */ +@FunctionalInterface +public interface DialectFeatureCheck { + boolean apply(Dialect dialect); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java new file mode 100644 index 0000000000..20a8445837 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -0,0 +1,211 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.dialect.Dialect; + + +/** + * Container class for different implementation of the {@link DialectFeatureCheck} interface. + * + * @author Hardy Ferentschik + * @author Steve Ebersole + */ +abstract public class DialectFeatureChecks { + public static class SupportsSequences implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSequences(); + } + } + + public static class SupportsExpectedLobUsagePattern implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExpectedLobUsagePattern(); + } + } + + public static class UsesInputStreamToInsertBlob implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.useInputStreamToInsertBlob(); + } + } + + public static class SupportsIdentityColumns implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getIdentityColumnSupport().supportsIdentityColumns(); + } + } + + public static class SupportsColumnCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsColumnCheck(); + } + } + + public static class SupportsEmptyInListCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsEmptyInList(); + } + } + + public static class CaseSensitiveCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.areStringComparisonsCaseInsensitive(); + } + } + + public static class SupportsResultSetPositioningOnForwardOnlyCursorCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsResultSetPositionQueryMethodsOnForwardOnlyCursor(); + } + } + + public static class SupportsCascadeDeleteCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsCascadeDelete(); + } + } + + public static class SupportsCircularCascadeDeleteCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsCircularCascadeDeleteConstraints(); + } + } + + public static class SupportsUnboundedLobLocatorMaterializationCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExpectedLobUsagePattern() && dialect.supportsUnboundedLobLocatorMaterialization(); + } + } + + public static class SupportSubqueryAsLeftHandSideInPredicate implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsSubselectAsInPredicateLHS(); + } + } + + public static class SupportLimitCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLimit(); + } + } + + public static class SupportLimitAndOffsetCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLimit() && dialect.supportsLimitOffset(); + } + } + + public static class SupportsParametersInInsertSelectCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsParametersInInsertSelect(); + } + } + + public static class HasSelfReferentialForeignKeyBugCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.hasSelfReferentialForeignKeyBug(); + } + } + + public static class SupportsRowValueConstructorSyntaxCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsRowValueConstructorSyntax(); + } + } + + public static class SupportsRowValueConstructorSyntaxInInListCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsRowValueConstructorSyntaxInInList(); + } + } + + public static class DoesReadCommittedCauseWritersToBlockReadersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.doesReadCommittedCauseWritersToBlockReaders(); + } + } + + public static class DoesReadCommittedNotCauseWritersToBlockReadersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return ! dialect.doesReadCommittedCauseWritersToBlockReaders(); + } + } + + public static class DoesRepeatableReadCauseReadersToBlockWritersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.doesRepeatableReadCauseReadersToBlockWriters(); + } + } + + public static class DoesRepeatableReadNotCauseReadersToBlockWritersCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return ! dialect.doesRepeatableReadCauseReadersToBlockWriters(); + } + } + + public static class SupportsExistsInSelectCheck implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsExistsInSelect(); + } + } + + public static class SupportsLobValueChangePropogation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLobValueChangePropogation(); + } + } + + public static class SupportsLockTimeouts implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsLockTimeouts(); + } + } + + public static class DoubleQuoteQuoting implements DialectFeatureCheck { + @Override + public boolean apply(Dialect dialect) { + return '\"' == dialect.openQuote() && '\"' == dialect.closeQuote(); + } + } + + public static class SupportSchemaCreation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.canCreateSchema(); + } + } + + public static class SupportCatalogCreation implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.canCreateCatalog(); + } + } + + public static class DoesNotSupportRowValueConstructorSyntax implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsRowValueConstructorSyntax() == false; + } + } + + public static class DoesNotSupportFollowOnLocking implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return !dialect.useFollowOnLocking( null, null ); + } + } + + public static class SupportPartitionBy implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.supportsPartitionBy(); + } + } + + public static class SupportDropConstraints implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.dropConstraints(); + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java new file mode 100644 index 0000000000..324bddc167 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java @@ -0,0 +1,137 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.List; +import java.util.Locale; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import org.jboss.logging.Logger; + +/** + * JUnit 5 extension used to add {@link RequiresDialect} and {@link SkipForDialect} + * handling + * + * @author Steve Ebersole + */ +public class DialectFilterExtension implements ExecutionCondition { + private static final Logger log = Logger.getLogger( DialectFilterExtension.class ); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if ( !context.getTestInstance().isPresent() ) { + assert !context.getTestMethod().isPresent(); + + return ConditionEvaluationResult.enabled( + "No test-instance was present - " + + "likely that test was not defined with a per-class test lifecycle; " + + "skipping Dialect checks for this context [" + context.getDisplayName() + "]" + ); + } + + final Dialect dialect = getDialect( context ); + if ( dialect == null ) { + throw new RuntimeException( "#getDialect returned null" ); + } + + log.debugf( "Checking Dialect [%s] - context = %s", dialect, context.getDisplayName() ); + + final List effectiveRequiresDialects = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialect.class, + RequiresDialects.class + ); + + if ( !effectiveRequiresDialects.isEmpty() ) { + StringBuilder requiredDialects = new StringBuilder( ); + for ( RequiresDialect requiresDialect : effectiveRequiresDialects ) { + requiredDialects.append(requiresDialect.value() ); + requiredDialects.append( " " ); + if ( requiresDialect.matchSubTypes() ) { + if ( requiresDialect.value().isInstance( dialect ) ) { + return ConditionEvaluationResult.enabled( "Matched @RequiresDialect" ); + } + } + else { + if ( requiresDialect.value().equals( dialect.getClass() ) ) { + return ConditionEvaluationResult.enabled( "Matched @RequiresDialect" ); + } + } + } + + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialect(dialect=%s) check - found %s]", + requiredDialects.toString(), + dialect.getClass().getName() + ) + ); + } + + final List effectiveSkips = TestingUtil.findEffectiveRepeatingAnnotation( + context, + SkipForDialect.class, + SkipForDialectGroup.class + ); + + for ( SkipForDialect effectiveSkipForDialect : effectiveSkips ) { + if ( effectiveSkipForDialect.matchSubTypes() ) { + if ( effectiveSkipForDialect.dialectClass().isInstance( dialect ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect(group)" ); + } + } + else { + if ( effectiveSkipForDialect.dialectClass().equals( dialect.getClass() ) ) { + return ConditionEvaluationResult.disabled( "Matched @SkipForDialect" ); + } + } + } + + List effectiveRequiresDialectFeatures = TestingUtil.findEffectiveRepeatingAnnotation( + context, + RequiresDialectFeature.class, + RequiresDialectFeatureGroup.class + ); + + for ( RequiresDialectFeature effectiveRequiresDialectFeature : effectiveRequiresDialectFeatures ) { + try { + final DialectFeatureCheck dialectFeatureCheck = effectiveRequiresDialectFeature.feature() + .newInstance(); + if ( !dialectFeatureCheck.apply( getDialect( context ) ) ) { + return ConditionEvaluationResult.disabled( + String.format( + Locale.ROOT, + "Failed @RequiresDialectFeature [%s]", + effectiveRequiresDialectFeature.feature() + ) ); + } + } + catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException( "Unable to instantiate DialectFeatureCheck class", e ); + } + } + + return ConditionEvaluationResult.enabled( "Passed all @SkipForDialects" ); + } + + private Dialect getDialect(ExtensionContext context) { + final StandardServiceRegistry serviceRegistry = ServiceRegistryExtension.findServiceRegistry( + context.getRequiredTestInstance(), + context + ); + + return serviceRegistry.getService( JdbcServices.class ).getJdbcEnvironment().getDialect(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java new file mode 100644 index 0000000000..72bfbc1bfc --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModel.java @@ -0,0 +1,96 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @asciidoc + * + * Used to define the test model ({@link org.hibernate.boot.spi.MetadataImplementor}) + * to be used for testing. + * + * Can be used by itself, along with {@link DomainModelScopeAware}, to test the MetadataImplementor. E.g. + * + * [source, JAVA, indent=0] + * ---- + * @TestDomain( ... ) + * class MyTest implements TestDomainAware { + * + * @Test + * public void doTheTest() { + * // use the injected MetadataImplementor + * } + * + * private MetadataImplementor model; + * + * @Override + * public void injectTestModelScope(MetadataImplementor model) { + * this.model = model; + * } + * } + * ---- + * + * + * Can optionally be used with {@link ServiceRegistry} to define the ServiceRegistry used to + * build the MetadataImplementor (passed to + * {@link org.hibernate.boot.MetadataSources#MetadataSources(org.hibernate.service.ServiceRegistry)}). + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry( ... ) + * @TestDomain( ... ) + * class MyTest implements TestDomainAware { + * + * @Test + * public void doTheTest() { + * // use the injected MetadataImplementor + * } + * + * private MetadataImplementor model; + * + * @Override + * public void injectTestModelScope(MetadataImplementor model) { + * this.model = model; + * } + * } + * ---- + * + * It can also be used in conjunction with {@link SessionFactory} + * + * @see DomainModelScopeAware + * + * @author Steve Ebersole + */ +@Inherited +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) +public @interface DomainModel { + StandardDomainModel[] standardModels() default {}; + Class[] modelDescriptorClasses() default {}; + Class[] annotatedClasses() default {}; + String[] annotatedClassNames() default {}; + String[] xmlMappings() default {}; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java new file mode 100644 index 0000000000..252ea0d532 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelExtension.java @@ -0,0 +1,192 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.Optional; + +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; + +import org.hibernate.testing.orm.domain.DomainModelDescriptor; +import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +/** + * @author Steve Ebersole + */ +public class DomainModelExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final String MODEL_KEY = MetadataImplementor.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( ServiceRegistryExtension.class, context, testInstance ); + } + + public static DomainModelScope findMetamodelScope(Object testInstance, ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final DomainModelScope existing = (DomainModelScope) store.get( MODEL_KEY ); + if ( existing != null ) { + return existing; + } + + + final ServiceRegistryScope serviceRegistryScope = ServiceRegistryExtension.findServiceRegistryScope( + testInstance, + context + ); + + final DomainModelProducer modelProducer; + + if ( testInstance instanceof DomainModelProducer ) { + modelProducer = (DomainModelProducer) testInstance; + } + else { + modelProducer = serviceRegistry -> { + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + final Optional testDomainAnnotationWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + DomainModel.class + ); + + if ( !testDomainAnnotationWrapper.isPresent() ) { + throw new RuntimeException( "Could not locate @TestDomain annotation : " + context.getDisplayName() ); + } + + final DomainModel domainModelAnnotation = testDomainAnnotationWrapper.get(); + + final MetadataSources metadataSources = new MetadataSources( serviceRegistry ); + + for ( StandardDomainModel standardDomainModel : domainModelAnnotation.standardModels() ) { + standardDomainModel.getDescriptor().applyDomainModel( metadataSources ); + } + + for ( Class modelDescriptorClass : domainModelAnnotation.modelDescriptorClasses() ) { + try { + final DomainModelDescriptor modelDescriptor = modelDescriptorClass.newInstance(); + modelDescriptor.applyDomainModel( metadataSources ); + } + catch (IllegalAccessException | InstantiationException e) { + throw new RuntimeException( "Error instantiating DomainModelDescriptor - " + modelDescriptorClass.getName(), e ); + } + } + + for ( Class annotatedClass : domainModelAnnotation.annotatedClasses() ) { + metadataSources.addAnnotatedClass( annotatedClass ); + } + + for ( String annotatedClassName : domainModelAnnotation.annotatedClassNames() ) { + metadataSources.addAnnotatedClassName( annotatedClassName ); + } + + for ( String xmlMapping : domainModelAnnotation.xmlMappings() ) { + metadataSources.addResource( xmlMapping ); + } + + return (MetadataImplementor) metadataSources.buildMetadata(); + }; + } + + final DomainModelScopeImpl scope = new DomainModelScopeImpl( serviceRegistryScope, modelProducer ); + + if ( testInstance instanceof DomainModelScopeAware ) { + ( (DomainModelScopeAware) testInstance ).injectTestModelScope( scope ); + } + + locateExtensionStore( testInstance, context ).put( MODEL_KEY, scope ); + + return scope; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + findMetamodelScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context ); + final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.remove( MODEL_KEY ); + + if ( scope != null ) { + scope.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + final ExtensionContext.Store store = locateExtensionStore( context.getRequiredTestInstance(), context ); + final DomainModelScopeImpl scope = (DomainModelScopeImpl) store.get( MODEL_KEY ); + + if ( scope != null ) { + scope.releaseModel(); + } + + throw throwable; + } + + public static class DomainModelScopeImpl implements DomainModelScope, ExtensionContext.Store.CloseableResource { + private final ServiceRegistryScope serviceRegistryScope; + private final DomainModelProducer producer; + + private MetadataImplementor model; + private boolean active = true; + + public DomainModelScopeImpl( + ServiceRegistryScope serviceRegistryScope, + DomainModelProducer producer) { + this.serviceRegistryScope = serviceRegistryScope; + this.producer = producer; + + this.model = createDomainModel(); + } + + private MetadataImplementor createDomainModel() { + verifyActive(); + + final StandardServiceRegistry registry = serviceRegistryScope.getRegistry(); + model = producer.produceModel( registry ); + + return model; + } + + @Override + public MetadataImplementor getDomainModel() { + verifyActive(); + + if ( model == null ) { + model = createDomainModel(); + } + return model; + } + + private void verifyActive() { + if ( !active ) { + throw new RuntimeException( "DomainModelScope no longer active" ); + } + } + + + @Override + public void close() { + active = false; + releaseModel(); + } + + public void releaseModel() { + model = null; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java new file mode 100644 index 0000000000..42f1f19a96 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelFunctionalTesting.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) + +//@ExtendWith( ServiceRegistryExtension.class ) +//@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ServiceRegistryFunctionalTesting + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) +public @interface DomainModelFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java new file mode 100644 index 0000000000..248992f6f8 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelParameterResolver.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.spi.MetadataImplementor; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class DomainModelParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, MetadataImplementor.class, DomainModelScope.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final DomainModelScope modelScope = DomainModelExtension.findMetamodelScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + final Class parameterType = parameterContext.getParameter().getType(); + + if ( parameterType.isAssignableFrom( DomainModelScope.class ) ) { + return modelScope; + } + + if ( parameterType.isAssignableFrom( MetadataImplementor.class ) ) { + return modelScope.getDomainModel(); + } + + throw new IllegalStateException( "Unsupported parameter type : " + parameterType.getName() ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java new file mode 100644 index 0000000000..c85fcdf2bb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelProducer.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataImplementor; + +/** + * @author Steve Ebersole + */ +public interface DomainModelProducer { + MetadataImplementor produceModel(StandardServiceRegistry serviceRegistry); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java new file mode 100644 index 0000000000..34211eddf1 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java @@ -0,0 +1,55 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.Locale; +import java.util.function.Consumer; + +import org.hibernate.UnknownEntityTypeException; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.RootClass; + +/** + * @author Steve Ebersole + */ +public interface DomainModelScope { + MetadataImplementor getDomainModel(); + + default void visitHierarchies(Consumer action) { + getDomainModel().getEntityBindings().forEach( + persistentClass -> { + if ( persistentClass instanceof RootClass ) { + action.accept( (RootClass) persistentClass ); + } + } + ); + } + + default void withHierarchy(Class rootType, Consumer action) { + withHierarchy( rootType.getName(), action ); + } + + default void withHierarchy(String rootTypeName, Consumer action) { + final PersistentClass entityBinding = getDomainModel().getEntityBinding( rootTypeName ); + + if ( entityBinding == null ) { + throw new UnknownEntityTypeException( + String.format( + Locale.ROOT, + "Could not resolve `%s` as an entity type", + rootTypeName + ) + ); + } + + action.accept( entityBinding.getRootClass() ); + } + + + // ... +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java new file mode 100644 index 0000000000..393c6fea06 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScopeAware.java @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface DomainModelScopeAware { + void injectTestModelScope(DomainModelScope modelScope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java new file mode 100644 index 0000000000..302c77edf3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedException.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation that can be used, in conjunction with {@link ExpectedExceptionExtension}, + * to indicate that a specific test is expected to fail in a particular way + * (throw the specified exception) as its "success condition". + * + * @see ExpectedExceptionExtension + * + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@ExtendWith( ExpectedExceptionExtension.class ) +public @interface ExpectedException { + Class value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java new file mode 100644 index 0000000000..8453660b81 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ExpectedExceptionExtension.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; + +import org.jboss.logging.Logger; + +/** + * TestExecutionExceptionHandler used in conjunction with {@link ExpectedException} + * to support annotating tests with a specific exception that indicates a + * success (we are expecting that exception in that tested condition). + * + * @see ExpectedException + * + * @author Steve Ebersole + */ +public class ExpectedExceptionExtension implements TestExecutionExceptionHandler { + private static final Logger log = Logger.getLogger( ExpectedExceptionExtension.class ); + + @Override + public void handleTestExecutionException( + ExtensionContext context, + Throwable throwable) throws Throwable { + final ExpectedException annotation = context.getRequiredTestMethod().getAnnotation( ExpectedException.class ); + if ( annotation != null ) { + if ( annotation.value().isInstance( throwable ) ) { + log.debugf( + "Test [%s] threw exception [%s] which matched @ExpectedException : swallowing exception", + context.getDisplayName(), + throwable + ); + return; + } + } + + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java new file mode 100644 index 0000000000..931e672c1d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpected.java @@ -0,0 +1,55 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hibernate.testing.junit5.StandardTags.FAILURE_EXPECTED; + +/** + * Marks a test method or class as being expected to fail. + * + * @see FailureExpectedExtension + * + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable( FailureExpectedGroup.class ) + +@Tag( FAILURE_EXPECTED ) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface FailureExpected { + /** + * Setting used to indicate that FailureExpected tests should be run and + * that we should validate they still fail. Note that in this "validation + * mode", a test failure is interpreted as a success which is the main + * difference from JUnit's support. + */ + String VALIDATE_FAILURE_EXPECTED = "hibernate.test.validatefailureexpected"; + + /** + * A reason why the failure is expected + */ + String value() default ""; + + /** + * The key of a JIRA issue which covers this expected failure. + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java new file mode 100644 index 0000000000..a7c4045071 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedExtension.java @@ -0,0 +1,164 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.Locale; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * JUnit 5 extension used to support {@link FailureExpected} handling + * + * @author Steve Ebersole + */ +public class FailureExpectedExtension + implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( FailureExpectedExtension.class ); + + private static final String IS_MARKED_STORE_KEY = "IS_MARKED"; + private static final String EXPECTED_FAILURE_STORE_KEY = "EXPECTED_FAILURE"; + + + private static final boolean failureExpectedValidation; + + static { + failureExpectedValidation = Boolean.getBoolean( FailureExpected.VALIDATE_FAILURE_EXPECTED ); + log.debugf( "FailureExpectedExtension#failureExpectedValidation = %s", failureExpectedValidation ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ExecutionCondition + // - used to disable tests that are an `@ExpectedFailure` when + // failureExpectedValidation == false which is the default. + // + // When failureExpectedValidation == true, the test is allowed to + // run and we validate that the test does in fact fail. + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + log.tracef( "#evaluateExecutionCondition(%s)", context.getDisplayName() ); + + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + log.debugf( "Evaluating context - %s [failureExpectedValidation = %s]", context.getDisplayName(), failureExpectedValidation ); + + if ( AnnotationSupport.findAnnotation( context.getElement().get(), FailureExpected.class ).isPresent() ) { + // The test is marked as `FailureExpected`... + if ( failureExpectedValidation ) { + log.debugf( "Executing test marked with `@FailureExpected` for validation" ); + return ConditionEvaluationResult.enabled( "@ExpectedFailure validation" ); + } + else { + return ConditionEvaluationResult.disabled( "Disabled : @ExpectedFailure" ); + } + } + + return ConditionEvaluationResult.enabled( "No @ExpectedFailure" ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // BeforeEachCallback + // - used to determine whether a test is considered as an expected + // failure. If so, + + @Override + public void beforeEach(ExtensionContext context) { + log.tracef( "#beforeEach(%s)", context.getDisplayName() ); + + final boolean markedExpectedFailure = TestingUtil.hasEffectiveAnnotation( context, FailureExpected.class ); + + log.debugf( "Checking for @FailureExpected [%s] - %s", context.getDisplayName(), markedExpectedFailure ); + + final ExtensionContext.Namespace namespace = generateNamespace( context ); + context.getStore( namespace ).put( IS_MARKED_STORE_KEY, markedExpectedFailure ); + } + + private ExtensionContext.Namespace generateNamespace(ExtensionContext context) { + return ExtensionContext.Namespace.create( + getClass().getName(), + context.getRequiredTestMethod().getClass(), + context.getRequiredTestMethod().getName() + ); + } + + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // AfterEachCallback - used to interpret the outcome of the test depending + // on whether it was marked as an `@ExpectedFailure` + + + + @Override + public void afterEach(ExtensionContext context) { + log.tracef( "#afterEach(%s)", context.getDisplayName() ); + + final ExtensionContext.Store store = context.getStore( generateNamespace( context ) ); + + final Boolean isMarked = (Boolean) store.remove( IS_MARKED_STORE_KEY ); + log.debugf( "Post-handling for @FailureExpected [%s] - %s", context.getDisplayName(), isMarked ); + + if ( isMarked == Boolean.TRUE ) { + final Throwable expectedFailure = (Throwable) store.remove( EXPECTED_FAILURE_STORE_KEY ); + log.debugf( " >> Captured exception - %s", expectedFailure ); + + if ( expectedFailure == null ) { + // even though we expected a failure, the test did not fail + throw new ExpectedFailureDidNotFail( context ); + } + } + } + + private static class ExpectedFailureDidNotFail extends RuntimeException { + ExpectedFailureDidNotFail(ExtensionContext context) { + super( + String.format( + Locale.ROOT, + "`%s#%s` was marked as `@ExpectedFailure`, but did not fail", + context.getRequiredTestClass().getName(), + context.getRequiredTestMethod().getName() + ) + ); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable.getClass().getName() ); + + final ExtensionContext.Store store = context.getStore( generateNamespace( context ) ); + + final Boolean isMarked = (Boolean) store.get( IS_MARKED_STORE_KEY ); + log.debugf( "Handling test exception [%s]; marked @FailureExcepted = %s", context.getDisplayName(), isMarked ); + + if ( isMarked == Boolean.TRUE ) { + // test is marked as an `@ExpectedFailure`: + + // 1) add the exception to the store + store.put( EXPECTED_FAILURE_STORE_KEY, throwable ); + log.debugf( " >> Stored expected failure - %s", throwable ); + + // 2) eat the failure + return; + } + + // otherwise, re-throw + throw throwable; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java new file mode 100644 index 0000000000..73e5536e3e --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/FailureExpectedGroup.java @@ -0,0 +1,32 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hibernate.testing.junit5.StandardTags.FAILURE_EXPECTED; + +/** + * @author Steve Ebersole + */ +@Inherited +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) + +@Tag( FAILURE_EXPECTED ) + +@ExtendWith( FailureExpectedExtension.class ) +public @interface FailureExpectedGroup { + FailureExpected[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/InjectedSessionFactoryFunctionalTest.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/InjectedSessionFactoryFunctionalTest.java new file mode 100644 index 0000000000..d263f7cd55 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/InjectedSessionFactoryFunctionalTest.java @@ -0,0 +1,16 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +/** + * Support for tests that wish to have the SessionFactory fixture + * injected as an instance variable for use. + * + * @author Steve Ebersole + */ +public abstract class InjectedSessionFactoryFunctionalTest { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java new file mode 100644 index 0000000000..ec885ea18b --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JUnitHelper.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.Internal; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; + +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.create; + +/** + * @author Steve Ebersole + */ +@Internal +@SuppressWarnings("WeakerAccess") +public class JUnitHelper { + public static ExtensionContext.Store locateExtensionStore( + Class extensionClass, + ExtensionContext context, + Object testInstance) { + return context.getStore( create( extensionClass.getName(), testInstance ) ); + } + + private JUnitHelper() { + } + + public static boolean supportsParameterInjection(ParameterContext parameterContext, Class... supportedTypes) { + for ( Class supportedType : supportedTypes ) { + if ( parameterContext.getParameter().getType().isAssignableFrom( supportedType ) ) { + return true; + } + } + + return false; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java new file mode 100644 index 0000000000..01a495a6ae --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKey.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Identifies the JIRA issue associated with a test. Is repeatable, so + * multiple JIRA issues can be indicated. + * + * @see JiraKeyGroup + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) +@Repeatable( JiraKeyGroup.class ) +public @interface JiraKey { + /** + * The key for the referenced Jira issue (e.g., HHH-99999) + */ + String value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java new file mode 100644 index 0000000000..85106fb857 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/JiraKeyGroup.java @@ -0,0 +1,25 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Grouping annotation for `@JiraKey` + * + * @see JiraKey + * + * @author Steve Ebersole + */ +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface JiraKeyGroup { + JiraKey[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java new file mode 100644 index 0000000000..9a2551f8f7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialect.java @@ -0,0 +1,42 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Indicates that the annotated test class/method should only + * be run when the indicated Dialect is being used. + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( RequiresDialects.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialect { + /** + * The Dialect class to match. + */ + Class value(); + + /** + * Should subtypes of {@link #value()} be matched? + */ + boolean matchSubTypes() default true; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java new file mode 100644 index 0000000000..d0476a032f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeature.java @@ -0,0 +1,49 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation used to indicate that a test should be run only when the current dialect supports the + * specified feature. + * + * @author Andrea Boriero + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( RequiresDialectFeatureGroup.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialectFeature { + /** + * @return Class which checks the necessary dialect feature + */ + Class feature(); + + /** + * Comment describing the reason why the feature is required. + * + * @return The comment + */ + String comment() default ""; + + /** + * The key of a JIRA issue which relates this this feature requirement. + * + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java new file mode 100644 index 0000000000..425be3e677 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialectFeatureGroup.java @@ -0,0 +1,37 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Annotation used to indicate that a test should be run only when the current dialect supports the + * specified feature. + * + * @author Hardy Ferentschik + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialectFeatureGroup { + RequiresDialectFeature[] value(); + + /** + * The key of a JIRA issue which relates this this feature requirement. + * + * @return The jira issue key + */ + String jiraKey() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java new file mode 100644 index 0000000000..263c505bfd --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/RequiresDialects.java @@ -0,0 +1,28 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Andrea Boriero + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) + +@ExtendWith( DialectFilterExtension.class ) +public @interface RequiresDialects { + RequiresDialect[] value(); +} + diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java new file mode 100644 index 0000000000..1a2ab737fa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistry.java @@ -0,0 +1,92 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.service.spi.ServiceContributor; + +/** + * @asciidoc + * + * Used to define the ServiceRegistry to be used for testing. Can be used alone: + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry( ... ) + * class MyTest extends ServiceRegistryAware { + * @Test + * public void doTheTest() { + * // use the injected registry... + * + * ... + * } + * + * private StandardServiceRegistry registry; + * + * @Override + * public void injectServiceRegistryScope(StandardServiceRegistry registry) { + * this.registry = registry; + * } + * } + * ---- + * + * It can also be used as the basis for building a + * {@link org.hibernate.boot.spi.MetadataImplementor} via {@link DomainModel} + * or {@link SessionFactoryImplementor} via {@link SessionFactory}, + * with or without {@link ServiceRegistryScopeAware}. E.g. + * + * [source, JAVA, indent=0] + * ---- + * @ServiceRegistry( ... ) + * @TestDomain( ... ) + * class MyTest ... { + * } + * ---- + * + * Here, the managed ServiceRegistry is used to create the + * {@link org.hibernate.boot.spi.MetadataImplementor} + * + * @see ServiceRegistryScopeAware + * + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@ServiceRegistryFunctionalTesting +//@TestInstance( TestInstance.Lifecycle.PER_CLASS ) +// +//@ExtendWith( FailureExpectedExtension.class ) +//@ExtendWith( ServiceRegistryExtension.class ) +//@ExtendWith( ServiceRegistryParameterResolver.class ) +public @interface ServiceRegistry { + Class[] serviceContributors() default {}; + + Class[] initiators() default {}; + + Service[] services() default {}; + + Setting[] settings() default {}; + + @interface Service { + Class role(); + Class impl(); + } + + @interface Setting { + String name(); + String value(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java new file mode 100644 index 0000000000..1a1f31752f --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryExtension.java @@ -0,0 +1,224 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.Optional; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.service.spi.ServiceContributor; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * JUnit extension used to manage the StandardServiceRegistry used by a test including + * creating the StandardServiceRegistry and releasing it afterwards + * + * @author Steve Ebersole + */ +public class ServiceRegistryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + private static final Logger log = Logger.getLogger( ServiceRegistryExtension.class ); + private static final String REGISTRY_KEY = ServiceRegistryScope.class.getName(); + + @SuppressWarnings("WeakerAccess") + public static StandardServiceRegistry findServiceRegistry( + Object testInstance, + ExtensionContext context) { + return findServiceRegistryScope( testInstance, context ).getRegistry(); + } + + private static ExtensionContext.Store locateExtensionStore( + Object testInstance, + ExtensionContext context) { + return JUnitHelper.locateExtensionStore( ServiceRegistryExtension.class, context, testInstance ); + } + + @SuppressWarnings("WeakerAccess") + public static ServiceRegistryScope findServiceRegistryScope(Object testInstance, ExtensionContext context) { + log.tracef( "#findServiceRegistryScope(%s, %s)", testInstance, context.getDisplayName() ); + + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + + ServiceRegistryScopeImpl scope = (ServiceRegistryScopeImpl) store.get( REGISTRY_KEY ); + + if ( scope == null ) { + log.debugf( "Creating ServiceRegistryScope - %s", context.getDisplayName() ); + + final ServiceRegistryProducer producer; + + if ( testInstance instanceof ServiceRegistryProducer ) { + producer = (ServiceRegistryProducer) testInstance; + } + else { + producer = ssrb -> { + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + final Optional serviceRegistryAnnWrapper = AnnotationSupport.findAnnotation( + context.getElement().get(), + ServiceRegistry.class + ); + + if ( serviceRegistryAnnWrapper.isPresent() ) { + final ServiceRegistry serviceRegistryAnn = serviceRegistryAnnWrapper.get(); + configureServices( serviceRegistryAnn, ssrb ); + } + + return ssrb.build(); + }; + } + + scope = new ServiceRegistryScopeImpl( producer ); + + locateExtensionStore( testInstance, context ).put( REGISTRY_KEY, scope ); + + if ( testInstance instanceof ServiceRegistryScopeAware ) { + ( (ServiceRegistryScopeAware) testInstance ).injectServiceRegistryScope( scope ); + } + } + + return scope; + } + + private static void configureServices(ServiceRegistry serviceRegistryAnn, StandardServiceRegistryBuilder ssrb) { + try { + for ( Class contributorClass : serviceRegistryAnn.serviceContributors() ) { + final ServiceContributor serviceContributor = contributorClass.newInstance(); + serviceContributor.contribute( ssrb ); + } + + for ( Class initiatorClass : serviceRegistryAnn.initiators() ) { + ssrb.addInitiator( initiatorClass.newInstance() ); + } + + for ( ServiceRegistry.Service service : serviceRegistryAnn.services() ) { + ssrb.addService( service.role(), service.impl().newInstance() ); + } + } + catch (Exception e) { + throw new RuntimeException( "Could not configure StandardServiceRegistryBuilder", e ); + } + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + + findServiceRegistryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof ServiceRegistryScopeAware ) { + ( (ServiceRegistryScopeAware) testInstance ).injectServiceRegistryScope( null ); + } + + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final ServiceRegistryScopeImpl scope = (ServiceRegistryScopeImpl) store.remove( REGISTRY_KEY ); + if ( scope != null ) { + scope.close(); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final ServiceRegistryScopeImpl scope = (ServiceRegistryScopeImpl) store.get( REGISTRY_KEY ); + scope.releaseRegistry(); + + throw throwable; + } + + private static class ServiceRegistryScopeImpl implements ServiceRegistryScope, ExtensionContext.Store.CloseableResource { + private final ServiceRegistryProducer producer; + + private StandardServiceRegistry registry; + private boolean active = true; + + public ServiceRegistryScopeImpl(ServiceRegistryProducer producer) { + this.producer = producer; + + this.registry = createRegistry(); + } + + private StandardServiceRegistry createRegistry() { + verifyActive(); + + final StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder(); + // we will close it ourselves explicitly. + ssrb.disableAutoClose(); + + return producer.produceServiceRegistry( ssrb ); + } + + private void verifyActive() { + if ( !active ) { + throw new IllegalStateException( "ServiceRegistryScope no longer active" ); + } + } + + @Override + public StandardServiceRegistry getRegistry() { + verifyActive(); + + if ( registry == null ) { + registry = createRegistry(); + } + + return registry; + } + + @Override + public void close() { + if ( ! active ) { + return; + } + + log.debugf( "Closing ServiceRegistryScope" ); + + active = false; + + if ( registry != null ) { + releaseRegistry(); + registry = null; + } + } + + private void releaseRegistry() { + if ( registry == null ) { + return; + } + + try { + log.tracef( "#releaseRegistry" ); + StandardServiceRegistryBuilder.destroy( registry ); + } + catch (Exception e) { + log.warn( "Unable to release StandardServiceRegistry", e ); + } + finally { + registry = null; + } + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java new file mode 100644 index 0000000000..65ebd558ce --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryFunctionalTesting.java @@ -0,0 +1,34 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for applying extensions needed for managing + * a StandardServiceRegistry as part of the test lifecycle. + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) +public @interface ServiceRegistryFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java new file mode 100644 index 0000000000..9a833928c5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryParameterResolver.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.StandardServiceRegistry; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class ServiceRegistryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( + parameterContext, + StandardServiceRegistry.class, + ServiceRegistryScope.class + ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + final ServiceRegistryScope scope = ServiceRegistryExtension.findServiceRegistryScope( + extensionContext.getRequiredTestInstance(), + extensionContext + ); + + final Class paramType = parameterContext.getParameter().getType(); + if ( paramType.isAssignableFrom( ServiceRegistryScope.class ) ) { + return scope; + } + else if ( paramType.isAssignableFrom( StandardServiceRegistry.class ) ) { + return scope.getRegistry(); + } + + throw new IllegalStateException( + "Unexpected parameter type [" + paramType.getName() + "] for service-registry injection" + ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java new file mode 100644 index 0000000000..6bacd85ea7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryProducer.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryProducer { + StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder builder); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java new file mode 100644 index 0000000000..8c09acdffa --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScope.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.service.Service; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryScope { + StandardServiceRegistry getRegistry(); + + default void withService(Class role, Consumer action) { + assert role != null; + + final S service = getRegistry().getService( role ); + + if ( service == null ) { + throw new IllegalArgumentException( "Could not locate requested service - " + role.getName() ); + } + + action.accept( service ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java new file mode 100644 index 0000000000..a0ae2ec4c7 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/ServiceRegistryScopeAware.java @@ -0,0 +1,14 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface ServiceRegistryScopeAware { + void injectServiceRegistryScope(ServiceRegistryScope registryScope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java new file mode 100644 index 0000000000..9e73795f25 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactory.java @@ -0,0 +1,49 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.Interceptor; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * @author Steve Ebersole + */ +@Inherited +@Target( ElementType.TYPE ) +@Retention( RetentionPolicy.RUNTIME ) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@ExtendWith( FailureExpectedExtension.class ) +@ExtendWith( ServiceRegistryExtension.class ) +@ExtendWith( ServiceRegistryParameterResolver.class ) + +@ExtendWith( DomainModelExtension.class ) +@ExtendWith( DomainModelParameterResolver.class ) + +@ExtendWith( SessionFactoryExtension.class ) +@ExtendWith( SessionFactoryParameterResolver.class ) +@ExtendWith( SessionFactoryScopeParameterResolver.class ) +@ExtendWith( DialectFilterExtension.class ) +public @interface SessionFactory { + String sessionFactoryName() default ""; + + boolean generateStatistics() default false; + + Class interceptorClass() default Interceptor.class; + + Class statementInspectorClass() default StatementInspector.class; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java new file mode 100644 index 0000000000..56aa92d1a5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java @@ -0,0 +1,347 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.Interceptor; +import org.hibernate.Transaction; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.resource.jdbc.spi.StatementInspector; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestExecutionExceptionHandler; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; + +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public class SessionFactoryExtension + implements TestInstancePostProcessor, AfterAllCallback, TestExecutionExceptionHandler { + + private static final Logger log = Logger.getLogger( SessionFactoryExtension.class ); + private static final String SESSION_FACTORY_KEY = SessionFactoryScope.class.getName(); + + private static ExtensionContext.Store locateExtensionStore(Object testInstance, ExtensionContext context) { + return JUnitHelper.locateExtensionStore( SessionFactoryExtension.class, context, testInstance ); + } + + @SuppressWarnings("WeakerAccess") + public static SessionFactoryScope findSessionFactoryScope(Object testInstance, ExtensionContext context) { + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final SessionFactoryScope existing = (SessionFactoryScope) store.get( SESSION_FACTORY_KEY ); + if ( existing != null ) { + return existing; + } + + SessionFactoryProducer producer = null; + + if ( testInstance instanceof SessionFactoryProducer ) { + producer = (SessionFactoryProducer) testInstance; + } + else { + if ( !context.getElement().isPresent() ) { + throw new RuntimeException( "Unable to determine how to handle given ExtensionContext : " + context.getDisplayName() ); + } + + final Optional sfAnnWrappper = AnnotationSupport.findAnnotation( + context.getElement().get(), + SessionFactory.class + ); + + if ( sfAnnWrappper.isPresent() ) { + final SessionFactory sessionFactoryConfig = sfAnnWrappper.get(); + + producer = model -> { + try { + final SessionFactoryBuilder sessionFactoryBuilder = model.getSessionFactoryBuilder(); + + if ( StringHelper.isNotEmpty( sessionFactoryConfig.sessionFactoryName() ) ) { + sessionFactoryBuilder.applyName( sessionFactoryConfig.sessionFactoryName() ); + } + + sessionFactoryBuilder.applyStatisticsSupport( sessionFactoryConfig.generateStatistics() ); + + if ( ! sessionFactoryConfig.interceptorClass().equals( Interceptor.class ) ) { + sessionFactoryBuilder.applyInterceptor( sessionFactoryConfig.interceptorClass().newInstance() ); + } + + if ( ! sessionFactoryConfig.statementInspectorClass().equals( StatementInspector.class ) ) { + sessionFactoryBuilder.applyStatementInspector( + sessionFactoryConfig.statementInspectorClass().newInstance() + ); + } + + return (SessionFactoryImplementor) sessionFactoryBuilder.build(); + } + catch (Exception e) { + throw new RuntimeException( "Could not build SessionFactory", e ); + } + }; + } + } + + if ( producer == null ) { + throw new IllegalStateException( "Could not determine SessionFactory producer" ); + } + + + final SessionFactoryScopeImpl sfScope = new SessionFactoryScopeImpl( + DomainModelExtension.findMetamodelScope( testInstance, context ), + producer + ); + + locateExtensionStore( testInstance, context ).put( SESSION_FACTORY_KEY, sfScope ); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( sfScope ); + } + + return sfScope; + } + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + log.tracef( "#postProcessTestInstance(%s, %s)", testInstance, context.getDisplayName() ); + + findSessionFactoryScope( testInstance, context ); + } + + @Override + public void afterAll(ExtensionContext context) { + log.tracef( "#afterAll(%s)", context.getDisplayName() ); + + final Object testInstance = context.getRequiredTestInstance(); + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + + final SessionFactoryScopeImpl removed = (SessionFactoryScopeImpl) locateExtensionStore( testInstance, context ).remove( SESSION_FACTORY_KEY ); + if ( removed != null ) { + removed.close(); + } + + if ( testInstance instanceof SessionFactoryScopeAware ) { + ( (SessionFactoryScopeAware) testInstance ).injectSessionFactoryScope( null ); + } + } + + @Override + public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { + log.tracef( "#handleTestExecutionException(%s, %s)", context.getDisplayName(), throwable ); + + try { + final Object testInstance = context.getRequiredTestInstance(); + final ExtensionContext.Store store = locateExtensionStore( testInstance, context ); + final SessionFactoryScopeImpl scope = (SessionFactoryScopeImpl) store.get( SESSION_FACTORY_KEY ); + scope.releaseSessionFactory(); + } + catch (Exception ignore) { + } + + throw throwable; + } + + private static class SessionFactoryScopeImpl implements SessionFactoryScope, ExtensionContext.Store.CloseableResource { + private final DomainModelScope modelScope; + private final SessionFactoryProducer producer; + + private SessionFactoryImplementor sessionFactory; + private boolean active = true; + + private SessionFactoryScopeImpl( + DomainModelScope modelScope, + SessionFactoryProducer producer) { + this.modelScope = modelScope; + this.producer = producer; + + this.sessionFactory = createSessionFactory(); + } + + @Override + public void close() { + if ( ! active ) { + return; + } + + log.debug( "Closing SessionFactoryScope" ); + + active = false; + releaseSessionFactory(); + } + + public void releaseSessionFactory() { + if ( sessionFactory != null ) { + log.debug( "Releasing SessionFactory" ); + + try { + sessionFactory.close(); + } + catch (Exception e) { + log.warn( "Error closing SF", e ); + } + finally { + sessionFactory = null; + } + } + } + + @Override + public SessionFactoryImplementor getSessionFactory() { + if ( sessionFactory == null ) { + sessionFactory = createSessionFactory(); + } + + return sessionFactory; + } + + private SessionFactoryImplementor createSessionFactory() { + if ( ! active ) { + throw new IllegalStateException( "SessionFactoryScope is no longer active" ); + } + + log.debug( "Creating SessionFactory" ); + + return producer.produceSessionFactory( modelScope.getDomainModel() ); + } + + public void inSession(Consumer action) { + log.trace( "#inSession(Consumer)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + action.accept( session ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public T fromSession(Function action) { + log.trace( "#fromSession(Function)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + return action.apply( session ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public void inTransaction(Consumer action) { + log.trace( "#inTransaction(Consumer)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + inTransaction( session, action ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public T fromTransaction(Function action) { + log.trace( "#fromTransaction(Function)" ); + + try (SessionImplementor session = (SessionImplementor) getSessionFactory().openSession()) { + log.trace( "Session opened, calling action" ); + return fromTransaction( session, action ); + } + finally { + log.trace( "Session close - auto-close block" ); + } + } + + @Override + public void inTransaction(SessionImplementor session, Consumer action) { + log.trace( "inTransaction(Session,Consumer)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + try { + log.trace( "Calling action in txn" ); + action.accept( session ); + log.trace( "Called action - in txn" ); + + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + } + + @Override + public T fromTransaction(SessionImplementor session, Function action) { + log.trace( "fromTransaction(Session,Function)" ); + + final Transaction txn = session.beginTransaction(); + log.trace( "Started transaction" ); + + try { + log.trace( "Calling action in txn" ); + final T result = action.apply( session ); + log.trace( "Called action - in txn" ); + + log.trace( "Committing transaction" ); + txn.commit(); + log.trace( "Committed transaction" ); + + return result; + } + catch (Exception e) { + log.tracef( + "Error calling action: %s (%s) - rolling back", + e.getClass().getName(), + e.getMessage() + ); + try { + txn.rollback(); + } + catch (Exception ignore) { + log.trace( "Was unable to roll back transaction" ); + // really nothing else we can do here - the attempt to + // rollback already failed and there is nothing else + // to clean up. + } + + throw e; + } + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java new file mode 100644 index 0000000000..d29580da53 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryFunctionalTesting.java @@ -0,0 +1,44 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Composite annotation for functional tests that + * require a functioning SessionFactory. + * + * @apiNote Logically this should also include + * `@TestInstance( TestInstance.Lifecycle.PER_CLASS )` + * but that annotation is not conveyed (is that the + * right word? its not applied to the thing using this annotation). + * Test classes should apply that themselves. + * + * @see SessionFactoryExtension + * @see DialectFilterExtension + * @see FailureExpectedExtension + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) + +@DomainModelFunctionalTesting + +@ExtendWith(SessionFactoryExtension.class ) +public @interface SessionFactoryFunctionalTesting { +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java new file mode 100644 index 0000000000..6ee6463661 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryParameterResolver.java @@ -0,0 +1,33 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.engine.spi.SessionFactoryImplementor; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class SessionFactoryParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, SessionFactoryImplementor.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return SessionFactoryExtension.findSessionFactoryScope( extensionContext.getRequiredTestInstance(), extensionContext ).getSessionFactory(); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java new file mode 100644 index 0000000000..9a6d16c8cf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryProducer.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.engine.spi.SessionFactoryImplementor; + +/** + * Contract for something that can build a SessionFactory. + * + * Used by SessionFactoryScopeExtension to create the + * SessionFactoryScope. + * + * Generally speaking, a test class would implement SessionFactoryScopeContainer + * and return the SessionFactoryProducer to be used for those tests. + * The SessionFactoryProducer is then used to build the SessionFactoryScope + * which is injected back into the SessionFactoryScopeContainer + * + * @see SessionFactoryExtension + * @see SessionFactoryScope + * + * @author Steve Ebersole + */ +public interface SessionFactoryProducer { + SessionFactoryImplementor produceSessionFactory(MetadataImplementor model); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java new file mode 100644 index 0000000000..249c4003bb --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.util.function.Consumer; +import java.util.function.Function; + +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; + +/** + * @author Steve Ebersole + */ +public interface SessionFactoryScope { + SessionFactoryImplementor getSessionFactory(); + + void inSession(Consumer action); + + void inTransaction(Consumer action); + void inTransaction(SessionImplementor session, Consumer action); + + T fromSession(Function action); + + T fromTransaction(Function action); + T fromTransaction(SessionImplementor session, Function action); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java new file mode 100644 index 0000000000..6ac1dad4a3 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeAware.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +/** + * @author Steve Ebersole + */ +public interface SessionFactoryScopeAware { + /** + * Callback to inject the SessionFactoryScope into the container + */ + void injectSessionFactoryScope(SessionFactoryScope scope); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java new file mode 100644 index 0000000000..c7f4bd2486 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScopeParameterResolver.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; + +/** + * @author Steve Ebersole + */ +public class SessionFactoryScopeParameterResolver implements ParameterResolver { + @Override + public boolean supportsParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return JUnitHelper.supportsParameterInjection( parameterContext, SessionFactoryScope.class ); + } + + @Override + public Object resolveParameter( + ParameterContext parameterContext, + ExtensionContext extensionContext) throws ParameterResolutionException { + return SessionFactoryExtension.findSessionFactoryScope( extensionContext.getRequiredTestInstance(), extensionContext ); + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java new file mode 100644 index 0000000000..781ccc093d --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialect.java @@ -0,0 +1,40 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.hibernate.dialect.Dialect; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Indicates that the annotated test class/method should be skipped + * when the indicated Dialect is being used. + * + * It is a repeatable annotation + * + * @see SkipForDialectGroup + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Repeatable( SkipForDialectGroup.class ) + +@ExtendWith( DialectFilterExtension.class ) +public @interface SkipForDialect { + Class dialectClass(); + boolean matchSubTypes() default false; + String reason() default ""; +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java new file mode 100644 index 0000000000..9de623d1bf --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SkipForDialectGroup.java @@ -0,0 +1,29 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * Grouping annotation for {@link SkipForDialect} + * + * @author Steve Ebersole + */ +@Inherited +@Retention( RetentionPolicy.RUNTIME ) +@Target({ElementType.TYPE, ElementType.METHOD}) + +@ExtendWith( DialectFilterExtension.class ) +public @interface SkipForDialectGroup { + SkipForDialect[] value(); +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java new file mode 100644 index 0000000000..fd464d82ba --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/TestingUtil.java @@ -0,0 +1,99 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.testing.orm.junit; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +public class TestingUtil { + + private TestingUtil() { + } + + public static Optional findEffectiveAnnotation( + ExtensionContext context, + Class annotationType) { + if ( !context.getElement().isPresent() ) { + return Optional.empty(); + } + + final AnnotatedElement annotatedElement = context.getElement().get(); + + final Optional direct = AnnotationSupport.findAnnotation( annotatedElement, annotationType ); + if ( direct.isPresent() ) { + return direct; + } + + if ( context.getTestInstance().isPresent() ) { + return AnnotationSupport.findAnnotation( context.getRequiredTestInstance().getClass(), annotationType ); + } + + return Optional.empty(); + } + + public static List findEffectiveRepeatingAnnotation( + ExtensionContext context, + Class annotationType, + Class groupAnnotationType) { + if ( !context.getElement().isPresent() ) { + return Collections.emptyList(); + } + + final Optional effectiveAnnotation = findEffectiveAnnotation( context, annotationType ); + final Optional effectiveGroupingAnnotation = findEffectiveAnnotation( + context, + groupAnnotationType + ); + + if ( effectiveAnnotation.isPresent() || effectiveGroupingAnnotation.isPresent() ) { + if ( !effectiveGroupingAnnotation.isPresent() ) { + return Collections.singletonList( effectiveAnnotation.get() ); + } + + final List list = new ArrayList<>(); + effectiveAnnotation.ifPresent( list::add ); + + final Method valueMethod; + try { + valueMethod = groupAnnotationType.getDeclaredMethod( "value", null ); + + Collections.addAll( list, (A[]) valueMethod.invoke( effectiveGroupingAnnotation.get() ) ); + } + catch (Exception e) { + throw new RuntimeException( "Could not locate repeated/grouped annotations", e ); + } + + return list; + } + + return Collections.emptyList(); + } + + public static boolean hasEffectiveAnnotation(ExtensionContext context, Class annotationType) { + return findEffectiveAnnotation( context, annotationType ).isPresent(); + } + + @SuppressWarnings("unchecked") + public static T cast(Object thing, Class type) { + assertThat( thing, instanceOf( type ) ); + return type.cast( thing ); + } +}