From hibernate-commits at lists.jboss.org Mon Apr 13 11:26:22 2009 Content-Type: multipart/mixed; boundary="===============5146307913699863023==" MIME-Version: 1.0 From: hibernate-commits at lists.jboss.org To: hibernate-commits at lists.jboss.org Subject: [hibernate-commits] Hibernate SVN: r16306 - in search/trunk/src: main/java and 4 other directories. Date: Mon, 13 Apr 2009 11:26:20 -0400 Message-ID: --===============5146307913699863023== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Author: sannegrinovero Date: 2009-04-13 11:26:20 -0400 (Mon, 13 Apr 2009) New Revision: 16306 Added: search/trunk/src/test/java/org/hibernate/search/test/engine/EventListene= rSerializationTest.java Removed: search/trunk/src/main/java/org/hibernate/search/event/IndexWorkFlushEven= tListener.java Modified: search/trunk/src/main/docbook/en-US/modules/configuration.xml search/trunk/src/main/java/ search/trunk/src/main/java/org/hibernate/search/backend/impl/EventSource= TransactionContext.java search/trunk/src/main/java/org/hibernate/search/backend/impl/Transaction= alWorker.java search/trunk/src/main/java/org/hibernate/search/event/EventListenerRegis= ter.java search/trunk/src/main/java/org/hibernate/search/event/FullTextIndexEvent= Listener.java search/trunk/src/test/java/org/hibernate/search/test/TestCase.java Log: HSEARCH-178 for trunk only Modified: search/trunk/src/main/docbook/en-US/modules/configuration.xml =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/main/docbook/en-US/modules/configuration.xml 2009-04-1= 1 04:54:37 UTC (rev 16305) +++ search/trunk/src/main/docbook/en-US/modules/configuration.xml 2009-04-1= 3 15:26:20 UTC (rev 16306) @@ -605,8 +605,8 @@ To enable Hibernate Search in Hibernate Core (ie. if you don't= use Hibernate Annotations), add the FullTextIndexEventListener for the following six - Hibernate events and add the IndexWorkFlushEventListener after - the default DefaultFlushEventListener, as in the = following example. + Hibernate events and also add it after the default = + DefaultFlushEventListener, as in the following ex= ample. = Explicitly enabling Hibernate Search by configuring the @@ -635,7 +635,7 @@ </event> <event type=3D"flush"> <listener class=3D"org.hibernate.event.def.DefaultFlushEven= tListener"/> - <listener class=3D"org.hibernate.search.event.IndexWorkFlus= hEventListener"/> + <listener class=3D"org.hibernate.search.event.FullTextIndex= EventListener"/> </event> </session-factory> </hibernate-configuration></programlisting> Property changes on: search/trunk/src/main/java ___________________________________________________________________ Name: svn:mergeinfo - = Modified: search/trunk/src/main/java/org/hibernate/search/backend/impl/Even= tSourceTransactionContext.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/main/java/org/hibernate/search/backend/impl/EventSourc= eTransactionContext.java 2009-04-11 04:54:37 UTC (rev 16305) +++ search/trunk/src/main/java/org/hibernate/search/backend/impl/EventSourc= eTransactionContext.java 2009-04-13 15:26:20 UTC (rev 16306) @@ -5,12 +5,12 @@ = 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.SearchException; import org.hibernate.search.backend.TransactionContext; -import org.hibernate.search.event.IndexWorkFlushEventListener; +import org.hibernate.search.event.FullTextIndexEventListener; import org.hibernate.search.util.LoggerFactory; import org.slf4j.Logger; = @@ -26,7 +26,9 @@ private static final Logger log =3D LoggerFactory.make(); = private final EventSource eventSource; - private final IndexWorkFlushEventListener flushListener; + = + //this transient is required to break recursive serialization + private transient FullTextIndexEventListener flushListener; = //constructor time is too early to define the value of realTxInProgress, //postpone it, otherwise doing @@ -36,7 +38,7 @@ = public EventSourceTransactionContext(EventSource eventSource) { this.eventSource =3D eventSource; - this.flushListener =3D findIndexWorkFlushEventListener(); + this.flushListener =3D getIndexWorkFlushEventListener(); } = public Object getTransactionIdentifier() { @@ -54,25 +56,32 @@ transaction.registerSynchronization( synchronization ); } else { - if ( flushListener !=3D null ) { + //registerSynchronization is only called if isRealTransactionInProgress= or if + // a flushListener was found; still we might need to find the listener = again + // as it might have been cleared by serialization (is transient). + FullTextIndexEventListener flushList =3D getIndexWorkFlushEventListener= (); + if ( flushList !=3D null ) { flushListener.addSynchronization( eventSource, synchronization ); } else { - //It appears we are flushing out of transaction and have no way to per= form the index update - //Not expected: see check in isTransactionInProgress() - throw new AssertionFailure( "On flush out of transaction: IndexWorkFlu= shEventListener not registered" ); + //shouldn't happen if the code about serialization is fine: + throw new SearchException( "AssertionFailure: flushListener not regist= ered any more."); } } } = - private IndexWorkFlushEventListener findIndexWorkFlushEventListener() { + private FullTextIndexEventListener getIndexWorkFlushEventListener() { + if ( this.flushListener !=3D null) { + //for the "transient" case: might have been nullified. + return flushListener; + } FlushEventListener[] flushEventListeners =3D eventSource.getListeners().= getFlushEventListeners(); for (FlushEventListener listener : flushEventListeners) { - if ( listener.getClass().equals( IndexWorkFlushEventListener.class ) ) { - return (IndexWorkFlushEventListener) listener; + if ( listener.getClass().equals( FullTextIndexEventListener.class ) ) { + return (FullTextIndexEventListener) listener; } } - log.debug( "No IndexWorkFlushEventListener was registered" ); + log.debug( "No FullTextIndexEventListener was registered" ); return null; } = @@ -81,7 +90,7 @@ //This is because we want to behave as "inTransaction" if the flushListen= er is registered. public boolean isTransactionInProgress() { // either it is a real transaction, or if we are capable to manage this = in the IndexWorkFlushEventListener - return isRealTransactionInProgress() || flushListener !=3D null; + return getIndexWorkFlushEventListener() !=3D null || isRealTransactionIn= Progress(); } = private boolean isRealTransactionInProgress() { Modified: search/trunk/src/main/java/org/hibernate/search/backend/impl/Tran= sactionalWorker.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/main/java/org/hibernate/search/backend/impl/Transactio= nalWorker.java 2009-04-11 04:54:37 UTC (rev 16305) +++ search/trunk/src/main/java/org/hibernate/search/backend/impl/Transactio= nalWorker.java 2009-04-13 15:26:20 UTC (rev 16306) @@ -30,8 +30,8 @@ = private static final Logger log =3D LoggerFactory.make(); = - //FIXME: discuss the next line! it looks like there actually is concurren= t access - //not a synchronized map since for a given transaction, we have not concu= rrent access + //this is being used from different threads, but doesn't need a + //synchronized map since for a given transaction, we have not concurrent = access protected final WeakIdentityHashMap<Object, Synchronization> synchronizat= ionPerTransaction =3D new WeakIdentityHashMap<Object, Synchronization>(); private QueueingProcessor queueingProcessor; = Modified: search/trunk/src/main/java/org/hibernate/search/event/EventListen= erRegister.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/main/java/org/hibernate/search/event/EventListenerRegi= ster.java 2009-04-11 04:54:37 UTC (rev 16305) +++ search/trunk/src/main/java/org/hibernate/search/event/EventListenerRegi= ster.java 2009-04-13 15:26:20 UTC (rev 16306) @@ -6,6 +6,7 @@ import org.slf4j.Logger; = import org.hibernate.event.EventListeners; +import org.hibernate.event.FlushEventListener; import org.hibernate.event.PostCollectionRecreateEventListener; import org.hibernate.event.PostCollectionRemoveEventListener; import org.hibernate.event.PostCollectionUpdateEventListener; @@ -15,7 +16,6 @@ import org.hibernate.search.Environment; import org.hibernate.search.util.LoggerFactory; = - /** * Helper methods initializing Hibernate Search event listeners. * @@ -94,13 +94,14 @@ new PostCollectionUpdateEventListener[] { searchListener } ) ); - // Adding IndexWorkFlushEventListener to manage events out-of-transaction - if ( ! isFlushEventListenerRegistered( listeners.getFlushEventListeners(= ) ) ) { - listeners.setFlushEventListeners( appendToArray( - listeners.getFlushEventListeners(), - new IndexWorkFlushEventListener() - ) ); - } + // Adding also as FlushEventListener to manage events out-of-transaction + listeners.setFlushEventListeners( + addIfNeeded( + listeners.getFlushEventListeners(), + searchListener, + new FlushEventListener[] { searchListener } + ) + ); } = /** @@ -166,18 +167,4 @@ return false; } = - /** - * Verifies if an IndexWorkFlushEventListener is contained in the array o= f 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 =3D=3D eventListener.getClass() = ) { - return true; - } - } - return false; - } - = } Modified: search/trunk/src/main/java/org/hibernate/search/event/FullTextInd= exEventListener.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/main/java/org/hibernate/search/event/FullTextIndexEven= tListener.java 2009-04-11 04:54:37 UTC (rev 16305) +++ search/trunk/src/main/java/org/hibernate/search/event/FullTextIndexEven= tListener.java 2009-04-13 15:26:20 UTC (rev 16306) @@ -1,15 +1,27 @@ //$Id$ package org.hibernate.search.event; = +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; = +import javax.transaction.Status; +import javax.transaction.Synchronization; + import org.slf4j.Logger; = +import org.hibernate.Session; import org.hibernate.cfg.Configuration; import org.hibernate.engine.EntityEntry; import org.hibernate.event.AbstractCollectionEvent; import org.hibernate.event.AbstractEvent; import org.hibernate.event.Destructible; +import org.hibernate.event.EventSource; +import org.hibernate.event.FlushEvent; +import org.hibernate.event.FlushEventListener; import org.hibernate.event.Initializable; import org.hibernate.event.PostCollectionRecreateEvent; import org.hibernate.event.PostCollectionRecreateEventListener; @@ -36,20 +48,29 @@ * @author Gavin King * @author Emmanuel Bernard * @author Mattias Arbin + * @author Sanne Grinovero */ -//TODO work on sharing the same indexWriters and readers across a single p= ost operation... //TODO implement and use a LockableDirectoryProvider that wraps a DP to ha= ndle the lock inside the LDP //TODO make this class final as soon as FullTextIndexCollectionEventListen= er is removed. @SuppressWarnings( "serial" ) public class FullTextIndexEventListener implements PostDeleteEventListener, PostInsertEventListener, PostUpdateEventListener, PostCollectionRecreateEventListener, PostCollectionRemoveEventListener, - PostCollectionUpdateEventListener, Initializable, Destructible { + PostCollectionUpdateEventListener, FlushEventListener, Initializable, De= structible { = private static final Logger log =3D LoggerFactory.make(); = protected boolean used; protected SearchFactoryImplementor searchFactoryImplementor; + = + //only used by the FullTextIndexEventListener instance playing in the Flu= shEventListener role. + // transient because it's not serializable (and state doesn't need to liv= e longer than a flush). + // final because it's initialization should be published to other threads. + // T.Local because different threads could be flushing on this listener, = still the reference + // to session should be weak so that sessions which had errors on flush c= an be discarded. + // ! update the readObject() method in case of name changes ! + // It's not static as we couldn't properly cleanup otherwise. + private transient final ThreadLocal<FlushContextContainer> flushSynch =3D= new ThreadLocal<FlushContextContainer>(); = /** * Initialize method called by Hibernate Core when the SessionFactory sta= rts @@ -157,4 +178,70 @@ } return id; } + + /** + * Make sure the indexes are updated right after the hibernate flush, + * avoiding object loading during a flush. Not needed during transactions. + */ + public void onFlush(FlushEvent event) { + if ( used ) { + Session session =3D event.getSession(); + FlushContextContainer flushContextContainer =3D this.flushSynch.get(); + if ( flushContextContainer !=3D null ) { + //first cleanup the ThreadLocal + this.flushSynch.set( null ); + EventSource registeringEventSource =3D flushContextContainer.eventSour= ce.get(); + //check that we are still in the same session which registered the flu= shSync: + if ( registeringEventSource !=3D null && registeringEventSource =3D=3D= session ) { + log.debug( "flush event causing index update out of transaction" ); + Synchronization synchronization =3D flushContextContainer.synchroniza= tion; + synchronization.beforeCompletion(); + synchronization.afterCompletion( Status.STATUS_COMMITTED ); + } + } + } + } + + public void addSynchronization(EventSource eventSource, Synchronization s= ynchronization) { + //no need to check for "unused" state, as this method is used by Search = itself only. + FlushContextContainer flushContext =3D new FlushContextContainer(eventSo= urce, synchronization); + //ignoring previously set data: if there was something, it's coming from= a previous thread + //which had some error when flushing and couldn't cleanup. + this.flushSynch.set( flushContext ); + } + + /* 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. + } + */ + + private static class FlushContextContainer { + = + private final WeakReference<EventSource> eventSource; + private final Synchronization synchronization; + + public FlushContextContainer(EventSource eventSource, Synchronization sy= nchronization) { + this.eventSource =3D new WeakReference<EventSource>( eventSource ); + this.synchronization =3D synchronization; + } + = + } + + private void writeObject(ObjectOutputStream os) throws IOException { + os.defaultWriteObject(); + } + + //needs to implement custom readObject to restore the transient fields + private void readObject(ObjectInputStream is) throws IOException, ClassNo= tFoundException, SecurityException, NoSuchFieldException, IllegalArgumentEx= ception, IllegalAccessException { + is.defaultReadObject(); + Class<FullTextIndexEventListener> cl =3D FullTextIndexEventListener.clas= s; + Field f =3D cl.getDeclaredField("flushSynch"); + f.setAccessible( true ); + ThreadLocal<FlushContextContainer> flushSynch =3D new ThreadLocal<FlushC= ontextContainer>(); + // setting a final field by reflection during a readObject is considered= as safe as in a constructor: + f.set( this, flushSynch ); + } + } Deleted: search/trunk/src/main/java/org/hibernate/search/event/IndexWorkFlu= shEventListener.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/main/java/org/hibernate/search/event/IndexWorkFlushEve= ntListener.java 2009-04-11 04:54:37 UTC (rev 16305) +++ search/trunk/src/main/java/org/hibernate/search/event/IndexWorkFlushEve= ntListener.java 2009-04-13 15:26:20 UTC (rev 16306) @@ -1,61 +0,0 @@ -// $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 FlushEventListen= er, Serializable { - = - private static final Logger log =3D LoggerFactory.make(); - = - private final ConcurrentHashMap<Session, Synchronization> synchronization= PerTransaction - =3D new ConcurrentHashMap<Session, Synchronization>(); - = - public IndexWorkFlushEventListener() { - } - - public void onFlush(FlushEvent event) throws HibernateException { - Session session =3D event.getSession(); - Synchronization synchronization =3D synchronizationPerTransaction.get( s= ession ); - if ( synchronization !=3D 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 s= ynchronization) { - Synchronization previousSync =3D synchronizationPerTransaction.put( even= tSource, synchronization ); - if ( previousSync !=3D 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. - } - */ - -} Modified: search/trunk/src/test/java/org/hibernate/search/test/TestCase.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/test/java/org/hibernate/search/test/TestCase.java 2009= -04-11 04:54:37 UTC (rev 16305) +++ search/trunk/src/test/java/org/hibernate/search/test/TestCase.java 2009= -04-13 15:26:20 UTC (rev 16306) @@ -15,7 +15,7 @@ import org.hibernate.dialect.Dialect; import org.hibernate.event.FlushEventListener; import org.hibernate.event.def.DefaultFlushEventListener; -import org.hibernate.search.event.IndexWorkFlushEventListener; +import org.hibernate.search.event.FullTextIndexEventListener; = /** * A modified base class for tests without annotations. @@ -154,7 +154,7 @@ cfg.setListener( "post-collection-remove", "org.hibernate.search.event.F= ullTextIndexEventListener" ); cfg.setListener( "post-collection-update", "org.hibernate.search.event.F= ullTextIndexEventListener" ); = - cfg.setListeners( "flush", new FlushEventListener[]{new DefaultFlushEven= tListener(), new IndexWorkFlushEventListener()} ); + cfg.setListeners( "flush", new FlushEventListener[]{new DefaultFlushEven= tListener(), new FullTextIndexEventListener()} ); = cfg.setProperty( "hibernate.search.default.directory_provider", RAMDirec= toryProvider.class.getName() ); cfg.setProperty( org.hibernate.search.Environment.ANALYZER_CLASS, StopAn= alyzer.class.getName() ); Added: search/trunk/src/test/java/org/hibernate/search/test/engine/EventLis= tenerSerializationTest.java =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- search/trunk/src/test/java/org/hibernate/search/test/engine/EventListen= erSerializationTest.java (rev 0) +++ search/trunk/src/test/java/org/hibernate/search/test/engine/EventListen= erSerializationTest.java 2009-04-13 15:26:20 UTC (rev 16306) @@ -0,0 +1,26 @@ +package org.hibernate.search.test.engine; + +import java.io.IOException; + +import junit.framework.TestCase; + +import org.hibernate.search.event.FullTextIndexEventListener; +import org.hibernate.search.test.SerializationTestHelper; + +/** + * Tests that the FullTextIndexEventListener is Serializable + * = + * @author Sanne Grinovero + */ +public class EventListenerSerializationTest extends TestCase { + + public void testEventListenerSerializable() throws IOException, ClassNotF= oundException { + FullTextIndexEventListener eventListener =3D new FullTextIndexEventListe= ner(); + eventListener.addSynchronization( null, null ); + Object secondListener =3D SerializationTestHelper + .duplicateBySerialization(eventListener); + assertNotNull(secondListener); + assertFalse(secondListener =3D=3D eventListener); + } + +} --===============5146307913699863023==--