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]] [[pc-no-filter-entity-query-example]]
.Query entities mapped without activating the `@Filter` .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, 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. 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]] [[pc-filter-entity-query-example]]
.Query entities mapped with `@Filter` .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] 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] [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] [IMPORTANT]
==== ====
Filters apply to entity queries, but not to direct fetching. 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. 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]] [[pc-no-filter-collection-query-example]]
.Traversing collections without activating the `@Filter` .Traversing collections without activating the `@Filter`

View File

@ -87,4 +87,12 @@ public interface Filter {
* @throws HibernateException If the state is not currently valid. * @throws HibernateException If the state is not currently valid.
*/ */
void validate() throws HibernateException; 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. * The names and types of the parameters of the filter.
*/ */
ParamDef[] parameters() default {}; 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.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.function.Supplier;
import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.RetentionPolicy.RUNTIME;
@ -52,4 +53,14 @@ public @interface ParamDef {
* </ul> * </ul>
*/ */
Class<?> type(); 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; package org.hibernate.binder.internal;
import java.util.Collections;
import java.util.function.Supplier;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.TenantId; import org.hibernate.annotations.TenantId;
import org.hibernate.binder.AttributeBinder; import org.hibernate.binder.AttributeBinder;
@ -54,7 +57,9 @@ public class TenantIdBinder implements AttributeBinder<TenantId> {
new FilterDefinition( new FilterDefinition(
FILTER_NAME, 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.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation;
@ -57,12 +58,16 @@ class FilterDefBinder {
if ( context.getMetadataCollector().getFilterDefinition( name ) != null ) { if ( context.getMetadataCollector().getFilterDefinition( name ) != null ) {
throw new AnnotationException( "Multiple '@FilterDef' annotations define a filter named '" + name + "'" ); throw new AnnotationException( "Multiple '@FilterDef' annotations define a filter named '" + name + "'" );
} }
final Map<String, JdbcMapping> explicitParamJaMappings; final Map<String, JdbcMapping> explicitParamJaMappings;
final Map<String, ManagedBean<? extends Supplier>> parameterResolvers;
if ( filterDef.parameters().length == 0 ) { if ( filterDef.parameters().length == 0 ) {
explicitParamJaMappings = emptyMap(); explicitParamJaMappings = emptyMap();
parameterResolvers = emptyMap();
} }
else { else {
explicitParamJaMappings = new HashMap<>(); explicitParamJaMappings = new HashMap<>();
parameterResolvers = new HashMap<>();
for ( ParamDef paramDef : filterDef.parameters() ) { for ( ParamDef paramDef : filterDef.parameters() ) {
final JdbcMapping jdbcMapping = resolveFilterParamType( paramDef.type(), context ); final JdbcMapping jdbcMapping = resolveFilterParamType( paramDef.type(), context );
if ( jdbcMapping == null ) { if ( jdbcMapping == null ) {
@ -76,14 +81,31 @@ class FilterDefBinder {
); );
} }
explicitParamJaMappings.put( paramDef.name(), jdbcMapping ); 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() ); LOG.debugf( "Binding filter definition: %s", filterDefinition.getFilterName() );
context.getMetadataCollector().addFilterDefinition( filterDefinition ); 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") @SuppressWarnings("unchecked")
private static JdbcMapping resolveFilterParamType(Class<?> type, MetadataBuildingContext context) { private static JdbcMapping resolveFilterParamType(Class<?> type, MetadataBuildingContext context) {
if ( UserType.class.isAssignableFrom( type ) ) { if ( UserType.class.isAssignableFrom( type ) ) {

View File

@ -7,11 +7,14 @@
package org.hibernate.engine.spi; package org.hibernate.engine.spi;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.resource.beans.spi.ManagedBean;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -32,6 +35,8 @@ public class FilterDefinition implements Serializable {
private final String filterName; private final String filterName;
private final String defaultFilterCondition; private final String defaultFilterCondition;
private final Map<String, JdbcMapping> explicitParamJaMappings = new HashMap<>(); 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. * 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. * @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) { 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.filterName = name;
this.defaultFilterCondition = defaultCondition; this.defaultFilterCondition = defaultCondition;
if ( explicitParamJaMappings != null ) { if ( explicitParamJaMappings != null ) {
this.explicitParamJaMappings.putAll( explicitParamJaMappings ); 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 ); 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() { public String getDefaultFilterCondition() {
return defaultFilterCondition; return defaultFilterCondition;
} }
@ -91,4 +110,13 @@ public class FilterDefinition implements Serializable {
return value; 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; this.sessionFactory = sessionFactory;
batchSize = options.getDefaultBatchFetchSize(); batchSize = options.getDefaultBatchFetchSize();
subselectFetchEnabled = options.isSubselectFetchEnabled(); 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) { public EffectiveEntityGraph applyEntityGraph(@Nullable RootGraphImplementor<?> rootGraph, @Nullable GraphSemantic graphSemantic) {

View File

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

View File

@ -7,6 +7,7 @@
package org.hibernate.engine.spi; package org.hibernate.engine.spi;
import java.io.Serializable; import java.io.Serializable;
import java.util.Collection;
import org.hibernate.CustomEntityDirtinessStrategy; import org.hibernate.CustomEntityDirtinessStrategy;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -164,6 +165,8 @@ public interface SessionFactoryImplementor
FilterDefinition getFilterDefinition(String filterName); 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) { private void logInconsistentOptions(SharedSessionCreationOptions sharedOptions) {
if ( sharedOptions.shouldAutoJoinTransactions() ) { if ( sharedOptions.shouldAutoJoinTransactions() ) {
log.debug( log.debug(

View File

@ -11,6 +11,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import org.hibernate.Filter; import org.hibernate.Filter;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -30,6 +31,7 @@ public class FilterImpl implements Filter, Serializable {
private transient FilterDefinition definition; private transient FilterDefinition definition;
private final String filterName; private final String filterName;
private final Map<String,Object> parameters = new HashMap<>(); private final Map<String,Object> parameters = new HashMap<>();
private final boolean autoEnabled;
void afterDeserialize(SessionFactoryImplementor factory) { void afterDeserialize(SessionFactoryImplementor factory) {
definition = factory.getFilterDefinition( filterName ); definition = factory.getFilterDefinition( filterName );
@ -44,6 +46,7 @@ public class FilterImpl implements Filter, Serializable {
public FilterImpl(FilterDefinition configuration) { public FilterImpl(FilterDefinition configuration) {
this.definition = configuration; this.definition = configuration;
filterName = definition.getFilterName(); filterName = definition.getFilterName();
this.autoEnabled = definition.isAutoEnabled();
} }
public FilterDefinition getFilterDefinition() { public FilterDefinition getFilterDefinition() {
@ -59,6 +62,15 @@ public class FilterImpl implements Filter, Serializable {
return definition.getFilterName(); 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() { public Map<String,?> getParameters() {
return parameters; return parameters;
} }
@ -136,6 +148,10 @@ public class FilterImpl implements Filter, Serializable {
return parameters.get( name ); 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 * Perform validation of the filter state. This is used to verify the
* state of the filter after its enablement and before its use. * 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 { public void validate() throws HibernateException {
// for each of the defined parameters, make sure its value // 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() ) { 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( 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 // todo : move to MetamodelImpl
private final transient Map<String, Generator> identifierGenerators; private final transient Map<String, Generator> identifierGenerators;
private final transient Map<String, FilterDefinition> filters; 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 Map<String, FetchProfile> fetchProfiles;
private final transient JavaType<Object> tenantIdentifierJavaType; private final transient JavaType<Object> tenantIdentifierJavaType;
@ -260,6 +261,11 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
//noinspection unchecked //noinspection unchecked
tenantIdentifierJavaType = jdbcMapping.getJavaTypeDescriptor(); tenantIdentifierJavaType = jdbcMapping.getJavaTypeDescriptor();
} }
for (Map.Entry<String, FilterDefinition> filterEntry : filters.entrySet()) {
if (filterEntry.getValue().isAutoEnabled()) {
autoEnabledFilters.add( filterEntry.getValue() );
}
}
entityNameResolver = new CoordinatingEntityNameResolver( this, getInterceptor() ); entityNameResolver = new CoordinatingEntityNameResolver( this, getInterceptor() );
schemaManager = new SchemaManagerImpl( this, bootMetamodel ); schemaManager = new SchemaManagerImpl( this, bootMetamodel );
@ -1089,6 +1095,11 @@ public class SessionFactoryImpl extends QueryParameterBindingTypeResolverImpl im
return def; return def;
} }
@Override
public java.util.Collection<FilterDefinition> getAutoEnabledFilters() {
return autoEnabledFilters;
}
public boolean containsFetchProfileDefinition(String name) { public boolean containsFetchProfileDefinition(String name) {
return fetchProfiles.containsKey( 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.hibernate.MappingException;
import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.FilterDefinition;
import org.hibernate.internal.FilterImpl; import org.hibernate.internal.FilterImpl;
import org.hibernate.internal.FilterJdbcParameter; import org.hibernate.internal.FilterJdbcParameter;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.JdbcMappingContainer; 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; import org.hibernate.sql.ast.SqlAstWalker;
/** /**
@ -116,7 +120,7 @@ public class FilterPredicate implements Predicate {
parameters = CollectionHelper.arrayList( parameterNames.size() ); parameters = CollectionHelper.arrayList( parameterNames.size() );
for ( int i = 0; i < parameterNames.size(); i++ ) { for ( int i = 0; i < parameterNames.size(); i++ ) {
final String paramName = parameterNames.get( 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 FilterDefinition filterDefinition = filter.getFilterDefinition();
final JdbcMapping jdbcMapping = filterDefinition.getParameterJdbcMapping( paramName ); final JdbcMapping jdbcMapping = filterDefinition.getParameterJdbcMapping( paramName );
@ -155,5 +159,15 @@ public class FilterPredicate implements Predicate {
public boolean isEmpty() { public boolean isEmpty() {
return false; 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:annotation>
<xsd:attribute name="name" use="required" type="xsd:string"/> <xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" use="required" type="xsd:string"/> <xsd:attribute name="type" use="required" type="xsd:string"/>
<xsd:attribute name="resolver" type="xsd:string"/>
</xsd:complexType> </xsd:complexType>
</xsd:element> </xsd:element>
<xsd:element name="condition" type="xsd:string" minOccurs="0"/> <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.sql.Types;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier;
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 org.hibernate.SharedSessionContract; import org.hibernate.SharedSessionContract;
import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef; import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.ParamDef; 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.AltibaseDialect;
import org.hibernate.community.dialect.FirebirdDialect; import org.hibernate.community.dialect.FirebirdDialect;
import org.hibernate.dialect.AbstractHANADialect; 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.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SkipForDialect; import org.hibernate.testing.orm.junit.SkipForDialect;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -57,7 +66,8 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DomainModel( annotatedClasses = { @DomainModel( annotatedClasses = {
FilterParameterTests.EntityOne.class, FilterParameterTests.EntityOne.class,
FilterParameterTests.EntityTwo.class, FilterParameterTests.EntityTwo.class,
FilterParameterTests.EntityThree.class FilterParameterTests.EntityThree.class,
FilterParameterTests.EntityFour.class
} ) } )
public class FilterParameterTests extends AbstractStatefulStatelessFilterTest { public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@ -65,11 +75,13 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@MethodSource("transactionKind") @MethodSource("transactionKind")
public void testYesNo(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) { public void testYesNo(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityOne loaded = session.byId( EntityOne.class ).load( 1 ); final EntityOne loaded = session.byId( EntityOne.class ).load( 1 );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
} ); } );
inTransaction.accept( scope, session -> { inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterYesNoConverter" ).setParameter( "yesNo", Boolean.FALSE ); session.enableFilter( "filterYesNoConverter" ).setParameter( "yesNo", Boolean.FALSE );
final EntityOne loaded = session.createQuery( "from EntityOne e where e.id = :id", EntityOne.class ) 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") @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) { public void testYesNoMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityOne loaded = session.byId( EntityOne.class ).load( 1 ); final EntityOne loaded = session.byId( EntityOne.class ).load( 1 );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
} ); } );
inTransaction.accept( scope, session -> { inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterYesNoBoolean" ).setParameter( "yesNo", Boolean.FALSE ); session.enableFilter( "filterYesNoBoolean" ).setParameter( "yesNo", Boolean.FALSE );
assertThatThrownBy( () -> session.createQuery( "from EntityOne e where e.id = :id", EntityOne.class ) assertThatThrownBy( () -> session.createQuery( "from EntityOne e where e.id = :id", EntityOne.class )
@ -115,11 +129,13 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
@MethodSource("transactionKind") @MethodSource("transactionKind")
public void testNumeric(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) { public void testNumeric(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityTwo loaded = session.byId( EntityTwo.class ).load( 1 ); final EntityTwo loaded = session.byId( EntityTwo.class ).load( 1 );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
} ); } );
inTransaction.accept( scope, session -> { inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterNumberConverter" ).setParameter( "zeroOne", Boolean.FALSE ); session.enableFilter( "filterNumberConverter" ).setParameter( "zeroOne", Boolean.FALSE );
final EntityTwo loaded = session.createQuery( "from EntityTwo e where e.id = :id", EntityTwo.class ) 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") @SkipForDialect(dialectClass = FirebirdDialect.class, matchSubTypes = true, reason = "Firebird silently converts a boolean to integral types")
public void testNumericMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) { public void testNumericMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityTwo loaded = session.byId( EntityTwo.class ).load( 1 ); final EntityTwo loaded = session.byId( EntityTwo.class ).load( 1 );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
} ); } );
inTransaction.accept( scope, session -> { inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterNumberBoolean" ).setParameter( "zeroOne", Boolean.FALSE ); session.enableFilter( "filterNumberBoolean" ).setParameter( "zeroOne", Boolean.FALSE );
assertThatThrownBy( () -> session.createQuery( "from EntityTwo e where e.id = :id", EntityTwo.class ) 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") @SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "PostgresPlus silently converts strings to integral types")
public void testMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) { public void testMismatch(BiConsumer<SessionFactoryScope, Consumer<? extends SharedSessionContract>> inTransaction) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
final EntityThree loaded = session.byId( EntityThree.class ).load( 1 ); final EntityThree loaded = session.byId( EntityThree.class ).load( 1 );
assertThat( loaded ).isNotNull(); assertThat( loaded ).isNotNull();
} ); } );
inTransaction.accept( scope, session -> { inTransaction.accept( scope, session -> {
session.disableFilter( "subDepartmentFilter" );
session.enableFilter( "filterMismatchConverter" ).setParameter( "mismatch", Boolean.FALSE ); session.enableFilter( "filterMismatchConverter" ).setParameter( "mismatch", Boolean.FALSE );
assertThatThrownBy( () -> session.createQuery( "from EntityThree e where e.id = :id", EntityThree.class ) 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 @BeforeEach
public void prepareTestData() { public void prepareTestData() {
@ -189,15 +276,21 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
session.persist( new EntityOne( 1, "one" ) ); session.persist( new EntityOne( 1, "one" ) );
session.persist( new EntityTwo( 1, "two" ) ); session.persist( new EntityTwo( 1, "two" ) );
session.persist( new EntityThree( 1, "three" ) ); 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 @AfterEach
public void dropTestData() { public void dropTestData() {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.disableFilter( "subDepartmentFilter" );
session.disableFilter( "departmentFilter" );
session.createMutationQuery( "delete EntityOne" ).executeUpdate(); session.createMutationQuery( "delete EntityOne" ).executeUpdate();
session.createMutationQuery( "delete EntityTwo" ).executeUpdate(); session.createMutationQuery( "delete EntityTwo" ).executeUpdate();
session.createMutationQuery( "delete EntityThree" ).executeUpdate(); session.createMutationQuery( "delete EntityThree" ).executeUpdate();
session.createMutationQuery( "delete EntityFour" ).executeUpdate();
} ); } );
} }
@ -357,4 +450,75 @@ public class FilterParameterTests extends AbstractStatefulStatelessFilterTest {
this.mismatch = mismatch; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
@ -360,4 +361,69 @@ public class FilterTest extends BaseEntityManagerFunctionalTestCase {
//tag::pc-filter-Account-example[] //tag::pc-filter-Account-example[]
} }
//end::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[]
} }