/*
* 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.engine.optimizations;
import java.lang.annotation.ElementType;
import org.hibernate.Transaction;
import org.hibernate.collection.internal.PersistentBag;
import org.hibernate.collection.internal.PersistentSet;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.cfg.EntityMapping;
import org.hibernate.search.cfg.IndexedMapping;
import org.hibernate.search.cfg.SearchMapping;
import org.hibernate.search.test.util.FullTextSessionBuilder;
import org.hibernate.search.testsupport.TestForIssue;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Verify that updates to collections that are not indexed do not trigger indexing.
* Updating a collection in an entity which is not indexed, but has some other property marked
* as containedIn, should generally not trigger indexing of the containedIn objects, unless
* we can't tell for sure the index state is not going to be affected.
*
* @author Sanne Grinovero
* @author Tom Waterhouse
*/
@TestForIssue(jiraKey = "HSEARCH-679")
public class CollectionUpdateEventTest {
private static boolean WITH_CLASS_BRIDGE_ON_ITEM = true;
private static boolean WITHOUT_CLASS_BRIDGE_ON_ITEM = false;
private static boolean WITH_CLASS_BRIDGE_ON_CATALOG = true;
private static boolean WITHOUT_CLASS_BRIDGE_ON_CATALOG = false;
/**
* If the top level class has a class bridge or dynamic boost, then we can't safely
* enable this optimization.
*/
@Test
public void testWithClassBridge() {
testScenario( WITH_CLASS_BRIDGE_ON_ITEM, 2, WITHOUT_CLASS_BRIDGE_ON_CATALOG );
}
/**
* The indexing should be skipped when no custom bridges are used.
*/
@Test
public void testWithoutClassBridge() {
testScenario( WITHOUT_CLASS_BRIDGE_ON_ITEM, 2, WITHOUT_CLASS_BRIDGE_ON_CATALOG );
}
/**
* Test having a depth which is not enough to reach the dirty collection.
* We should be able to optimize this case.
*/
@Test
public void testWithNoEnoughDepth() {
testScenario( WITH_CLASS_BRIDGE_ON_ITEM, 1, WITHOUT_CLASS_BRIDGE_ON_CATALOG );
}
/**
* Test again with a no-enough-depth scenario, but having the class bridge
* defined close to the collection (far from the root entity)
*/
@Test
public void testWithDeepClassBridge() {
testScenario( WITHOUT_CLASS_BRIDGE_ON_ITEM, 1, WITH_CLASS_BRIDGE_ON_CATALOG );
}
private void testScenario(boolean withClassBridgeOnItem, int depth, boolean withClassBridgeOnCatalog) {
FullTextSessionBuilder fulltextSessionBuilder = configure(
withClassBridgeOnItem,
depth,
withClassBridgeOnCatalog
);
try {
initializeData( fulltextSessionBuilder );
FullTextSession fullTextSession = fulltextSessionBuilder.openFullTextSession();
try {
Catalog catalog = (Catalog) fullTextSession.get( Catalog.class, 1L );
PersistentSet catalogItems = (PersistentSet) catalog.getCatalogItems();
PersistentBag consumers = (PersistentBag) catalog.getConsumers();
assertFalse( "consumers should not be initialized", consumers.wasInitialized() );
assertFalse( "catalogItems should not be initialized", consumers.wasInitialized() );
updateCatalogsCollection( fullTextSession, catalog );
if ( ( withClassBridgeOnItem || withClassBridgeOnCatalog ) && depth > 1 ) {
assertTrue( "catalogItems should have been initialized", catalogItems.wasInitialized() );
}
else {
assertFalse( "catalogItems should not be initialized", catalogItems.wasInitialized() );
}
}
finally {
fullTextSession.close();
}
}
finally {
fulltextSessionBuilder.close();
}
}
private FullTextSessionBuilder configure(boolean withClassBridgeOnItem, int depth, boolean withClassBridgeOnCatalog) {
FullTextSessionBuilder builder = new FullTextSessionBuilder()
.addAnnotatedClass( Catalog.class )
.addAnnotatedClass( CatalogItem.class )
.addAnnotatedClass( Consumer.class )
.addAnnotatedClass( Item.class );
SearchMapping fluentMapping = builder.fluentMapping();
// mapping for Catalog
EntityMapping catalogMapping = fluentMapping
.entity( Catalog.class );
if ( withClassBridgeOnCatalog ) {
catalogMapping.classBridge( NoopClassBridge.class );
}
catalogMapping
.property( "catalogItems", ElementType.FIELD ).containedIn();
// mapping for CatalogItem
fluentMapping.entity( CatalogItem.class )
.property( "item", ElementType.FIELD ).containedIn()
.property( "catalog", ElementType.FIELD ).indexEmbedded();
// mapping for Item
IndexedMapping itemMapping = fluentMapping
.entity( Item.class )
.indexed();
if ( withClassBridgeOnItem ) {
itemMapping.classBridge( NoopClassBridge.class );
}
itemMapping.property( "catalogItems", ElementType.FIELD ).indexEmbedded().depth( depth );
return builder.build();
}
private void initializeData(FullTextSessionBuilder fulltextSessionBuilder) {
FullTextSession fullTextSession = fulltextSessionBuilder.openFullTextSession();
try {
final Transaction transaction = fullTextSession.beginTransaction();
Catalog catalog = new Catalog();
catalog.setCatalogId( 1L );
catalog.setName( "parts" );
fullTextSession.persist( catalog );
for ( int i = 0; i < 5; i++ ) {
Item item = new Item();
item.setName( "battery" );
fullTextSession.persist( item );
CatalogItem catalogItem = new CatalogItem();
catalogItem.setCatalog( catalog );
catalogItem.setItem( item );
fullTextSession.persist( catalogItem );
item.getCatalogItems().add( catalogItem );
fullTextSession.merge( item );
catalog.getCatalogItems().add( catalogItem );
fullTextSession.merge( catalog );
}
transaction.commit();
}
finally {
fullTextSession.close();
}
}
/**
* Update a non-indexed collection of an entity contained in a collection. No indexing work should be created.
*/
private void updateCatalogsCollection(FullTextSession fullTextSession, Catalog catalog) {
final Transaction transaction = fullTextSession.beginTransaction();
Consumer consumer = new Consumer();
consumer.setName( "consumer" );
consumer.getCatalogs().add( catalog );
fullTextSession.persist( consumer );
catalog.getConsumers().add( consumer );
fullTextSession.merge( catalog );
transaction.commit();
}
}