[hibernate-commits] Hibernate SVN: r16272 - in search/branches/Branch_3_1: src/java/org/hibernate/search/backend/impl and 4 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Apr 7 16:49:33 EDT 2009


Author: sannegrinovero
Date: 2009-04-07 16:49:32 -0400 (Tue, 07 Apr 2009)
New Revision: 16272

Added:
   search/branches/Branch_3_1/src/java/org/hibernate/search/event/IndexWorkFlushEventListener.java
   search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/
   search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusLine.java
   search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusStop.java
   search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/LazyCollectionsUpdatingTest.java
   search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/RollbackTransactionTest.java
Modified:
   search/branches/Branch_3_1/doc/reference/en/modules/configuration.xml
   search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/EventSourceTransactionContext.java
   search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/TransactionalWorker.java
   search/branches/Branch_3_1/src/java/org/hibernate/search/event/EventListenerRegister.java
   search/branches/Branch_3_1/src/test/org/hibernate/search/test/TestCase.java
   search/branches/Branch_3_1/src/test/org/hibernate/search/test/classloading/NoAnnotationsTest.java
Log:
HSEARCH-178 for 3.1 branch

Modified: search/branches/Branch_3_1/doc/reference/en/modules/configuration.xml
===================================================================
--- search/branches/Branch_3_1/doc/reference/en/modules/configuration.xml	2009-04-07 20:20:12 UTC (rev 16271)
+++ search/branches/Branch_3_1/doc/reference/en/modules/configuration.xml	2009-04-07 20:49:32 UTC (rev 16272)
@@ -600,7 +600,8 @@
       <para>To enable Hibernate Search in Hibernate Core (ie. if you don't use
       Hibernate Annotations), add the
       <literal>FullTextIndexEventListener</literal> for the following six
-      Hibernate events.</para>
+      Hibernate events and add the <literal>IndexWorkFlushEventListener</literal> after
+      the default <literal>DefaultFlushEventListener</literal>, as in the following example.</para>
 
       <example>
         <title>Explicitly enabling Hibernate Search by configuring the
@@ -609,24 +610,28 @@
         <programlisting>&lt;hibernate-configuration&gt;
      &lt;session-factory&gt;
         ...
-        &lt;event type="post-update"/&gt;
+        &lt;event type="post-update"&gt;
             &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
         &lt;/event&gt;
-        &lt;event type="post-insert"/&gt;
+        &lt;event type="post-insert"&gt;
             &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
         &lt;/event&gt;
-        &lt;event type="post-delete"/&gt;
+        &lt;event type="post-delete"&gt;
             &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
         &lt;/event&gt;
-        &lt;event type="post-collection-recreate"/&gt;
+        &lt;event type="post-collection-recreate"&gt;
             &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
         &lt;/event&gt;
-        &lt;event type="post-collection-remove"/&gt;
+        &lt;event type="post-collection-remove"&gt;
             &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
         &lt;/event&gt;
-        &lt;event type="post-collection-update"/&gt;
+        &lt;event type="post-collection-update"&gt;
             &lt;listener class="org.hibernate.search.event.FullTextIndexEventListener"/&gt;
         &lt;/event&gt;
+        &lt;event type="flush"&gt;
+            &lt;listener class="org.hibernate.event.def.DefaultFlushEventListener"/&gt;
+            &lt;listener class="org.hibernate.search.event.IndexWorkFlushEventListener"/&gt;
+        &lt;/event&gt;
     &lt;/session-factory&gt;
 &lt;/hibernate-configuration&gt;</programlisting>
       </example>

Modified: search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/EventSourceTransactionContext.java
===================================================================
--- search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/EventSourceTransactionContext.java	2009-04-07 20:20:12 UTC (rev 16271)
+++ search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/EventSourceTransactionContext.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -2,35 +2,93 @@
 package org.hibernate.search.backend.impl;
 
 import java.io.Serializable;
+
 import javax.transaction.Synchronization;
 
+import org.hibernate.AssertionFailure;
 import org.hibernate.Transaction;
+import org.hibernate.event.EventSource;
+import org.hibernate.event.FlushEventListener;
 import org.hibernate.search.backend.TransactionContext;
-import org.hibernate.event.EventSource;
+import org.hibernate.search.event.IndexWorkFlushEventListener;
+import org.hibernate.search.util.LoggerFactory;
+import org.slf4j.Logger;
 
 /**
  * Implementation of the transactional context on top of an EventSource (Session)
  * 
  * @author Navin Surtani  - navin at surtani.org
+ * @author Emmanuel Bernard
+ * @author Sanne Grinovero
  */
 public class EventSourceTransactionContext implements TransactionContext, Serializable {
-	EventSource eventSource;
+	
+	private static final Logger log = LoggerFactory.make();
+	
+	private final EventSource eventSource;
+	private final IndexWorkFlushEventListener flushListener;
 
+	//constructor time is too early to define the value of realTxInProgress,
+	//postpone it, otherwise doing
+	// " openSession - beginTransaction "
+	//will behave as "out of transaction" in the whole session lifespan.
+	private Boolean realTxInProgress = null;
+	
 	public EventSourceTransactionContext(EventSource eventSource) {
 		this.eventSource = eventSource;
+		this.flushListener = findIndexWorkFlushEventListener();
 	}
 
 	public Object getTransactionIdentifier() {
-		return eventSource.getTransaction();
+		if ( isRealTransactionInProgress() ) {
+			return eventSource.getTransaction();
+		}
+		else {
+			return eventSource;
+		}
 	}
 
 	public void registerSynchronization(Synchronization synchronization) {
-		Transaction transaction = eventSource.getTransaction();
-		transaction.registerSynchronization( synchronization );
+		if ( isRealTransactionInProgress() ) {
+			Transaction transaction = eventSource.getTransaction();
+			transaction.registerSynchronization( synchronization );
+		}
+		else {
+			if ( flushListener != null ) {
+				flushListener.addSynchronization( eventSource, synchronization );
+			}
+			else {
+				//It appears we are flushing out of transaction and have no way to perform the index update
+				//Not expected: see check in isTransactionInProgress()
+				throw new AssertionFailure( "On flush out of transaction: IndexWorkFlushEventListener not registered" );
+			}
+		}
 	}
+	
+	private IndexWorkFlushEventListener findIndexWorkFlushEventListener() {
+		FlushEventListener[] flushEventListeners = eventSource.getListeners().getFlushEventListeners();
+		for (FlushEventListener listener : flushEventListeners) {
+			if ( listener.getClass().equals( IndexWorkFlushEventListener.class ) ) {
+				return (IndexWorkFlushEventListener) listener;
+			}
+		}
+		log.debug( "No IndexWorkFlushEventListener was registered" );
+		return null;
+	}
 
+	//The code is not really fitting the method name;
+	//(unless you consider a flush as a mini-transaction)
+	//This is because we want to behave as "inTransaction" if the flushListener is registered.
 	public boolean isTransactionInProgress() {
-		return eventSource.isTransactionInProgress();
+		// either it is a real transaction, or if we are capable to manage this in the IndexWorkFlushEventListener
+		return isRealTransactionInProgress() || flushListener != null;
 	}
-
+	
+	private boolean isRealTransactionInProgress() {
+		if ( realTxInProgress == null ) {
+			realTxInProgress = eventSource.isTransactionInProgress();
+		}
+		return realTxInProgress;
+	}
+	
 }

Modified: search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/TransactionalWorker.java
===================================================================
--- search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/TransactionalWorker.java	2009-04-07 20:20:12 UTC (rev 16271)
+++ search/branches/Branch_3_1/src/java/org/hibernate/search/backend/impl/TransactionalWorker.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -1,71 +1,83 @@
-//$Id$
-package org.hibernate.search.backend.impl;
-
-import java.util.Properties;
-
-import javax.transaction.Synchronization;
-
-import org.hibernate.search.backend.QueueingProcessor;
-import org.hibernate.search.backend.Work;
-import org.hibernate.search.backend.WorkQueue;
-import org.hibernate.search.backend.Worker;
-import org.hibernate.search.backend.TransactionContext;
-import org.hibernate.search.engine.SearchFactoryImplementor;
-import org.hibernate.search.util.WeakIdentityHashMap;
-
-/**
- * Queue works per transaction.
- * If out of transaction, the work is executed right away
- * <p/>
- * When <code>hibernate.search.worker.type</code> is set to <code>async</code>
- * the work is done in a separate thread (threads are pooled)
- *
- * @author Emmanuel Bernard
- */
-public class TransactionalWorker implements Worker {
-	//not a synchronized map since for a given transaction, we have not concurrent access
-	protected final WeakIdentityHashMap<Object, Synchronization> synchronizationPerTransaction = new WeakIdentityHashMap<Object, Synchronization>();
-	private QueueingProcessor queueingProcessor;
-
-	public void performWork(Work work, TransactionContext transactionContext) {
-		if ( transactionContext.isTransactionInProgress() ) {
-			Object transaction = transactionContext.getTransactionIdentifier();
-			PostTransactionWorkQueueSynchronization txSync = ( PostTransactionWorkQueueSynchronization )
-					synchronizationPerTransaction.get( transaction );
-			if ( txSync == null || txSync.isConsumed() ) {
-				txSync = new PostTransactionWorkQueueSynchronization(
-						queueingProcessor, synchronizationPerTransaction
-				);
-				transactionContext.registerSynchronization( txSync );
-				synchronizationPerTransaction.put( transaction, txSync );
-			}
-			txSync.add( work );
-		}
-		else {
-			WorkQueue queue = new WorkQueue( 2 ); //one work can be split
-			queueingProcessor.add( work, queue );
-			queueingProcessor.prepareWorks( queue );
-			queueingProcessor.performWorks( queue );
-		}
-	}
-
-	public void initialize(Properties props, SearchFactoryImplementor searchFactory) {
-		this.queueingProcessor = new BatchedQueueingProcessor( searchFactory, props );
-	}
-
-	public void close() {
-		queueingProcessor.close();
-	}
-
-	public void flushWorks(TransactionContext transactionContext) {
-		if ( transactionContext.isTransactionInProgress() ) {
-			Object transaction = transactionContext.getTransactionIdentifier();
-			PostTransactionWorkQueueSynchronization txSync = ( PostTransactionWorkQueueSynchronization )
-					synchronizationPerTransaction.get( transaction );
-			if ( txSync != null && !txSync.isConsumed() ) {
-				txSync.flushWorks();
-			}
-		}
-	}
-
-}
+//$Id$
+package org.hibernate.search.backend.impl;
+
+import java.util.Properties;
+
+import javax.transaction.Synchronization;
+
+import org.hibernate.search.backend.QueueingProcessor;
+import org.hibernate.search.backend.Work;
+import org.hibernate.search.backend.WorkQueue;
+import org.hibernate.search.backend.Worker;
+import org.hibernate.search.backend.TransactionContext;
+import org.hibernate.search.engine.SearchFactoryImplementor;
+import org.hibernate.search.util.LoggerFactory;
+import org.hibernate.search.util.WeakIdentityHashMap;
+import org.slf4j.Logger;
+
+/**
+ * Queue works per transaction.
+ * If out of transaction, the work is executed right away
+ * <p/>
+ * When <code>hibernate.search.worker.type</code> is set to <code>async</code>
+ * the work is done in a separate thread (threads are pooled)
+ * 
+ * @author Emmanuel Bernard
+ */
+public class TransactionalWorker implements Worker {
+	
+	//note: there is one Worker instance per SearchFactory, reused concurrently for all sessions.
+	
+	private static final Logger log = LoggerFactory.make();
+	
+	//FIXME: discuss the next line! it looks like there actually is concurrent access
+	//not a synchronized map since for a given transaction, we have not concurrent access
+	protected final WeakIdentityHashMap<Object, Synchronization> synchronizationPerTransaction = new WeakIdentityHashMap<Object, Synchronization>();
+	private QueueingProcessor queueingProcessor;
+
+	public void performWork(Work work, TransactionContext transactionContext) {
+		if ( transactionContext.isTransactionInProgress() ) {
+			Object transactionIdentifier = transactionContext.getTransactionIdentifier();
+			PostTransactionWorkQueueSynchronization txSync = ( PostTransactionWorkQueueSynchronization )
+					synchronizationPerTransaction.get( transactionIdentifier );
+			if ( txSync == null || txSync.isConsumed() ) {
+				txSync = new PostTransactionWorkQueueSynchronization(
+						queueingProcessor, synchronizationPerTransaction
+				);
+				transactionContext.registerSynchronization( txSync );
+				synchronizationPerTransaction.put( transactionIdentifier, txSync );
+			}
+			txSync.add( work );
+		}
+		else {
+			// this is a workaround: isTransactionInProgress should return "true"
+			// for correct configurations.
+			log.warn( "It appears changes are being pushed to the index out of a transaction. " +
+					"Register the IndexWorkFlushEventListener listener on flush to correctly manage Collections!" );
+			WorkQueue queue = new WorkQueue( 2 ); //one work can be split
+			queueingProcessor.add( work, queue );
+			queueingProcessor.prepareWorks( queue );
+			queueingProcessor.performWorks( queue );
+		}
+	}
+
+	public void initialize(Properties props, SearchFactoryImplementor searchFactory) {
+		this.queueingProcessor = new BatchedQueueingProcessor( searchFactory, props );
+	}
+
+	public void close() {
+		queueingProcessor.close();
+	}
+
+	public void flushWorks(TransactionContext transactionContext) {
+		if ( transactionContext.isTransactionInProgress() ) {
+			Object transaction = transactionContext.getTransactionIdentifier();
+			PostTransactionWorkQueueSynchronization txSync = ( PostTransactionWorkQueueSynchronization )
+					synchronizationPerTransaction.get( transaction );
+			if ( txSync != null && !txSync.isConsumed() ) {
+				txSync.flushWorks();
+			}
+		}
+	}
+
+}

Modified: search/branches/Branch_3_1/src/java/org/hibernate/search/event/EventListenerRegister.java
===================================================================
--- search/branches/Branch_3_1/src/java/org/hibernate/search/event/EventListenerRegister.java	2009-04-07 20:20:12 UTC (rev 16271)
+++ search/branches/Branch_3_1/src/java/org/hibernate/search/event/EventListenerRegister.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -94,7 +94,13 @@
 						new PostCollectionUpdateEventListener[] { searchListener }
 				)
 		);
-
+		// Adding IndexWorkFlushEventListener to manage events out-of-transaction
+		if ( ! isFlushEventListenerRegistered( listeners.getFlushEventListeners() ) ) {
+			listeners.setFlushEventListeners( appendToArray(
+					listeners.getFlushEventListeners(),
+					new IndexWorkFlushEventListener()
+					) );
+		}
 	}
 
 	/**
@@ -159,4 +165,19 @@
 		}
 		return false;
 	}
+	
+	/**
+	 * Verifies if an IndexWorkFlushEventListener is contained in the array of listeners.
+	 * @param listeners
+	 * @return true if it found in the listeners, false otherwise.
+	 */
+	private static boolean isFlushEventListenerRegistered(Object[] listeners) {
+		for ( Object eventListener : listeners ) {
+			if ( IndexWorkFlushEventListener.class == eventListener.getClass() ) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
 }

Added: search/branches/Branch_3_1/src/java/org/hibernate/search/event/IndexWorkFlushEventListener.java
===================================================================
--- search/branches/Branch_3_1/src/java/org/hibernate/search/event/IndexWorkFlushEventListener.java	                        (rev 0)
+++ search/branches/Branch_3_1/src/java/org/hibernate/search/event/IndexWorkFlushEventListener.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -0,0 +1,61 @@
+// $Id$
+package org.hibernate.search.event;
+
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+
+import org.hibernate.AssertionFailure;
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.hibernate.event.EventSource;
+import org.hibernate.event.FlushEvent;
+import org.hibernate.event.FlushEventListener;
+import org.hibernate.search.util.LoggerFactory;
+import org.slf4j.Logger;
+
+/**
+ * FlushEventListener to make sure the indexes are updated right after the hibernate flush,
+ * avoiding object loading during a flush. Not needed during transactions.
+ * 
+ * @author Sanne Grinovero
+ */
+public final class IndexWorkFlushEventListener implements FlushEventListener, Serializable {
+	
+	private static final Logger log = LoggerFactory.make();
+	
+	private final ConcurrentHashMap<Session, Synchronization> synchronizationPerTransaction
+		= new ConcurrentHashMap<Session, Synchronization>();
+	
+	public IndexWorkFlushEventListener() {
+	}
+
+	public void onFlush(FlushEvent event) throws HibernateException {
+		Session session = event.getSession();
+		Synchronization synchronization = synchronizationPerTransaction.get( session );
+		if ( synchronization != null ) {
+			log.debug( "flush event causing index update out of transaction" );
+			synchronizationPerTransaction.remove( session );
+			synchronization.beforeCompletion();
+			synchronization.afterCompletion( Status.STATUS_COMMITTED );
+		}
+	}
+
+	public void addSynchronization(EventSource eventSource, Synchronization synchronization) {
+		Synchronization previousSync = synchronizationPerTransaction.put( eventSource, synchronization );
+		if ( previousSync != null ) {
+			throw new AssertionFailure( "previous registered sync not discarded in IndexWorkFlushEventListener" );
+		}
+	}
+
+	/*
+	 * Might want to implement AutoFlushEventListener in future?
+	public void onAutoFlush(AutoFlushEvent event) throws HibernateException {
+		// Currently not needed as auto-flush is not happening
+		// when out of transaction.
+	}
+	*/
+
+}


Property changes on: search/branches/Branch_3_1/src/java/org/hibernate/search/event/IndexWorkFlushEventListener.java
___________________________________________________________________
Name: svn:keywords
   + Id

Modified: search/branches/Branch_3_1/src/test/org/hibernate/search/test/TestCase.java
===================================================================
--- search/branches/Branch_3_1/src/test/org/hibernate/search/test/TestCase.java	2009-04-07 20:20:12 UTC (rev 16271)
+++ search/branches/Branch_3_1/src/test/org/hibernate/search/test/TestCase.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -13,6 +13,9 @@
 import org.hibernate.cfg.Configuration;
 import org.hibernate.cfg.Environment;
 import org.hibernate.dialect.Dialect;
+import org.hibernate.event.FlushEventListener;
+import org.hibernate.event.def.DefaultFlushEventListener;
+import org.hibernate.search.event.IndexWorkFlushEventListener;
 
 /**
  * A modified base class for tests without annotations.
@@ -143,9 +146,15 @@
 	}
 
 	protected void configure(Configuration cfg) {
+		//needs to register all event listeners:
 		cfg.setListener( "post-update", "org.hibernate.search.event.FullTextIndexEventListener" );
 		cfg.setListener( "post-insert", "org.hibernate.search.event.FullTextIndexEventListener" );
 		cfg.setListener( "post-delete", "org.hibernate.search.event.FullTextIndexEventListener" );
+		cfg.setListener( "post-collection-recreate", "org.hibernate.search.event.FullTextIndexEventListener" );
+		cfg.setListener( "post-collection-remove", "org.hibernate.search.event.FullTextIndexEventListener" );
+		cfg.setListener( "post-collection-update", "org.hibernate.search.event.FullTextIndexEventListener" );
+		
+		cfg.setListeners( "flush", new FlushEventListener[]{new DefaultFlushEventListener(), new IndexWorkFlushEventListener()} );
 
 		cfg.setProperty( "hibernate.search.default.directory_provider", RAMDirectoryProvider.class.getName() );
 		cfg.setProperty( org.hibernate.search.Environment.ANALYZER_CLASS, StopAnalyzer.class.getName() );

Modified: search/branches/Branch_3_1/src/test/org/hibernate/search/test/classloading/NoAnnotationsTest.java
===================================================================
--- search/branches/Branch_3_1/src/test/org/hibernate/search/test/classloading/NoAnnotationsTest.java	2009-04-07 20:20:12 UTC (rev 16271)
+++ search/branches/Branch_3_1/src/test/org/hibernate/search/test/classloading/NoAnnotationsTest.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -10,7 +10,6 @@
 import org.hibernate.Transaction;
 import org.hibernate.search.Search;
 
-
 /**
  * @author Hardy Ferentschik
  */
@@ -39,7 +38,28 @@
 		tx.commit();
 		s.close();
 	}
+	
+	public void testFlushListenerRegistrationWithoutAnnotations() throws Exception {
+		// This test should pass even if the flushListener is not registered,
+		// as a workaround is done in code (you'll see a warning in logs).
+		Animal pinguin = new Animal();
+		pinguin.setName( "Penguin" );
 
+		Session s = openSession();
+		s.save( pinguin );
+		s.flush();
+		s.clear();
+
+		Transaction tx = s.beginTransaction();
+		tx = s.beginTransaction();
+		List results = Search.getFullTextSession( s ).createFullTextQuery(
+				new TermQuery( new Term( "name", "penguin" ) )
+		).list();
+		assertEquals( 1, results.size() );
+		tx.commit();
+		s.close();
+	}
+
 	protected String[] getXmlFiles() {
 		return new String[] {
 				"org/hibernate/search/test/classloading/Animal.hbm.xml"

Added: search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusLine.java
===================================================================
--- search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusLine.java	                        (rev 0)
+++ search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusLine.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -0,0 +1,87 @@
+// $Id$
+package org.hibernate.search.test.engine;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.annotations.IndexedEmbedded;
+import org.hibernate.search.annotations.Store;
+
+/**
+ * Test entity: BusLine have many BusStops: needed to verify
+ * indexing of a lazy-loaded collection in out-of-transaction use case.
+ * 
+ * @author Sanne Grinovero
+ */
+ at Entity
+ at Indexed
+public class BusLine {
+	
+	private Long id;
+	private String busLineName;
+	private Set<BusStop> stops = new HashSet<BusStop>();
+
+	@Id
+	@GeneratedValue 
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	@Field(index=Index.NO,store=Store.YES)
+	public String getBusLineName() {
+		return busLineName;
+	}
+
+	public void setBusLineName(String busLine) {
+		this.busLineName = busLine;
+	}
+
+	@ManyToMany(cascade=CascadeType.PERSIST)
+	@IndexedEmbedded
+	public Set<BusStop> getStops() {
+		return stops;
+	}
+	
+	public void setStops(Set<BusStop> stops) {
+		this.stops = stops;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((busLineName == null) ? 0 : busLineName.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		BusLine other = (BusLine) obj;
+		if (busLineName == null) {
+			if (other.busLineName != null)
+				return false;
+		} else if (!busLineName.equals(other.busLineName))
+			return false;
+		return true;
+	}
+	
+}

Added: search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusStop.java
===================================================================
--- search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusStop.java	                        (rev 0)
+++ search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusStop.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -0,0 +1,82 @@
+// $Id$
+package org.hibernate.search.test.engine;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+
+import org.hibernate.search.annotations.ContainedIn;
+import org.hibernate.search.annotations.Field;
+
+/**
+ * Test entity: BusStop is @ContainedIn BusLine
+ * 
+ * @author Sanne Grinovero
+ */
+ at Entity
+public class BusStop {
+	
+	private Long id;
+	private String roadName;
+	private Set<BusLine> busses = new HashSet<BusLine>();
+
+	@Id
+	@GeneratedValue
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	@Field
+	public String getRoadName() {
+		return roadName;
+	}
+
+	public void setRoadName(String roadName) {
+		this.roadName = roadName;
+	}
+
+	@ManyToMany(mappedBy="stops")
+	@ContainedIn
+	public Set<BusLine> getBusses() {
+		return busses;
+	}
+
+	public void setBusses(Set<BusLine> busses) {
+		this.busses = busses;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((roadName == null) ? 0 : roadName.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		BusStop other = (BusStop) obj;
+		if (roadName == null) {
+			if (other.roadName != null)
+				return false;
+		} else if (!roadName.equals(other.roadName))
+			return false;
+		return true;
+	}
+	
+}


Property changes on: search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/BusStop.java
___________________________________________________________________
Name: svn:keywords
   + Id

Added: search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/LazyCollectionsUpdatingTest.java
===================================================================
--- search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/LazyCollectionsUpdatingTest.java	                        (rev 0)
+++ search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/LazyCollectionsUpdatingTest.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -0,0 +1,119 @@
+// $Id$
+package org.hibernate.search.test.engine;
+
+import java.util.List;
+
+import org.apache.lucene.analysis.SimpleAnalyzer;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.TermQuery;
+import org.hibernate.Transaction;
+import org.hibernate.search.Environment;
+import org.hibernate.search.FullTextQuery;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.store.RAMDirectoryProvider;
+import org.hibernate.search.test.SearchTestCase;
+
+/**
+ * TestCase for HSEARCH-178 (Search hitting HHH-2763)
+ * Verifies that it's possible to index lazy loaded collections from
+ * indexed entities even when no transactions are used.
+ *
+ * @author Sanne Grinovero
+ */
+public class LazyCollectionsUpdatingTest extends SearchTestCase {
+	
+	public void testUpdatingInTransaction() {
+		assertFindsByRoadName( "buonarroti" );
+		FullTextSession fullTextSession = Search.getFullTextSession( sessions.openSession() );
+		try {
+			Transaction tx = fullTextSession.beginTransaction();
+			BusStop busStop = (BusStop) fullTextSession.get( BusStop.class, 1L );
+			busStop.setRoadName( "new road" );
+			tx.commit();
+		}
+		catch (org.hibernate.AssertionFailure ass) {
+			fail( ass.getMessage() );
+		}
+		finally {
+			fullTextSession.close();
+		}
+		assertFindsByRoadName( "new" );
+	}
+	
+	public void testUpdatingOutOfTransaction() {
+		assertFindsByRoadName( "buonarroti" );
+		FullTextSession fullTextSession = Search.getFullTextSession( sessions.openSession() );
+		try {
+			BusStop busStop = (BusStop) fullTextSession.get( BusStop.class, 1L );
+			busStop.setRoadName( "new road" );
+			fullTextSession.flush();
+		}
+		catch (org.hibernate.AssertionFailure ass) {
+			fail( ass.getMessage() );
+		}
+		finally {
+			fullTextSession.close();
+		}
+		assertFindsByRoadName( "new" );
+	}
+	
+	public void assertFindsByRoadName(String analyzedRoadname) {
+		FullTextSession fullTextSession = Search.getFullTextSession( sessions.openSession() );
+		Transaction tx = fullTextSession.beginTransaction();
+		TermQuery ftQuery = new TermQuery( new Term( "stops.roadName", analyzedRoadname ) );
+		FullTextQuery query = fullTextSession.createFullTextQuery( ftQuery, BusLine.class );
+		query.setProjection( "busLineName" );
+		assertEquals( 1, query.list().size() );
+		List results = query.list();
+		String resultName = (String) ((Object[])results.get(0))[0];
+		assertEquals( "Linea 64", resultName );
+		tx.commit();
+		fullTextSession.close();
+	}
+	
+	@Override
+	public void setUp() throws Exception {
+		super.setUp();
+		openSession();
+		Transaction tx = null;
+		try {
+			tx = session.beginTransaction();
+			BusLine bus = new BusLine();
+			bus.setBusLineName( "Linea 64" );
+			addBusStop( bus, "Stazione Termini" );
+			addBusStop( bus, "via Gregorio VII" );
+			addBusStop( bus, "via Alessandro III" );
+			addBusStop( bus, "via M.Buonarroti" );
+			session.persist( bus );
+			tx.commit();
+		} catch (Throwable t) {
+			if ( tx != null )
+				tx.rollback();
+		} finally {
+			session.close();
+		}
+	}
+	
+	private void addBusStop(BusLine bus, String roadName) {
+		BusStop stop = new BusStop();
+		stop.setRoadName( roadName );
+		bus.getStops().add( stop );
+		stop.getBusses().add( bus );
+	}
+
+	// Test setup options - Entities
+	@Override
+	protected Class[] getMappings() {
+		return new Class[] { BusLine.class, BusStop.class };
+	}
+	
+	// Test setup options - SessionFactory Properties
+	@Override
+	protected void configure(org.hibernate.cfg.Configuration configuration) {
+		super.configure( configuration );
+		cfg.setProperty( "hibernate.search.default.directory_provider", RAMDirectoryProvider.class.getName() );
+		cfg.setProperty( Environment.ANALYZER_CLASS, SimpleAnalyzer.class.getName() );
+	}
+
+}


Property changes on: search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/LazyCollectionsUpdatingTest.java
___________________________________________________________________
Name: svn:keywords
   + Id

Added: search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/RollbackTransactionTest.java
===================================================================
--- search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/RollbackTransactionTest.java	                        (rev 0)
+++ search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/RollbackTransactionTest.java	2009-04-07 20:49:32 UTC (rev 16272)
@@ -0,0 +1,90 @@
+// $Id$
+package org.hibernate.search.test.engine;
+
+import org.apache.lucene.analysis.SimpleAnalyzer;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.hibernate.Transaction;
+import org.hibernate.search.Environment;
+import org.hibernate.search.FullTextQuery;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.store.RAMDirectoryProvider;
+import org.hibernate.search.test.SearchTestCase;
+
+/**
+ * Verify index changes queued during a transaction are canceled
+ * when the transaction is rolled back.
+ * 
+ * @author Sanne Grinovero
+ */
+public class RollbackTransactionTest extends SearchTestCase {
+	
+	public void testTransactionBehaviour() {
+		assertEquals( 0, countBusLinesByFullText() );
+		assertEquals( 0, countBusLineByDatabaseCount() );
+		createBusLines( 5, true );
+		assertEquals( 0, countBusLinesByFullText() );
+		assertEquals( 0, countBusLineByDatabaseCount() );
+		createBusLines( 5, false );
+		assertEquals( 5, countBusLinesByFullText() );
+		assertEquals( 5, countBusLineByDatabaseCount() );
+		createBusLines( 7, true );
+		assertEquals( 5, countBusLinesByFullText() );
+		assertEquals( 5, countBusLineByDatabaseCount() );
+		createBusLines( 7, false );
+		assertEquals( 12, countBusLinesByFullText() );
+		assertEquals( 12, countBusLineByDatabaseCount() );
+	}
+	
+	private void createBusLines(int number, boolean rollback) {
+		FullTextSession fullTextSession = Search.getFullTextSession( sessions.openSession() );
+		Transaction tx = fullTextSession.beginTransaction();
+		for (int i=0; i<number; i++ ) {
+			BusLine line = new BusLine();
+			line.setBusLineName( "line " + i );
+			fullTextSession.persist( line );
+		}
+		if ( rollback ) {
+			tx.rollback();
+		}
+		else {
+			tx.commit();
+		}
+		fullTextSession.close();
+	}
+
+	public int countBusLinesByFullText() {
+		FullTextSession fullTextSession = Search.getFullTextSession( sessions.openSession() );
+		Transaction tx = fullTextSession.beginTransaction();
+		org.apache.lucene.search.Query ftQuery = new MatchAllDocsQuery();
+		FullTextQuery query = fullTextSession.createFullTextQuery( ftQuery, BusLine.class );
+		int count = query.list().size();
+		tx.commit();
+		fullTextSession.close();
+		return count;
+	}
+	
+	public int countBusLineByDatabaseCount() {
+		FullTextSession fullTextSession = Search.getFullTextSession( sessions.openSession() );
+		Transaction tx = fullTextSession.beginTransaction();
+		int count = fullTextSession.createCriteria( BusLine.class ).list().size();
+		tx.commit();
+		fullTextSession.close();
+		return count;
+	}
+	
+	// Test setup options - Entities
+	@Override
+	protected Class[] getMappings() {
+		return new Class[] { BusLine.class, BusStop.class };
+	}
+	
+	// Test setup options - SessionFactory Properties
+	@Override
+	protected void configure(org.hibernate.cfg.Configuration configuration) {
+		super.configure( configuration );
+		cfg.setProperty( "hibernate.search.default.directory_provider", RAMDirectoryProvider.class.getName() );
+		cfg.setProperty( Environment.ANALYZER_CLASS, SimpleAnalyzer.class.getName() );
+	}
+
+}


Property changes on: search/branches/Branch_3_1/src/test/org/hibernate/search/test/engine/RollbackTransactionTest.java
___________________________________________________________________
Name: svn:keywords
   + Id




More information about the hibernate-commits mailing list