HHH-14968 - Support for auto-enabled filters

This commit is contained in:
Gregorio Palamà 2024-01-23 11:55:21 +01:00 committed by Steve Ebersole
parent a5e276571c
commit 527beb0bdb
17 changed files with 409 additions and 17 deletions

View File

@ -508,7 +508,7 @@ include::{extrasdir}/pc-filter-persistence-example.sql[]
----
====
By default, without explicitly enabling the filter, Hibernate is going to fetch all `Account` entities.
By default, without enabling the filter, Hibernate is going to fetch all `Account` entities.
[[pc-no-filter-entity-query-example]]
.Query entities mapped without activating the `@Filter`
@ -526,6 +526,8 @@ include::{extrasdir}/pc-no-filter-entity-query-example.sql[]
If the filter is enabled and the filter parameter value is provided,
then Hibernate is going to apply the filtering criteria to the associated `Account` entities.
The filter can be enabled explicitly on the session or by specifying
that it will be enabled by default directly on its `@FilterDef`.
[[pc-filter-entity-query-example]]
.Query entities mapped with `@Filter`
@ -534,6 +536,10 @@ then Hibernate is going to apply the filtering criteria to the associated `Accou
----
include::{example-dir-pc}/FilterTest.java[tags=pc-filter-entity-query-example]
----
[source, JAVA, indent=0]
----
include::{example-dir-pc}/FilterTest.java[tags=pc-filter-auto-enabled-Account-example]
----
[source, SQL, indent=0]
----
@ -541,6 +547,15 @@ include::{extrasdir}/pc-filter-entity-query-example.sql[]
----
====
A parameter's value can be explicitly set on the filter itself, or can be
resolved by using a custom `Supplier`. The resolver must implement
the interface `java.util.function.Supplier` and must be defined as a managed bean.
[source, JAVA, indent=0]
----
include::{example-dir-pc}/FilterTest.java[tags=pc-filter-resolver-Account-example]
----
[IMPORTANT]
====
Filters apply to entity queries, but not to direct fetching.
@ -562,7 +577,8 @@ include::{extrasdir}/pc-filter-entity-example.sql[]
As you can see from the example above, contrary to an entity query, the filter does not prevent the entity from being loaded.
====
Just like with entity queries, collections can be filtered as well, but only if the filter is explicitly enabled on the currently running Hibernate `Session`.
Just like with entity queries, collections can be filtered as well, but only if the filter is enabled on the currently running Hibernate `Session`,
either if the filter is enabled explicitly or by setting `autoEnabled` to `true`.
[[pc-no-filter-collection-query-example]]
.Traversing collections without activating the `@Filter`

View File

@ -87,4 +87,12 @@ public interface Filter {
* @throws HibernateException If the state is not currently valid.
*/
void validate() throws HibernateException;
/**
* Get the associated {@link FilterDefinition autoEnabled} of this
* named filter.
*
* @return The flag value
*/
boolean isAutoEnabled();
}

View File

@ -90,4 +90,9 @@ public @interface FilterDef {
* The names and types of the parameters of the filter.
*/
ParamDef[] parameters() default {};
/**
* The flag used to auto-enable the filter on the session.
*/
boolean autoEnabled() default false;
}

View File

@ -8,6 +8,7 @@ package org.hibernate.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.function.Supplier;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -52,4 +53,14 @@ public @interface ParamDef {
* </ul>
*/
Class<?> type();
/**
* The resolver to use when retrieving the parameter value.
* <p>
* The parameter value can either be defined using the {@link org.hibernate.Filter setParameter}
* method or by providing a resolver, that will be executed to retrieve the value.
* <p>
* The supplied Class must implement {@link Supplier}
*/
Class<? extends Supplier> resolver() default Supplier.class;
}

View File

@ -6,6 +6,9 @@
*/
package org.hibernate.binder.internal;
import java.util.Collections;
import java.util.function.Supplier;
import org.hibernate.MappingException;
import org.hibernate.annotations.TenantId;
import org.hibernate.binder.AttributeBinder;
@ -54,7 +57,9 @@ public class TenantIdBinder implements AttributeBinder<TenantId> {
new FilterDefinition(
FILTER_NAME,
"",
singletonMap( PARAMETER_NAME, tenantIdType )
singletonMap( PARAMETER_NAME, tenantIdType ),
Collections.emptyMap(),
true
)
);
}

View File

@ -38,6 +38,7 @@ import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Supplier;
import static java.util.Collections.emptyMap;
import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation;
@ -57,12 +58,16 @@ class FilterDefBinder {
if ( context.getMetadataCollector().getFilterDefinition( name ) != null ) {
throw new AnnotationException( "Multiple '@FilterDef' annotations define a filter named '" + name + "'" );
}
final Map<String, JdbcMapping> explicitParamJaMappings;
final Map<String, ManagedBean<? extends Supplier>> parameterResolvers;
if ( filterDef.parameters().length == 0 ) {
explicitParamJaMappings = emptyMap();
parameterResolvers = emptyMap();
}
else {
explicitParamJaMappings = new HashMap<>();
parameterResolvers = new HashMap<>();
for ( ParamDef paramDef : filterDef.parameters() ) {
final JdbcMapping jdbcMapping = resolveFilterParamType( paramDef.type(), context );
if ( jdbcMapping == null ) {
@ -76,14 +81,31 @@ class FilterDefBinder {
);
}
explicitParamJaMappings.put( paramDef.name(), jdbcMapping );
if ( paramDef.resolver() != Supplier.class ) {
parameterResolvers.put( paramDef.name(), resolveParamResolver( paramDef, context ) );
}
}
final FilterDefinition filterDefinition =
new FilterDefinition( name, filterDef.defaultCondition(), explicitParamJaMappings );
}
final FilterDefinition filterDefinition = new FilterDefinition(
name,
filterDef.defaultCondition(),
explicitParamJaMappings,
parameterResolvers,
filterDef.autoEnabled()
);
LOG.debugf( "Binding filter definition: %s", filterDefinition.getFilterName() );
context.getMetadataCollector().addFilterDefinition( filterDefinition );
}
private static ManagedBean<? extends Supplier> resolveParamResolver(ParamDef paramDef, MetadataBuildingContext context) {
Class<? extends Supplier> clazz = paramDef.resolver();
assert clazz != Supplier.class;
final ManagedBeanRegistry beanRegistry = context.getBootstrapContext().getServiceRegistry().getService( ManagedBeanRegistry.class );
return beanRegistry.getBean( clazz, context.getBootstrapContext().getCustomTypeProducer() );
}
@SuppressWarnings("unchecked")
private static JdbcMapping resolveFilterParamType(Class<?> type, MetadataBuildingContext context) {
if ( UserType.class.isAssignableFrom( type ) ) {

View File

@ -7,11 +7,14 @@
package org.hibernate.engine.spi;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.resource.beans.spi.ManagedBean;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -32,6 +35,8 @@ public class FilterDefinition implements Serializable {
private final String filterName;
private final String defaultFilterCondition;
private final Map<String, JdbcMapping> explicitParamJaMappings = new HashMap<>();
private final Map<String, ManagedBean<? extends Supplier>> parameterResolverMap = new HashMap<>();
private final boolean autoEnabled;
/**
* Construct a new FilterDefinition instance.
@ -39,11 +44,20 @@ public class FilterDefinition implements Serializable {
* @param name The name of the filter for which this configuration is in effect.
*/
public FilterDefinition(String name, String defaultCondition, @Nullable Map<String, JdbcMapping> explicitParamJaMappings) {
this( name, defaultCondition, explicitParamJaMappings, Collections.emptyMap(), false);
}
public FilterDefinition(String name, String defaultCondition, @Nullable Map<String, JdbcMapping> explicitParamJaMappings,
Map<String, ManagedBean<? extends Supplier>> parameterResolverMap, boolean autoEnabled) {
this.filterName = name;
this.defaultFilterCondition = defaultCondition;
if ( explicitParamJaMappings != null ) {
this.explicitParamJaMappings.putAll( explicitParamJaMappings );
}
if ( parameterResolverMap != null ) {
this.parameterResolverMap.putAll( parameterResolverMap );
}
this.autoEnabled = autoEnabled;
}
/**
@ -77,6 +91,11 @@ public class FilterDefinition implements Serializable {
return explicitParamJaMappings.get( parameterName );
}
public @Nullable Supplier getParameterResolver(String parameterName) {
final ManagedBean<? extends Supplier> resolver = parameterResolverMap.get( parameterName );
return resolver == null ? null : resolver.getBeanInstance();
}
public String getDefaultFilterCondition() {
return defaultFilterCondition;
}
@ -91,4 +110,13 @@ public class FilterDefinition implements Serializable {
return value;
}
/**
* Get a flag that defines if the filter should be enabled by default.
*
* @return The flag value.
*/
public boolean isAutoEnabled() {
return autoEnabled;
}
}

View File

@ -72,6 +72,13 @@ public class LoadQueryInfluencers implements Serializable {
this.sessionFactory = sessionFactory;
batchSize = options.getDefaultBatchFetchSize();
subselectFetchEnabled = options.isSubselectFetchEnabled();
for (FilterDefinition filterDefinition : sessionFactory.getAutoEnabledFilters()) {
FilterImpl filter = new FilterImpl( filterDefinition );
if ( enabledFilters == null ) {
this.enabledFilters = new HashMap<>();
}
enabledFilters.put( filterDefinition.getFilterName(), filter );
}
}
public EffectiveEntityGraph applyEntityGraph(@Nullable RootGraphImplementor<?> rootGraph, @Nullable GraphSemantic graphSemantic) {

View File

@ -7,6 +7,7 @@
package org.hibernate.engine.spi;
import java.sql.Connection;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -170,6 +171,11 @@ public class SessionFactoryDelegatingImpl implements SessionFactoryImplementor,
return delegate.getFilterDefinition( filterName );
}
@Override
public Collection<FilterDefinition> getAutoEnabledFilters() {
return delegate.getAutoEnabledFilters();
}
@Override
public boolean containsFetchProfileDefinition(String name) {
return delegate.containsFetchProfileDefinition( name );

View File

@ -7,6 +7,7 @@
package org.hibernate.engine.spi;
import java.io.Serializable;
import java.util.Collection;
import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.HibernateException;
@ -164,6 +165,8 @@ public interface SessionFactoryImplementor
FilterDefinition getFilterDefinition(String filterName);
Collection<FilterDefinition> getAutoEnabledFilters();

View File

@ -248,6 +248,14 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
}
}
protected final void setupAutoEnabledFilters(SessionFactoryImplementor factory, LoadQueryInfluencers loadQueryInfluencers) {
factory.getDefinedFilterNames()
.stream()
.map( filterName -> factory.getFilterDefinition( filterName ) )
.filter( filterDefinition -> filterDefinition.isAutoEnabled() )
.forEach( filterDefinition -> loadQueryInfluencers.enableFilter( filterDefinition.getFilterName() ) );
}
private void logInconsistentOptions(SharedSessionCreationOptions sharedOptions) {
if ( sharedOptions.shouldAutoJoinTransactions() ) {
log.debug(

View File

@ -11,6 +11,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.Filter;
import org.hibernate.HibernateException;
@ -30,6 +31,7 @@ public class FilterImpl implements Filter, Serializable {
private transient FilterDefinition definition;
private final String filterName;
private final Map<String,Object> parameters = new HashMap<>();
private final boolean autoEnabled;
void afterDeserialize(SessionFactoryImplementor factory) {
definition = factory.getFilterDefinition( filterName );
@ -44,6 +46,7 @@ public class FilterImpl implements Filter, Serializable {
public FilterImpl(FilterDefinition configuration) {
this.definition = configuration;
filterName = definition.getFilterName();
this.autoEnabled = definition.isAutoEnabled();
}
public FilterDefinition getFilterDefinition() {
@ -59,6 +62,15 @@ public class FilterImpl implements Filter, Serializable {
return definition.getFilterName();
}
/**
* Get a flag that defines if the filter should be enabled by default.
*
* @return The flag value.
*/
public boolean isAutoEnabled() {
return autoEnabled;
}
public Map<String,?> getParameters() {
return parameters;
}
@ -136,6 +148,10 @@ public class FilterImpl implements Filter, Serializable {
return parameters.get( name );
}
public Supplier getParameterResolver(String name) {
return definition.getParameterResolver(name);
}
/**
* Perform validation of the filter state. This is used to verify the
* state of the filter after its enablement and before its use.
@ -144,12 +160,13 @@ public class FilterImpl implements Filter, Serializable {
*/
public void validate() throws HibernateException {
// for each of the defined parameters, make sure its value
// has been set
// has been set or a resolver has been implemented and specified
for ( final String parameterName : definition.getParameterNames() ) {
if ( parameters.get( parameterName ) == null ) {
if ( parameters.get( parameterName ) == null &&
(getParameterResolver( parameterName ) == null || getParameterResolver( parameterName ).getClass().isInterface()) ) {
throw new HibernateException(
"Filter [" + getName() + "] parameter [" + parameterName + "] value not set"
"Either value and resolver for filter [" + getName() + "] parameter [" + parameterName + "] not set"
);
}
}

View File

@ -192,6 +192,7 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
// todo : move to MetamodelImpl
private final transient Map<String, Generator> identifierGenerators;
private final transient Map<String, FilterDefinition> filters;
private final transient java.util.Collection<FilterDefinition> autoEnabledFilters = new HashSet<>();
private final transient Map<String, FetchProfile> fetchProfiles;
private final transient JavaType<Object> tenantIdentifierJavaType;
@ -260,6 +261,11 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
//noinspection unchecked
tenantIdentifierJavaType = jdbcMapping.getJavaTypeDescriptor();
}
for (Map.Entry<String, FilterDefinition> filterEntry : filters.entrySet()) {
if (filterEntry.getValue().isAutoEnabled()) {
autoEnabledFilters.add( filterEntry.getValue() );
}
}
entityNameResolver = new CoordinatingEntityNameResolver( this, getInterceptor() );
schemaManager = new SchemaManagerImpl( this, bootMetamodel );
@ -1089,6 +1095,11 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
return def;
}
@Override
public java.util.Collection<FilterDefinition> getAutoEnabledFilters() {
return autoEnabledFilters;
}
public boolean containsFetchProfileDefinition(String name) {
return fetchProfiles.containsKey( name );
}

View File

@ -8,13 +8,17 @@ package org.hibernate.sql.ast.tree.predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.FilterImpl;
import org.hibernate.internal.FilterJdbcParameter;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.resource.beans.container.spi.BeanContainer;
import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer;
import org.hibernate.sql.ast.SqlAstWalker;
/**
@ -116,7 +120,7 @@ public class FilterPredicate implements Predicate {
parameters = CollectionHelper.arrayList( parameterNames.size() );
for ( int i = 0; i < parameterNames.size(); i++ ) {
final String paramName = parameterNames.get( i );
final Object paramValue = filter.getParameter( paramName );
final Object paramValue = retrieveParamValue(filter, paramName);
final FilterDefinition filterDefinition = filter.getFilterDefinition();
final JdbcMapping jdbcMapping = filterDefinition.getParameterJdbcMapping( paramName );
@ -155,5 +159,15 @@ public class FilterPredicate implements Predicate {
public boolean isEmpty() {
return false;
}
private Object retrieveParamValue(FilterImpl filter, String paramName) {
Object value = filter.getParameter(paramName);
if (value != null) {
return value;
}
final Supplier filterParamResolver = filter.getParameterResolver( paramName );
return filterParamResolver == null ? null : filterParamResolver.get();
}
}
}

View File

@ -2277,6 +2277,7 @@
</xsd:annotation>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" use="required" type="xsd:string"/>
<xsd:attribute name="resolver" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="condition" type="xsd:string" minOccurs="0"/>

View File

@ -9,19 +9,18 @@ package org.hibernate.orm.test.filter;
import java.sql.Types;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.function.Supplier;
import org.hibernate.SharedSessionContract;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.ParamDef;
import org.hibernate.boot.registry.BootstrapServiceRegistry;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.community.dialect.AltibaseDialect;
import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.AbstractHANADialect;
@ -43,11 +42,21 @@ import org.hibernate.type.YesNoConverter;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import jakarta.persistence.Basic;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -57,7 +66,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DomainModel( annotatedClasses = {
FilterParameterTests.EntityOne.class,
FilterParameterTests.EntityTwo.class,
FilterParameterTests.EntityThree.class
FilterParameterTests.EntityThree.class,
FilterParameterTests.EntityFour.class
} )
public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@ -65,11 +75,13 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@MethodSource("transactionKind")
public void testYesNo(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityOne loaded = session.byId( EntityOne.class ).load( 1 );
assertThat( loaded ).isNotNull();
} );
inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterYesNoConverter" ).setParameter( "yesNo", Boolean.FALSE );
final EntityOne loaded = session.createQuery( "from EntityOne e where e.id = :id", EntityOne.class )
@ -97,11 +109,13 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@SkipForDialect(dialectClass = OracleDialect.class, majorVersion = 23, reason = "Oracle 23 interprets Y and T as true and N and F as false, so this works")
public void testYesNoMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityOne loaded = session.byId( EntityOne.class ).load( 1 );
assertThat( loaded ).isNotNull();
} );
inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterYesNoBoolean" ).setParameter( "yesNo", Boolean.FALSE );
assertThatThrownBy( () -> session.createQuery( "from EntityOne e where e.id = :id", EntityOne.class )
@ -115,11 +129,13 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@MethodSource("transactionKind")
public void testNumeric(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityTwo loaded = session.byId( EntityTwo.class ).load( 1 );
assertThat( loaded ).isNotNull();
} );
inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterNumberConverter" ).setParameter( "zeroOne", Boolean.FALSE );
final EntityTwo loaded = session.createQuery( "from EntityTwo e where e.id = :id", EntityTwo.class )
@ -146,11 +162,13 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@SkipForDialect(dialectClass = FirebirdDialect.class, matchSubTypes = true, reason = "Firebird silently converts a boolean to integral types")
public void testNumericMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityTwo loaded = session.byId( EntityTwo.class ).load( 1 );
assertThat( loaded ).isNotNull();
} );
inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterNumberBoolean" ).setParameter( "zeroOne", Boolean.FALSE );
assertThatThrownBy( () -> session.createQuery( "from EntityTwo e where e.id = :id", EntityTwo.class )
@ -168,11 +186,13 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "PostgresPlus silently converts strings to integral types")
public void testMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityThree loaded = session.byId( EntityThree.class ).load( 1 );
assertThat( loaded ).isNotNull();
} );
inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterMismatchConverter" ).setParameter( "mismatch", Boolean.FALSE );
assertThatThrownBy( () -> session.createQuery( "from EntityThree e where e.id = :id", EntityThree.class )
@ -182,6 +202,73 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
} );
}
@ParameterizedTest
@MethodSource("transactionKind")
public void testAutoEnableWithResolver() {
final SeContainerInitializer cdiInitializer = SeContainerInitializer.newInstance()
.disableDiscovery()
.addBeanClasses( EntityFourDepartmentResolver.class );
try ( final SeContainer cdiContainer = cdiInitializer.initialize() ) {
BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder().build();
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistryBuilder( bsr )
.applySetting( AvailableSettings.CDI_BEAN_MANAGER, cdiContainer.getBeanManager() )
.build();
try {
scope.inTransaction( (session) -> {
session.getEnabledFilter("subDepartmentFilter").setParameter("subdepartment", "FIRST_A" );
final EntityFour first_a = session.createQuery( "from EntityFour e where e.id = :id", EntityFour.class )
.setParameter( "id", 1 )
.getSingleResultOrNull();
assertThat( first_a ).isNotNull();
assertThat( first_a.getDepartment() ).isEqualTo( "FIRST" );
session.getEnabledFilter("subDepartmentFilter").setParameter("subdepartment", "SECOND_A" );
final EntityFour second = session.createQuery( "from EntityFour e where e.id = :id", EntityFour.class )
.setParameter( "id", 3 )
.getSingleResultOrNull();
assertThat( second ).isNull();
} );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
}
@ParameterizedTest
@MethodSource("transactionKind")
public void testAutoEnableWithoutResolver() {
final SeContainerInitializer cdiInitializer = SeContainerInitializer.newInstance()
.disableDiscovery()
.addBeanClasses( EntityFourDepartmentResolver.class );
try ( final SeContainer cdiContainer = cdiInitializer.initialize() ) {
BootstrapServiceRegistry bsr = new BootstrapServiceRegistryBuilder().build();
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistryBuilder( bsr )
.applySetting( AvailableSettings.CDI_BEAN_MANAGER, cdiContainer.getBeanManager() )
.build();
try {
scope.inTransaction( (session) -> {
session.getEnabledFilter("subDepartmentFilter").setParameter("subdepartment", "FIRST_A" );
final EntityFour first_a = session.createQuery( "from EntityFour e where e.id = :id", EntityFour.class )
.setParameter( "id", 1 )
.getSingleResultOrNull();
assertThat( first_a ).isNotNull();
assertThat( first_a.getDepartment() ).isEqualTo( "FIRST" );
final EntityFour first_b = session.createQuery( "from EntityFour e where e.id = :id", EntityFour.class )
.setParameter( "id", 2 )
.getSingleResultOrNull();
assertThat( first_b ).isNull();
} );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
}
@BeforeEach
public void prepareTestData() {
@ -189,15 +276,21 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
session.persist( new EntityOne( 1, "one" ) );
session.persist( new EntityTwo( 1, "two" ) );
session.persist( new EntityThree( 1, "three" ) );
session.persist( new EntityFour( 1, "four", "FIRST", "FIRST_A" ) );
session.persist( new EntityFour( 2, "four", "FIRST", "FIRST_B" ) );
session.persist( new EntityFour( 3, "four", "SECOND", "SECOND_A" ) );
} );
}
@AfterEach
public void dropTestData() {
scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
session.disableFilter( "departmentFilter" );
session.createMutationQuery( "delete EntityOne" ).executeUpdate();
session.createMutationQuery( "delete EntityTwo" ).executeUpdate();
session.createMutationQuery( "delete EntityThree" ).executeUpdate();
session.createMutationQuery( "delete EntityFour" ).executeUpdate();
} );
}
@ -357,4 +450,75 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
this.mismatch = mismatch;
}
}
@FilterDef(
name = "departmentFilter",
defaultCondition = "department = :department",
parameters = @ParamDef( name = "department", type = String.class, resolver = EntityFourDepartmentResolver.class),
autoEnabled = true
)
@Filter( name = "departmentFilter" )
@FilterDef(
name = "subDepartmentFilter",
defaultCondition = "subdepartment = :subdepartment",
parameters = @ParamDef( name = "subdepartment", type = String.class ),
autoEnabled = true
)
@Filter( name = "subDepartmentFilter" )
@Entity( name = "EntityFour" )
@Table( name = "EntityFour" )
public static class EntityFour {
@Id
private Integer id;
@Basic
private String name;
private String department;
private String subdepartment;
private EntityFour() {
// for use by Hibernate
}
public EntityFour(Integer id, String name, String department, String subdepartment) {
this.id = id;
this.name = name;
this.department = department;
this.subdepartment = subdepartment;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public String getSubdepartment() {
return subdepartment;
}
public void setSubdepartment(String subdepartment) {
this.subdepartment = subdepartment;
}
}
public static class EntityFourDepartmentResolver implements Supplier<String> {
@Override
public String get() {
return "FIRST";
}
}
}

View File

@ -9,6 +9,7 @@ package org.hibernate.orm.test.pc;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
@ -360,4 +361,69 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
//tag::pc-filter-Account-example[]
}
//end::pc-filter-Account-example[]
@Entity(name = "AutoFilteredAccount")
@Table(name = "autofilteredaccount")
//tag::pc-filter-auto-enabled-Account-example[]
@FilterDef(
name="activeAccount",
parameters = @ParamDef(
name="active",
type=Boolean.class
),
autoEnabled = true
)
//end::pc-filter-auto-enabled-Account-example[]
@Filter(
name="activeAccount",
condition="active_status = :active"
)
//tag::pc-filter-resolver-Account-example[]
@FilterDef(
name="activeAccountWithResolver",
parameters = @ParamDef(
name="active",
type=Boolean.class,
resolver = AccountIsActiveResolver.class
),
autoEnabled = true
)
//end::pc-filter-resolver-Account-example[]
public static class AutoFilteredAccount {
@Id
private Long id;
@Column(name = "active_status")
private boolean active;
public Long getId() {
return id;
}
public AutoFilteredAccount setId(Long id) {
this.id = id;
return this;
}
public boolean isActive() {
return active;
}
public AutoFilteredAccount setActive(boolean active) {
this.active = active;
return this;
}
}
//tag::pc-filter-resolver-Account-example[]
public static class AccountIsActiveResolver implements Supplier<Boolean> {
@Override
public Boolean get() {
return true;
}
}
//end::pc-filter-resolver-Account-example[]
}