diff --git a/documentation/src/main/asciidoc/reference/mapper-orm-bridge-routingkeybridge.asciidoc b/documentation/src/main/asciidoc/reference/mapper-orm-bridge-routingkeybridge.asciidoc index ec62cb9994c..8befdb8999f 100644 --- a/documentation/src/main/asciidoc/reference/mapper-orm-bridge-routingkeybridge.asciidoc +++ b/documentation/src/main/asciidoc/reference/mapper-orm-bridge-routingkeybridge.asciidoc @@ -3,27 +3,123 @@ == Basics -include::todo-placeholder.asciidoc[] -//TODO HSEARCH-3709 basic example with annotation, binder, bridge, mapping +include::components/sharding-intro-note.asciidoc[] +A routing key bridge is a pluggable component that implements +the mapping of an entity type to a routing key, +which will determine <>. +It is applied to a type with the `@RoutingKeyBinding` annotation +or with a <>. + +Implementing a routing key bridge requires two components: + +. A custom implementation of `RoutingKeyBinder`, to bind the bridge to a type at bootstrap. +This involves declaring the properties of the type that will be used +and instantiating the type bridge. +. A custom implementation of `RoutingKeyBridge`, to generate a routing key at runtime. +This involves extracting data from an instance of the type, transforming the data if necessary, +and returning the routing key. + +Below is an example of a custom routing key bridge that +uses the `genre` property of the `Book` class as a routing key. + +.Implementing and using a `RoutingKeyBridge` +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/BookGenreRoutingKeyBinder.java[tags=binder] +---- +<1> The binder must implement the `RoutingKeyBinder` interface. +<2> Implement the `bind` method in the binder. +<3> Declare the dependencies of the bridge, +i.e. the parts of the type instances that the bridge will actually use. +See <> +for more information about declaring dependencies. +<4> Call `context.bridge(...)` to define the type bridge to use, +and pass an instance of the bridge. + +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/BookGenreRoutingKeyBinder.java[tags=bridge] +---- +<1> The bridge must implement the `RoutingKeyBridge` interface. +Here the bridge class is nested in the binder class, +because it is more convenient, +but you are obviously free to implement it in a separate java file. +<2> Implement the `toRoutingKey` method in the bridge. +This method is called on indexing. +<3> The bridged element is passed as an `Object`, +so cast it to the correct type. +<4> Extract data from the bridged element, +and optionally transform it, +to generate the routing key. +<5> Return the routing key. + +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/Book.java[tags=include;!getters-setters] +---- +<1> Apply the bridge using the `@RoutingKeyBridge` annotation. +<2> Properties used in the bridge can still be mapped as index fields. +==== + +See <> for an example of how to use routing in search queries, with the same mapping as above. + +[[mapper-orm-bridge-routingkeybridge-parameters]] == Passing parameters -include::todo-placeholder.asciidoc[] -//TODO HSEARCH-3709 parameterized example +Routing key bridges are usually applied with the built-in `@RoutingKeyBinding` annotation. +which does not accept any parameter other than the routing key binder. + +In some cases, it is necessary to pass parameters directly to the routing key binder. +This is achieved by defining a <> with attributes. + +Refer to <>, +which is fairly similar to what you'll need for a `RoutingKeyBinder`. == Accessing the ORM session from the bridge -include::todo-placeholder.asciidoc[] -// TODO HSEARCH-3709 HibernateOrmExtension.get()? Make sure to warn that not all operations are valid. +Contexts passed to the bridge methods can be used to retrieve the Hibernate ORM session. + +.Retrieving the ORM session from a `RoutingKeyBridge` +==== +[source, JAVA, indent=0, subs="+callouts"] +---- +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyEntityRoutingKeyBinder.java[tags=include] +---- +<1> Apply an extension to the context to access content specific to Hibernate ORM. +<2> Retrieve the `Session` from the extended context. +==== == Injecting beans into the binder -include::todo-placeholder.asciidoc[] -// TODO HSEARCH-3709 say it's supported, give some basic information and link to <> +With <>, +Hibernate Search supports injecting beans into: + +* the `TypeMappingAnnotationProcessor` if you use custom annotations and instantiate the binder yourself. +* the `RoutingKeyBinder` if you use the `@RoutingKeyBinding` annotation and let Hibernate Search +instantiate the binder using your dependency injection framework. + +NOTE: This only applies to binders instantiated by Hibernate Search itself. +As a rule of thumb, if you need to call `new MyBinder()` at some point, +the binder won't get auto-magically injected. + +The context passed to the type binder's `bind` method +also exposes a `getBeanResolver` method to access the bean resolver and instantiate beans explicitly. + +See <> for more details. == Incubating features include::components/incubating-warning.asciidoc[] -include::todo-placeholder.asciidoc[] -// TODO HSEARCH-3709 incubating support for reflection with bridgedElement() (advanced use, no example) +The context passed to the routing key binder's `bind` method +exposes a `bridgedElement()` method that gives access to metadata about the type being bound. + +The metadata can in particular be used to inspect the type in details: + +* Getting accessors to properties. +* Detecting properties with markers. +Markers are applied by specific annotations carrying a `@MarkerBinding` meta-annotation. + +See the javadoc for more information. diff --git a/documentation/src/main/asciidoc/reference/search-dsl-query.asciidoc b/documentation/src/main/asciidoc/reference/search-dsl-query.asciidoc index 402655d6de8..dccb6c19306 100644 --- a/documentation/src/main/asciidoc/reference/search-dsl-query.asciidoc +++ b/documentation/src/main/asciidoc/reference/search-dsl-query.asciidoc @@ -284,7 +284,7 @@ when building the query: ==== [source, JAVA, indent=0, subs="+callouts"] ---- -include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/routing/HibernateOrmRoutingIT.java[tags=routing-single] +include::{sourcedir}/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/RoutingKeyBridgeSimpleIT.java[tags=routing-single] ---- <1> Start building the query. <2> Define that only documents matching the given `genre` should be returned. diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyData.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyData.java new file mode 100644 index 00000000000..0adc63467b5 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyData.java @@ -0,0 +1,14 @@ +/* + * 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 . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.ormcontext; + +public enum MyData { + + INDEXED, + PROJECTED; + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyEntity.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyEntity.java new file mode 100644 index 00000000000..6b265ca579c --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyEntity.java @@ -0,0 +1,29 @@ +/* + * 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 . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.ormcontext; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.search.mapper.pojo.bridge.mapping.annotation.RoutingKeyBinderRef; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.RoutingKeyBinding; + +@Entity +@Indexed +@RoutingKeyBinding(binder = @RoutingKeyBinderRef(type = MyEntityRoutingKeyBinder.class)) +public class MyEntity { + + @Id + @GeneratedValue + private Integer id; + + public Integer getId() { + return id; + } +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyEntityRoutingKeyBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyEntityRoutingKeyBinder.java new file mode 100644 index 00000000000..d1b7d53d525 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/MyEntityRoutingKeyBinder.java @@ -0,0 +1,49 @@ +/* + * 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 . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.ormcontext; + +import org.hibernate.Session; +import org.hibernate.search.mapper.orm.HibernateOrmExtension; +import org.hibernate.search.mapper.pojo.bridge.RoutingKeyBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.RoutingKeyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.RoutingKeyBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.RoutingKeyBridgeToRoutingKeyContext; + +public class MyEntityRoutingKeyBinder implements RoutingKeyBinder { + + @Override + public void bind(RoutingKeyBindingContext context) { + context.dependencies() + .useRootOnly(); + + context.bridge( new Bridge() ); + } + + //tag::include[] + private static class Bridge implements RoutingKeyBridge { + + @Override + public String toRoutingKey(String tenantIdentifier, Object entityIdentifier, + Object bridgedElement, RoutingKeyBridgeToRoutingKeyContext context) { + Session session = context.extension( HibernateOrmExtension.get() ) // <1> + .session(); // <2> + String routingKey; + // ... do something with the session ... + //end::include[] + /* + * I don't know what to do with the session here, + * so I'm just going to extract data from it. + * This is silly, but at least it allows us to check the session was successfully retrieved. + */ + MyData dataFromSession = (MyData) session.getProperties().get( "test.data.indexed" ); + routingKey = dataFromSession.name(); + //tag::include[] + return routingKey; + } + } + //end::include[] +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/RoutingKeyBridgeOrmContextIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/RoutingKeyBridgeOrmContextIT.java new file mode 100644 index 00000000000..dacb19d70ff --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/ormcontext/RoutingKeyBridgeOrmContextIT.java @@ -0,0 +1,81 @@ +/* + * 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 . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.ormcontext; + + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import javax.persistence.EntityManagerFactory; + +import org.hibernate.search.documentation.testsupport.BackendConfigurations; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.automaticindexing.session.AutomaticIndexingSynchronizationStrategyNames; +import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; +import org.hibernate.search.mapper.orm.session.SearchSession; +import org.hibernate.search.util.impl.integrationtest.common.rule.BackendConfiguration; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmSetupHelper; +import org.hibernate.search.util.impl.integrationtest.mapper.orm.OrmUtils; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class RoutingKeyBridgeOrmContextIT { + + private static final int SHARD_COUNT = 4; + + @Parameterized.Parameters(name = "{0}") + public static Object[] backendSetups() { + return BackendConfigurations.hashBasedSharding( SHARD_COUNT ).toArray(); + } + + @Rule + public OrmSetupHelper setupHelper; + + private EntityManagerFactory entityManagerFactory; + + public RoutingKeyBridgeOrmContextIT(BackendConfiguration backendConfiguration) { + this.setupHelper = OrmSetupHelper.withSingleBackend( backendConfiguration ); + } + + @Before + public void setup() { + entityManagerFactory = setupHelper.start() + .withProperty( + HibernateOrmMapperSettings.AUTOMATIC_INDEXING_SYNCHRONIZATION_STRATEGY, + AutomaticIndexingSynchronizationStrategyNames.SYNC + ) + .setup( MyEntity.class ); + } + + @Test + public void smoke() { + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + // See MyDataPropertyBinder + entityManager.setProperty( "test.data.indexed", MyData.INDEXED ); + + MyEntity myEntity = new MyEntity(); + entityManager.persist( myEntity ); + } ); + + OrmUtils.withinJPATransaction( entityManagerFactory, entityManager -> { + SearchSession searchSession = Search.session( entityManager ); + + List result = searchSession.search( MyEntity.class ) + .where( f -> f.matchAll() ) + .routing( "INDEXED" ) + .fetchHits( 20 ); + + assertThat( result ).hasSize( 1 ); + } ); + } + +} diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/Book.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/Book.java similarity index 79% rename from documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/Book.java rename to documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/Book.java index bd77b738b5d..c844f984af0 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/Book.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/Book.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.documentation.mapper.orm.routing; +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.simple; import javax.persistence.Basic; import javax.persistence.Entity; @@ -16,9 +16,10 @@ import org.hibernate.search.mapper.pojo.mapping.definition.annotation.KeywordField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.RoutingKeyBinding; +// tag::include[] @Entity @Indexed -@RoutingKeyBinding(binder = @RoutingKeyBinderRef(type = BookRoutingKeyBridge.Binder.class)) +@RoutingKeyBinding(binder = @RoutingKeyBinderRef(type = BookGenreRoutingKeyBinder.class)) // <1> public class Book { @Id @@ -28,12 +29,16 @@ public class Book { private String title; @Basic(optional = false) - @KeywordField + @KeywordField // <2> private Genre genre; public Book() { } + // Getters and setters + // ... + + // tag::getters-setters[] public Integer getId() { return id; } @@ -57,5 +62,6 @@ public Genre getGenre() { public void setGenre(Genre genre) { this.genre = genre; } - +// end::getters-setters[] } +// end::include[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/BookGenreRoutingKeyBinder.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/BookGenreRoutingKeyBinder.java new file mode 100644 index 00000000000..5acd7f94d72 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/BookGenreRoutingKeyBinder.java @@ -0,0 +1,42 @@ +/* + * 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 . + */ +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.simple; + +import org.hibernate.search.mapper.pojo.bridge.RoutingKeyBridge; +import org.hibernate.search.mapper.pojo.bridge.binding.RoutingKeyBindingContext; +import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.RoutingKeyBinder; +import org.hibernate.search.mapper.pojo.bridge.runtime.RoutingKeyBridgeToRoutingKeyContext; + +//tag::binder[] +public class BookGenreRoutingKeyBinder implements RoutingKeyBinder { // <1> + + @Override + public void bind(RoutingKeyBindingContext context) { // <2> + context.dependencies() // <3> + .use( "genre" ); + + context.bridge( new Bridge() ); // <4> + } + + // ... class continues below + //end::binder[] + //tag::bridge[] + // ... class BookGenreRoutingKeyBinder (continued) + + public static class Bridge implements RoutingKeyBridge { // <1> + + @Override + public String toRoutingKey(String tenantIdentifier, Object entityIdentifier, // <2> + Object bridgedElement, RoutingKeyBridgeToRoutingKeyContext context) { + Book book = (Book) bridgedElement; // <3> + String routingKey = book.getGenre().name(); // <4> + return routingKey; // <5> + } + + } +} +//end::bridge[] diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/Genre.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/Genre.java similarity index 77% rename from documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/Genre.java rename to documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/Genre.java index e0b8ff1b9b5..4b145e8592f 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/Genre.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/Genre.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.documentation.mapper.orm.routing; +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.simple; public enum Genre { diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/HibernateOrmRoutingIT.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/RoutingKeyBridgeSimpleIT.java similarity index 94% rename from documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/HibernateOrmRoutingIT.java rename to documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/RoutingKeyBridgeSimpleIT.java index 85715a2164d..4e465b5b654 100644 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/HibernateOrmRoutingIT.java +++ b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/bridge/routingkeybridge/simple/RoutingKeyBridgeSimpleIT.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or . */ -package org.hibernate.search.documentation.mapper.orm.routing; +package org.hibernate.search.documentation.mapper.orm.bridge.routingkeybridge.simple; import static org.assertj.core.api.Assertions.assertThat; @@ -27,7 +27,7 @@ import org.junit.runners.Parameterized; @RunWith(Parameterized.class) -public class HibernateOrmRoutingIT { +public class RoutingKeyBridgeSimpleIT { private static final int SHARD_COUNT = 4; @@ -46,7 +46,7 @@ public static Object[] backendSetups() { private EntityManagerFactory entityManagerFactory; - public HibernateOrmRoutingIT(BackendConfiguration backendConfiguration) { + public RoutingKeyBridgeSimpleIT(BackendConfiguration backendConfiguration) { this.setupHelper = OrmSetupHelper.withSingleBackend( backendConfiguration ); } diff --git a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/BookRoutingKeyBridge.java b/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/BookRoutingKeyBridge.java deleted file mode 100644 index d4a602cb52c..00000000000 --- a/documentation/src/test/java/org/hibernate/search/documentation/mapper/orm/routing/BookRoutingKeyBridge.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 . - */ -package org.hibernate.search.documentation.mapper.orm.routing; - -import org.hibernate.search.mapper.pojo.bridge.RoutingKeyBridge; -import org.hibernate.search.mapper.pojo.bridge.binding.RoutingKeyBindingContext; -import org.hibernate.search.mapper.pojo.bridge.mapping.programmatic.RoutingKeyBinder; -import org.hibernate.search.mapper.pojo.bridge.runtime.RoutingKeyBridgeToRoutingKeyContext; - -public class BookRoutingKeyBridge implements RoutingKeyBridge { - - @Override - public String toRoutingKey(String tenantIdentifier, Object entityIdentifier, Object bridgedElement, - RoutingKeyBridgeToRoutingKeyContext context) { - return ( (Book) bridgedElement ).getGenre().name(); - } - - public static class Binder implements RoutingKeyBinder { - @Override - public void bind(RoutingKeyBindingContext context) { - context.dependencies().use( "genre" ); - context.bridge( new BookRoutingKeyBridge() ); - } - } -}