/* * 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 <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.test.envers; import java.util.List; import org.apache.lucene.analysis.core.StopAnalyzer; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.hibernate.Transaction; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.envers.AuditReader; import org.hibernate.envers.AuditReaderFactory; import org.hibernate.envers.query.AuditEntity; import org.hibernate.search.FullTextSession; import org.hibernate.search.Search; import org.hibernate.search.test.SearchTestBase; import org.hibernate.search.testsupport.TestForIssue; import org.hibernate.testing.SkipForDialect; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Unit test covering proper behavior and integration between Hibernate Search and Envers. * As well as it verifies that the index is in sync with the latest transaction state. * * @author Davide Di Somma <davide.disomma@gmail.com> */ @SkipForDialect(jiraKey = "HSEARCH-1943", value = PostgreSQL81Dialect.class) public class SearchAndEnversIntegrationTest extends SearchTestBase { private Person harryPotter; private Person hermioneGranger; private Address privetDrive; private Address grimmauldPlace; /** * This test case aims to verify that insertion, updating and deleting operations work correctly * for both Hibernate Search and Hibernate Envers */ @TestForIssue(jiraKey = "HSEARCH-1293") @Test public void testHibernateSearchAndEnversIntegration() { atRevision1(); atRevision2(); atRevision3(); atRevision4(); } /** * It verifies that insertion operation works correctly */ private void atRevision1() { // Objects creation privetDrive = new Address( "Privet Drive", 121 ); privetDrive.setFlatNumber( 2 ); harryPotter = new Person( "Harry", "Potter", privetDrive ); grimmauldPlace = new Address( "Grimmauld Place", 12 ); hermioneGranger = new Person( "Hermione", "Granger", grimmauldPlace ); { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); session.save( privetDrive ); session.save( grimmauldPlace ); session.save( harryPotter ); session.save( hermioneGranger ); tx.commit(); } finally { session.close(); } } { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); //Let's assert that Hibernate Envers has audited correctly AuditReader auditReader = AuditReaderFactory.get( session ); assertEquals( 1, findLastRevisionForEntity( auditReader, Person.class ) ); assertEquals( 1, findLastRevisionForEntity( auditReader, Address.class ) ); assertEquals( 2, howManyEntitiesChangedAtRevisionNumber( auditReader, Person.class, 1 ) ); assertEquals( 2, howManyEntitiesChangedAtRevisionNumber( auditReader, Address.class, 1 ) ); assertEquals( 2, howManyAuditedObjectsSoFar( auditReader, Person.class ) ); assertEquals( 2, howManyAuditedObjectsSoFar( auditReader, Address.class ) ); //Let's compares that entities from Hibernate Search and last revision entities from Hibernate Envers are equals Person hermioneFromHibSearch = findPersonFromIndexBySurname( session, "Granger" ); Person hermioneAtRevision1 = findPersonFromAuditBySurname( auditReader, "Granger" ); assertEquals( hermioneFromHibSearch, hermioneAtRevision1 ); Person harryFromHibSearch = findPersonFromIndexBySurname( session, "Potter" ); Person harryAtRevision1 = findPersonFromAuditBySurname( auditReader, "Potter" ); assertEquals( harryFromHibSearch, harryAtRevision1 ); tx.commit(); } finally { session.close(); } } } /** * It verifies that updating operation on Address entity works correctly */ private void atRevision2() { // Changing the address's house number and flat number { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); privetDrive = (Address) session.merge( privetDrive ); privetDrive.setHouseNumber( 5 ); privetDrive.setFlatNumber( null ); tx.commit(); } finally { session.close(); } } { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); AuditReader auditReader = AuditReaderFactory.get( session ); //Let's assert that Hibernate Envers has audited everything correctly assertEquals( 1, findLastRevisionForEntity( auditReader, Person.class ) ); assertEquals( 2, findLastRevisionForEntity( auditReader, Address.class ) ); assertEquals( 0, howManyEntitiesChangedAtRevisionNumber( auditReader, Person.class, 2 ) ); assertEquals( 1, howManyEntitiesChangedAtRevisionNumber( auditReader, Address.class, 2 ) ); assertEquals( 2, howManyAuditedObjectsSoFar( auditReader, Person.class ) ); assertEquals( 3, howManyAuditedObjectsSoFar( auditReader, Address.class ) ); @SuppressWarnings("unchecked") List<Address> houseNumberAddressChangedAtRevision2 = auditReader.createQuery() .forEntitiesModifiedAtRevision( Address.class, 2 ) .add( AuditEntity.property( "houseNumber" ).hasChanged() ) .add( AuditEntity.property( "flatNumber" ).hasChanged() ) .add( AuditEntity.property( "streetName" ).hasNotChanged() ).getResultList(); assertEquals( 1, houseNumberAddressChangedAtRevision2.size() ); //Let's assert that Hibernate Search has indexed everything correctly List<Person> peopleLivingInPrivetDriveFromHibSearch = findPeopleFromIndexByStreetName( session, "Privet" ); assertEquals( 1, peopleLivingInPrivetDriveFromHibSearch.size() ); //Let's compare that entities from Hibernate Search and last revision entities from Hibernate Envers are equals Person harryFromHibSearch = peopleLivingInPrivetDriveFromHibSearch.get( 0 ); Person harryAtRevision2 = findPersonFromAuditBySurname( auditReader, "Potter" ); assertEquals( harryFromHibSearch, harryAtRevision2 ); tx.commit(); } finally { session.close(); } } } /** * It verifies that updating operation on Person entity works correctly */ private void atRevision3() { // Moving Hermione to Harry { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); hermioneGranger = (Person) session.merge( hermioneGranger ); hermioneGranger.setAddress( privetDrive ); tx.commit(); } finally { session.close(); } } { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); AuditReader auditReader = AuditReaderFactory.get( session ); //Let's assert that Hibernate Envers has audited everything correctly @SuppressWarnings("unchecked") List<Person> peopleWhoHasMovedHouseAtRevision3 = auditReader.createQuery() .forEntitiesModifiedAtRevision( Person.class, 3 ).add( AuditEntity.property( "address" ).hasChanged() ) .getResultList(); assertEquals( 1, peopleWhoHasMovedHouseAtRevision3.size() ); assertEquals( 3, findLastRevisionForEntity( auditReader, Person.class ) ); assertEquals( 3, findLastRevisionForEntity( auditReader, Address.class ) ); assertEquals( 1, howManyEntitiesChangedAtRevisionNumber( auditReader, Person.class, 3 ) ); assertEquals( 2, howManyEntitiesChangedAtRevisionNumber( auditReader, Address.class, 3 ) ); assertEquals( 3, howManyAuditedObjectsSoFar( auditReader, Person.class ) ); assertEquals( 5, howManyAuditedObjectsSoFar( auditReader, Address.class ) ); //Let's assert that Hibernate Search has indexed everything correctly List<Person> peopleLivingInPrivetDriveFromHibSearch = findPeopleFromIndexByStreetName( session, "Privet" ); assertEquals( 2, peopleLivingInPrivetDriveFromHibSearch.size() ); tx.commit(); } finally { session.close(); } } } /** * It verifies that deleting operation on Person entity works correctly */ private void atRevision4() { // Now let's clean up everything { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); session.delete( harryPotter ); session.delete( hermioneGranger ); session.delete( grimmauldPlace ); session.delete( privetDrive ); tx.commit(); } finally { session.close(); } } { FullTextSession session = Search.getFullTextSession( openSession() ); try { Transaction tx = session.beginTransaction(); AuditReader auditReader = AuditReaderFactory.get( session ); //Let's assert that Hibernate Envers has audited everything correctly assertEquals( 4, findLastRevisionForEntity( auditReader, Person.class ) ); assertEquals( 4, findLastRevisionForEntity( auditReader, Address.class ) ); assertEquals( 2, howManyEntitiesChangedAtRevisionNumber( auditReader, Person.class, 4 ) ); assertEquals( 2, howManyEntitiesChangedAtRevisionNumber( auditReader, Address.class, 4 ) ); assertEquals( 7, howManyAuditedObjectsSoFar( auditReader, Address.class ) ); assertEquals( 5, howManyAuditedObjectsSoFar( auditReader, Person.class ) ); //Let's assert that Hibernate Search has indexed everything correctly assertNull( findPersonFromIndexBySurname( session, "Potter" ) ); assertNull( findPersonFromIndexBySurname( session, "Granger" ) ); assertEquals( 0, findPeopleFromIndexByStreetName( session, "Privet" ).size() ); assertEquals( 0, findPeopleFromIndexByStreetName( session, "Guillaume" ).size() ); tx.commit(); } finally { session.close(); } } } /** * 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(); } private Person findPersonFromAuditBySurname(AuditReader auditReader, String value) { return (Person) auditReader.createQuery().forEntitiesAtRevision( Person.class, 1 ) .add( AuditEntity.property( "surname" ).eq( value ) ).getSingleResult(); } @SuppressWarnings("unchecked") private List<Person> findPeopleFromIndex(FullTextSession session, String term, String value) { Query luceneQuery = createLuceneQuery( term, value ); return session.createFullTextQuery( luceneQuery, Person.class ) .setSort( new Sort( new SortField( "surname", SortField.Type.STRING ) ) ).list(); } private Person findPersonFromIndexBySurname(FullTextSession session, String surname) { List<Person> people = findPeopleFromIndex( session, "surname", surname ); if ( people.isEmpty() ) { return null; } else if ( people.size() > 1 ) { throw new RuntimeException( "I've found too many people!!!" ); } return people.get( 0 ); } private List<Person> findPeopleFromIndexByStreetName(FullTextSession session, String streetName) { return findPeopleFromIndex( session, "address.streetName", streetName ); } private Query createLuceneQuery(String term, String value) { String searchQuery = term + ":" + value; QueryParser parser = new QueryParser( term, new StopAnalyzer() ); Query luceneQuery; try { luceneQuery = parser.parse( searchQuery ); } catch (ParseException e) { throw new RuntimeException( "Unable to parse query", e ); } return luceneQuery; } @Override public Class<?>[] getAnnotatedClasses() { return new Class[] { Person.class, Address.class }; } }