Skip to content

Commit

Permalink
HSEARCH-1383 Add more examples to the docs
Browse files Browse the repository at this point in the history
  • Loading branch information
marko-bekhta committed May 2, 2023
1 parent 16ed0e3 commit 2e19e0d
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 41 deletions.
Expand Up @@ -99,53 +99,90 @@ Automatic indexing is affected by the synchronization strategy in use in the `Se

See <<indexing-plan-synchronization,this section>> for more information.

[[indexing-automatic-filter]]
== Automatic indexing type filter
[[indexing-plan-filter]]
== Indexing plan filter

include::components/incubating-warning.asciidoc[]

In some scenarios, it might be helpful to pause the automatic indexing programmatically, for example,
In some scenarios, it might be helpful to pause the indexing programmatically, for example,
when importing larger amounts of data. Hibernate Search allows configuring application-wide
and session-level filters to manage which types are tracked for changes and indexed.

.Configuring an application-wide filter
====
[source, JAVA, indent=0, subs="+callouts"]
----
include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/automaticindexing/HibernateOrmAutomaticIndexingFilterIT.java[tags=application-filter]
include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/automaticindexing/HibernateOrmIndexingPlanFilterIT.java[tags=application-filter]
----
Configuring an application-wide filter requires either an instance of `EntityManagerFactory` or `SessionFactory`.
Configuring an application-wide filter requires an instance of the `SearchMapping`.
<1> Start the declaration of the automatic indexing filter.
<2> Configure included/excluded types through the `PojoAutomaticIndexingTypeFilterConfigurer`
<1> <<entrypoints-search-mapping,Retrieve the `SearchMapping`>>.
<2> Start the declaration of the indexing plan filter.
<3> Configure included/excluded types through the `SearchIndexingPlanFilter`
====

.Configuring a session-level filter
====
[source, JAVA, indent=0, subs="+callouts"]
----
include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/automaticindexing/HibernateOrmAutomaticIndexingFilterIT.java[tags=session-filter]
include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/automaticindexing/HibernateOrmIndexingPlanFilterIT.java[tags=session-filter]
----
Configuring a session level filter is available through an instance of the `SearchSession`.
<1> Receive an instance of the `SearchSession`.
<2> Configure included/excluded types through the `PojoAutomaticIndexingTypeFilterConfigurer`
<1> <<entrypoints-search-session,Retrieve the `SearchSession`>>
<2> Configure included/excluded types through the `SearchIndexingPlanFilter`
====

Filter can be defined by providing indexed and contained types as well as their supertypes.
Interfaces are not allowed and passing an interface class to any of the filter definition methods will result in an exception.
If dynamic types represented by a `Map` are used then their names must be used to configure the filter.
Filter rules are:

* If the type `A` is explicitly included by the filter, then a change to an object that is exactly of an indexed type `A` is processed.
* If the type `A` is explicitly excluded by the filter, then a change to an object that is exactly of an indexed type `A` is ignored.
* If the type `A` is explicitly included by the filter, then a change to an object that is exactly of an indexed type `B`,
* If the type `A` is explicitly included by the filter, then a change to an object that is exactly of a type `A` is processed.
* If the type `A` is explicitly excluded by the filter, then a change to an object that is exactly of a type `A` is ignored.
* If the type `A` is explicitly included by the filter, then a change to an object that is exactly of a type `B`,
which is a subtype of the type `A`, is processed unless the filter explicitly excludes a more specific supertype of a type `B`.
* If the type `A` is excluded by the filter explicitly, then a change to an object that is exactly of an indexed type `B`,
* If the type `A` is excluded by the filter explicitly, then a change to an object that is exactly of a type `B`,
which is a subtype of type the `A`, is ignored unless the filter explicitly includes a more specific supertype of a type `B`.

A session-level filter takes precedence over an application-wide one. If the session-level filter configuration does not
either explicitly or through inheritance include/exclude the exact type of entity, then the decision will be made by
either explicitly or through inheritance include/exclude the exact type of an entity, then the decision will be made by
the application-wide filter. If an application-wide filter also has no explicit configuration for a type, then this type
is considered to be included.

In some cases we might need to disable the indexing entirely. Listing all entities one by one might be cumbersome,
but since filter configuration is implicitly applied to subtypes, `.exclude(Object.class)` can be used to exclude all types.
Conversely, `.include(Object.class)` can be used to enable indexing within a session filter when
the application-wide filter disables indexing completely.

.Disable all indexing within a session
====
[source, JAVA, indent=0, subs="+callouts"]
----
include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/automaticindexing/HibernateOrmIndexingPlanFilterIT.java[tags=session-filter-exclude-all]
----
Configuring a session level filter is available through an instance of the `SearchSession`.
<1> <<entrypoints-search-session,Retrieve the `SearchSession`>>
<2> Excluding `Object.class` will lead to excluding all its subtypes which means nothing will be included.
====

.Enable indexing in the session while application-wide indexing is paused
====
[source, JAVA, indent=0, subs="+callouts"]
----
include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/automaticindexing/HibernateOrmIndexingPlanFilterIT.java[tags=session-filter-exclude-include-all-application]
----
----
include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/automaticindexing/HibernateOrmIndexingPlanFilterIT.java[tags=session-filter-exclude-include-all-session]
----
<1> <<entrypoints-search-mapping,Retrieve the `SearchMapping`>>.
<2> An application-wide filter disables any indexing
<3> <<entrypoints-search-session,Retrieve the `SearchSession`>>
<4> A session level filter re-enables indexing of all indexed types *for changes happening in current session only*
====

[NOTE]
====
Trying to configure the same type as both included and excluded at the same time by the same filter
Expand All @@ -154,7 +191,7 @@ will lead to an exception being thrown.

[NOTE]
====
Only an application-wide filter is safe to use when using the outbox polling coordination strategy.
Only an application-wide filter is safe to use when using the <<coordination-database-polling,`outbox-polling` coordination strategy>>.
When this coordination strategy is in use, entities are loaded and indexed in a different session from
the one where they were changed. It might lead to unexpected results as the session where events are processed will not
apply the filter configured by the session in which entities were modified.
Expand Down
Expand Up @@ -19,6 +19,8 @@
import org.hibernate.search.documentation.testsupport.BackendConfigurations;
import org.hibernate.search.documentation.testsupport.DocumentationSetupHelper;
import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.mapping.SearchMapping;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;

Expand All @@ -43,8 +45,12 @@ public void setup() {
@Test
public void applicationFilterOnly() {
// tag::application-filter[]
Search.mapping( entityManagerFactory ).indexingPlanFilter( // <1>
ctx -> ctx.exclude( EntityA.class ) // <2>
SearchMapping searchMapping = /* ... */ // <1>
// end::application-filter[]
Search.mapping( entityManagerFactory );
// tag::application-filter[]
searchMapping.indexingPlanFilter( // <2>
ctx -> ctx.exclude( EntityA.class ) // <3>
.include( EntityExtendsA2.class )
);
// end::application-filter[]
Expand All @@ -68,11 +74,14 @@ public void applicationFilterOnly() {
public void sessionFilterOnly() {
with( entityManagerFactory ).runInTransaction( entityManager -> {
// tag::session-filter[]
Search.session( entityManager ) // <1>
.indexingPlanFilter( // <2>
ctx -> ctx.exclude( EntityA.class )
.include( EntityExtendsA2.class )
);
SearchSession session = /* ... */ // <1>
// end::session-filter[]
Search.session( entityManager );
// tag::session-filter[]
session.indexingPlanFilter(
ctx -> ctx.exclude( EntityA.class ) // <2>
.include( EntityExtendsA2.class )
);
// end::session-filter[]

entityManager.persist( new EntityA( 10, "test" ) );
Expand All @@ -90,6 +99,73 @@ public void sessionFilterOnly() {
} );
}

@Test
public void disableAll() {
with( entityManagerFactory ).runInTransaction( entityManager -> {
// tag::session-filter-exclude-all[]
SearchSession searchSession = /* ... */ // <1>
// end::session-filter-exclude-all[]
Search.session( entityManager );
// tag::session-filter-exclude-all[]
searchSession.indexingPlanFilter(
ctx -> ctx.exclude( Object.class ) // <2>
);
// end::session-filter-exclude-all[]

entityManager.persist( new EntityA( 10, "test" ) );
entityManager.persist( new EntityExtendsA1( 20, "test" ) );
entityManager.persist( new EntityExtendsA2( 30, "test" ) );
} );

with( entityManagerFactory ).runInTransaction( entityManager -> {
// Nothing is getting indexed:
assertThat( Search.session( entityManager ).search( EntityA.class )
.select( f -> f.id() )
.where( f -> f.matchAll() )
.fetchAllHits()
).isEmpty();
} );
}

@Test
public void disableAllApplicationEnableSession() {
// tag::session-filter-exclude-include-all-application[]
SearchMapping searchMapping = /* ... */ // <1>
// end::session-filter-exclude-include-all-application[]
Search.mapping( entityManagerFactory );
// tag::session-filter-exclude-include-all-application[]
searchMapping.indexingPlanFilter(
ctx -> ctx.exclude( Object.class ) // <2>
);
// end::session-filter-exclude-include-all-application[]
with( entityManagerFactory ).runInTransaction( entityManager -> {
// tag::session-filter-exclude-include-all-session[]
SearchSession searchSession = /* ... */ // <3>
// end::session-filter-exclude-include-all-session[]
Search.session( entityManager );
// tag::session-filter-exclude-include-all-session[]
searchSession.indexingPlanFilter(
ctx -> ctx.include( Object.class ) // <4>
);
// end::session-filter-exclude-include-all-session[]

entityManager.persist( new EntityA( 10, "test" ) );
entityManager.persist( new EntityExtendsA1( 20, "test" ) );
entityManager.persist( new EntityExtendsA2( 30, "test" ) );
} );

with( entityManagerFactory ).runInTransaction( entityManager -> {
// Nothing is getting indexed:
assertThat( Search.session( entityManager ).search( EntityA.class )
.select( f -> f.id() )
.where( f -> f.matchAll() )
.fetchAllHits()
).containsOnly(
10, 20, 30
);
} );
}

@Entity(name = EntityA.INDEX)
@Indexed
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
Expand Down
Expand Up @@ -123,7 +123,9 @@ default <T> SearchScope<T> scope(Class<T> expectedSuperType, String entityName)
Backend backend(String backendName);

/**
* Set a filter defining which types must be included/excluded when indexed within the current application.
* Set a filter defining which types must be included/excluded when indexed within indexing plans (either automatically or manually).
* <p>
* This does not affect indexing that does not rely on indexing plans, like the mass indexer.
* <p>
* By default, all indexed types are included.
*
Expand Down
Expand Up @@ -250,8 +250,13 @@ default <T> SearchScope<T> scope(Class<T> expectedSuperType, String entityName)
void indexingPlanSynchronizationStrategy(IndexingPlanSynchronizationStrategy synchronizationStrategy);

/**
* Set a filter configuration and define which types must be included/excluded when indexed within the current session.
* If the type is not explicitly included/excluded directly or as a supertype the decision will be made by
* Set a filter configuration and define which types must be included/excluded when indexed within indexing plans
* of the current session (either automatically or manually).
* <p>
* This does not affect indexing that does not rely on indexing plans, like the mass indexer.
* <p>
* If a type is not explicitly included/excluded directly or through an included/excluded supertype,
* the decision will be made by
* {@link SearchMapping#indexingPlanFilter(SearchIndexingPlanFilter) an application filter}, which defaults to including all types.
*
* @param filter The filter that includes/excludes types when indexed.
Expand Down
Expand Up @@ -10,22 +10,26 @@
import org.hibernate.search.util.common.annotation.Incubating;

/**
* A context that helps with the indexing filter configuration.
* A context that helps with the indexing plan filter configuration.
* <p>
* Note that only indexed types can be passed to this context's methods. Passing any other types will lead to an
* exception being thrown. Methods only accept classes and not interfaces.
* Note that only indexed and contained types with their supertypes can be passed to this context's methods.
* Methods that accept {@link Class classes} as their parameters will not allow interfaces, even if they are among supertypes.
* Passing any other types will lead to an exception being thrown.
* <p>
* {@code Object.class} being a supertype to other types can be passed to context methods and will result in including/excluding all types
* with possible further fine-tuning using more specific types.
* <p>
* Include/exclude rules work as follows:
* <ul>
* <li>If the type {@code A} is explicitly included by the filter, then a change to an object that is exactly of an indexed type {@code A} is processed.</li>
* <li>If the type {@code A} is explicitly excluded by the filter, then a change to an object that is exactly of an indexed type {@code A} is ignored.</li>
* <li>If the type {@code A} is explicitly included by the filter, then a change to an object that is exactly of an type {@code A} is processed.</li>
* <li>If the type {@code A} is explicitly excluded by the filter, then a change to an object that is exactly of an type {@code A} is ignored.</li>
* <li>
* If the type {@code A} is explicitly included by the filter, then a change to an object that is exactly of an indexed type {@code B},
* If the type {@code A} is explicitly included by the filter, then a change to an object that is exactly of an type {@code B},
* which is a subtype of the type {@code A},
* is processed unless the filter explicitly excludes a more specific supertype of a type {@code B}.
* </li>
* <li>
* If the type {@code A} is excluded by the filter explicitly, then a change to an object that is exactly of an indexed type {@code B},
* If the type {@code A} is excluded by the filter explicitly, then a change to an object that is exactly of an type {@code B},
* which is a subtype of the type {@code A},
* is ignored unless the filter explicitly includes a more specific supertype of a type {@code B}.
* </li>
Expand All @@ -37,37 +41,37 @@
public interface SearchIndexingPlanFilterContext {

/**
* Specify an indexed type to include, along with (unless specified otherwise) all its subtypes.
* Specify a name of an indexed/contained type (or a name of its named supertype) to include, along with (unless specified otherwise) all its subtypes.
*
* @param name The name of an indexed type.
* @param name The name of a named type to include according to include/exclude rules.
* @return The same context, for chained calls.
*
* @see Indexed#index()
*/
SearchIndexingPlanFilterContext include(String name);

/**
* Specify an indexed type to include, along with (unless specified otherwise) all its subtypes.
* Specify an indexed/contained type (or its supertype class) to include, along with (unless specified otherwise) all its subtypes.
*
* @param clazz The class of an indexed type.
* @param clazz The class to include according to include/exclude rules.
* @return The same context, for chained calls.
*/
SearchIndexingPlanFilterContext include(Class<?> clazz);

/**
* Specify an indexed type to exclude, along with (unless specified otherwise) all its subtypes.
* Specify a name of an indexed/contained type (or a name of its named supertype) to exclude, along with (unless specified otherwise) all its subtypes.
*
* @param name The name of an indexed type.
* @param name The name of a named type to exclude according to include/exclude rules.
* @return The same context, for chained calls.
*
* @see Indexed#index()
*/
SearchIndexingPlanFilterContext exclude(String name);

/**
* Specify an indexed type to exclude, along with (unless specified otherwise) all its subtypes.
* Specify an indexed/contained type (or its supertype class) to exclude, along with (unless specified otherwise) all its subtypes.
*
* @param clazz The class of an indexed type.
* @param clazz The class to exclude according to include/exclude rules.
* @return The same context, for chained calls.
*/
SearchIndexingPlanFilterContext exclude(Class<?> clazz);
Expand Down

0 comments on commit 2e19e0d

Please sign in to comment.