HHH-14968 - Support for auto-enabled filters
This commit is contained in:
parent
a5e276571c
commit
527beb0bdb
|
@ -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`
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 ) ) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue