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