Skip to content

Commit

Permalink
HSEARCH-168 Add application level filter
Browse files Browse the repository at this point in the history
  • Loading branch information
marko-bekhta committed Mar 31, 2023
1 parent 0febdab commit f7222bc
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 12 deletions.
Expand Up @@ -28,6 +28,7 @@
import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper;
import org.hibernate.search.util.impl.integrationtest.mapper.orm.ReusableOrmSetupHolder;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
Expand Down Expand Up @@ -62,6 +63,14 @@ public void setup(OrmSetupHelper.SetupContext setupContext) {
);
}

@Before
public void clearFilter() throws Exception {
Search.automaticIndexingFilter(
setupHolder.entityManagerFactory(),
ctx -> { /*clear out any settings from tests*/ }
);
}

@Test
public void directPersistUpdateDelete() {
setupHolder.runInTransaction( session -> {
Expand Down Expand Up @@ -106,6 +115,53 @@ public void directPersistUpdateDelete() {
backendMock.verifyExpectationsMet();
}

@Test
public void directPersistUpdateDeleteApplicationFilter() {
Search.automaticIndexingFilter(
setupHolder.entityManagerFactory(),
ctx -> ctx.exclude( IndexedEntity.class )
);
setupHolder.runInTransaction( session -> {

IndexedEntity entity1 = new IndexedEntity();
entity1.setId( 1 );
entity1.setIndexedField( "initialValue" );

ContainedEntity entity2 = new ContainedEntity();
entity2.setId( 100 );
entity2.setIndexedField( "initialValue" );

entity2.setContainingAsIndexedEmbedded( entity1 );
entity1.setContainedIndexedEmbedded( Arrays.asList( entity2 ) );

session.persist( entity1 );
session.persist( entity2 );

} );
backendMock.verifyExpectationsMet();

Search.automaticIndexingFilter(
setupHolder.entityManagerFactory(),
ctx -> ctx.exclude( IndexedEntity.class )
);
setupHolder.runInTransaction( session -> {
IndexedEntity entity1 = session.get( IndexedEntity.class, 1 );
entity1.setIndexedField( "updatedValue" );

} );
backendMock.verifyExpectationsMet();

setupHolder.runInTransaction( session -> {
IndexedEntity entity1 = session.get( IndexedEntity.class, 1 );

entity1.getContainedIndexedEmbedded().forEach( e -> e.setContainingAsIndexedEmbedded( null ) );

session.remove( entity1 );

} );
backendMock.verifyExpectationsMet();
}

@Test
public void hierarchyFiltering() {
// exclude all except one specific class.
Expand Down Expand Up @@ -188,6 +244,74 @@ public void sameClassFails() {
} );
}

@Test
public void applicationFilterDisableAll() {
Search.automaticIndexingFilter(
setupHolder.entityManagerFactory(),
ctx -> ctx.exclude( EntityA.class )
);
setupHolder.runInTransaction( session -> {
session.persist( new EntityA( 1, "test" ) );
session.persist( new Entity1A( 2, "test" ) );
session.persist( new Entity1B( 3, "test" ) );
session.persist( new Entity2A( 4, "test" ) );
} );
backendMock.verifyExpectationsMet();

setupHolder.runInTransaction( session -> {
Search.session( session ).automaticIndexingFilter( ctx -> ctx.include( Entity2A.class ) );

session.persist( new EntityA( 10, "test" ) );
session.persist( new Entity1A( 20, "test" ) );
session.persist( new Entity1B( 30, "test" ) );
session.persist( new Entity2A( 40, "test" ) );

backendMock.expectWorks( Entity2A.INDEX )
.add( "40", b -> b.field( "indexedField", "test" ) );
} );
backendMock.verifyExpectationsMet();
}

@Test
public void applicationFilterOnly() {
Search.automaticIndexingFilter(
setupHolder.entityManagerFactory(),
ctx -> ctx.exclude( EntityA.class )
.include( Entity2A.class )
);

setupHolder.runInTransaction( session -> {
Search.session( session ).automaticIndexingFilter( ctx -> ctx.include( Entity2A.class ) );

session.persist( new EntityA( 10, "test" ) );
session.persist( new Entity1A( 20, "test" ) );
session.persist( new Entity1B( 30, "test" ) );
session.persist( new Entity2A( 40, "test" ) );

backendMock.expectWorks( Entity2A.INDEX )
.add( "40", b -> b.field( "indexedField", "test" ) );
} );
backendMock.verifyExpectationsMet();
}

@Test
public void applicationFilterExcludeSessionInclude() {
Search.automaticIndexingFilter(
setupHolder.entityManagerFactory(),
ctx -> ctx.exclude( Entity2A.class )
);

setupHolder.runInTransaction( session -> {
Search.session( session ).automaticIndexingFilter( ctx -> ctx.include( Entity2A.class ) );

session.persist( new Entity2A( 40, "test" ) );

backendMock.expectWorks( Entity2A.INDEX )
.add( "40", b -> b.field( "indexedField", "test" ) );
} );
backendMock.verifyExpectationsMet();
}

@Entity(name = "containing")
@Indexed(index = IndexedEntity.INDEX)
public static class IndexedEntity {
Expand Down
Expand Up @@ -15,12 +15,16 @@
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.Query;
import org.hibernate.search.engine.search.query.SearchQuery;
import org.hibernate.search.mapper.orm.automaticindexing.filter.impl.HibernateOrmApplicationAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.orm.common.impl.HibernateOrmUtils;
import org.hibernate.search.mapper.orm.mapping.SearchMapping;
import org.hibernate.search.mapper.orm.mapping.impl.HibernateOrmMapping;
import org.hibernate.search.mapper.orm.mapping.impl.HibernateSearchContextProviderService;
import org.hibernate.search.mapper.orm.search.query.impl.HibernateOrmSearchQueryAdapter;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hibernate.search.mapper.orm.session.impl.DelegatingSearchSession;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.PojoAutomaticIndexingTypeFilterConfigurer;
import org.hibernate.search.util.common.annotation.Incubating;

public final class Search {

Expand Down Expand Up @@ -119,7 +123,43 @@ public static <H> Query<H> toOrmQuery(SearchQuery<H> searchQuery) {
return HibernateOrmSearchQueryAdapter.create( searchQuery );
}

private static SearchMapping getSearchMapping(SessionFactoryImplementor sessionFactoryImplementor) {
/**
* Set a filter configuration and define which types must be included/excluded from indexing within the current application
* using a Hibernate ORM {@link SessionFactory}.
* <p>
* By default, all indexed types are included.
*
* @param sessionFactory A Hibernate ORM session factory.
* @param configurer The configurer that provides access to filter configuration.
*/
@Incubating
public static void automaticIndexingFilter(SessionFactory sessionFactory,
PojoAutomaticIndexingTypeFilterConfigurer configurer) {
HibernateOrmApplicationAutomaticIndexingTypeFilter.configureFilter(
getSearchMapping( HibernateOrmUtils.toSessionFactoryImplementor( sessionFactory ) ),
configurer
);
}

/**
* Set a filter configuration and define which types must be included/excluded from indexing within the current application
* using a JPA {@link EntityManagerFactory}.
* <p>
* By default, all indexed types are included.
*
* @param entityManagerFactory A JPA entity manager factory.
* @param configurer The configurer that provides access to filter configuration.
*/
@Incubating
public static void automaticIndexingFilter(EntityManagerFactory entityManagerFactory,
PojoAutomaticIndexingTypeFilterConfigurer configurer) {
HibernateOrmApplicationAutomaticIndexingTypeFilter.configureFilter(
getSearchMapping( HibernateOrmUtils.toSessionFactoryImplementor( entityManagerFactory ) ),
configurer
);
}

private static HibernateOrmMapping getSearchMapping(SessionFactoryImplementor sessionFactoryImplementor) {
HibernateSearchContextProviderService mappingContextProvider =
HibernateSearchContextProviderService.get( sessionFactoryImplementor );
return mappingContextProvider.get();
Expand Down
@@ -0,0 +1,33 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.mapper.orm.automaticindexing.filter.impl;

import org.hibernate.search.mapper.orm.mapping.impl.HibernateOrmMapping;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.PojoAutomaticIndexingTypeFilterConfigurer;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.spi.PojoAutomaticIndexingTypeFilterHolder;

public final class HibernateOrmApplicationAutomaticIndexingTypeFilter {

private static final PojoAutomaticIndexingTypeFilterHolder INDEXING_TYPE_FILTER_HOLDER = new PojoAutomaticIndexingTypeFilterHolder();
private static final PojoAutomaticIndexingTypeFilterHolder INCLUDE_ALL_INDEXING_TYPE_FILTER_HOLDER = new PojoAutomaticIndexingTypeFilterHolder();

private HibernateOrmApplicationAutomaticIndexingTypeFilter() {
}

public static void configureFilter(HibernateOrmMapping mapping,
PojoAutomaticIndexingTypeFilterConfigurer configurer) {

HibernateOrmAutomaticIndexingTypeFilterContext context = new HibernateOrmAutomaticIndexingTypeFilterContext(
mapping.typeContextProvider() );
configurer.configure( context );
INDEXING_TYPE_FILTER_HOLDER.filter( context.createFilter( INCLUDE_ALL_INDEXING_TYPE_FILTER_HOLDER ) );
}

public static PojoAutomaticIndexingTypeFilterHolder applicationFilter() {
return INDEXING_TYPE_FILTER_HOLDER;
}
}
Expand Up @@ -9,15 +9,17 @@
import java.util.Set;

import org.hibernate.search.mapper.pojo.automaticindexing.filter.spi.PojoAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.spi.PojoAutomaticIndexingTypeFilterHolder;
import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier;

public class HibernateOrmAutomaticIndexingTypeFilter implements PojoAutomaticIndexingTypeFilter {

private final PojoAutomaticIndexingTypeFilter fallback;
private final PojoAutomaticIndexingTypeFilterHolder fallback;
private final Set<PojoRawTypeIdentifier<?>> includes;
private final Set<PojoRawTypeIdentifier<?>> excludes;

public HibernateOrmAutomaticIndexingTypeFilter(PojoAutomaticIndexingTypeFilter fallback, Set<PojoRawTypeIdentifier<?>> includes,
public HibernateOrmAutomaticIndexingTypeFilter(PojoAutomaticIndexingTypeFilterHolder fallback,
Set<PojoRawTypeIdentifier<?>> includes,
Set<PojoRawTypeIdentifier<?>> excludes) {
this.fallback = fallback;
this.includes = includes;
Expand All @@ -34,6 +36,6 @@ public boolean isIncluded(PojoRawTypeIdentifier<?> typeIdentifier) {
return true;
}

return fallback.isIncluded( typeIdentifier );
return fallback.filter().isIncluded( typeIdentifier );
}
}
Expand Up @@ -14,7 +14,7 @@

import org.hibernate.search.mapper.orm.session.impl.HibernateOrmSessionTypeContextProvider;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.PojoAutomaticIndexingTypeFilterContext;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.spi.PojoAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.spi.PojoAutomaticIndexingTypeFilterHolder;
import org.hibernate.search.mapper.pojo.logging.impl.Log;
import org.hibernate.search.mapper.pojo.model.spi.PojoRawTypeIdentifier;
import org.hibernate.search.mapper.pojo.model.spi.PojoTypeContext;
Expand Down Expand Up @@ -74,7 +74,7 @@ public PojoAutomaticIndexingTypeFilterContext exclude(Class<?> clazz) {
}


public HibernateOrmAutomaticIndexingTypeFilter createFilter(PojoAutomaticIndexingTypeFilter fallback) {
public HibernateOrmAutomaticIndexingTypeFilter createFilter(PojoAutomaticIndexingTypeFilterHolder fallback) {
Set<PojoRawTypeIdentifier<?>> allIncludes = new HashSet<>();
Set<PojoRawTypeIdentifier<?>> allExcludes = new HashSet<>();

Expand Down
Expand Up @@ -14,6 +14,7 @@
import javax.persistence.EntityManager;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.search.engine.search.query.dsl.SearchQuerySelectStep;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.PojoAutomaticIndexingTypeFilterConfigurer;
import org.hibernate.search.mapper.orm.common.EntityReference;
Expand Down Expand Up @@ -249,7 +250,8 @@ default <T> SearchScope<T> scope(Class<T> expectedSuperType, String entityName)

/**
* Set a filter configuration and define which types must be included/excluded from indexing within the current session.
* By default, all indexed types are included.
* If the type is not explicitly included/excluded directly or as a supertype the decision will be made by
* {@link org.hibernate.search.mapper.orm.Search#automaticIndexingFilter(SessionFactory, PojoAutomaticIndexingTypeFilterConfigurer) an application filter}.
*
* @param configurer The configurer that provides access to filter configuration.
*/
Expand Down
Expand Up @@ -7,6 +7,7 @@
package org.hibernate.search.mapper.orm.session.impl;

import java.lang.invoke.MethodHandles;
import java.util.Collections;
import java.util.function.Function;
import javax.transaction.Synchronization;

Expand All @@ -16,6 +17,8 @@
import org.hibernate.search.engine.cfg.spi.OptionalConfigurationProperty;
import org.hibernate.search.engine.environment.bean.BeanHolder;
import org.hibernate.search.engine.environment.bean.BeanReference;
import org.hibernate.search.mapper.orm.automaticindexing.filter.impl.HibernateOrmApplicationAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.orm.automaticindexing.filter.impl.HibernateOrmAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.orm.automaticindexing.impl.AutomaticIndexingStrategyStartContext;
import org.hibernate.search.mapper.orm.automaticindexing.impl.HibernateOrmIndexingQueueEventSendingPlan;
import org.hibernate.search.mapper.orm.automaticindexing.spi.AutomaticIndexingEventSendingSessionContext;
Expand Down
Expand Up @@ -10,6 +10,7 @@

import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Collections;
import javax.persistence.EntityManager;
import javax.transaction.Synchronization;

Expand All @@ -22,6 +23,8 @@
import org.hibernate.search.engine.backend.common.DocumentReference;
import org.hibernate.search.engine.backend.common.spi.DocumentReferenceConverter;
import org.hibernate.search.engine.search.query.dsl.SearchQuerySelectStep;
import org.hibernate.search.mapper.orm.automaticindexing.filter.impl.HibernateOrmApplicationAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.orm.automaticindexing.filter.impl.HibernateOrmAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.orm.automaticindexing.filter.impl.HibernateOrmAutomaticIndexingTypeFilterContext;
import org.hibernate.search.mapper.orm.automaticindexing.session.impl.DelegatingAutomaticIndexingSynchronizationStrategy;
import org.hibernate.search.mapper.orm.automaticindexing.spi.AutomaticIndexingEventSendingSessionContext;
Expand All @@ -43,7 +46,6 @@
import org.hibernate.search.mapper.orm.work.impl.SearchIndexingPlanImpl;
import org.hibernate.search.mapper.orm.work.impl.SearchIndexingPlanSessionContext;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.PojoAutomaticIndexingTypeFilterConfigurer;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.spi.PojoAutomaticIndexingTypeFilter;
import org.hibernate.search.mapper.pojo.automaticindexing.filter.spi.PojoAutomaticIndexingTypeFilterHolder;
import org.hibernate.search.mapper.pojo.loading.spi.PojoSelectionLoadingContext;
import org.hibernate.search.mapper.pojo.model.spi.PojoRuntimeIntrospector;
Expand Down Expand Up @@ -117,7 +119,14 @@ private HibernateOrmSearchSession(Builder builder) {
this.automaticIndexingStrategy = builder.automaticIndexingStrategy;
this.sessionImplementor = builder.sessionImplementor;
this.runtimeIntrospector = builder.buildRuntimeIntrospector();
this.automaticIndexingTypeFilterHolder = new PojoAutomaticIndexingTypeFilterHolder();
this.automaticIndexingTypeFilterHolder = new PojoAutomaticIndexingTypeFilterHolder(
// make sure that even if session filter is not configured we will fall back to an application one if needed.
new HibernateOrmAutomaticIndexingTypeFilter(
HibernateOrmApplicationAutomaticIndexingTypeFilter.applicationFilter(),
Collections.emptySet(),
Collections.emptySet()
)
);
this.indexingPlanSynchronizationStrategy = automaticIndexingStrategy.defaultIndexingPlanSynchronizationStrategy();
}

Expand Down Expand Up @@ -214,7 +223,9 @@ public void indexingPlanSynchronizationStrategy(IndexingPlanSynchronizationStrat
public void automaticIndexingFilter(PojoAutomaticIndexingTypeFilterConfigurer configurer) {
HibernateOrmAutomaticIndexingTypeFilterContext context = new HibernateOrmAutomaticIndexingTypeFilterContext( typeContextProvider );
configurer.configure( context );
automaticIndexingTypeFilterHolder.filter( context.createFilter( PojoAutomaticIndexingTypeFilter.ACCEPT_ALL ) );
automaticIndexingTypeFilterHolder.filter( context.createFilter(
HibernateOrmApplicationAutomaticIndexingTypeFilter.applicationFilter()
) );
}

@Override
Expand Down
Expand Up @@ -9,8 +9,15 @@
import java.util.concurrent.atomic.AtomicReference;

public class PojoAutomaticIndexingTypeFilterHolder {
private AtomicReference<PojoAutomaticIndexingTypeFilter> filter = new AtomicReference<>(
PojoAutomaticIndexingTypeFilter.ACCEPT_ALL );
private AtomicReference<PojoAutomaticIndexingTypeFilter> filter = new AtomicReference<>();

public PojoAutomaticIndexingTypeFilterHolder() {
this( PojoAutomaticIndexingTypeFilter.ACCEPT_ALL );
}

public PojoAutomaticIndexingTypeFilterHolder(PojoAutomaticIndexingTypeFilter filter) {
this.filter.set( filter );
}

public PojoAutomaticIndexingTypeFilter filter() {
return filter.get();
Expand Down

0 comments on commit f7222bc

Please sign in to comment.