[hibernate-commits] Hibernate SVN: r11085 - in branches/Branch_3_2/HibernateExt/metadata/src: java/org/hibernate/search/backend/impl/jms and 4 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Tue Jan 23 14:26:42 EST 2007


Author: epbernard
Date: 2007-01-23 14:26:42 -0500 (Tue, 23 Jan 2007)
New Revision: 11085

Added:
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSHibernateSearchController.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSMasterDirectoryProvider.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSSwitchableDirectoryProvider.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/FileHelper.java
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/util/
   branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/util/FileHelperTest.java
Modified:
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/SearchFactory.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSQueueWorker.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java
   branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java
Log:
ANN-519 pluggable worker
ANN-523 JMS implementation

Modified: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/SearchFactory.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/SearchFactory.java	2007-01-23 19:24:37 UTC (rev 11084)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/SearchFactory.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -82,7 +82,7 @@
 			if ( mappedClass != null ) {
 				XClass mappedXClass = reflectionManager.toXClass( mappedClass );
 				if ( mappedXClass != null && mappedXClass.isAnnotationPresent( Indexed.class ) ) {
-					DirectoryProvider provider = factory.createDirectoryProvider( mappedXClass, cfg );
+					DirectoryProvider provider = factory.createDirectoryProvider( mappedXClass, cfg, this );
 					if ( !lockableDirectoryProviders.containsKey( provider ) ) {
 						lockableDirectoryProviders.put( provider, new ReentrantLock() );
 					}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSHibernateSearchController.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSHibernateSearchController.java	                        (rev 0)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSHibernateSearchController.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -0,0 +1,71 @@
+//$Id: $
+package org.hibernate.search.backend.impl.jms;
+
+import java.util.List;
+import java.util.Properties;
+import javax.jms.MessageListener;
+import javax.jms.Message;
+import javax.jms.ObjectMessage;
+import javax.jms.JMSException;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.logging.Log;
+import org.hibernate.search.backend.Work;
+import org.hibernate.search.backend.QueueWorker;
+import org.hibernate.search.backend.impl.BatchedQueueWorker;
+import org.hibernate.search.util.ContextHelper;
+import org.hibernate.search.SearchFactory;
+import org.hibernate.Session;
+import org.hibernate.engine.SessionImplementor;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public abstract class JMSHibernateSearchController implements MessageListener {
+	private static Log log = LogFactory.getLog( JMSHibernateSearchController.class );
+
+	protected abstract Session getSession();
+
+	public void onMessage(Message message) {
+		if (! (message instanceof ObjectMessage) ) {
+			log.error( "Incorrect message type: " + message.getClass() );
+			return;
+		}
+		ObjectMessage objectMessage = (ObjectMessage) message;
+		List<Work> queue;
+		try {
+			queue = (List<Work>) objectMessage.getObject();
+		}
+		catch (JMSException e) {
+			log.error( "Unable to retrieve object from message: " + message.getClass(), e );
+			return;
+		}
+		catch( ClassCastException e ) {
+			log.error( "Illegal object retrieved from message", e );
+			return;
+		}
+		QueueWorker worker = getWorker( queue );
+		worker.run();
+	}
+
+	private QueueWorker getWorker(List<Work> queue) {
+		//FIXME casting sucks becasue we do not control what get session from
+		SearchFactory factory = ContextHelper.getSearchFactory( (SessionImplementor) getSession() );
+		QueueWorker worker = new BatchedQueueWorker();
+		worker.initialize( new Properties(), factory );
+		worker.setQueue( queue );
+		return worker;
+	}
+
+	@PostConstruct
+	public void initialize() {
+		//init the source copy process
+		
+	}
+	@PreDestroy
+	public void shutdown() {
+		//stop the source copy process
+	}
+}

Modified: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSQueueWorker.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSQueueWorker.java	2007-01-23 19:24:37 UTC (rev 11084)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/backend/impl/jms/JMSQueueWorker.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -2,11 +2,9 @@
 package org.hibernate.search.backend.impl.jms;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Properties;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.concurrent.locks.ReentrantLock;
 import java.io.Serializable;
 
 import javax.jms.QueueSender;
@@ -22,8 +20,7 @@
 
 import org.hibernate.search.backend.QueueWorker;
 import org.hibernate.search.backend.Work;
-import org.hibernate.search.engine.DocumentBuilder;
-import org.hibernate.search.store.DirectoryProvider;
+import org.hibernate.search.SearchFactory;
 import org.hibernate.search.Environment;
 import org.hibernate.HibernateException;
 
@@ -32,8 +29,6 @@
  */
 public class JMSQueueWorker implements QueueWorker {
 	private List<Work> queue;
-	private Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders;
-	private Map<Class, DocumentBuilder<Object>> documentBuilders;
 	private String jmsQueueName;
 	private String jmsConnectionFactoryName;
 	private static final String JNDI_PREFIX = Environment.WORKER_PREFIX + "jndi.";
@@ -43,9 +38,9 @@
 
 	public void run() {
 		resetJMSTools();
-		QueueConnection cnn = null;
-		QueueSender sender = null;
-		QueueSession session = null;
+		QueueConnection cnn;
+		QueueSender sender;
+		QueueSession session;
 		try {
 			cnn = factory.createQueueConnection();
 			//TODO make transacted parameterized
@@ -75,10 +70,7 @@
 		}
 	}
 
-	public void initialize(Properties props, Map<Class, DocumentBuilder<Object>> documentBuilders,
-						   Map<DirectoryProvider, ReentrantLock> lockableDirectoryProviders) {
-		this.documentBuilders = documentBuilders;
-		this.lockableDirectoryProviders = lockableDirectoryProviders;
+	public void initialize(Properties props, SearchFactory searchFactory) {
 		this.properties = props;
 		this.jmsConnectionFactoryName = props.getProperty( Environment.WORKER_PREFIX + "jms.connection_factory" );
 		this.jmsQueueName = props.getProperty( Environment.WORKER_PREFIX + "jms.queue" );

Modified: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java	2007-01-23 19:24:37 UTC (rev 11084)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProvider.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -4,6 +4,7 @@
 import java.util.Properties;
 
 import org.apache.lucene.store.Directory;
+import org.hibernate.search.SearchFactory;
 
 
 /**
@@ -21,7 +22,7 @@
 	/**
 	 * get the information to initialize the directory and build its hashCode
 	 */
-	void initialize(String directoryProviderName, Properties properties);
+	void initialize(String directoryProviderName, Properties properties, SearchFactory searchFactory);
 
 	/**
 	 * Returns an initialized Lucene Directory. This method call <b>must</b> be threadsafe

Modified: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java	2007-01-23 19:24:37 UTC (rev 11084)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/DirectoryProviderFactory.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -10,6 +10,7 @@
 import org.hibernate.cfg.AnnotationConfiguration;
 import org.hibernate.cfg.Configuration;
 import org.hibernate.search.annotations.Indexed;
+import org.hibernate.search.SearchFactory;
 import org.hibernate.mapping.PersistentClass;
 import org.hibernate.reflection.ReflectionManager;
 import org.hibernate.reflection.XClass;
@@ -40,9 +41,7 @@
 	private static String DEFAULT_DIRECTORY_PROVIDER = FSDirectoryProvider.class.getName();
 
 	//TODO for the public?
-	//public DirectoryProvider<?> createDirectoryProvider(XClass entity, Configuration cfg) {
-
-	public DirectoryProvider<?> createDirectoryProvider(XClass entity, Configuration cfg) {
+	public DirectoryProvider<?> createDirectoryProvider(XClass entity, Configuration cfg, SearchFactory searchFactory) {
 		//get properties
 		String directoryProviderName = getDirectoryProviderName( entity, cfg );
 		Properties indexProps = getDirectoryProperties( cfg, directoryProviderName );
@@ -63,7 +62,12 @@
 		catch (Exception e) {
 			throw new HibernateException( "Unable to instanciate directory provider: " + className, e );
 		}
-		provider.initialize( directoryProviderName, indexProps );
+		try {
+			provider.initialize( directoryProviderName, indexProps, searchFactory );
+		}
+		catch (Exception e) {
+			throw new HibernateException( "Unable to initialize: " + directoryProviderName, e);
+		}
 		int index = providers.indexOf( provider );
 		if ( index != -1 ) {
 			//share the same Directory provider for the same underlying store
@@ -93,7 +97,7 @@
 		return indexProps;
 	}
 
-	public static String getDirectoryProviderName(XClass clazz, Configuration cfg) {
+	private static String getDirectoryProviderName(XClass clazz, Configuration cfg) {
 		//yuk
 		ReflectionManager reflectionManager =
 				( (AnnotationConfiguration) cfg ).createExtendedMappings().getReflectionManager();

Modified: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java	2007-01-23 19:24:37 UTC (rev 11084)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSDirectoryProvider.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -3,7 +3,6 @@
 
 import java.io.File;
 import java.io.IOException;
-import java.text.MessageFormat;
 import java.util.Properties;
 
 import org.apache.commons.logging.Log;
@@ -12,6 +11,8 @@
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.store.FSDirectory;
 import org.hibernate.HibernateException;
+import org.hibernate.search.util.DirectoryProviderHelper;
+import org.hibernate.search.SearchFactory;
 
 /**
  * Use a Lucene FSDirectory
@@ -26,24 +27,11 @@
 	private static Log log = LogFactory.getLog( FSDirectoryProvider.class );
 	private String indexName;
 
-	public void initialize(String directoryProviderName, Properties properties) {
-		String indexBase = properties.getProperty( "indexBase", "." );
-		File indexDir = new File( indexBase );
-
-		if ( !( indexDir.exists() && indexDir.isDirectory() ) ) {
-			//TODO create the directory?
-			throw new HibernateException( MessageFormat.format( "Index directory does not exists: {0}", indexBase ) );
-		}
-		if ( !indexDir.canWrite() ) {
-			throw new HibernateException( "Cannot write into index directory: " + indexBase );
-		}
-		log.info( "Setting index dir to " + indexDir );
-
-		File file = new File( indexDir, directoryProviderName );
-
+	public void initialize(String directoryProviderName, Properties properties, SearchFactory searchFactory) {
+		File indexDir = DirectoryProviderHelper.determineIndexDir( directoryProviderName, properties );
 		try {
-			boolean create = !file.exists();
-			indexName = file.getCanonicalPath();
+			boolean create = !indexDir.exists();
+			indexName = indexDir.getCanonicalPath();
 			directory = FSDirectory.getDirectory( indexName, create );
 			if ( create ) {
 				IndexWriter iw = new IndexWriter( directory, new StandardAnalyzer(), create );
@@ -53,7 +41,6 @@
 		catch (IOException e) {
 			throw new HibernateException( "Unable to initialize index: " + directoryProviderName, e );
 		}
-
 	}
 
 	public FSDirectory getDirectory() {

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSMasterDirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSMasterDirectoryProvider.java	                        (rev 0)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSMasterDirectoryProvider.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -0,0 +1,193 @@
+//$Id: $
+package org.hibernate.search.store;
+
+import java.util.Timer;
+import java.util.Properties;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.locks.Lock;
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.search.util.DirectoryProviderHelper;
+import org.hibernate.search.util.FileHelper;
+import org.hibernate.search.SearchFactory;
+import org.hibernate.HibernateException;
+
+/**
+ * Use a Lucene FSDirectory
+ * The base directory is represented by hibernate.search.<index>.indexBase
+ * The index is created in <base directory>/<index name>
+ *
+ * @author Emmanuel Bernard
+ */
+public class FSMasterDirectoryProvider implements DirectoryProvider<FSDirectory> {
+	private static Log log = LogFactory.getLog( FSMasterDirectoryProvider.class );
+	private FSDirectory directory;
+	private int current;
+	private String indexName;
+	private Timer timer;
+	private SearchFactory searchFactory;
+
+	public void initialize(String directoryProviderName, Properties properties, SearchFactory searchFactory) {
+		//source guessing
+		String source = DirectoryProviderHelper.getSourceDirectory( "sourceBase", "source", properties );
+		if (source == null)
+			throw new IllegalStateException("FSMasterDirectoryProvider requires a viable source directory");
+		File indexDir = DirectoryProviderHelper.determineIndexDir( directoryProviderName, properties );
+		String refreshPeriod = properties.getProperty( "refresh", "60" );
+		long period = Long.parseLong( refreshPeriod );
+
+		try {
+			boolean create = !indexDir.exists();
+			indexName = indexDir.getCanonicalPath();
+			if (create) {
+				log.debug( "Index directory '" + indexName + "' will be initialized");
+				indexDir.mkdir();
+			}
+			directory = FSDirectory.getDirectory( indexName, create );
+			if ( create ) {
+				IndexWriter iw = new IndexWriter( directory, new StandardAnalyzer(), create );
+				iw.close();
+			}
+
+			//copy to source
+			if ( new File(source, "current1").exists() ) {
+				current = 2;
+			}
+			else if ( new File(source, "current2").exists() ) {
+				current = 1;
+			}
+			else {
+				log.debug( "Source directory for '" + indexName + "' will be initialized");
+				current = 1;
+			}
+			String currentString = Integer.valueOf( current ).toString();
+			File subDir = new File(source, currentString );
+			FileHelper.synchronize( indexDir, subDir, true );
+			new File(source, "current1").delete();
+			new File(source, "current2").delete();
+			//TODO small hole, no file can be found here
+			new File(source, "current" + currentString).createNewFile();
+			log.debug( "Current directory: " + current);
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to initialize index: " + directoryProviderName, e );
+		}
+		timer = new Timer();
+		TimerTask task = new FSMasterDirectoryProvider.TriggerTask(indexName, source, this );
+		timer.scheduleAtFixedRate( task, period, period );
+		this.searchFactory = searchFactory;
+	}
+
+	public FSDirectory getDirectory() {
+		return directory;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		if ( obj == this ) return true;
+		if ( obj == null || !( obj instanceof FSMasterDirectoryProvider ) ) return false;
+		return indexName.equals( ( (FSMasterDirectoryProvider) obj ).indexName );
+	}
+
+	@Override
+	public int hashCode() {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		int hash = 11;
+		return 37 * hash + indexName.hashCode();
+	}
+
+	class TriggerTask extends TimerTask {
+
+		private ExecutorService executor;
+		private FSMasterDirectoryProvider.CopyDirectory copyTask;
+
+		public TriggerTask(String source, String destination, DirectoryProvider directoryProvider) {
+			executor = Executors.newSingleThreadExecutor();
+			copyTask = new FSMasterDirectoryProvider.CopyDirectory( source, destination, directoryProvider );
+		}
+
+		public void run() {
+			if (!copyTask.inProgress) {
+				executor.execute( copyTask );
+			}
+			else {
+				log.info( "Skipping directory synchronization, previous work still in progress: " + indexName);
+			}
+		}
+	}
+
+	class CopyDirectory implements Runnable {
+		private String source;
+		private String destination;
+		private volatile boolean inProgress;
+		private Lock directoryProviderLock;
+		private DirectoryProvider directoryProvider;
+
+		public CopyDirectory(String source, String destination, DirectoryProvider directoryProvider) {
+			this.source = source;
+			this.destination = destination;
+			this.directoryProvider = directoryProvider;
+		}
+
+		public void run() {
+			//TODO get rid of current and use the marker file instead?
+			inProgress = true;
+			if (directoryProviderLock == null) {
+				directoryProviderLock = searchFactory.getLockableDirectoryProviders().get( directoryProvider );
+				directoryProvider = null;
+				searchFactory = null; //get rid of any useless link (help hot redeployment?)
+			}
+			try {
+				directoryProviderLock.lock();
+				int oldIndex = current;
+				int index = current == 1 ? 2 : 1;
+				File sourceFile = new File(source);
+
+				File destinationFile = new File(destination, Integer.valueOf(index).toString() );
+				//TODO make smart a parameter
+				try {
+					FileHelper.synchronize( sourceFile, destinationFile, true);
+					current = index;
+				}
+				catch (IOException e) {
+					//don't change current
+					log.error( "Unable to synchronize source of " + indexName, e);
+					inProgress = false;
+					return;
+				}
+				if ( ! new File(source, "current" + oldIndex).delete() ) {
+					log.warn( "Unable to remove previous marker file from source of " + indexName );
+				}
+				try {
+					new File(source, "current" + index).createNewFile();
+				}
+				catch( IOException e ) {
+					log.warn( "Unable to create current marker in source of " + indexName, e );
+				}
+			}
+			finally {
+				directoryProviderLock.unlock();
+				inProgress = false;
+			}
+		}
+	}
+
+	public void finalize() throws Throwable {
+		super.finalize();
+		timer.cancel();
+		//TODO find a better cycle from Hibernate core
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSSwitchableDirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSSwitchableDirectoryProvider.java	                        (rev 0)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/FSSwitchableDirectoryProvider.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -0,0 +1,217 @@
+//$Id: $
+package org.hibernate.search.store;
+
+import java.util.Properties;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.hibernate.HibernateException;
+import org.hibernate.AssertionFailure;
+import org.hibernate.search.util.FileHelper;
+import org.hibernate.search.util.DirectoryProviderHelper;
+import org.hibernate.search.SearchFactory;
+
+/**
+ * Use a Lucene FSDirectory
+ * The base directory is represented by hibernate.search.<index>.indexBase
+ * The index is created in <base directory>/<index name>
+ *
+ * @author Emmanuel Bernard
+ */
+public class FSSwitchableDirectoryProvider implements DirectoryProvider<FSDirectory> {
+	private static Log log = LogFactory.getLog( FSSwitchableDirectoryProvider.class );
+	private FSDirectory directory1;
+	private FSDirectory directory2;
+	private int current;
+	private String indexName;
+	private Timer timer;
+
+	public void initialize(String directoryProviderName, Properties properties, SearchFactory searchFactory) {
+		//source guessing
+		String source = DirectoryProviderHelper.getSourceDirectory( "sourceBase", "source", properties );
+		if (source == null)
+			throw new IllegalStateException("FSSwitchableDirectoryProvider requires a viable source directory");
+		if ( ! new File(source, "current1").exists() && ! new File(source, "current2").exists() ) {
+			throw new IllegalStateException("No current marker in source directory");
+		}
+		File indexDir = DirectoryProviderHelper.determineIndexDir( directoryProviderName, properties );
+		String refreshPeriod = properties.getProperty( "refresh", "60" );
+		long period = Long.parseLong( refreshPeriod );
+
+		try {
+			boolean create = !indexDir.exists();
+			indexName = indexDir.getCanonicalPath();
+			if (create) indexDir.mkdir();
+
+			File subDir = new File( indexName, "1" );
+			create = ! subDir.exists();
+			directory1 = FSDirectory.getDirectory( subDir.getCanonicalPath(), create );
+			if ( create ) {
+				IndexWriter iw = new IndexWriter( directory1, new StandardAnalyzer(), create );
+				iw.close();
+			}
+
+			subDir = new File( indexName, "2" );
+			create = ! subDir.exists();
+			directory2 = FSDirectory.getDirectory( subDir.getCanonicalPath(), create );
+			if ( create ) {
+				IndexWriter iw = new IndexWriter( directory2, new StandardAnalyzer(), create );
+				iw.close();
+			}
+			File currentMarker = new File(indexName, "current1");
+			File current2Marker = new File(indexName, "current2");
+			if ( currentMarker.exists() ) {
+				current = 1;
+			}
+			else if ( current2Marker.exists() ) {
+				current = 2;
+			}
+			else {
+				//no default
+				log.debug( "Setting directory 1 as current");
+				current = 1;
+				File sourceFile = new File(source);
+				File destinationFile = new File(indexName, Integer.valueOf(current).toString() );
+				//TODO make smart a parameter
+				try {
+					FileHelper.synchronize( sourceFile, destinationFile, true);
+				}
+				catch (IOException e) {
+					throw new HibernateException("Umable to synchonize directory: " + indexName, e);
+				}
+				if (! currentMarker.createNewFile() ) {
+					throw new HibernateException("Unable to create the directory marker file: " + indexName);
+				}
+			}
+			log.debug( "Current directory: " + current);
+		}
+		catch (IOException e) {
+			throw new HibernateException( "Unable to initialize index: " + directoryProviderName, e );
+		}
+		timer = new Timer();
+		TimerTask task = new TriggerTask(source, indexName);
+		timer.scheduleAtFixedRate( task, period, period );
+	}
+
+	public FSDirectory getDirectory() {
+		if (current == 1) {
+			return directory1;
+		}
+		else if (current == 2) {
+			return directory2;
+		}
+		else {
+			throw new AssertionFailure("Illegal current directory: " + current);
+		}
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		if ( obj == this ) return true;
+		if ( obj == null || !( obj instanceof FSSwitchableDirectoryProvider ) ) return false;
+		return indexName.equals( ( (FSSwitchableDirectoryProvider) obj ).indexName );
+	}
+
+	@Override
+	public int hashCode() {
+		// this code is actually broken since the value change after initialize call
+		// but from a practical POV this is fine since we only call this method
+		// after initialize call
+		int hash = 11;
+		return 37 * hash + indexName.hashCode();
+	}
+
+	class TriggerTask extends TimerTask {
+
+		private ExecutorService executor;
+		private CopyDirectory copyTask;
+
+		public TriggerTask(String source, String destination) {
+			executor = Executors.newSingleThreadExecutor();
+			copyTask = new CopyDirectory( source, destination  );
+		}
+
+		public void run() {
+			if (!copyTask.inProgress) {
+				executor.execute( copyTask );
+			}
+			else {
+				log.info( "Skipping directory synchronization, previous work still in progress: " + indexName);
+			}
+		}
+	}
+
+	class CopyDirectory implements Runnable {
+		private String source;
+		private String destination;
+		private volatile boolean inProgress;
+
+		public CopyDirectory(String source, String destination) {
+			this.source = source;
+			this.destination = destination;
+		}
+
+		public void run() {
+			try {
+				inProgress = true;
+				int oldIndex = current;
+				int index = current == 1 ? 2 : 1;
+				File sourceFile;
+				if ( new File( source, "current1" ).exists() ) {
+					sourceFile = new File(source, "1");
+				}
+				else if ( new File( source, "current2" ).exists() ) {
+					sourceFile = new File(source, "2");
+				}
+				else {
+					log.error("Unable to determine current in source directory");
+					inProgress = false;
+					return;
+				}
+
+				File destinationFile = new File(destination, Integer.valueOf(index).toString() );
+				//TODO make smart a parameter
+				try {
+					FileHelper.synchronize( sourceFile, destinationFile, true);
+					current = index;
+				}
+				catch (IOException e) {
+					//don't change current
+					log.error( "Unable to synchronize " + indexName, e);
+					inProgress = false;
+					return;
+				}
+				if ( ! new File(indexName, "current" + oldIndex).delete() ) {
+					log.warn( "Unable to remove previous marker file in " + indexName );
+				}
+				try {
+					new File(indexName, "current" + index).createNewFile();
+				}
+				catch( IOException e ) {
+					log.warn( "Unable to create current marker file in " + indexName, e );
+				}
+			}
+			finally {
+				inProgress = false;
+			}
+		}
+	}
+
+	public void finalize() throws Throwable {
+		super.finalize();
+		timer.cancel();
+		//TODO find a better cycle from Hibernate core
+	}
+}

Modified: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java	2007-01-23 19:24:37 UTC (rev 11084)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/store/RAMDirectoryProvider.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -8,6 +8,7 @@
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.store.RAMDirectory;
 import org.hibernate.HibernateException;
+import org.hibernate.search.SearchFactory;
 
 /**
  * Use a Lucene RAMDirectory
@@ -20,7 +21,7 @@
 	private RAMDirectory directory;
 	private String indexName;
 
-	public void initialize(String directoryProviderName, Properties properties) {
+	public void initialize(String directoryProviderName, Properties properties, SearchFactory searchFactory) {
 		indexName = directoryProviderName;
 		directory = new RAMDirectory();
 		try {

Added: branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/FileHelper.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/FileHelper.java	                        (rev 0)
+++ branches/Branch_3_2/HibernateExt/metadata/src/java/org/hibernate/search/util/FileHelper.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -0,0 +1,86 @@
+//$Id: $
+package org.hibernate.search.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.nio.channels.FileChannel;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public abstract class FileHelper {
+	private static final int FAT_PRECISION = 2000;
+
+	public static void synchronize(File source, File destination, boolean smart) throws IOException {
+		if ( source.isDirectory() ) {
+			if ( ! destination.exists() ) {
+				destination.mkdirs();
+			}
+			else if ( ! destination.isDirectory() ) {
+				throw new IOException("Source and Destination not of the same type:"
+						+ source.getCanonicalPath() + " , " + destination.getCanonicalPath() );
+			}
+			String[] sources = source.list();
+			Set<String> srcNames = new HashSet<String>( Arrays.asList( sources ) );
+			String[] dests = destination.list();
+
+			//delete files not present in source
+			for (String fileName : dests) {
+				if ( ! srcNames.contains( fileName ) ) {
+					delete( new File(destination, fileName) );
+				}
+			}
+			//copy each file from source
+			for (String fileName : sources) {
+				File srcFile = new File(source, fileName);
+				File destFile = new File(destination, fileName);
+				synchronize( srcFile, destFile, smart );
+			}
+		}
+		else {
+			if ( destination.exists() && destination.isDirectory() ) {
+				delete( destination );
+			}
+			if ( destination.exists() ) {
+				long sts = source.lastModified() / FAT_PRECISION;
+				long dts = destination.lastModified() / FAT_PRECISION;
+				//do not copy if smart and same timestamp and same length
+				if ( !smart || sts == 0 || sts != dts || source.length() != destination.length() ) {
+					copyFile(source, destination);
+				}
+			}
+			else {
+				copyFile(source, destination);
+			}
+		}
+	}
+
+	private static void copyFile(File srcFile, File destFile) throws IOException {
+		FileInputStream is = null;
+		FileOutputStream os = null;
+		try {
+			is = new FileInputStream(srcFile);
+			FileChannel iChannel = is.getChannel();
+			os = new FileOutputStream( destFile, false );
+			FileChannel oChannel = os.getChannel();
+			oChannel.transferFrom( iChannel, 0, srcFile.length() );
+		}
+		finally {
+			if (is != null) is.close();
+			if (os != null) os.close();
+		}
+		destFile.setLastModified( srcFile.lastModified() );
+	}
+
+	public static void delete(File file) {
+		if ( file.isDirectory() ) {
+			for ( File subFile : file.listFiles() ) delete( subFile );
+		}
+		if ( file.exists() ) file.delete();
+	}
+}

Added: branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/util/FileHelperTest.java
===================================================================
--- branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/util/FileHelperTest.java	                        (rev 0)
+++ branches/Branch_3_2/HibernateExt/metadata/src/test/org/hibernate/search/test/util/FileHelperTest.java	2007-01-23 19:26:42 UTC (rev 11085)
@@ -0,0 +1,82 @@
+//$Id: $
+package org.hibernate.search.test.util;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+import org.hibernate.search.util.FileHelper;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class FileHelperTest extends TestCase {
+	public void testTiti() throws Exception {
+		File titi = new File("file:/c:/titi", "file:/d:/toito");
+		assertFalse ( titi.exists() );
+	}
+
+	protected void setUp() throws Exception {
+		super.setUp();
+		File dir =  new File("./filehelpersrc");
+		dir.mkdir();
+		String name = "a";
+		createFile( dir, name );
+		name = "b";
+		createFile( dir, name );
+		dir =  new File(dir, "subdir");
+		dir.mkdir();
+		name = "c";
+		createFile( dir, name );
+	}
+
+	private void createFile(File dir, String name) throws IOException {
+		File a = new File(dir, name);
+		a.createNewFile();
+		FileOutputStream os = new FileOutputStream( a, false );
+		os.write( 1 );
+		os.write( 2 );
+		os.write( 3 );
+		os.flush();
+		os.close();
+	}
+
+	protected void tearDown() throws Exception {
+		super.setUp();
+		File dir =  new File("./filehelpersrc");
+		FileHelper.delete( dir );
+		dir =  new File("./filehelperdest");
+		FileHelper.delete( dir );
+	}
+
+	public void testSynchronize() throws Exception {
+		File src =  new File("./filehelpersrc");
+		File dest =  new File("./filehelpertest");
+		FileHelper.synchronize( src, dest, true );
+		File test = new File(dest, "b");
+		assertTrue( test.exists() );
+		test = new File( new File(dest, "subdir"), "c");
+		assertTrue( test.exists() );
+
+		//change
+		Thread.sleep( 2*2000 );
+		test = new File( src, "c");
+		FileOutputStream os = new FileOutputStream( test, true );
+		os.write( 1 );
+		os.write( 2 );
+		os.write( 3 );
+		os.flush();
+		os.close();
+		File destTest = new File(dest, "c");
+		assertNotSame( test.lastModified(), destTest.lastModified() );
+		FileHelper.synchronize( src, dest, true );
+		assertEquals( test.lastModified(), destTest.lastModified() );
+		assertEquals( test.length(), destTest.length() );
+
+		//delete file
+		test.delete();
+		FileHelper.synchronize( src, dest, true );
+		assertTrue( ! destTest.exists() );
+	}
+}




More information about the hibernate-commits mailing list