/* * 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.query.initandlookup; import static org.fest.assertions.Assertions.assertThat; import java.util.List; import java.util.Locale; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.MappedSuperclass; import javax.persistence.Table; import org.apache.lucene.index.Term; import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.TermQuery; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.search.FullTextSession; import org.hibernate.search.Search; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.SortableField; import org.hibernate.search.test.SearchTestBase; import org.hibernate.search.testsupport.BytemanHelper; import org.hibernate.search.testsupport.TestForIssue; import org.hibernate.search.testsupport.BytemanHelper.BytemanAccessor; import org.hibernate.search.testsupport.junit.ElasticsearchSupportInProgress; import org.jboss.byteman.contrib.bmunit.BMRule; import org.jboss.byteman.contrib.bmunit.BMUnitRunner; import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; /** * Memo to debug Byteman rules: * -Dorg.jboss.byteman.transform.all=true -Dorg.jboss.byteman.verbose=true -Dorg.jboss.byteman.dump.generated.classes -Dorg.jboss.byteman.dump.generated.classes.directory=/home/sanne/bytemandump * * Dumping the compiled classes makes it easier to decompile them and figure out what's wrong, * e.g. invoke javap -c -p -v BytemanHelper_HelperAdapter_Interpreted_1.class * The directory to store the dumped classes must exist already. */ @RunWith(BMUnitRunner.class) @Category(ElasticsearchSupportInProgress.class) // HSEARCH-2481 Byteman-based tests re-executed in the Elasticsearch module won't work public class CriteriaObjectInitializerAndHierarchyInheritanceTest extends SearchTestBase { @Rule public BytemanAccessor byteman = BytemanHelper.createAccessor(); @Override public Class<?>[] getAnnotatedClasses() { return new Class<?>[]{ BaseEntity.class, A.class, AA.class, AAA.class, AAB.class, AB.class, ABA.class, AC.class, B.class, BA.class }; } @BMRule( name = "trackCriteriaEntityType", targetClass = "org.hibernate.search.query.hibernate.impl.CriteriaObjectInitializer", targetMethod = "buildUpCriteria(java.util.List, org.hibernate.search.query.hibernate.impl.ObjectInitializationContext)", targetLocation = "EXIT", helper = "org.hibernate.search.testsupport.BytemanHelper", binding = "c : org.hibernate.internal.CriteriaImpl = $!.get(0);", action = "pushEvent(c.getEntityOrClassName())" ) @Test @TestForIssue(jiraKey = "HSEARCH-2301") @SuppressWarnings("unchecked") public void testJoinsAreOnlyOnUsefulEntityTypes() throws Exception { Session s = openSession(); Transaction tx = s.beginTransaction(); int i = 1; createInstance( s, A.class, i++, "A" ); createInstance( s, A.class, i++, "A" ); createInstance( s, AA.class, i++, "A AA" ); createInstance( s, AA.class, i++, "A AA" ); createInstance( s, AAA.class, i++, "A AA AAA" ); createInstance( s, AAA.class, i++, "A AA AAA" ); createInstance( s, AAA.class, i++, "A AA AAA" ); createInstance( s, AAB.class, i++, "A AA AAB" ); createInstance( s, AAB.class, i++, "A AA AAB" ); createInstance( s, AB.class, i++, "A AB" ); createInstance( s, AB.class, i++, "A AB" ); createInstance( s, ABA.class, i++, "A AB ABA" ); createInstance( s, ABA.class, i++, "A AB ABA" ); createInstance( s, AC.class, i++, "A AC" ); createInstance( s, AC.class, i++, "A AC" ); createInstance( s, B.class, i++, "B" ); createInstance( s, B.class, i++, "B" ); createInstance( s, BA.class, i++, "B BA" ); createInstance( s, BA.class, i++, "B BA" ); tx.commit(); s.clear(); FullTextSession session = Search.getFullTextSession( s ); List<?> results = getResults( session, AAA.class ); assertThat( results ).onProperty( "name" ).containsOnly( "A AA AAA" ); assertThat( byteman.consumeNextRecordedEvent() ).isEqualTo( AAA.class.getName() ); results = getResults( session, AAA.class, AAB.class ); assertThat( results ).onProperty( "name" ).containsOnly( "A AA AAA", "A AA AAB" ); assertThat( byteman.consumeNextRecordedEvent() ).isEqualTo( AA.class.getName() ); results = getResults( session, AAA.class, AB.class ); assertThat( results ).onProperty( "name" ).containsOnly( "A AA AAA", "A AB", "A AB ABA" ); assertThat( byteman.consumeNextRecordedEvent() ).isEqualTo( A.class.getName() ); results = getResults( session, AAA.class, BA.class ); assertThat( results ).onProperty( "name" ).containsOnly( "A AA AAA", "B BA" ); // here, we have 2 Criterias returned: we only test the first one assertThat( byteman.consumeNextRecordedEvent() ).isIn( AAA.class.getName(), BA.class.getName() ); results = getResultsFiltered( session, new MatchAllDocsQuery(), A.class ); assertThat( byteman.consumeNextRecordedEvent() ).isEqualTo( A.class.getName() ); // and finally we verify that if the full-text query is narrowing results to a subset of types, // only these are being targeted by the loading criteria. // First the simple case, narrowing down to a single type: final TermQuery termQueryAAA = new TermQuery( new Term( "name", "aaa" ) ); results = getResultsFiltered( session, termQueryAAA, A.class ); assertThat( byteman.consumeNextRecordedEvent() ).isEqualTo( AAA.class.getName() ); // And then when it narrows down to two types, use a Join Criteria on the first upper shared type: BooleanQuery.Builder bqb = new BooleanQuery.Builder(); final TermQuery termQueryAAB = new TermQuery( new Term( "name", "aab" ) ); bqb.add( termQueryAAA, Occur.SHOULD ); bqb.add( termQueryAAB, Occur.SHOULD ); results = getResultsFiltered( session, bqb.build(), A.class ); assertThat( byteman.consumeNextRecordedEvent() ).isEqualTo( AA.class.getName() ); s.close(); } private void createInstance(Session s, Class<? extends BaseEntity> clazz, Integer id, String name) throws Exception { BaseEntity entity = clazz.newInstance(); entity.id = id; entity.name = name; s.persist( entity ); } @SuppressWarnings("unchecked") private List<?> getResults(FullTextSession session, Class<? extends BaseEntity>... classes) { BooleanQuery.Builder bqb = new BooleanQuery.Builder(); for ( Class<? extends BaseEntity> clazz : classes ) { bqb.add( new TermQuery( new Term( "name", clazz.getSimpleName().toLowerCase( Locale.ENGLISH ) ) ), Occur.SHOULD ); } return getResultsFiltered( session, bqb.build(), BaseEntity.class ); } private List<?> getResultsFiltered(FullTextSession session, Query query, Class<? extends BaseEntity>... classes) { return session.createFullTextQuery( query, classes ) .setSort( new Sort( new SortField( "idSort", SortField.Type.INT ) ) ) .list(); } @MappedSuperclass @Inheritance(strategy = InheritanceType.JOINED) @Table(name = "BaseEntity") public abstract static class BaseEntity { @Id @DocumentId @Field(name = "idSort") @SortableField(forField = "idSort") Integer id; @Field String name; public String getName() { return name; } } @Entity @Indexed @Table(name = "A") public static class A extends BaseEntity { } @Entity @Indexed @Table(name = "AA") public static class AA extends A { } @Entity @Indexed @Table(name = "AAA") public static class AAA extends AA { } @Entity @Indexed @Table(name = "AAB") public static class AAB extends AA { } @Entity @Indexed @Table(name = "AB") public static class AB extends A { } @Entity @Indexed @Table(name = "ABA") public static class ABA extends AB { } @Entity @Indexed @Table(name = "AC") public static class AC extends A { } @Entity @Indexed @Table(name = "B") public static class B extends BaseEntity { } @Entity @Indexed @Table(name = "BA") public static class BA extends B { } }