Skip to content

Commit

Permalink
Merge pull request #13696 from yrodiere/search-inject
Browse files Browse the repository at this point in the history
Allow injection of SearchSession/SearchMapping
  • Loading branch information
gsmet authored Dec 7, 2020
2 parents ca3fe91 + abf99e7 commit 606aa6c
Show file tree
Hide file tree
Showing 20 changed files with 704 additions and 31 deletions.
46 changes: 24 additions & 22 deletions docs/src/main/asciidoc/hibernate-search-orm-elasticsearch.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ We will also explore how you can can query your Elasticsearch cluster using the

include::./status-include.adoc[]

[WARNING]
====
This extension is based on a beta version of Hibernate Search.
While APIs are quite stable and the code is of production quality and thoroughly tested, some features are still missing, performance might not be optimal and some APIs or configuration properties might change as the extension matures.
====

== Prerequisites

To complete this guide, you need:
Expand Down Expand Up @@ -481,16 +475,26 @@ It is definitely too aggressive for person names but it is perfect for the book

== Adding full text capabilities to our REST service

In our existing `LibraryResource`, we just need to inject the following methods (and add a few ``import``s):
In our existing `LibraryResource`, we just need to inject the `SearchSession`:

[source,java]
----
@Inject
SearchSession searchSession; // <1>
----
<1> Inject a Hibernate Search session, which relies on the `EntityManager` under the hood.
Applications with multiple persistence units can use a CDI qualifier to select the right one:
either `@Named` or `@io.quarkus.hibernate.orm.PersistenceUnit`.

And then we can add the following methods (and a few ``import``s):

[source,java]
----
@Transactional // <1>
void onStart(@Observes StartupEvent ev) throws InterruptedException { // <2>
// only reindex if we imported some content
if (Book.count() > 0) {
Search.session(em)
.massIndexer()
searchSession.massIndexer()
.startAndWait();
}
}
Expand All @@ -500,16 +504,15 @@ In our existing `LibraryResource`, we just need to inject the following methods
@Transactional
public List<Author> searchAuthors(@QueryParam String pattern, // <4>
@QueryParam Optional<Integer> size) {
return Search.session(em) // <5>
.search(Author.class) // <6>
return searchSession.search(Author.class) // <5>
.where(f ->
pattern == null || pattern.trim().isEmpty() ?
f.matchAll() : // <7>
f.matchAll() : // <6>
f.simpleQueryString()
.fields("firstName", "lastName", "books.title").matching(pattern) // <8>
.fields("firstName", "lastName", "books.title").matching(pattern) // <7>
)
.sort(f -> f.field("lastName_sort").then().field("firstName_sort")) // <9>
.fetchHits(size.orElse(20)); // <10>
.sort(f -> f.field("lastName_sort").then().field("firstName_sort")) // <8>
.fetchHits(size.orElse(20)); // <9>
}
----
<1> Important point: we need a transactional context for these methods.
Expand All @@ -520,12 +523,11 @@ If you don't import data manually in the database, you don't need that:
the mass indexer should then only be used when you change your indexing configuration (adding a new field, changing an analyzer's configuration...) and you want the new configuration to be applied to your existing entities.
<3> This is where the magic begins: just adding the annotations to our entities makes them available for full text search: we can now query the index using the Hibernate Search DSL.
<4> Use the `org.jboss.resteasy.annotations.jaxrs.QueryParam` annotation type to avoid repeating the parameter name.
<5> First, we get an Hibernate Search session from the injected entity manager.
<6> We indicate that we are searching for ``Author``s.
<7> We create a predicate: if the pattern is empty, we use a `matchAll()` predicate.
<8> If we have a valid pattern, we create a https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html[`simpleQueryString()`] predicate on the `firstName`, `lastName` and `books.title` fields matching our pattern.
<9> We define the sort order of our results. Here we sort by last name, then by first name. Note that we use the specific fields we created for sorting.
<10> Fetch the `size` top hits, `20` by default. Obviously, paging is also supported.
<5> We indicate that we are searching for ``Author``s.
<6> We create a predicate: if the pattern is empty, we use a `matchAll()` predicate.
<7> If we have a valid pattern, we create a https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html[`simpleQueryString()`] predicate on the `firstName`, `lastName` and `books.title` fields matching our pattern.
<8> We define the sort order of our results. Here we sort by last name, then by first name. Note that we use the specific fields we created for sorting.
<9> Fetch the `size` top hits, `20` by default. Obviously, paging is also supported.

[NOTE]
====
Expand Down Expand Up @@ -675,7 +677,7 @@ This couldn't be done in the 5.x code base so we decided to go with the in-progr

=== Can I really use it?

While Hibernate Search 6 is still at Beta stage, the code is of production quality and can be relied on.
Hibernate Search 6 is currently in the Candidate Release phase: the code is of production quality and can be relied on.

What we don't guarantee is that there might be API changes along the way to the final release of Hibernate Search 6 and you might have to adapt your code.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,11 @@ public boolean isJoinedToTransaction() {
}

@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> cls) {
if (cls.isAssignableFrom(Session.class)) {
return (T) this;
}
return delegate().unwrap(cls);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,11 @@ public boolean isJoinedToTransaction() {
}

@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> cls) {
if (cls.isAssignableFrom(Session.class)) {
return (T) this;
}
checkBlocking();
try (SessionResult emr = acquireSession()) {
return emr.session.unwrap(cls);
Expand Down Expand Up @@ -498,9 +502,7 @@ public Transaction getTransaction() {

@Override
public EntityManagerFactory getEntityManagerFactory() {
try (SessionResult emr = acquireSession()) {
return emr.session.getEntityManagerFactory();
}
return sessionFactory;
}

@Override
Expand Down Expand Up @@ -592,9 +594,7 @@ public CacheMode getCacheMode() {

@Override
public SessionFactory getSessionFactory() {
try (SessionResult emr = acquireSession()) {
return emr.session.getSessionFactory();
}
return sessionFactory;
}

@Override
Expand Down
127 changes: 127 additions & 0 deletions extensions/hibernate-search-orm-elasticsearch/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -52,6 +67,118 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>


<profiles>
<profile>
<id>test-elasticsearch</id>
<activation>
<property>
<name>test-containers</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</profile>

<profile>
<id>docker-elasticsearch</id>
<activation>
<property>
<name>start-containers</name>
</property>
</activation>
<properties>
<elasticsearch.hosts>localhost:9200</elasticsearch.hosts>
<elasticsearch.protocol>http</elasticsearch.protocol>
</properties>
<build>
<plugins>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<images>
<image>
<name>${elasticsearch.image}</name>
<alias>elasticsearch</alias>
<run>
<env>
<discovery.type>single-node</discovery.type>
</env>
<ports>
<port>9200:9200</port>
</ports>
<log>
<prefix>Elasticsearch: </prefix>
<date>default</date>
<color>cyan</color>
</log>
<wait>
<http>
<url>http://localhost:9200</url>
<method>GET</method>
<status>200</status>
</http>
<time>20000</time>
</wait>
</run>
</image>
</images>
<allContainers>true</allContainers>
</configuration>
<executions>
<execution>
<id>docker-start</id>
<phase>process-test-classes</phase>
<goals>
<goal>stop</goal>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>docker-stop</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>docker-prune</id>
<phase>generate-resources</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${basedir}/../../../.github/docker-prune.sh</executable>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package io.quarkus.hibernate.search.orm.elasticsearch;

import java.util.List;
import java.util.function.Supplier;

import javax.enterprise.inject.Default;
import javax.inject.Singleton;

import org.hibernate.search.mapper.orm.mapping.SearchMapping;
import org.hibernate.search.mapper.orm.session.SearchSession;

import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.hibernate.orm.PersistenceUnit;
import io.quarkus.hibernate.orm.deployment.PersistenceUnitDescriptorBuildItem;
import io.quarkus.hibernate.orm.runtime.PersistenceUnitUtil;
import io.quarkus.hibernate.search.orm.elasticsearch.runtime.HibernateSearchElasticsearchRecorder;

public class HibernateSearchElasticsearchCdiProcessor {

@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void generateSearchBeans(HibernateSearchElasticsearchRecorder recorder,
List<PersistenceUnitDescriptorBuildItem> persistenceUnitDescriptors,
List<JdbcDataSourceBuildItem> jdbcDataSources, // just make sure the datasources are initialized
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer) {
for (PersistenceUnitDescriptorBuildItem persistenceUnitDescriptor : persistenceUnitDescriptors) {
String persistenceUnitName = persistenceUnitDescriptor.getPersistenceUnitName();

boolean isDefaultPersistenceUnit = PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName);
syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
isDefaultPersistenceUnit,
SearchMapping.class,
recorder.searchMappingSupplier(persistenceUnitName, isDefaultPersistenceUnit)));

syntheticBeanBuildItemBuildProducer
.produce(createSyntheticBean(persistenceUnitName,
PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName),
SearchSession.class,
recorder.searchSessionSupplier(persistenceUnitName, isDefaultPersistenceUnit)));
}
}

private static <T> SyntheticBeanBuildItem createSyntheticBean(String persistenceUnitName, boolean isDefaultPersistenceUnit,
Class<T> type, Supplier<T> supplier) {
SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem
.configure(type)
.scope(Singleton.class)
.unremovable()
.supplier(supplier);

if (isDefaultPersistenceUnit) {
configurator.addQualifier(Default.class);
}

configurator.addQualifier().annotation(DotNames.NAMED).addValue("value", persistenceUnitName).done();
configurator.addQualifier().annotation(PersistenceUnit.class).addValue("value", persistenceUnitName).done();

return configurator.done();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.hibernate.search.elasticsearch.test;
package io.quarkus.hibernate.search.elasticsearch.test.configuration;

import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.hibernate.search.elasticsearch.test;
package io.quarkus.hibernate.search.elasticsearch.test.configuration;

import java.sql.SQLException;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.quarkus.hibernate.search.elasticsearch.test;
package io.quarkus.hibernate.search.elasticsearch.test.configuration;

import java.sql.SQLException;

Expand Down
Loading

0 comments on commit 606aa6c

Please sign in to comment.