diff --git a/Jenkinsfile b/Jenkinsfile
index ffe0c4e36f5..ad2366ad00c 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -494,7 +494,7 @@ stage('Non-default environments') {
helper.withMavenWorkspace {
mavenNonDefaultBuild buildEnv, """ \
clean install \
- -pl org.hibernate.search:hibernate-search-integrationtest-orm,org.hibernate.search:hibernate-search-integrationtest-showcase-library \
+ -pl org.hibernate.search:hibernate-search-integrationtest-orm,org.hibernate.search:hibernate-search-integrationtest-mapper-orm-envers,org.hibernate.search:hibernate-search-integrationtest-showcase-library \
-P$buildEnv.mavenProfile \
"""
}
diff --git a/integrationtest/mapper/orm-envers/pom.xml b/integrationtest/mapper/orm-envers/pom.xml
new file mode 100644
index 00000000000..0f4793960a4
--- /dev/null
+++ b/integrationtest/mapper/orm-envers/pom.xml
@@ -0,0 +1,65 @@
+
+ 4.0.0
+
+ org.hibernate.search
+ hibernate-search-integrationtest
+ 6.0.0-SNAPSHOT
+ ../..
+
+ hibernate-search-integrationtest-mapper-orm-envers
+
+ Hibernate Search Integration Tests - ORM - Envers
+ Hibernate Search integration tests for the Hibernate ORM integration with Envers
+
+
+
+ org.hibernate.search
+ hibernate-search-mapper-orm
+ test
+
+
+ org.hibernate
+ hibernate-envers
+ test
+
+
+ org.hibernate.search
+ hibernate-search-util-internal-integrationtest-orm
+ test
+
+
+
+ ${jdbc.driver.groupId}
+ ${jdbc.driver.artifactId}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ it
+
+ integration-test
+ verify
+
+
+
+
+
+
+
+
+
diff --git a/integrationtest/mapper/orm-envers/src/test/java/org/hibernate/search/integrationtest/mapper/orm/envers/EnversIT.java b/integrationtest/mapper/orm-envers/src/test/java/org/hibernate/search/integrationtest/mapper/orm/envers/EnversIT.java
new file mode 100644
index 00000000000..0e1696158f4
--- /dev/null
+++ b/integrationtest/mapper/orm-envers/src/test/java/org/hibernate/search/integrationtest/mapper/orm/envers/EnversIT.java
@@ -0,0 +1,317 @@
+/*
+ * 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.integrationtest.mapper.orm.envers;
+
+import static org.hibernate.search.util.impl.integrationtest.common.stub.backend.StubBackendUtils.reference;
+
+import java.util.Arrays;
+import java.util.Optional;
+import javax.persistence.Basic;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.OneToOne;
+
+import org.hibernate.SessionFactory;
+import org.hibernate.envers.AuditReader;
+import org.hibernate.envers.AuditReaderFactory;
+import org.hibernate.envers.Audited;
+import org.hibernate.envers.query.AuditEntity;
+import org.hibernate.search.mapper.orm.Search;
+import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
+import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
+import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded;
+import org.hibernate.search.util.impl.integrationtest.common.rule.BackendMock;
+import org.hibernate.search.util.impl.integrationtest.common.rule.StubSearchWorkBehavior;
+import org.hibernate.search.util.impl.integrationtest.orm.OrmSetupHelper;
+import org.hibernate.search.util.impl.integrationtest.orm.OrmUtils;
+import org.hibernate.search.util.impl.test.annotation.PortedFromSearch5;
+import org.hibernate.search.util.impl.test.annotation.TestForIssue;
+import org.hibernate.search.util.impl.test.rule.StaticCounters;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import org.assertj.core.api.SoftAssertions;
+
+@TestForIssue(jiraKey = { "HSEARCH-1293", "HSEARCH-3667" })
+@PortedFromSearch5(original = "org.hibernate.search.test.envers.SearchAndEnversIntegrationTest")
+public class EnversIT {
+
+ @Rule
+ public BackendMock backendMock = new BackendMock( "stubBackend" );
+
+ @Rule
+ public OrmSetupHelper ormSetupHelper = OrmSetupHelper.withBackendMock( backendMock );
+
+ @Rule
+ public StaticCounters counters = new StaticCounters();
+
+ private SessionFactory sessionFactory;
+
+ @Before
+ public void setup() {
+ backendMock.expectSchema( IndexedEntity.INDEX, b -> b
+ .field( "text", String.class )
+ .objectField( "contained", b2 -> b2
+ .field( "text", String.class )
+ )
+ );
+
+ sessionFactory = ormSetupHelper.start()
+ .setup(
+ IndexedEntity.class,
+ ContainedEntity.class
+ );
+ backendMock.verifyExpectationsMet();
+ }
+
+ @Test
+ public void test() {
+ // Initial insert
+ OrmUtils.withinTransaction( sessionFactory, session -> {
+ IndexedEntity indexed = new IndexedEntity();
+ indexed.setId( 1 );
+ indexed.setText( "initial" );
+ ContainedEntity contained = new ContainedEntity();
+ contained.setId( 1 );
+ contained.setText( "initial" );
+ indexed.setContained( contained );
+ contained.setContaining( indexed );
+
+ session.persist( indexed );
+ session.persist( contained );
+
+ backendMock.expectWorks( IndexedEntity.INDEX )
+ .add( "1", b -> b
+ .field( "text", "initial" )
+ .objectField( "contained", b2 -> b2
+ .field( "text", "initial" )
+ )
+ )
+ .preparedThenExecuted();
+ } );
+ backendMock.verifyExpectationsMet();
+ checkEnversAuditedCorrectly( IndexedEntity.class,
+ 1, 1, 1, 1 );
+ checkEnversAuditedCorrectly( ContainedEntity.class,
+ 1, 1, 1, 1 );
+ checkSearchLoadedEntityIsLastVersion( "1", "initial", "initial" );
+
+ // Update the indexed entity
+ OrmUtils.withinTransaction( sessionFactory, session -> {
+ IndexedEntity indexed = session.getReference( IndexedEntity.class, 1 );
+ indexed.setText( "updated" );
+
+ backendMock.expectWorks( IndexedEntity.INDEX )
+ .update( "1", b -> b
+ .field( "text", "updated" )
+ .objectField( "contained", b2 -> b2
+ .field( "text", "initial" )
+ )
+ )
+ .preparedThenExecuted();
+ } );
+ backendMock.verifyExpectationsMet();
+ checkEnversAuditedCorrectly( IndexedEntity.class,
+ 2, 2, 1, 2 );
+ checkEnversAuditedCorrectly( ContainedEntity.class,
+ 2, 1, 0, 1 );
+ checkSearchLoadedEntityIsLastVersion( "1", "updated", "initial" );
+
+ // Update the contained entity
+ OrmUtils.withinTransaction( sessionFactory, session -> {
+ ContainedEntity contained = session.getReference( ContainedEntity.class, 1 );
+ contained.setText( "updated" );
+
+ backendMock.expectWorks( IndexedEntity.INDEX )
+ .update( "1", b -> b
+ .field( "text", "updated" )
+ .objectField( "contained", b2 -> b2
+ .field( "text", "updated" )
+ )
+ )
+ .preparedThenExecuted();
+ } );
+ backendMock.verifyExpectationsMet();
+ checkEnversAuditedCorrectly( IndexedEntity.class,
+ 3, 2, 0, 2 );
+ checkEnversAuditedCorrectly( ContainedEntity.class,
+ 3, 3, 1, 2 );
+ checkSearchLoadedEntityIsLastVersion( "1", "updated", "updated" );
+
+ // Delete the indexed entity
+ OrmUtils.withinTransaction( sessionFactory, session -> {
+ IndexedEntity indexed = session.getReference( IndexedEntity.class, 1 );
+ session.delete( indexed );
+
+ backendMock.expectWorks( IndexedEntity.INDEX )
+ .delete( "1" )
+ .preparedThenExecuted();
+ } );
+ backendMock.verifyExpectationsMet();
+ checkEnversAuditedCorrectly( IndexedEntity.class,
+ 4, 4, 1, 3 );
+ checkEnversAuditedCorrectly( ContainedEntity.class,
+ 4, 4, 1, 3 );
+ }
+
+ private void checkEnversAuditedCorrectly(Class> type,
+ int lastRevisionOverall,
+ int expectedLastRevisionForType,
+ int expectedEntityChangeCountAtLastRevisionOverall,
+ int expectedAuditedObjectCountSoFar) {
+ OrmUtils.withinTransaction( sessionFactory, session -> {
+ AuditReader auditReader = AuditReaderFactory.get( session );
+ SoftAssertions.assertSoftly( assertions -> {
+ assertions.assertThat( findLastRevisionForEntity( auditReader, type ) )
+ .as( "Last revision for entity type " + type )
+ .isEqualTo( expectedLastRevisionForType );
+ assertions.assertThat( howManyEntitiesChangedAtRevisionNumber( auditReader, type, lastRevisionOverall ) )
+ .as( "Number of entity changed at revision " + lastRevisionOverall + " for entity type " + type )
+ .isEqualTo( expectedEntityChangeCountAtLastRevisionOverall );
+ assertions.assertThat( howManyAuditedObjectsSoFar( auditReader, type ) )
+ .as( "Number of audited objects so far for entity type " + type )
+ .isEqualTo( expectedAuditedObjectCountSoFar );
+ } );
+ } );
+ }
+
+ private void checkSearchLoadedEntityIsLastVersion(String id,
+ String expectedIndexedEntityText, String expectedContainedEntityText) {
+ OrmUtils.withinTransaction( sessionFactory, session -> {
+ backendMock.expectSearchObjects(
+ Arrays.asList( IndexedEntity.INDEX ),
+ b -> b.limit( 2 ), // fetchSingleHit() (see below) sets the limit to 2 to check if there really is a single hit
+ StubSearchWorkBehavior.of(
+ 1L,
+ reference( IndexedEntity.INDEX, id )
+ )
+ );
+ Optional loadedEntity = Search.session( session ).search( IndexedEntity.class )
+ .predicate( f -> f.matchAll() )
+ .fetchSingleHit();
+ SoftAssertions.assertSoftly( assertions -> {
+ assertions.assertThat( loadedEntity ).get()
+ .as( "getText()" )
+ .extracting( IndexedEntity::getText )
+ .isEqualTo( expectedIndexedEntityText );
+ assertions.assertThat( loadedEntity ).get()
+ .as( "getContained().getText()" )
+ .extracting( e -> e.getContained().getText() )
+ .isEqualTo( expectedContainedEntityText );
+ } );
+ } );
+ }
+
+ /**
+ * It returns how many entities are modified for a specific class and number revision.
+ */
+ private int howManyEntitiesChangedAtRevisionNumber(AuditReader auditReader, Class> clazz, Number revision) {
+ return ( (Long) auditReader.createQuery().forEntitiesModifiedAtRevision( clazz, revision )
+ .addProjection( AuditEntity.id().count() ).getSingleResult() ).intValue();
+ }
+
+ /**
+ * It returns how many audited objects are there globally for a specific class.
+ */
+ private int howManyAuditedObjectsSoFar(AuditReader auditReader, Class> clazz) {
+ return auditReader.createQuery().forRevisionsOfEntity( clazz, true, true ).getResultList().size();
+ }
+
+ /**
+ * It returns the last revision for a specific class.
+ */
+ private Number findLastRevisionForEntity(AuditReader auditReader, Class> clazz) {
+ return (Number) auditReader.createQuery().forRevisionsOfEntity( clazz, false, true )
+ .addProjection( AuditEntity.revisionNumber().max() ).getSingleResult();
+ }
+
+ @Entity(name = "indexed")
+ @Indexed(index = IndexedEntity.INDEX)
+ @Audited(withModifiedFlag = true)
+ public static final class IndexedEntity {
+
+ static final String INDEX = "IndexedEntity";
+
+ @Id
+ private Integer id;
+
+ @Basic
+ @GenericField
+ private String text;
+
+ @OneToOne
+ @IndexedEmbedded
+ private ContainedEntity contained;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public ContainedEntity getContained() {
+ return contained;
+ }
+
+ public void setContained(
+ ContainedEntity contained) {
+ this.contained = contained;
+ }
+ }
+
+ @Entity(name = "idxembedded")
+ @Audited(withModifiedFlag = true)
+ public static class ContainedEntity {
+
+ @Id
+ private Integer id;
+
+ @Basic
+ @GenericField
+ private String text;
+
+ @OneToOne(mappedBy = "contained")
+ private IndexedEntity containing;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public IndexedEntity getContaining() {
+ return containing;
+ }
+
+ public void setContaining(IndexedEntity containing) {
+ this.containing = containing;
+ }
+ }
+
+}
diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml
index 0b6a95ce93d..a0600c0f3ef 100644
--- a/integrationtest/pom.xml
+++ b/integrationtest/pom.xml
@@ -23,6 +23,7 @@
backend/lucene
mapper/pojo
mapper/orm
+ mapper/orm-envers
showcase/library
diff --git a/legacy/orm/src/test/java/org/hibernate/search/test/envers/SearchAndEnversIntegrationTest.java b/legacy/orm/src/test/java/org/hibernate/search/test/envers/SearchAndEnversIntegrationTest.java
index 24094c824b9..fc4e0c5b7f0 100644
--- a/legacy/orm/src/test/java/org/hibernate/search/test/envers/SearchAndEnversIntegrationTest.java
+++ b/legacy/orm/src/test/java/org/hibernate/search/test/envers/SearchAndEnversIntegrationTest.java
@@ -22,8 +22,11 @@
import org.hibernate.search.Search;
import org.hibernate.search.test.SearchTestBase;
import org.hibernate.search.testsupport.TestForIssue;
+import org.hibernate.search.testsupport.junit.PortedToSearch6;
+
import org.hibernate.testing.SkipForDialect;
import org.junit.Test;
+import org.junit.experimental.categories.Category;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -35,6 +38,7 @@
* @author Davide Di Somma
*/
@SkipForDialect(jiraKey = "HSEARCH-1943", value = PostgreSQL81Dialect.class)
+@Category(PortedToSearch6.class)
public class SearchAndEnversIntegrationTest extends SearchTestBase {
private Person harryPotter;
diff --git a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java
index 93ba0a2acb2..abdc1ec7e6d 100644
--- a/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java
+++ b/mapper/orm/src/main/java/org/hibernate/search/mapper/orm/model/impl/HibernateOrmBootstrapIntrospector.java
@@ -16,6 +16,7 @@
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.hibernate.AssertionFailure;
@@ -56,9 +57,12 @@ public class HibernateOrmBootstrapIntrospector extends AbstractPojoHCAnnBootstra
public static HibernateOrmBootstrapIntrospector create(Metadata metadata,
ReflectionManager ormReflectionManager,
ConfigurationPropertySource propertySource) {
- Collection persistentClasses = metadata.getEntityBindings();
+ Collection persistentClasses = metadata.getEntityBindings()
+ .stream()
+ .filter( PersistentClass::hasPojoRepresentation )
+ .collect( Collectors.toList() );
Map, HibernateOrmBasicTypeMetadata> typeMetadata = new HashMap<>();
- collectPersistentTypes( typeMetadata, metadata.getEntityBindings() );
+ collectPersistentTypes( typeMetadata, persistentClasses );
for ( PersistentClass persistentClass : persistentClasses ) {
collectEmbeddedTypesRecursively( typeMetadata, persistentClass.getIdentifier() );
collectEmbeddedTypesRecursively( typeMetadata, persistentClass.getPropertyIterator() );
diff --git a/pom.xml b/pom.xml
index d066c53e01e..3f86ef89b13 100644
--- a/pom.xml
+++ b/pom.xml
@@ -578,6 +578,11 @@
hibernate-search-integrationtest-orm
${project.version}
+
+ org.hibernate.search
+ hibernate-search-integrationtest-mapper-orm-envers
+ ${project.version}
+
org.hibernate.search
hibernate-search-integrationtest-showcase-library
@@ -730,6 +735,11 @@
hibernate-core
${version.org.hibernate}
+
+ org.hibernate
+ hibernate-envers
+ ${version.org.hibernate}
+
org.hibernate
hibernate-orm-jbossmodules
diff --git a/reports/pom.xml b/reports/pom.xml
index a4cf5f94fc8..c3b69788c75 100644
--- a/reports/pom.xml
+++ b/reports/pom.xml
@@ -85,6 +85,11 @@
hibernate-search-integrationtest-orm
test
+
+ org.hibernate.search
+ hibernate-search-integrationtest-mapper-orm-envers
+ test
+
org.hibernate.search
hibernate-search-integrationtest-showcase-library