Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/CacheKey.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/CacheKey.java	Fri Sep 18 17:08:15 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/CacheKey.java	Fri Sep 18 17:08:15 CEST 2009
@@ -0,0 +1,71 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.Serializable;
+
+/**
+ * Abstract class used as a key for Infinispan cache to distinct its values.
+ * Connection of fields: indexName and fileName is unique, even if we share one cache between all DirectoryProvider's
+ *
+ * @author Lukasz Moren
+ * @see org.hibernate.search.store.infinispan.InfinispanDirectory#cache
+ */
+public abstract class CacheKey implements Serializable {
+
+	protected final String indexName;
+	protected final String fileName;
+
+	protected int hashCode;
+
+	protected CacheKey(String indexName, String fileName) {
+		this.indexName = indexName;
+		this.fileName = fileName;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if ( this == o ) {
+			return true;
+		}
+		if ( o == null || getClass() != o.getClass() ) {
+			return false;
+		}
+
+		CacheKey that = ( CacheKey ) o;
+
+		if ( fileName != null ? !fileName.equals( that.fileName ) : that.fileName != null ) {
+			return false;
+		}
+		if ( indexName != null ? !indexName.equals( that.indexName ) : that.indexName != null ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	@Override
+	public int hashCode() {
+		if ( hashCode == 0 ) {
+			hashCode = indexName != null ? indexName.hashCode() : 0;
+			hashCode = 31 * hashCode + ( fileName != null ? fileName.hashCode() : 0 );
+			hashCode = 31 * hashCode + ( this.getClass().getName() != null ? this.getClass().getName().hashCode() : 0 );
+		}
+		return hashCode;
+	}
+
+	@Override
+	public String toString() {
+		return "CacheKey{" +
+				"fileName='" + fileName + '\'' +
+				", indexName='" + indexName + '\'' +
+				'}';
+	}
+
+	public final String getIndexName() {
+		return indexName;
+	}
+
+	public final String getFileName() {
+		return fileName;
+	}
+
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/backend/configuration/IndexWriterSetting.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/backend/configuration/IndexWriterSetting.java	(revision 15155)
+++ ../HibernateSearch/src/main/java/org/hibernate/search/backend/configuration/IndexWriterSetting.java	Wed Sep 02 17:10:02 CEST 2009
@@ -2,9 +2,13 @@
 package org.hibernate.search.backend.configuration;
 
 import java.io.Serializable;
+import java.io.IOException;
 
 import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.MergeScheduler;
+
 import org.hibernate.search.SearchException;
+import org.hibernate.search.util.PluginLoader;
 
 /**
  * Represents possible options to be applied to an
@@ -18,69 +22,86 @@
 	 * @see org.apache.lucene.index.IndexWriter#setMaxBufferedDeleteTerms(int)
 	 */
 	MAX_BUFFERED_DELETE_TERMS( "max_buffered_delete_terms" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setMaxBufferedDeleteTerms( value );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setMaxBufferedDeleteTerms( ( Integer ) value );
 		}
 	} ,
 	/**
 	 * @see org.apache.lucene.index.IndexWriter#setMaxBufferedDocs(int)
 	 */
 	MAX_BUFFERED_DOCS( "max_buffered_docs" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setMaxBufferedDocs( value );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setMaxBufferedDocs( ( Integer ) value );
 		}
 	} ,
 	/**
 	 * @see org.apache.lucene.index.IndexWriter#setMaxFieldLength(int)
 	 */
 	MAX_FIELD_LENGTH( "max_field_length" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setMaxFieldLength( value );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setMaxFieldLength( ( Integer ) value );
 		}
 	} ,
 	/**
 	 * @see org.apache.lucene.index.IndexWriter#setMaxMergeDocs(int)
 	 */
 	MAX_MERGE_DOCS( "max_merge_docs" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setMaxMergeDocs( value );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setMaxMergeDocs( ( Integer ) value );
 		}
 	} ,
 	/**
 	 * @see org.apache.lucene.index.IndexWriter#setMergeFactor(int)
 	 */
 	MERGE_FACTOR( "merge_factor" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setMergeFactor( value );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setMergeFactor( ( Integer ) value );
 		}
 	} ,
 	/**
 	 * @see org.apache.lucene.index.IndexWriter#setRAMBufferSizeMB(double)
 	 */
 	RAM_BUFFER_SIZE( "ram_buffer_size" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setRAMBufferSizeMB( value );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setRAMBufferSizeMB( ( Double ) value );
 		}
 	} ,
 	/**
 	 * @see org.apache.lucene.index.IndexWriter#setTermIndexInterval(int)
 	 */
 	TERM_INDEX_INTERVAL( "term_index_interval" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setTermIndexInterval( value );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setTermIndexInterval( ( Integer ) value );
 		}
 	} ,
 	/**
 	 * @see org.apache.lucene.index.IndexWriter#setUseCompoundFile(boolean)
 	 */
 	USE_COMPOUND_FILE( "use_compound_file" ) {
-		public void applySetting(IndexWriter writer, int value) {
-			writer.setUseCompoundFile( intToBoolean( value ) );
+		public void applySetting(IndexWriter writer, Object value) {
+			writer.setUseCompoundFile( intToBoolean( ( Integer ) value ) );
 		}
 		@Override
 		public Integer parseVal(String value) {
 			return USE_COMPOUND_FILE.parseBoolean( value );
 		}
+	},
+	/**
+	 * @see org.apache.lucene.index.IndexWriter#setMergeScheduler(org.apache.lucene.index.MergeScheduler)
+	 */
+	MERGE_SCHEDULER( "merge_scheduler" ) {
+		public void applySetting(IndexWriter writer, Object value) {
+			try {
+				writer.setMergeScheduler( ( MergeScheduler ) value );
+			}
+			catch ( IOException e ) {
+				throw new SearchException( "Unable to set Merge Scheduler", e);
+			}
+		}
+		@Override
+		public MergeScheduler parseVal(String value) {
+			return PluginLoader.instanceFromName( MergeScheduler.class, value, IndexWriterSetting.class, "MergeScheduler");
+		}
 	};
 	
 	private static final Integer TRUE = Integer.valueOf( 1 );
@@ -95,7 +116,7 @@
 	/**
 	 * @throws IllegalArgumentException when user selects an invalid value; should be wrapped.
 	 */
-	public abstract void applySetting(IndexWriter writer, int value);
+	public abstract void applySetting(IndexWriter writer, Object value);
 
 	/**
 	 * @return The key used in configuration files to select an option.
@@ -110,7 +131,7 @@
 	 * @return the integer value going to be set as parameter
 	 * @throws SearchException for unrecognized values
 	 */
-	public Integer parseVal(String value) {
+	public Object parseVal(String value) {
 		return ConfigurationParseHelper.parseInt( value,
 				"Invalid value for " + cfgKey + ": " + value );
 	}
Index: ../HibernateSearch/src/test/resources/org/hibernate/search/test/infinispan/config/ispn-cache-default-conf.xml
===================================================================
--- ../HibernateSearch/src/test/resources/org/hibernate/search/test/infinispan/config/ispn-cache-default-conf.xml	Tue Sep 15 01:35:23 CEST 2009
+++ ../HibernateSearch/src/test/resources/org/hibernate/search/test/infinispan/config/ispn-cache-default-conf.xml	Tue Sep 15 01:35:23 CEST 2009
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<infinispan xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:infinispan:config:4.0">
+
+
+    <global>
+
+        <transport transportClass="org.infinispan.remoting.transport.jgroups.JGroupsTransport"
+                   clusterName="Hibernate-Search-Cluster"
+                   distributedSyncTimeout="50000">
+            <!--Note that the JGroups transport uses sensible defaults if no configuration property is defined.-->
+            <properties>
+                <property name="configurationFile" value="flush-udp.xml"/>
+            </properties>
+            <!--See the JGroupsTransport javadocs for more flags-->
+        </transport>
+
+    </global>
+
+    <default>
+        <locking concurrencyLevel="500" lockAcquisitionTimeout="40000" useLockStriping="false"/>
+        <jmxStatistics enabled="false"/>
+    </default>
+
+    <namedCache name="Movie's cache">
+        <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup"
+                     syncRollbackPhase="true"
+                     syncCommitPhase="true"/>
+        <clustering mode="repl">
+            <stateRetrieval fetchInMemoryState="false"/>
+            <sync replTimeout="30000"/>
+        </clustering>
+    </namedCache>
+
+    <namedCache name="Hibernate Search Cache">
+        <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup"
+                     syncRollbackPhase="true"
+                     syncCommitPhase="true"/>
+        <clustering mode="repl">
+            <stateRetrieval fetchInMemoryState="false"/>
+            <sync replTimeout="30000"/>
+        </clustering>
+    </namedCache>
+
+</infinispan>
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/configuration/ConfigurationReadTestCase.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/configuration/ConfigurationReadTestCase.java	(revision 15844)
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/configuration/ConfigurationReadTestCase.java	Wed Sep 09 11:56:49 CEST 2009
@@ -51,6 +51,10 @@
 				getParameter( shard, batch, setting, testEntity ) );
 	}
 
+	protected final void assertValueIsSet(Class testEntity, TransactionType parmGroup, IndexWriterSetting setting, Object expectedValue) {
+		assertValueIsSet( testEntity, 0, parmGroup, setting, expectedValue );
+	}
+
 	protected final void assertValueIsSet(Class testEntity, TransactionType parmGroup, IndexWriterSetting setting, int expectedValue) {
 		assertValueIsSet( testEntity, 0, parmGroup, setting, expectedValue );
 	}
@@ -60,9 +64,16 @@
 		assertNotNull( "shard:" + shard + " batch=" + batch + " setting:" + setting.getKey(),
 				getParameter( shard, batch, setting, testEntity ) );
 		assertEquals( "shard:" + shard + " batch=" + batch + " setting:" + setting.getKey(), expectedValue,
-				(int) getParameter( shard, batch, setting, testEntity ) );
+				getParameter( shard, batch, setting, testEntity ) );
 	}
 
+	protected final void assertValueIsSet(Class testEntity, int shard, TransactionType parmGroup, IndexWriterSetting setting, Object expectedValue) {
+		boolean batch = isBatch( parmGroup );
+		Object realValue = getParameter( shard, batch, setting, testEntity ) ;
+		assertNotNull( "shard:" + shard + " batch=" + batch + " setting:" + setting.getKey(), realValue);
+		assertEquals( "shard:" + shard + " batch=" + batch + " setting:" + setting.getKey(),  expectedValue.getClass(), realValue.getClass());
+	}
+
 	protected final SearchFactoryImplementor getSearchFactory() {
 		return searchFactory;
 	}
@@ -71,7 +82,7 @@
 		return parmGroup == TransactionType.BATCH;
 	}
 
-	private Integer getParameter(int shard, boolean batch, IndexWriterSetting setting, Class testEntity) {
+	private Object getParameter(int shard, boolean batch, IndexWriterSetting setting, Class testEntity) {
 		if ( batch ) {
 			return searchFactory.getIndexingParameters( searchFactory.getDirectoryProviders( testEntity )[shard] )
 															.getBatchIndexParameters().getCurrentValueFor( setting );
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanIndexIO.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanIndexIO.java	Mon Sep 14 16:24:45 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanIndexIO.java	Mon Sep 14 16:24:45 CEST 2009
@@ -0,0 +1,317 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
+
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.infinispan.Cache;
+import org.slf4j.Logger;
+
+import org.hibernate.search.util.LoggerFactory;
+
+/**
+ * Deal with input-output operations on Infinispan based Directory
+ *
+ * @author Lukasz Moren
+ * @see org.apache.lucene.store.Directory
+ * @see org.apache.lucene.store.IndexInput
+ * @see org.apache.lucene.store.IndexOutput
+ */
+public class InfinispanIndexIO {
+
+	protected static final Logger log = LoggerFactory.make();
+
+	//used as default chunk size if not privided in conf
+	//each Lucene index is splitted into parts with default size defined here
+	public final static int DEFAULT_BUFFER_SIZE = 16 * 1024;
+
+	private static byte[] getChunkFromPosition(Map<CacheKey, Object> cache, CacheKey fileKey, int pos, int bufferSize) {
+		CacheKey key = new ChunkCacheKey(
+				fileKey.getIndexName(), fileKey.getFileName(), getChunkNumberFromPosition( pos, bufferSize )
+		);
+		return ( byte[] ) cache.get( key );
+	}
+
+	private static int getPositionInBuffer(int pos, int bufferSize) {
+		return ( pos % bufferSize );
+	}
+
+	private static int getChunkNumberFromPosition(int pos, int bufferSize) {
+		return ( ( pos ) / ( bufferSize ) );
+	}
+
+	/**
+	 * Responsible for writing into <code>Directory</code>
+	 */
+	public static class InfinispanIndexInput extends IndexInput {
+
+		protected static final Logger log = LoggerFactory.make();
+
+		private final int bufferSize;
+
+		private Cache<CacheKey, Object> cache;
+		private ConcurrentHashMap<CacheKey, Object> localCache = new ConcurrentHashMap<CacheKey, Object>();
+		private FileMetadata file;
+		private CacheKey fileKey;
+		private byte[] buffer;
+		private int bufferPosition = 0;
+		private int filePosition = 0;
+
+		public InfinispanIndexInput(Cache<CacheKey, Object> cache, CacheKey fileKey)
+				throws IOException {
+			this( cache, fileKey, InfinispanIndexIO.DEFAULT_BUFFER_SIZE );
+		}
+
+		public InfinispanIndexInput(Cache<CacheKey, Object> cache, CacheKey fileKey, int bufferSize)
+				throws IOException {
+			this.cache = cache;
+			this.fileKey = fileKey;
+			this.bufferSize = bufferSize;
+			buffer = new byte[this.bufferSize];
+
+			//get file header from file
+			this.file = ( FileMetadata ) cache.get( fileKey );
+
+			if ( file == null ) {
+				throw new IOException( "File [ " + fileKey.getFileName() + " ] for index [ " + fileKey.getIndexName() + " ] was not found" );
+			}
+
+			//get records to local cache
+			int i = 0;
+			Object fileChunk;
+			ChunkCacheKey chunkKey = new ChunkCacheKey( fileKey.getIndexName(), fileKey.getFileName(), i );
+			while ( ( fileChunk = cache.get( chunkKey ) ) != null ) {
+				localCache.put( chunkKey, fileChunk );
+				chunkKey = new ChunkCacheKey( fileKey.getIndexName(), fileKey.getFileName(), ++i );
+			}
+
+			if ( log.isDebugEnabled() ) {
+				log.debug(
+						"Opened new IndexInput for file:{} in index: {}", fileKey.getFileName(), fileKey.getIndexName()
+				);
+			}
+		}
+
+		private byte[] getChunkFromPosition(Cache<CacheKey, Object> cache, CacheKey fileKey, int pos, int bufferSize) {
+			Object object = InfinispanIndexIO.getChunkFromPosition( cache, fileKey, pos, bufferSize );
+			if ( object == null ) {
+				object = InfinispanIndexIO.getChunkFromPosition( localCache, fileKey, pos, bufferSize );
+			}
+			return ( byte[] ) object;
+		}
+
+		public byte readByte() throws IOException {
+			if ( file == null ) {
+				throw new IOException( "File " + fileKey + " does not exist" );
+			}
+
+			if ( filePosition == 0 && file.getSize() == 0 ) {
+				if ( log.isTraceEnabled() ) {
+					log.trace( "pointer and file sizes are both 0; returning -1" );
+				}
+				return -1;
+			}
+
+			buffer = getChunkFromPosition( cache, fileKey, filePosition, bufferSize );
+			if ( buffer == null ) {
+				throw new IOException(
+						"Chunk id = [ " + getChunkNumberFromPosition(
+								filePosition, bufferSize
+						) + " ] does not exist for file [ " + fileKey
+								.getFileName() + " ] for index [ " + fileKey.getIndexName() + " ]"
+				);
+			}
+
+			bufferPosition = getPositionInBuffer( filePosition++, bufferSize );
+			return buffer[bufferPosition];
+		}
+
+		public void readBytes(byte[] b, int offset, int len) throws IOException {
+
+			if ( file == null ) {
+				throw new IOException( "(null): File does not exist" );
+			}
+
+			if ( filePosition == 0 && file.getSize() == 0 ) {
+				if ( log.isTraceEnabled() ) {
+					log.trace( "pointer and file sizes are both 0; returning -1" );
+				}
+			}
+
+			int bytesToRead = len;
+			while ( bytesToRead > 0 ) {
+				buffer = getChunkFromPosition( cache, fileKey, filePosition, bufferSize );
+				if ( buffer == null ) {
+					throw new IOException(
+							"Chunk id = [ " + getChunkNumberFromPosition(
+									filePosition, bufferSize
+							) + " ] does not exist for file [ " + fileKey.getFileName() + " ] for index [ " + fileKey
+									.getIndexName() + " ], file position: [ " + filePosition + " ], file size: [ " + file
+									.getSize() + " ]"
+					);
+				}
+				bufferPosition = getPositionInBuffer( filePosition, bufferSize );
+				int bytesToCopy = Math.min( buffer.length - bufferPosition, bytesToRead );
+				System.arraycopy( buffer, bufferPosition, b, offset, bytesToCopy );
+				offset += bytesToCopy;
+				bytesToRead -= bytesToCopy;
+				filePosition += bytesToCopy;
+			}
+		}
+
+		public void close() throws IOException {
+			filePosition = 0;
+			bufferPosition = 0;
+			buffer = null;
+			localCache = null;
+			if ( log.isDebugEnabled() ) {
+				log.debug(
+						"Closed IndexInput for file:{} in index: {}", fileKey.getFileName(), fileKey.getIndexName()
+				);
+			}
+		}
+
+		public long getFilePointer() {
+			return filePosition;
+		}
+
+		public void seek(long pos) throws IOException {
+			filePosition = ( int ) pos;
+		}
+
+		public long length() {
+			return file.getSize();
+		}
+	}
+
+	/**
+	 * Responsible for reading from <code>Directory</code>
+	 */
+	public static class InfinispanIndexOutput extends IndexOutput {
+
+		protected static final Logger log = LoggerFactory.make();
+
+		private final int bufferSize;
+
+		private Cache<CacheKey, Object> cache;
+		private FileMetadata file;
+		private CacheKey fileKey;
+
+		private byte[] buffer;
+		private int bufferPosition = 0;
+		private int filePosition = 0;
+		private int chunkNumber;
+
+		public InfinispanIndexOutput(Cache<CacheKey, Object> cache, CacheKey fileKey) throws IOException {
+			this( cache, fileKey, InfinispanIndexIO.DEFAULT_BUFFER_SIZE );
+		}
+
+		public InfinispanIndexOutput(Cache<CacheKey, Object> cache, CacheKey fileKey, int bufferSize)
+				throws IOException {
+			this.cache = cache;
+			this.fileKey = fileKey;
+			this.bufferSize = bufferSize;
+
+			buffer = new byte[this.bufferSize];
+
+			this.file = ( FileMetadata ) cache.get( fileKey );
+			if ( log.isDebugEnabled() ) {
+				log.debug(
+						"Opened new IndexOutput for file:{} in index: {}", fileKey.getFileName(), fileKey.getIndexName()
+				);
+			}
+		}
+
+		private void newChunk() throws IOException {
+			flush();//save data first
+
+			//check if we have to create new chunk, or get already existing in cache for modification
+			if ( ( buffer = getChunkFromPosition( cache, fileKey, filePosition, bufferSize ) ) == null ) {
+				buffer = new byte[bufferSize];
+			}
+			bufferPosition = 0;
+		}
+
+		public void writeByte(byte b) throws IOException {
+			if ( isNewChunkNeeded() ) {
+				newChunk();
+			}
+			buffer[bufferPosition++] = b;
+			filePosition++;
+		}
+
+		public void writeBytes(byte[] b, int offset, int length) throws IOException {
+
+			int writedBytes = 0;
+			while ( writedBytes < length ) {
+				int pieceLength = Math.min( buffer.length - bufferPosition, length - writedBytes );
+				System.arraycopy( b, offset + writedBytes, buffer, bufferPosition, pieceLength );
+				bufferPosition += pieceLength;
+				filePosition += pieceLength;
+				writedBytes += pieceLength;
+				if ( isNewChunkNeeded() ) {
+					newChunk();
+				}
+			}
+		}
+
+		private boolean isNewChunkNeeded() {
+			return ( bufferPosition == buffer.length );
+		}
+
+		public void flush() throws IOException {
+			//select right chunkNumber
+			chunkNumber = getChunkNumberFromPosition( filePosition - 1, bufferSize );
+			//and create distict key for it
+			ChunkCacheKey key = new ChunkCacheKey( fileKey.getIndexName(), fileKey.getFileName(), chunkNumber );
+			//size changed, apply change to file header
+			setFileLength();
+			//add chunk to cache
+			cache.put( key, buffer );
+			//override existing gile header with new size and last time acess
+			cache.put( fileKey, file );
+		}
+
+		public void close() throws IOException {
+			flush();
+			bufferPosition = 0;
+			filePosition = 0;
+			buffer = null;
+			if ( log.isDebugEnabled() ) {
+				log.debug(
+						"Closed IndexOutput for file:{} in index: {}", fileKey.getFileName(), fileKey.getIndexName()
+				);
+			}
+		}
+
+		public long getFilePointer() {
+			return filePosition;
+		}
+
+		public void seek(long pos) throws IOException {
+			flush();
+
+			if ( pos > file.getSize() ) {
+				throw new IOException( fileKey.getFileName() + ": seeking past of the file" );
+			}
+
+			buffer = getChunkFromPosition( cache, fileKey, ( int ) pos, bufferSize );
+			bufferPosition = getPositionInBuffer( ( int ) pos, bufferSize );
+			filePosition = ( int ) pos;
+		}
+
+		public long length() throws IOException {
+			return file.getSize();
+		}
+
+		protected void setFileLength() {
+			file.setLastModified( System.currentTimeMillis() );
+			if ( file.getSize() < filePosition ) {
+				file.setSize( filePosition );
+			}
+		}
+	}
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanDirectoryProvider.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanDirectoryProvider.java	Wed Sep 23 13:40:28 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanDirectoryProvider.java	Wed Sep 23 13:40:28 CEST 2009
@@ -0,0 +1,343 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.MergeScheduler;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.infinispan.Cache;
+import org.infinispan.manager.CacheManager;
+import org.slf4j.Logger;
+
+import org.hibernate.search.SearchException;
+import org.hibernate.search.backend.LuceneIndexingParameters;
+import org.hibernate.search.backend.configuration.ConfigurationParseHelper;
+import org.hibernate.search.backend.configuration.IndexWriterSetting;
+import org.hibernate.search.backend.configuration.MaskedProperty;
+import org.hibernate.search.engine.SearchFactoryImplementor;
+import org.hibernate.search.store.DirectoryProvider;
+import org.hibernate.search.util.LoggerFactory;
+import org.hibernate.search.util.PluginLoader;
+import org.hibernate.util.ConfigHelper;
+import org.hibernate.util.StringHelper;
+
+/**
+ * Connects Lucene Directory based on Infinispan: {@link org.hibernate.search.store.infinispan.InfinispanDirectory}
+ * with Hibernate Search.
+ * <p><b>Configuration</b>
+ * <br/> All configuration described here have to be added into Hibernate configuration file
+ * (<code>hibernate.cfg.xml</code> or <code>persistence.xml</code>)</p><p>
+ * <p/>
+ * To bring in InfinispanDirectoryProvider into live is used property:
+ * <pre>{@code <property name="hibernate.search.default.directory_provider"
+ * value="org.hibernate.search.store.infinispan.InfinispanDirectoryProvider"/>}</pre>
+ * Lucene search data for all indexed entities will be stored in a single grid.</p><p>
+ * <p/>
+ * Storage place can be scoped per indexed entity, e.g.:
+ * <pre>{@code <property name="hibernate.search.Movie.directory_provider"
+ * value="org.hibernate.search.store.infinispan.InfinispanDirectoryProvider"/>}</pre>
+ * <pre>{@code <property name="hibernate.search.default.directory_provider"
+ * value="org.hibernate.search.store.FSDirectoryProvider"/>}</pre>
+ * With that configuration only search data for entity {@code Movie} will be stored in Infinispan grid.
+ * All other use FSDirectoryProvider.</p><p>
+ * <p/>
+ * It is possible to have one shared Infinispan cache for all indexes, one cache for each index, and provide different
+ * configuration to each of them. With below configuration indexes for entity <code>Movie</code> will be stored
+ * in a grid called <code>movie's cache</code>. All other use default grid.
+ * <pre>{@code <property name="hibernate.search.Movie.infinispan.cache_name"
+ * value="movie's cache"/>}</pre>
+ * Configuration for cache named <code>movie's cache</code> have to be defined in configuration file (either
+ * cache_config_xml or cache_config_class) accordingly to Infinispan configuration reference.
+ * Default settings are used otherwise.</p><p>
+ * </p>
+ * Infinispan data grid can be configured according to user requirements,
+ * see: <a href="http://www.jboss.org/community/wiki/SettingupanInfinispanCluster">Infinispan configuration reference</a>}
+ * Configuration can be provided in two ways, through XML or programmatically, e.g.:<ul>
+ * <li>{@code <property name="hibernate.search.default.infinispan.cache_config_xml"
+ * value="ispn-cache-default-conf.xml"/>}</li>
+ * <li>{@code <property name="hibernate.search.default.infinispan.cache_config_class"
+ * value="org.hibernate.search.store.infinispan.DefaultInfinispanCacheManagerConfiguration"/>}</li></pre>
+ * </ul>
+ * Notice that all configuration classes passed as value for property <code>hibernate.search.default.infinispan.cache_config_class</code>
+ * have to implement {@link org.hibernate.search.store.infinispan.InfinispanCacheManagerConfiguration}.</p><p>
+ * <p/>
+ * For better performance data stored in Infinispan grid is splitted into parts. Size (in bytes) of each part can be
+ * configured and it is also scoped per index, e.g.:
+ * <pre{@code <property name="hibernate.search.Movie.infinispan.buffer_size" value="512"/>}</pre></p>
+ *
+ * @author Lukasz Moren
+ */
+public class InfinispanDirectoryProvider
+		implements DirectoryProvider<InfinispanDirectory>, InfinispanCacheLifecycleManager {
+
+	private static final Logger log = LoggerFactory.make();
+
+	//default cache name if not provided in cofiguration
+	public static final String DEFAULT_CACHE_NAME = "Hibernate Search Cache";
+	//configuration params keys:
+	//xml file with Infinispan configuration
+	public static final String CACHE_CONFIG_XML = "cache_config_xml";
+	//class providing Infinispan cache configuration - must implement org.hibernate.search.store.infinispan.InfinispanCacheManagerConfiguration
+	public static final String CACHE_CONFIG_CLASS = "cache_config_class";
+	//cache name, parameter can be used e.g. when want to use independent store per each indexed entity
+	public static final String CACHE_NAME = "cache_name";
+	//cache buffer size
+	public static final String BUFFER_SIZE = "buffer_size";
+
+	private InfinispanDirectory directory;
+	private SearchFactoryImplementor factory;
+
+	private CacheManager cacheManager;
+	private Cache<CacheKey, Object> cache;
+
+	private String directoryProviderName;
+	private Properties properties;
+
+	public void initialize(String directoryProviderName, Properties properties, SearchFactoryImplementor searchFactoryImplementor) {
+		this.directoryProviderName = directoryProviderName;
+		this.factory = searchFactoryImplementor;
+
+		Properties infinispanProperties = new MaskedProperty( properties, "infinispan" );
+		this.properties = infinispanProperties;
+
+		//check if InfinispanDirectoryProvider was created already before, if so, do not create new
+		//Infinsipan cache manager, use already existing
+		Set<DirectoryProvider<?>> directoryProviders = factory.getDirectoryProviders();
+		for ( DirectoryProvider<?> directoryProvider : directoryProviders ) {
+			if ( directoryProvider instanceof InfinispanCacheLifecycleManager ) {
+				cacheManager = ( ( InfinispanCacheLifecycleManager ) directoryProvider ).getInfinispanCacheManager();
+				break;
+			}
+		}
+		//cache manager was not created yet
+		if ( cacheManager == null ) {
+			cacheManager = createInfinispanCacheManager( infinispanProperties );
+		}
+	}
+
+	public void start() {
+
+		cache = createCacheFromConfiguration( cacheManager, properties );
+		if ( log.isInfoEnabled() ) {
+			log.info(
+					"Cache: {} started in cluster: {} with address: {}", new Object[] {
+							cache.getName(),
+							cache.getCacheManager().getClusterName(),
+							cache.getCacheManager().getAddress()
+					}
+			);
+		}
+		//get chunk size from conf
+		int bufferSize = ConfigurationParseHelper.getIntValue(
+				properties, BUFFER_SIZE, InfinispanIndexIO.DEFAULT_BUFFER_SIZE
+		);
+		directory = new InfinispanDirectory(
+				cache, directoryProviderName, bufferSize
+		);
+
+		//add Lucene Index writer parameters specific for InfinispanDirectory
+		LuceneIndexingParameters luceneParams = factory.getIndexingParameters( this );
+		factory.addIndexingParameters( this, setCustomLuceneMergeScheduler( luceneParams ) );
+
+		try {
+			IndexWriter.MaxFieldLength fieldLength = new IndexWriter.MaxFieldLength( IndexWriter.DEFAULT_MAX_FIELD_LENGTH );
+			IndexWriter iw = new IndexWriter( directory, new StandardAnalyzer(), true, fieldLength );
+			iw.close();
+		}
+		catch ( IOException e ) {
+			throw new SearchException( "Unable to initialize index: " + directoryProviderName, e );
+		}
+
+		if ( log.isInfoEnabled() ) {
+			log.info( "Directory provider started: {}", directoryProviderName );
+		}
+	}
+
+	public void stop() {
+		try {
+			if ( directory != null ) {
+				directory.close();
+			}
+
+			//Cache Manager is shared between all Directory Providers in each HS instance,
+			//have to be stopped when last directory provider is being closed
+			Set<DirectoryProvider<?>> directoryProviders = factory.getDirectoryProviders();
+			Iterator<DirectoryProvider<?>> iterator = directoryProviders.iterator();
+			InfinispanDirectoryProvider lastDP = null;
+			while ( iterator.hasNext() ) {
+				DirectoryProvider dp = iterator.next();
+				if ( dp instanceof InfinispanDirectoryProvider ) {
+					lastDP = ( InfinispanDirectoryProvider ) dp;
+				}
+			}
+
+			//last DP is being closed, stop also Cache Manager
+			if ( this == lastDP ) {
+				cacheManager.stop();
+				cacheManager = null;
+			}
+		}
+		catch ( IOException e ) {
+			log.error( "Unable to properly close Lucene directory {}" + directoryProviderName, e );
+		}
+		finally {
+			directory = null;
+		}
+	}
+
+	public InfinispanDirectory getDirectory() {
+		return directory;
+	}
+
+	public Cache<CacheKey, Object> getCache() {
+		return cache;
+	}
+
+	public void setCache(Cache<CacheKey, Object> cache) {
+		this.cache = cache;
+	}
+
+	protected LuceneIndexingParameters setCustomLuceneMergeScheduler(LuceneIndexingParameters luceneParams) {
+		//check existing configuration and show warning if different than SerialMergeScheduler
+		MergeScheduler transactionalMergeScheduler = ( MergeScheduler ) luceneParams.getTransactionIndexParameters()
+				.getCurrentValueFor( IndexWriterSetting.MERGE_SCHEDULER );
+
+		MergeScheduler serialMergeScheduler = ( MergeScheduler ) luceneParams.getTransactionIndexParameters()
+				.getCurrentValueFor( IndexWriterSetting.MERGE_SCHEDULER );
+
+		if ( transactionalMergeScheduler != null || serialMergeScheduler != null ) {
+			if ( !( transactionalMergeScheduler instanceof SerialMergeScheduler ) ||
+					!( serialMergeScheduler instanceof SerialMergeScheduler ) ) {
+				if ( log.isWarnEnabled() ) {
+					log.warn(
+							"{} class was found in configuration to be used as Lucene merge scheduler. InfinispanDirectoryProvider does not support this. Setting will be overriden to {}",
+							transactionalMergeScheduler != null ? transactionalMergeScheduler.getClass()
+									.getName() : serialMergeScheduler.getClass().getName(),
+							SerialMergeScheduler.class.getName()
+					);
+				}
+			}
+
+		}
+		//set SerialMergeScheduler as MergeScheduler used by IndexWriter
+		luceneParams.getBatchIndexParameters()
+				.setCurrentValueFor( IndexWriterSetting.MERGE_SCHEDULER, new SerialMergeScheduler() );
+		luceneParams.getTransactionIndexParameters()
+				.setCurrentValueFor( IndexWriterSetting.MERGE_SCHEDULER, new SerialMergeScheduler() );
+		return luceneParams;
+
+	}
+
+	protected CacheManager createInfinispanCacheManager(Properties properties) {
+		CacheManager manager = null;
+
+		//try first to read configuration from xml
+		String configXml = properties.getProperty( CACHE_CONFIG_XML );
+		if ( StringHelper.isNotEmpty( configXml ) ) {
+			try {
+				manager = InfinispanCacheManagerFactory.getInstance(
+						ConfigHelper.getConfigStream(
+								configXml
+						)
+				);
+			}
+			catch ( Exception e ) {
+				log.error( "Cannot create Infinispan cache from xml config file: {}.", configXml );
+				throw new SearchException( e );
+			}
+		}
+
+		//if cache still not initialized, get configuration provided in class
+		if ( manager == null ) {
+			//apply default config in not found any in properties
+			String configClass = properties.getProperty(
+					CACHE_CONFIG_CLASS, DefaultInfinispanCacheManagerConfiguration.class.getName()
+			);
+
+			InfinispanCacheManagerConfiguration cacheManagerConf = PluginLoader.instanceFromName(
+					InfinispanCacheManagerConfiguration.class, configClass,
+					InfinispanDirectoryProvider.class, "Infinispan Cache Configuration"
+			);
+
+			try {
+				manager = InfinispanCacheManagerFactory.getInstance( cacheManagerConf );
+			}
+			catch ( Exception e ) {
+				log.error(
+						"Cannot create Infinispan cache from configuration class: {}.",
+						cacheManagerConf.getClass().getName()
+				);
+				throw new SearchException( e );
+			}
+		}
+
+		if ( manager == null ) {
+			throw new SearchException( "Unable to initialize Infinispan cache manager" );
+		}
+
+		return manager;
+	}
+
+	/**
+	 * Retrieves a cache from manager with a name provided in properties, if name does not exist default one is used
+	 *
+	 * @param manager cache manager
+	 * @param properties contains configuration parameters for cache
+	 *
+	 * @return cache used to store Lucene indexes
+	 */
+	protected Cache<CacheKey, Object> createCacheFromConfiguration(CacheManager manager, Properties properties) {
+		Cache<CacheKey, Object> cache;
+
+		//check if there is provided Infinispan cache name in conf,
+		String cacheName = properties.getProperty( CACHE_NAME );
+		if ( StringHelper.isEmpty( cacheName ) ) {
+			cacheName = DEFAULT_CACHE_NAME;
+		}
+
+		try {
+			cache = manager.getCache( cacheName );
+		}
+		catch ( Exception e ) {
+			throw new SearchException( "Unable to initialize Infinispan cache", e );
+		}
+
+		return cache;
+	}
+
+	public CacheManager getInfinispanCacheManager() {
+		return cacheManager;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if ( this == o ) {
+			return true;
+		}
+		if ( o == null || getClass() != o.getClass() ) {
+			return false;
+		}
+
+		InfinispanDirectoryProvider that = ( InfinispanDirectoryProvider ) o;
+
+		return !( directoryProviderName != null ? !directoryProviderName.equals( that.directoryProviderName ) : that.directoryProviderName != null );
+
+	}
+
+	@Override
+	public int hashCode() {
+		return directoryProviderName != null ? directoryProviderName.hashCode() : 0;
+	}
+
+	@Override
+	public String toString() {
+		return "InfinispanDirectoryProvider{" +
+				"directoryProviderName='" + directoryProviderName + '\'' +
+				'}';
+	}
+
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/ChunkCacheKey.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/ChunkCacheKey.java	Fri Sep 18 17:06:38 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/ChunkCacheKey.java	Fri Sep 18 17:06:38 CEST 2009
@@ -0,0 +1,66 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.Serializable;
+
+/**
+ * Used as a key to distinguish file chunk in cache.
+ *
+ * @author Lukasz Moren
+ */
+public class ChunkCacheKey extends CacheKey implements Serializable {
+
+	protected final Integer chunkId;
+
+	public ChunkCacheKey(String indexName, String fileName, Integer chunkId) {
+		super( indexName, fileName );
+		this.chunkId = chunkId;
+	}
+
+	public Integer getChunkId() {
+		return Integer.valueOf( chunkId );
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if ( this == o ) {
+			return true;
+		}
+		if ( o == null || getClass() != o.getClass() ) {
+			return false;
+		}
+		if ( !super.equals( o ) ) {
+			return false;
+		}
+
+		ChunkCacheKey that = ( ChunkCacheKey ) o;
+
+		if ( fileName != null ? !fileName.equals( that.fileName ) : that.fileName != null ) {
+			return false;
+		}
+		if ( indexName != null ? !indexName.equals( that.indexName ) : that.indexName != null ) {
+			return false;
+		}
+
+		if ( chunkId != null ? !chunkId.equals( that.chunkId ) : that.chunkId != null ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	@Override
+	public int hashCode() {
+		if ( hashCode == 0) {
+			hashCode = super.hashCode();
+			hashCode = 31 * hashCode + ( chunkId != null ? chunkId.hashCode() : 0 );
+		}
+		return hashCode;
+	}
+
+	@Override
+	public String toString() {
+		return "ChunkCacheKey{" +
+				"chunkId=" + chunkId +
+				"} " + super.toString();
+	}
+}
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/Movie.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/Movie.java	Wed Sep 09 13:07:48 CEST 2009
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/Movie.java	Wed Sep 09 13:07:48 CEST 2009
@@ -0,0 +1,75 @@
+package org.hibernate.search.test.directoryProvider.infinispan;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Indexed;
+
+/**
+* @author Lukasz Moren
+*/
+@Entity
+@Indexed(index = "Movie")
+public class Movie {
+
+	@Id
+	@GeneratedValue
+	@DocumentId
+	private Long id;
+
+	@Field(index = Index.TOKENIZED)
+	private String name;
+
+	@Field(index = Index.TOKENIZED)
+	private String director;
+
+	private byte[] trailerData;
+
+	@ManyToOne
+	private Genre genre;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getDirector() {
+		return director;
+	}
+
+	public void setDirector(String director) {
+		this.director = director;
+	}
+
+	public Genre getGenre() {
+		return genre;
+	}
+
+	public void setGenre(Genre genre) {
+		this.genre = genre;
+	}
+
+	public byte[] getTrailerData() {
+		return trailerData;
+	}
+
+	public void setTrailerData(byte[] trailerData) {
+		this.trailerData = trailerData;
+	}
+}
Index: ../HibernateSearch/pom.xml
===================================================================
--- ../HibernateSearch/pom.xml	(revision 17516)
+++ ../HibernateSearch/pom.xml	Fri Sep 18 12:57:38 CEST 2009
@@ -124,6 +124,18 @@
             <version>${slf4jVersion}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>jboss.jbossts</groupId>
+            <artifactId>jbossts-common</artifactId>
+            <version>4.6.1.GA</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>jboss.jbossts</groupId>
+            <artifactId>jbossjta</artifactId>
+            <version>4.4.0.GA</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -689,10 +701,22 @@
                 <dependency>
                     <groupId>jgroups</groupId>
                     <artifactId>jgroups</artifactId>
-                    <version>2.6.7.GA</version>
+                    <version>2.8.0.CR1</version>
+                    <exclusions>
+                        <exclusion>
+                            <groupId>org.testng</groupId>
+                            <artifactId>testng</artifactId>
+                        </exclusion>
+                    </exclusions>
                     <optional>true</optional>
                 </dependency>
                 <dependency>
+                    <groupId>org.infinispan</groupId>
+                    <artifactId>infinispan-core</artifactId>
+                    <version>4.0.0.BETA1</version>
+                    <optional>true</optional>
+                </dependency>
+                <dependency>
                     <groupId>javax.annotation</groupId>
                     <artifactId>jsr250-api</artifactId>
                     <version>1.0</version>
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanCacheEntryListenerTest.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanCacheEntryListenerTest.java	Wed Sep 09 13:05:41 CEST 2009
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanCacheEntryListenerTest.java	Wed Sep 09 13:05:41 CEST 2009
@@ -0,0 +1,83 @@
+package org.hibernate.search.test.directoryProvider.infinispan;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import org.infinispan.Cache;
+import org.infinispan.manager.CacheManager;
+
+import org.hibernate.search.store.infinispan.ChunkCacheKey;
+import org.hibernate.search.store.infinispan.FileCacheKey;
+import org.hibernate.search.store.infinispan.FileListCacheKey;
+import org.hibernate.search.store.infinispan.InfinispanCacheEntryListener;
+import org.hibernate.search.store.infinispan.InfinispanCacheManagerConfiguration;
+import org.hibernate.search.store.infinispan.InfinispanCacheManagerFactory;
+import org.hibernate.search.store.infinispan.CacheKey;
+import org.hibernate.search.store.infinispan.FileMetadata;
+import org.hibernate.search.store.infinispan.DefaultInfinispanCacheManagerConfiguration;
+
+/**
+ * @author Lukasz Moren
+ */
+public class InfinispanCacheEntryListenerTest extends TestCase {
+
+	Cache<CacheKey, Object> cache;
+	CacheManager cm;
+
+	public void testCacheEntryCreatedListener() {
+		final FileListCacheKey fileListKey = new FileListCacheKey( "index" );
+
+		//add some data
+		cache.put( fileListKey, Collections.synchronizedMap( new HashMap<String, String>() ) );
+		cache.addListener( new InfinispanCacheEntryListener( "index" ) );
+
+		final FileCacheKey key1 = new FileCacheKey( "index", "Hello.txt" );
+		cache.put( key1, new FileMetadata() );
+		cache.put( new ChunkCacheKey( "index", "Chunk.txt", 0 ), new byte[15] );
+		cache.put( new FileCacheKey( "index", "write.lock", true ), "");
+
+		final FileCacheKey key2 = new FileCacheKey( "index", "World.txt" );
+		cache.put( key2, new FileMetadata() );
+
+		final FileCacheKey key3 = new FileCacheKey( "index2", "Other.txt" );
+		cache.put( key3, new FileMetadata() );
+
+		//check if cache listener properly added file names to the list
+		Map map = ( Map ) cache.get( fileListKey );
+
+		assertTrue( map.containsKey( "Hello.txt" ) );
+		assertFalse( map.containsKey( "Chunk.txt" ) );
+		assertFalse( map.containsKey( "write.lock" ) );
+		assertTrue( map.containsKey( "World.txt" ) );
+		assertFalse( map.containsKey( "Other.txt" ) );
+
+		cache.remove( key1 );
+		cache.remove( key2 );
+		cache.remove( key3 );
+
+		//and properly removed file names from the list
+		map = ( Map ) cache.get( fileListKey );
+
+		assertFalse( map.containsKey( "Hello.txt" ) );
+		assertFalse( map.containsKey( "World.txt" ) );
+		assertFalse( map.containsKey( "Other.txt" ) );
+	}
+
+	public void setUp() {
+		InfinispanCacheManagerConfiguration conf = new DefaultInfinispanCacheManagerConfiguration();
+		cm = InfinispanCacheManagerFactory.getInstance( conf );
+		cache = cm.getCache();
+
+	}
+
+	public void tearDown() {
+		if ( cm != null ) {
+			cm.stop();
+		}
+		if ( cache != null ) {
+			cache.stop();
+		}
+	}
+}
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryIOTest.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryIOTest.java	Wed Sep 09 13:22:11 CEST 2009
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryIOTest.java	Wed Sep 09 13:22:11 CEST 2009
@@ -0,0 +1,250 @@
+package org.hibernate.search.test.directoryProvider.infinispan;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.infinispan.Cache;
+
+import org.hibernate.search.store.infinispan.CacheKey;
+import org.hibernate.search.store.infinispan.ChunkCacheKey;
+import org.hibernate.search.store.infinispan.FileCacheKey;
+import org.hibernate.search.store.infinispan.InfinispanDirectory;
+import org.hibernate.search.store.infinispan.InfinispanIndexIO;
+import org.hibernate.search.store.infinispan.FileMetadata;
+
+
+public class InfinispanDirectoryIOTest extends CacheTestSupport {
+
+	public void testReadChunks() throws Exception {
+		final int BUFFER_SIZE = 64;
+
+		Cache<CacheKey, Object> cache = createCache();
+		InfinispanDirectory dir = new InfinispanDirectory( cache, "index", BUFFER_SIZE );
+
+		//create file headers
+		FileMetadata file1 = new FileMetadata();
+		CacheKey key1 = new FileCacheKey( "index", "Hello.txt" );
+		cache.put( key1, file1 );
+
+		FileMetadata file2 = new FileMetadata();
+		CacheKey key2 = new FileCacheKey( "index", "World.txt" );
+		cache.put( key2, file2 );
+
+		// byte array for Hello.txt
+		String helloText = "Hello world.  This is some text.";
+		cache.put( new ChunkCacheKey( "index", "Hello.txt", 0 ), helloText.getBytes() );
+
+		// byte array for World.txt - shouldbe in at least 2 chunks.
+		String worldText = "This String should contain more than sixty four characters but less than one hundred and twenty eight.";
+
+		byte[] buf = new byte[BUFFER_SIZE];
+		System.arraycopy( worldText.getBytes(), 0, buf, 0, BUFFER_SIZE );
+		cache.put( new ChunkCacheKey( "index", "World.txt", 0 ), buf );
+
+		String part1 = new String( buf );
+		buf = new byte[BUFFER_SIZE];
+		System.arraycopy(
+				worldText.getBytes(),
+				BUFFER_SIZE,
+				buf,
+				0,
+				worldText.length() - BUFFER_SIZE
+		);
+		cache.put( new ChunkCacheKey( "index", "World.txt", 1 ), buf );
+		String part2 = new String( buf );
+
+		// make sure the generated bytes do add up!
+		assertEquals( worldText, part1 + part2.trim() );
+
+		file1.setSize( helloText.length() );
+		file2.setSize( worldText.length() );
+
+
+		Set<String> s = new HashSet<String>();
+		s.add( "Hello.txt" );
+		s.add( "World.txt" );
+		Set other = new HashSet( Arrays.asList( dir.list() ) );
+
+		// ok, file listing works.
+		assertEquals( s, other );
+
+		IndexInput ii = dir.openInput( "Hello.txt" );
+
+		assertEquals( ii.length(), helloText.length() );
+
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+		for ( int i = 0; i < ii.length(); i++ ) {
+			baos.write( ii.readByte() );
+		}
+
+		assertEquals( new String( baos.toByteArray() ), helloText );
+
+		ii = dir.openInput( "World.txt" );
+
+		assertEquals( ii.length(), worldText.length() );
+
+		baos = new ByteArrayOutputStream();
+
+		for ( int i = 0; i < ii.length(); i++ ) {
+			baos.write( ii.readByte() );
+		}
+
+		assertEquals( new String( baos.toByteArray() ), worldText );
+
+		// now with buffered reading
+
+		ii = dir.openInput( "Hello.txt" );
+
+		assertEquals( ii.length(), helloText.length() );
+
+		baos = new ByteArrayOutputStream();
+
+		long toRead = ii.length();
+		while ( toRead > 0 ) {
+			buf = new byte[19]; // suitably arbitrary
+			int bytesRead = ( int ) Math.min( toRead, 19 );
+			ii.readBytes( buf, 0, bytesRead );
+			toRead = toRead - bytesRead;
+			baos.write( buf, 0, bytesRead );
+		}
+
+		assertEquals( new String( baos.toByteArray() ), helloText );
+
+		ii = dir.openInput( "World.txt" );
+
+		assertEquals( ii.length(), worldText.length() );
+
+		baos = new ByteArrayOutputStream();
+
+		toRead = ii.length();
+		while ( toRead > 0 ) {
+			buf = new byte[19]; // suitably arbitrary
+			int bytesRead = ( int ) Math.min( toRead, 19 );
+			ii.readBytes( buf, 0, bytesRead );
+			toRead = toRead - bytesRead;
+			baos.write( buf, 0, bytesRead );
+		}
+
+		assertEquals( new String( baos.toByteArray() ), worldText );
+
+		dir.deleteFile( "Hello.txt" );
+		assertNull( cache.get( new FileCacheKey( "index", "Hello.txt" ) ) );
+		assertNull( cache.get( new ChunkCacheKey( "index", "Hello.txt", 0 ) ) );
+
+		Object ob1 = cache.get( new FileCacheKey( "index", "World.txt" ) );
+		Object ob2 = cache.get( new ChunkCacheKey( "index", "World.txt", 0 ) );
+		Object ob3 = cache.get( new ChunkCacheKey( "index", "World.txt", 1 ) );
+
+		dir.renameFile( "World.txt", "HelloWorld.txt" );
+		assertNull( cache.get( new FileCacheKey( "index", "Hello.txt" ) ) );
+		assertNull( cache.get( new ChunkCacheKey( "index", "Hello.txt", 0 ) ) );
+		assertNull( cache.get( new ChunkCacheKey( "index", "Hello.txt", 1 ) ) );
+
+		assertEquals( cache.get( new FileCacheKey( "index", "HelloWorld.txt" ) ), ob1 );
+		assertEquals( cache.get( new ChunkCacheKey( "index", "HelloWorld.txt", 0 ) ), ob2 );
+		assertEquals( cache.get( new ChunkCacheKey( "index", "HelloWorld.txt", 1 ) ), ob3 );
+
+
+		// test that contents survive a move
+		ii = dir.openInput( "HelloWorld.txt" );
+
+		assertEquals( ii.length(), worldText.length() );
+
+		baos = new ByteArrayOutputStream();
+
+		toRead = ii.length();
+		while ( toRead > 0 ) {
+			buf = new byte[19]; // suitably arbitrary
+			int bytesRead = ( int ) Math.min( toRead, 19 );
+			ii.readBytes( buf, 0, bytesRead );
+			toRead = toRead - bytesRead;
+			baos.write( buf, 0, bytesRead );
+		}
+
+		assertEquals( new String( baos.toByteArray() ), worldText );
+
+		cache.getCacheManager().stop();
+		dir.close();
+
+	}
+
+	public void testWriteChunks() throws Exception {
+		final int BUFFER_SIZE = 64;
+
+		Cache<CacheKey, Object> cache = createCache();
+		InfinispanDirectory dir = new InfinispanDirectory( cache, "index", BUFFER_SIZE );
+
+		IndexOutput io = dir.createOutput( "MyNewFile.txt" );
+
+		io.writeByte( ( byte ) 66 );
+		io.writeByte( ( byte ) 69 );
+
+		io.close();
+
+		assertTrue( dir.fileExists( "MyNewFile.txt" ) );
+		assertNotNull( cache.get( new ChunkCacheKey( "index", "MyNewFile.txt", 0 ) ) );
+
+		// test contents by reading:
+		byte[] buf = new byte[9];
+		IndexInput ii = dir.openInput( "MyNewFile.txt" );
+		ii.readBytes( buf, 0, ( int ) ii.length() );
+
+		assertEquals( new String( new byte[] { 66, 69 } ), new String( buf ).trim() );
+
+		String testText = "This is some rubbish again that will span more than one chunk - one hopes.  Who knows, maybe even three or four chunks.";
+		io.seek( 0 );
+		io.writeBytes( testText.getBytes(), 0, testText.length() );
+		io.close();
+		// now compare.
+		byte[] chunk1 = ( byte[] ) cache.get( new ChunkCacheKey( "index", "MyNewFile.txt", 0 ) );
+		byte[] chunk2 = ( byte[] ) cache.get( new ChunkCacheKey( "index", "MyNewFile.txt", 1 ) );
+		assertNotNull( chunk1 );
+		assertNotNull( chunk2 );
+
+		assertEquals(
+				testText, new String( chunk1 ) + new String( chunk2 ).trim()
+		);
+
+		cache.getCacheManager().stop();
+		dir.close();
+
+	}
+
+	public void testWriteChunksDefaultChunks() throws Exception {
+		Cache<CacheKey, Object> cache = createCache();
+		InfinispanDirectory dir = new InfinispanDirectory( cache, "index" );
+
+		String testText = "This is some rubbish";
+		byte[] testTextAsBytes = testText.getBytes();
+
+		IndexOutput io = dir.createOutput( "MyNewFile.txt" );
+
+		io.writeByte( ( byte ) 1 );
+		io.writeByte( ( byte ) 2 );
+		io.writeByte( ( byte ) 3 );
+		io.writeBytes( testTextAsBytes, testTextAsBytes.length );
+		io.close();
+
+		assertNotNull( cache.get( new FileCacheKey( "index", "MyNewFile.txt" ) ) );
+		assertNotNull( cache.get( new ChunkCacheKey( "index", "MyNewFile.txt", 0 ) ) );
+
+		// test contents by reading:
+		IndexInput ii = dir.openInput( "MyNewFile.txt" );
+		assertEquals( ii.readByte(), 1 );
+		assertEquals( ii.readByte(), 2 );
+		assertEquals( ii.readByte(), 3 );
+		byte[] buf = new byte[32];
+
+		ii.readBytes( buf, 0, testTextAsBytes.length );
+
+		assertEquals( testText, ( new String( buf ).trim() ) );
+
+		cache.getCacheManager().stop();
+		dir.close();
+	}
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheLifecycleManager.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheLifecycleManager.java	Tue Sep 08 18:42:24 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheLifecycleManager.java	Tue Sep 08 18:42:24 CEST 2009
@@ -0,0 +1,11 @@
+package org.hibernate.search.store.infinispan;
+
+import org.infinispan.manager.CacheManager;
+
+/**
+ * Class uses Infinispan cache manager and manages its lifecyle (creates/stops it)
+ * @author Lukasz Moren
+ */
+public interface InfinispanCacheLifecycleManager {
+	public CacheManager getInfinispanCacheManager();
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileCacheKey.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileCacheKey.java	Fri Sep 18 17:08:15 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileCacheKey.java	Fri Sep 18 17:08:15 CEST 2009
@@ -0,0 +1,68 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.Serializable;
+
+/**
+ * Used as a key for file headers in a cache
+ *
+ * @author Lukasz Moren
+ */
+public class FileCacheKey extends CacheKey implements Serializable {
+
+	protected final boolean isLockKey;
+
+	public FileCacheKey(String indexName, String fileName) {
+		this( indexName, fileName, false );
+	}
+
+	public FileCacheKey(String indexName, String fileName, boolean isLockKey) {
+		super( indexName, fileName );
+		this.isLockKey = isLockKey;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if ( this == o ) {
+			return true;
+		}
+		if ( o == null || getClass() != o.getClass() ) {
+			return false;
+		}
+		if ( !super.equals( o ) ) {
+			return false;
+		}
+
+		FileCacheKey that = ( FileCacheKey ) o;
+
+		if ( fileName != null ? !fileName.equals( that.fileName ) : that.fileName != null ) {
+			return false;
+		}
+		if ( indexName != null ? !indexName.equals( that.indexName ) : that.indexName != null ) {
+			return false;
+		}
+
+		if ( isLockKey != that.isLockKey ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	@Override
+	public int hashCode() {
+		if ( hashCode == 0 ) {
+			hashCode = super.hashCode();
+			hashCode = 31 * hashCode + ( isLockKey ? 1 : 0 );
+		}
+		return hashCode;
+	}
+
+	@Override
+	public String toString() {
+		return "CacheKey{" +
+				"fileName='" + getFileName() + '\'' +
+				"indexName='" + getIndexName() + '\'' +
+				"isLockKey='" + isLockKey + '\'' +
+				'}';
+	}
+}
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/CacheTestSupport.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/CacheTestSupport.java	Wed Sep 23 13:45:00 CEST 2009
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/CacheTestSupport.java	Wed Sep 23 13:45:00 CEST 2009
@@ -0,0 +1,119 @@
+package org.hibernate.search.test.directoryProvider.infinispan;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.util.Random;
+
+import javax.transaction.TransactionManager;
+
+import junit.framework.TestCase;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.DateTools;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.LockObtainFailedException;
+import org.infinispan.Cache;
+import org.infinispan.util.concurrent.IsolationLevel;
+import org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup;
+import org.infinispan.config.GlobalConfiguration;
+import org.infinispan.manager.CacheManager;
+import org.infinispan.manager.DefaultCacheManager;
+import org.slf4j.Logger;
+
+import org.hibernate.search.store.infinispan.CacheKey;
+import org.hibernate.search.store.infinispan.InfinispanDirectory;
+import org.hibernate.search.util.LoggerFactory;
+
+public abstract class CacheTestSupport extends TestCase {
+
+	protected static final Logger log = LoggerFactory.make();
+
+	protected Cache<CacheKey, Object> createCache() {
+		GlobalConfiguration gc = GlobalConfiguration.getClusteredDefault();
+		org.infinispan.config.Configuration c = new org.infinispan.config.Configuration();
+		c.setCacheMode( org.infinispan.config.Configuration.CacheMode.REPL_SYNC );
+		c.setSyncReplTimeout( 10000 );
+		c.setLockAcquisitionTimeout( 10000 );
+		c.setUseLockStriping( false );
+		c.setSyncCommitPhase( true );
+		c.setSyncRollbackPhase( true );
+		c.setTransactionManagerLookupClass( JBossStandaloneJTAManagerLookup.class.getName() );
+	//c.setDeadlockDetectionSpinDuration( 10000 );
+		CacheManager manager = new DefaultCacheManager( gc, c );
+		return manager.getCache( "TestCache" );
+	}
+
+	protected File createDummyDocToIndex(String fileName, int sz) throws Exception {
+		File dummyDocToIndex = new File( fileName );
+		if ( dummyDocToIndex.exists() ) {
+			dummyDocToIndex.delete();
+		}
+		dummyDocToIndex.createNewFile();
+		Random r = new Random();
+		FileWriter fw = new FileWriter( dummyDocToIndex );
+		for ( int i = 0; i < sz; i++ ) {
+			fw.write( Integer.toHexString( r.nextInt( 16 ) ) );
+		}
+		fw.close();
+		dummyDocToIndex.deleteOnExit();
+		return dummyDocToIndex;
+	}
+
+	protected void doWriteOperation(Directory d, File document) throws Exception {
+		// this is a write
+		IndexWriter writer = null;
+
+		try {
+			writer = new IndexWriter( d, new StandardAnalyzer(), IndexWriter.MaxFieldLength.UNLIMITED );
+			writer.setMergeScheduler( new SerialMergeScheduler() );
+			log.info( "IndexWriter was constructed" );
+
+			Document doc = new Document();
+			doc.add( new Field( "path", document.getPath(), Field.Store.YES, Field.Index.NOT_ANALYZED ) );
+			doc.add(
+					new Field(
+							"modified",
+							DateTools.timeToString( document.lastModified(), DateTools.Resolution.MINUTE ),
+							Field.Store.YES, Field.Index.NOT_ANALYZED
+					)
+			);
+			doc.add( new Field( "contents", new FileReader( document ) ) );
+			doc.add(new Field( "info", "good", Field.Store.YES, Field.Index.ANALYZED));
+
+			writer.addDocument( doc );
+		}
+		catch ( LockObtainFailedException lofe ) {
+			//can happen
+		}
+		finally {
+			if ( writer != null ) {
+				writer.close();
+				log.info( "IndexWriter was closed" );
+			}
+		}
+	}
+
+	protected void doReadOperation(Directory d) throws Exception {
+		IndexSearcher search = null;
+		try {
+			// this is a read
+			search = new IndexSearcher( d );
+
+			// dummy query that probably won't return anything
+			QueryParser qp = new QueryParser( "path", new StandardAnalyzer() );
+			search.search( qp.parse( "good" ), null, 1 );
+		}
+		finally {
+			if ( search != null ) {
+				search.close();
+			}
+		}
+	}
+
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheManagerConfiguration.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheManagerConfiguration.java	Tue Sep 08 18:48:37 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheManagerConfiguration.java	Tue Sep 08 18:48:37 CEST 2009
@@ -0,0 +1,40 @@
+package org.hibernate.search.store.infinispan;
+
+import java.util.List;
+import java.util.Map;
+
+import org.infinispan.config.GlobalConfiguration;
+import org.infinispan.config.Configuration;
+
+/**
+ * Common interface for all clases providing Infinispan configuration for {@link InfinispanDirectoryProvider}.
+ * Implementation of this interface have to be passed as parameter with Hibernate configuration:
+ * <pre>{@code <property name="hibernate.search.default.infinispan.cache_config_class"
+ * value="org.hibernate.search.store.infinispan.DefaultInfinispanCacheManagerConfiguration"/>}</pre>
+ *
+ * @author Lukasz Moren
+ * @see org.hibernate.search.store.infinispan.DefaultInfinispanCacheManagerConfiguration
+ */
+
+public interface InfinispanCacheManagerConfiguration {
+
+	/**
+	 * Create configuration used to setup Infinispan cluster
+	 * <br/>See for details: <a href="http://www.jboss.org/community/wiki/SettingupanInfinispanCluster">Infinispan cluster configuration</a>
+	 *
+	 * @return global Infinispan configuration
+	 */
+	public GlobalConfiguration getGlobalConfiguration();
+
+	/**
+	 * Create configuration to setup caches used for storing Lucene indexes.
+	 * Configurations are stored as a pair: key:cache name - value:configuration.
+	 * <br/>Key has to be equals to property:
+	 * <pre>{@code <property name="hibernate.search.Movie.infinispan.cache_name"
+	 * value="movie's cache"/>}</pre> passed in hibernate configuration
+	 * <br/>See for details: <a href="http://www.jboss.org/community/wiki/5minutetutorialonInfinispan">Infinispan tutorial</a>
+	 *
+	 * @return configuration for Infinispan caches
+	 */
+	public Map<String, Configuration> getCustomConfiguration();
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanDirectory.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanDirectory.java	Tue Sep 22 01:05:35 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanDirectory.java	Tue Sep 22 01:05:35 CEST 2009
@@ -0,0 +1,237 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.store.LockFactory;
+import org.apache.lucene.store.AlreadyClosedException;
+import org.infinispan.Cache;
+import org.slf4j.Logger;
+
+import org.hibernate.search.util.LoggerFactory;
+
+/**
+ * Implementation that uses Infinispan to store Lucene indices
+ * <p/>
+ * Directory locking is assured with {@link org.hibernate.search.store.infinispan.InfinispanLockFactory.InfinispanLock}
+ *
+ * @author Lukasz Moren
+ * @see org.hibernate.search.store.infinispan.InfinispanLockFactory
+ */
+//todo add support for ConcurentMergeSheduler
+public class InfinispanDirectory extends Directory {
+
+	protected static final Logger log = LoggerFactory.make();
+	// own flag required if we are not in this same package what org.apache.lucene.store.Directory,
+	//access type will be changed in the next Lucene version
+	volatile boolean isOpen = true;
+
+	private Cache<CacheKey, Object> cache;
+	//indexName is required when one common cache is used
+	private String indexName;
+	//chunk size used in this directory, static filed not used as we want to have different chunk size per dir
+	private int chunkSize;
+
+	public InfinispanDirectory(Cache<CacheKey, Object> cache, String indexName, LockFactory lf, int chunkSize) {
+		this.cache = cache;
+		this.indexName = indexName;
+		this.setLockFactory( lf );
+		this.chunkSize = chunkSize;
+
+		//fixme change it to ConcurrentHashMap when [JBMAR-68] bug is fixed
+		//Infinispan does not provide efficient API (existing .keySet() is not recommended for production use) to retrieve list of available
+		//objects in cache. One entry in cache, store list of lucene file names.
+		cache.put( new FileListCacheKey( indexName ), Collections.synchronizedMap( new HashMap<String, String>() ) );
+		//register listener which add/remove file names to/from above list
+		cache.addListener( new InfinispanCacheEntryListener( indexName ) );
+
+	}
+
+	public InfinispanDirectory(Cache<CacheKey, Object> cache, String indexName, LockFactory lf) {
+		this( cache, indexName, lf, InfinispanIndexIO.DEFAULT_BUFFER_SIZE );
+	}
+
+
+	public InfinispanDirectory(Cache<CacheKey, Object> cache, String indexName, int chunkSize) {
+		this( cache, indexName, new InfinispanLockFactory( cache, indexName ), chunkSize );
+	}
+
+	public InfinispanDirectory(Cache<CacheKey, Object> cache, String indexName) {
+		this( cache, indexName, new InfinispanLockFactory( cache, indexName ), InfinispanIndexIO.DEFAULT_BUFFER_SIZE );
+	}
+
+	public InfinispanDirectory(Cache<CacheKey, Object> cache) {
+		this( cache, "" );
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@SuppressWarnings("unchecked")
+	public synchronized String[] list() throws IOException {
+		checkIsOpen();
+		Map<String, String> filesList = ( Map<String, String> ) cache.get( new FileListCacheKey( indexName ) );
+		return ( String[] ) filesList.values().toArray( new String[] { } );
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public boolean fileExists(String name) throws IOException {
+		checkIsOpen();
+		return cache.containsKey( new FileCacheKey( indexName, name ) );
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public long fileModified(String name) throws IOException {
+		checkIsOpen();
+		FileMetadata file = getFile( indexName, name );
+		if ( file == null ) {
+			throw new FileNotFoundException( name );
+		}
+		return file.getLastModified();
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public void touchFile(String name) throws IOException {
+		checkIsOpen();
+		FileMetadata file = getFile( indexName, name );
+		if ( file == null ) {
+			throw new FileNotFoundException( name );
+		}
+		file.setLastModified( System.currentTimeMillis() );
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public void deleteFile(String name) throws IOException {
+		checkIsOpen();
+		CacheKey key = new FileCacheKey( indexName, name );
+		//remove main file
+		cache.remove( key );
+
+		//and all of its chunks
+		int i = 0;
+		Object removed;
+		ChunkCacheKey chunkKey = new ChunkCacheKey( indexName, name, i );
+		do {
+			removed = cache.remove( chunkKey );
+			chunkKey = new ChunkCacheKey( indexName, name, ++i );
+		} while ( removed != null );
+
+		if ( log.isDebugEnabled() ) {
+			log.debug(
+					"Removed file: {} from index: {}",
+					new Object[] { key.getFileName(), indexName}
+			);
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public void renameFile(String from, String to) throws IOException {
+		checkIsOpen();
+		//rename main file header
+		FileMetadata fileFrom = getFile( indexName, from );
+		cache.remove( new FileCacheKey( indexName, from ) );
+		cache.put( new FileCacheKey( indexName, to ), fileFrom );
+
+		//rename also all chunks
+		int i = -1;
+		Object ob;
+		do {
+			ChunkCacheKey chunkKey = new ChunkCacheKey( indexName, from, ++i );
+			ob = cache.get( chunkKey );
+			if ( ob == null ) {
+				break;
+			}
+			chunkKey = new ChunkCacheKey( indexName, to, i );
+			cache.put( chunkKey, ob );
+		} while ( true );
+
+		if ( log.isTraceEnabled() ) {
+			log.trace( "Renamed file from: {} to: {} in index {}", new Object[] { from, to, indexName } );
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public synchronized long fileLength(String name) throws IOException {
+		checkIsOpen();
+		final FileMetadata file = getFile( indexName, name );
+		if ( file == null ) {
+			throw new FileNotFoundException( name );
+		}
+		return file.getSize();
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public synchronized IndexOutput createOutput(String name) throws IOException {
+		final CacheKey key = new FileCacheKey( indexName, name );
+		if ( !fileExists( name ) ) {
+			cache.put( key, new FileMetadata() );
+		}
+		return new InfinispanIndexIO.InfinispanIndexOutput( cache, key, chunkSize );
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public IndexInput openInput(String name) throws IOException {
+		final FileCacheKey fileKey = new FileCacheKey( indexName, name );
+		return new InfinispanIndexIO.InfinispanIndexInput( cache, fileKey, chunkSize );
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public void close() throws IOException {
+		isOpen = false;
+		if ( cache != null ) {
+			cache.stop();
+			cache = null;
+		}
+	}
+
+	private void checkIsOpen() throws AlreadyClosedException {
+		if ( !isOpen ) {
+			throw new AlreadyClosedException( "this Directory is closed" );
+		}
+	}
+
+
+	private FileMetadata getFile(String indexName, String fileName) {
+		CacheKey key = new FileCacheKey( indexName, fileName );
+		return ( FileMetadata ) cache.get( key );
+	}
+
+	@Override
+	public String toString() {
+		return "InfinispanDirectory{" +
+				"indexName='" + indexName + '\'' +
+				'}';
+	}
+
+	public Cache<CacheKey, Object> getCache() {
+		return cache;
+	}
+}
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryTest.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryTest.java	Wed Sep 23 14:11:58 CEST 2009
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryTest.java	Wed Sep 23 14:11:58 CEST 2009
@@ -0,0 +1,147 @@
+package org.hibernate.search.test.directoryProvider.infinispan;
+
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Random;
+
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.Hits;
+import org.infinispan.Cache;
+import org.slf4j.Logger;
+
+import org.hibernate.search.store.infinispan.InfinispanDirectory;
+import org.hibernate.search.store.infinispan.CacheKey;
+import org.hibernate.search.util.LoggerFactory;
+
+
+/**
+ * @author Lukasz Moren
+ */
+public class InfinispanDirectoryTest extends CacheTestSupport {
+	protected static final Logger log = LoggerFactory.make();
+
+
+	public static final int THREADS_NUM = 50;
+	public static final int TURNS_NUM = 300;
+
+	private AtomicInteger writeCount = new AtomicInteger( 0 );
+
+	public void testInfinispanDirectory() throws Exception {
+		final int OPERATIONS = 100;
+		Cache<CacheKey, Object> cache = createCache();
+		Directory directory = new InfinispanDirectory( cache, "indexName" );
+		File document = createDummyDocToIndex( "document.lucene", 10000 );
+
+		for ( int i = 0; i < OPERATIONS; i++ ) {
+			doWriteOperation( directory, document );
+			doReadOperation( directory );
+		}
+
+		IndexSearcher search = new IndexSearcher( directory );
+		Term t = new Term( "info", "good" );
+		Query query = new TermQuery( t );
+		Hits hits = search.search( query );
+
+		assertEquals( OPERATIONS, hits.length() );
+
+		cache.getCacheManager().stop();
+		directory.close();
+	}
+
+	public void testDirectoryWithMultipleThreads() throws Exception {
+		final CountDownLatch latch = new CountDownLatch( 1 );
+		List<InfinispanDirectoryThread> threads = new ArrayList<InfinispanDirectoryThread>();
+		Cache<CacheKey, Object> cache = createCache();
+		Cache<CacheKey, Object> cache2 = createCache(); //dummy cache, to force replication
+		Directory directory = new InfinispanDirectory( cache, "indexName" );
+
+		IndexWriter.MaxFieldLength fieldLength = new IndexWriter.MaxFieldLength( IndexWriter.DEFAULT_MAX_FIELD_LENGTH );
+		IndexWriter iw = new IndexWriter( directory, new StandardAnalyzer(), true, fieldLength );
+		iw.close();
+
+		//create first writing thread
+		InfinispanDirectoryThread tr = new InfinispanDirectoryThread( latch, directory, true );
+		threads.add( tr );
+		tr.start();
+		//others reading threads
+		for ( int i = 0; i < THREADS_NUM - 1; i++ ) {
+			InfinispanDirectoryThread thread = new InfinispanDirectoryThread( latch, directory, false );
+			threads.add( thread );
+			thread.start();
+		}
+
+		latch.countDown();
+
+		for ( InfinispanDirectoryThread thread : threads ) {
+			thread.join();
+		}
+
+		for ( InfinispanDirectoryThread thread : threads ) {
+			if(thread.e != null) {
+				log.error( "Exception was catched during the test: ", thread.e );
+				fail();
+			}
+		}
+
+
+		IndexSearcher search = new IndexSearcher( directory );
+		Term t = new Term( "info", "good" );
+		Query query = new TermQuery( t );
+		Hits hits = search.search( query );
+
+		assertEquals( writeCount.get(), hits.length() );
+
+		search.close();
+		cache.getCacheManager().stop();
+		directory.close();
+	}
+
+	class InfinispanDirectoryThread extends Thread {
+		Exception e;
+		CountDownLatch latch;
+		File document;
+		Directory dir;
+		boolean isWritingThread = false;
+
+		protected InfinispanDirectoryThread(CountDownLatch latch, Directory dir, boolean isWritingThread)
+				throws Exception {
+			this.latch = latch;
+			this.dir = dir;
+			this.isWritingThread = isWritingThread;
+			document = createDummyDocToIndex( "document.lucene", 10000 );
+		}
+
+		@Override
+		public void run() {
+			try {
+				latch.await();
+				for ( int i = 0; i < TURNS_NUM; i++ ) {
+
+					if ( !isWritingThread ) {
+						doReadOperation( dir );
+					}
+					else {
+						writeCount.incrementAndGet();
+						doWriteOperation( dir, document );
+					}
+				}
+
+			}
+			catch ( Exception ex ) {
+				log.error( "Error", ex );
+				e = ex;
+			}
+		}
+	}
+
+
+}
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryProviderTest.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryProviderTest.java	Wed Sep 23 13:46:34 CEST 2009
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/InfinispanDirectoryProviderTest.java	Wed Sep 23 13:46:34 CEST 2009
@@ -0,0 +1,315 @@
+package org.hibernate.search.test.directoryProvider.infinispan;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+
+import org.apache.lucene.analysis.StopAnalyzer;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.Query;
+import org.slf4j.Logger;
+
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+import org.hibernate.Transaction;
+import org.hibernate.cfg.Configuration;
+import org.hibernate.search.FullTextSession;
+import org.hibernate.search.Search;
+import org.hibernate.search.store.infinispan.InfinispanDirectoryProvider;
+import org.hibernate.search.test.SearchTestCase;
+import org.hibernate.search.util.LoggerFactory;
+
+/**
+ * Test InfinispanDirectoryProvider with multiple writing and reading threads
+ *
+ * @author Lukasz Moren
+ */
+
+public class InfinispanDirectoryProviderTest extends SearchTestCase {
+
+	protected static final Logger log = LoggerFactory.make();
+
+	private static final int NUM_INSTANCES = 4;
+	private static final int WRITING_TURNS = 30;
+	private static final int READING_TURNS = 120;
+	private static final int NUM_OF_WRITING_THREADS = 1;
+	private static final int NUM_OF_READING_THREADS = 50;
+
+	protected List<SessionFactory> factories;
+	protected Random rand;
+
+
+	public void testInfinispanDirectory() throws Exception {
+
+		final CountDownLatch latch = new CountDownLatch( 1 );
+		List<InfinispanDirectoryThread> threads = new ArrayList<InfinispanDirectoryThread>();
+
+		for ( int i = 0; i < NUM_OF_WRITING_THREADS; i++ ) {
+			InfinispanDirectoryThread thread = new InfinispanDirectoryWritingThread( "Writing thread " + i, latch );
+			thread.start();
+			threads.add( thread );
+		}
+
+		for ( int i = 0; i < NUM_OF_READING_THREADS; i++ ) {
+			InfinispanDirectoryThread thread = new InfinispanDirectoryReadingThread( "Reading thread " + i, latch );
+			thread.start();
+			threads.add( thread );
+		}
+
+		latch.countDown();
+
+		for ( Thread thread : threads ) {
+			thread.join();
+		}
+
+		for ( InfinispanDirectoryThread thread : threads ) {
+			if ( thread.e != null ) {
+				log.error( "Exception in Thread [ " + thread.getName() + " ]", thread.e );
+				assertNull( thread.e );
+			}
+		}
+
+		//check in loop if data was indexed and replicated to all nodes
+		for ( int i = 0; i < NUM_INSTANCES; i++ ) {
+			FullTextSession ftSess = Search.getFullTextSession( getSession( i ) );
+
+			QueryParser parser = new QueryParser( "id", new StopAnalyzer() );
+			Query luceneQuery = parser.parse( "director:Brian DePalma" );
+			org.hibernate.Query query = ftSess.createFullTextQuery( luceneQuery );
+
+			List result = query.list();
+			assertEquals( NUM_OF_WRITING_THREADS * WRITING_TURNS, result.size() );
+
+			luceneQuery = parser.parse( "name:Carlito's Way" );
+			query = ftSess.createFullTextQuery( luceneQuery );
+
+			result = query.list();
+			assertEquals( NUM_OF_WRITING_THREADS * WRITING_TURNS, result.size() );
+
+			luceneQuery = parser.parse( "name:The Untouchables or name:Heat" );
+			query = ftSess.createFullTextQuery( luceneQuery );
+
+			result = query.list();
+			assertEquals( 0, result.size() );
+
+			luceneQuery = parser.parse( "name:Other or director:Unknown" );
+			query = ftSess.createFullTextQuery( luceneQuery );
+
+			result = query.list();
+			assertEquals( 0, result.size() );
+
+
+			ftSess.close();
+		}
+
+
+	}
+
+	public class InfinispanDirectoryWritingThread extends InfinispanDirectoryThread {
+
+		protected InfinispanDirectoryWritingThread(String name, CountDownLatch latch) {
+			super( name, latch );
+		}
+
+		public void run() {
+			try {
+				latch.await();
+				for ( int i = 0; i < WRITING_TURNS; i++ ) {
+
+					//one writing node
+					Session session = getSession( 0 );
+
+					Genre genre = new Genre();
+					genre.setName( "Criminal" );
+
+					Movie movie = new Movie();
+					movie.setDirector( "Michael Mann" );
+					movie.setName( "Heat" );
+					movie.setGenre( genre );
+
+					Movie movie2 = new Movie();
+					movie2.setDirector( "Eliot Ness" );
+					movie2.setName( "The Untouchables" );
+					movie2.setGenre( genre );
+
+					Transaction tx = session.beginTransaction();
+					session.persist( genre );
+					session.persist( movie2 );
+					session.persist( movie );
+					tx.commit();
+
+					tx = session.beginTransaction();
+					movie = ( Movie ) session.get( Movie.class, movie.getId() );
+					movie.setName( "Carlito's Way" );
+					session.update( movie );
+					tx.commit();
+
+					tx = session.beginTransaction();
+					session.delete( movie2 );
+					tx.commit();
+
+					tx = session.beginTransaction();
+					movie = new Movie();
+					movie.setName( "Scarface" );
+					movie.setDirector( "Brian DePalma 2" );
+					session.persist( movie );
+					tx.commit();
+
+					tx = session.beginTransaction();
+					movie = new Movie();
+					movie.setName( "Other" );
+					movie.setDirector( "Unknown" );
+
+					session.persist( movie );
+
+					session.flush();
+					tx.rollback();
+
+					session.close();
+
+				}
+			}
+			catch ( Exception e ) {
+				this.e = e;
+				log.error( "Exception when writing to index", e );
+			}
+		}
+	}
+
+	public class InfinispanDirectoryReadingThread extends InfinispanDirectoryThread {
+
+		protected InfinispanDirectoryReadingThread(String name, CountDownLatch latch) {
+			super( name, latch );
+		}
+
+		public void run() {
+			try {
+				latch.await();
+				for ( int i = 0; i < READING_TURNS; i++ ) {
+					FullTextSession ftSess = Search.getFullTextSession( openSession() );
+
+
+					QueryParser parser = new QueryParser( "id", new StopAnalyzer() );
+					Query luceneQuery = parser.parse( "name:Lokomotywa" );
+					org.hibernate.Query query = ftSess.createFullTextQuery( luceneQuery );
+
+					Transaction tx = ftSess.beginTransaction();
+					List result = query.list();	 //does not compare results, just to check if there are no exceptions during reading 
+					tx.commit();
+
+					parser = new QueryParser( "id", new StopAnalyzer() );
+					luceneQuery = parser.parse( "name:Scarface or director:Michael Mann" );
+					query = ftSess.createFullTextQuery( luceneQuery );
+
+					tx = ftSess.beginTransaction();
+					result = query.list();
+					tx.commit();
+
+					ftSess.close();
+				}
+			}
+			catch ( Exception e ) {
+				this.e = e;
+				log.error( "Exception when reading index", e );
+			}
+		}
+	}
+
+	abstract class InfinispanDirectoryThread extends Thread {
+		Exception e;
+		String name;
+		CountDownLatch latch;
+
+		protected InfinispanDirectoryThread(String name, CountDownLatch latch) {
+			super( name );
+			this.name = name;
+			this.latch = latch;
+		}
+	}
+
+	@Override
+	protected void setUp() throws Exception {
+		factories = new ArrayList<SessionFactory>();
+		rand = new Random();
+		super.setUp();
+	}
+
+	@Override
+	protected void configure(Configuration cfg) {
+		super.configure( cfg );
+		cfg.setProperty(
+				"hibernate.search.default.infinispan.cache_config_xml",
+				"org/hibernate/search/test/infinispan/config/ispn-cache-default-conf.xml"
+		);
+		cfg.setProperty( "hibernate.search.default.directory_provider", InfinispanDirectoryProvider.class.getName() );
+		cfg.setProperty( "hibernate.search.Genre.infinispan.cache_name", "Hibernate Search Cache" );
+
+		//Infinispan global configuration is not scoped per entity class, so also in movie case ispn-cache-default-conf.xml
+		//will be used. However we can define Infinispan named cache with a custom configuration, I think it is enough.
+
+		//here we define cache name where "Movies" will be stored, appropriate configuration for cache with this name
+		//have to be definde in Infinispan configuration file (ispn-cache-default-conf.xml), if not default conf is used
+		cfg.setProperty( "hibernate.search.Movie.infinispan.cache_name", "Movie's cache" );
+		cfg.setProperty( "hibernate.show_sql", "false" );
+	}
+
+	//todo check if exists and close sessionFactories at the beginning
+	@Override
+	protected void buildSessionFactory(Class<?>[] classes, String[] packages, String[] xmlFiles) throws Exception {
+		for ( int i = 0; i < NUM_INSTANCES; i++ ) {
+			super.buildSessionFactory(
+					classes, packages, xmlFiles
+			);
+		}
+	}
+
+	protected Session getSession(int num) {
+		return factories.get( num ).openSession();
+	}
+
+	@Override
+	protected void setSessions(SessionFactory sessions) {
+		factories.add( sessions );
+	}
+
+
+	@Override
+	public synchronized Session openSession() throws HibernateException {
+		//return random session for reading
+		int r = rand.nextInt( factories.size() );
+		return factories.get( r ).openSession();
+	}
+
+	@Override
+	protected void tearDown() throws Exception {
+		super.tearDown();
+		factories.clear();
+		factories = null;
+
+	}
+
+	@Override
+	protected void runTest() throws Throwable {
+		super.runTest();
+		for ( SessionFactory factory : factories ) {
+			try {
+				if ( factory != null ) {
+					factory.close();
+				}
+			}
+			catch ( Exception ignore ) {
+			}
+		}
+	}
+
+	@Override
+	protected Class<?>[] getMappings() {
+		return new Class[] {
+				Movie.class, Genre.class
+		};
+	}
+
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileMetadata.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileMetadata.java	Tue Sep 08 18:34:11 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileMetadata.java	Tue Sep 08 18:34:11 CEST 2009
@@ -0,0 +1,70 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.Serializable;
+
+/**
+ * Header for Lucene files. Store only basic info about file.
+ * File data is divided into byte[] chunks and stored under {@link org.hibernate.search.store.infinispan.ChunkCacheKey}
+ *
+ * @author Lukasz Moren
+ * @see org.hibernate.search.store.infinispan.FileCacheKey
+ */
+
+public class FileMetadata implements Serializable {
+	private long lastModified;
+	private long size = 0;
+
+	public FileMetadata() {
+		touch();
+	}
+
+	private void touch() {
+		setLastModified( System.currentTimeMillis() );
+	}
+
+	public long getLastModified() {
+		return lastModified;
+	}
+
+	public void setLastModified(long lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	public long getSize() {
+		return size;
+	}
+
+	public void setSize(long size) {
+		this.size = size;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if ( this == o ) {
+			return true;
+		}
+		if ( o == null || getClass() != o.getClass() ) {
+			return false;
+		}
+
+		FileMetadata metadata = ( FileMetadata ) o;
+
+		return lastModified == metadata.lastModified && size == metadata.size;
+
+	}
+
+	@Override
+	public int hashCode() {
+		int result = ( int ) ( lastModified ^ ( lastModified >>> 32 ) );
+		result = 31 * result + ( int ) ( size ^ ( size >>> 32 ) );
+		return result;
+	}
+
+	@Override
+	public String toString() {
+		return "FileMetadata{" +
+				"lastModified=" + lastModified +
+				", size=" + size +
+				'}';
+	}
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileListCacheKey.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileListCacheKey.java	Tue Sep 01 14:29:40 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/FileListCacheKey.java	Tue Sep 01 14:29:40 CEST 2009
@@ -0,0 +1,13 @@
+package org.hibernate.search.store.infinispan;
+
+/**
+ * Cache key for a list with current files in cache
+ *
+ * @author Lukasz Moren
+ */
+public class FileListCacheKey extends CacheKey {
+	public FileListCacheKey(String indexName) {
+		super( indexName, "" );
+	}
+
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanLockFactory.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanLockFactory.java	Wed Sep 23 14:17:45 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanLockFactory.java	Wed Sep 23 14:17:45 CEST 2009
@@ -0,0 +1,226 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.IOException;
+import javax.transaction.RollbackException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.apache.lucene.store.Lock;
+import org.apache.lucene.store.LockFactory;
+import org.infinispan.Cache;
+import org.infinispan.CacheException;
+import org.slf4j.Logger;
+
+import org.hibernate.search.util.LoggerFactory;
+
+/**
+ * Factory for locks obtained in <code>InfinispanDirectory</code>
+ *
+ * @author Lukasz Moren
+ * @see org.hibernate.search.store.infinispan.InfinispanDirectory
+ * @see org.hibernate.search.store.infinispan.InfinispanLockFactory.InfinispanLock
+ */
+public class InfinispanLockFactory extends LockFactory {
+
+	protected final Logger log = LoggerFactory.make();
+
+	private Cache<CacheKey, Object> cache;
+	private String indexName;
+
+	public InfinispanLockFactory(Cache<CacheKey, Object> cache, String indexName) {
+		this.cache = cache;
+		this.indexName = indexName;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public Lock makeLock(String lockName) {
+		try {
+			return new InfinispanLock( cache, indexName, lockName );
+		}
+		finally {
+			if ( log.isTraceEnabled() ) {
+				log.trace( "Created new lock: {} for index {}", lockName, indexName );
+			}
+		}
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public void clearLock(String lockName) throws IOException {
+		try {
+			cache.remove( new FileCacheKey( indexName, lockName, true ) );
+		}
+		finally {
+			if ( log.isTraceEnabled() ) {
+				log.trace( "Removed lock: {} for index {}", lockName, indexName );
+			}
+		}
+	}
+
+	/**
+	 * Interprocess Lucene index lock
+	 *
+	 * @see org.apache.lucene.store.Directory#makeLock(String)
+	 */
+	public static class InfinispanLock extends Lock {
+
+		protected final Logger log = LoggerFactory.make();
+
+		private final Cache<CacheKey, Object> cache;
+		private String lockName;
+		private String indexName;
+
+		final TransactionManager tm;
+
+		InfinispanLock(Cache<CacheKey, Object> cache, String indexName, String lockName) {
+			this.cache = cache;
+			this.lockName = lockName;
+			this.indexName = indexName;
+
+			tm = cache.getAdvancedCache().getComponentRegistry().getComponent( TransactionManager.class );
+			if ( tm == null ) {
+				throw new CacheException(
+						"Failed looking up TransactionManager, check if any transaction manager is associated with Infinispan cache: " + cache
+								.getName()
+				);
+			}
+		}
+
+		/**
+		 * {@inheritDoc}
+		 */
+		public boolean obtain() throws IOException {
+			boolean acquired = false;
+
+			synchronized ( cache ) {
+				try {
+					//begin transaction for lock obtaining
+					tm.begin();
+					CacheKey lock = new FileCacheKey( indexName, lockName, true );
+					if ( !cache.containsKey( lock ) ) {
+						cache.put( lock, lock );
+						acquired = true;
+					}
+				}
+				catch ( Exception e ) {
+					log.error( "Cannot obtain lock for: " + indexName, e );
+				}
+				finally {
+					try {
+						if ( tm.getTransaction() != null ) {
+							if ( acquired ) {
+								tm.commit();
+								if ( log.isTraceEnabled() ) {
+									log.trace(
+											"Lock: {} acquired for index: {} ", new Object[] { lockName, indexName }
+									);
+								}
+							}
+							else {
+								tm.rollback();
+							}
+						}
+					}
+					catch ( RollbackException e ) {
+						log.error( "Cannot obtain lock for: " + indexName, e );
+						acquired = false;
+					}
+					catch ( Exception e ) {
+						throw new CacheException( e );
+					}
+				}
+
+				if ( acquired ) {
+					try {
+						//begin new transaction to batch all changes, tx commited when lock is released.
+						tm.begin();
+						if ( log.isTraceEnabled() ) {
+							log.trace( "Batch transaction started for index: {}", indexName );
+						}
+					}
+					catch ( Exception e ) {
+						log.error( "Unable to start transaction", e );
+					}
+				}
+			}
+
+
+			return acquired;
+		}
+
+		/**
+		 * {@inheritDoc}
+		 */
+		public void release() throws IOException {
+			boolean removed = false;
+			synchronized ( cache ) {
+				try {
+					//commit changes in batch, transaction was started when lock was acquired
+					tm.commit();
+					if ( log.isTraceEnabled() ) {
+						log.trace( "Batch transaction commited for index: {}", indexName );
+					}
+
+					tm.begin();
+					removed = cache.remove( new FileCacheKey( indexName, lockName, true ) ) != null;
+				}
+				catch ( Exception e ) {
+					throw new CacheException( "Unable to commit work done or release lock!", e );
+				}
+				finally {
+					try {
+						if ( removed ) {
+							tm.commit();
+							if ( log.isTraceEnabled() ) {
+								log.trace(
+										"Lock: {} removed for index: {} ", new Object[] { lockName, indexName }
+								);
+							}
+						}
+						else {
+							tm.rollback();
+						}
+					}
+					catch ( Exception e ) {
+						throw new CacheException( "Unable to release lock!", e );
+					}
+				}
+			}
+		}
+
+		/**
+		 * {@inheritDoc}
+		 */
+		public boolean isLocked() {
+			boolean locked = false;
+			synchronized ( cache ) {
+				Transaction tx = null;
+				try {
+					// if there is an ongoing transaction we need to suspend it
+					if ( ( tx = tm.getTransaction() ) != null ) {
+						tm.suspend();
+					}
+					locked = cache.containsKey( new FileCacheKey( indexName, lockName, true ) );
+				}
+				catch ( Exception e ) {
+					log.error( "Error in suspending transaction", e );
+				}
+				finally {
+					if ( tx != null ) {
+						try {
+							tm.resume( tx );
+						}
+						catch ( Exception e ) {
+							throw new CacheException( "Unable to resume suspended transaction " + tx, e );
+						}
+					}
+				}
+			}
+			return locked;
+		}
+	}
+
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheManagerFactory.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheManagerFactory.java	Tue Sep 08 14:54:06 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheManagerFactory.java	Tue Sep 08 14:54:06 CEST 2009
@@ -0,0 +1,58 @@
+package org.hibernate.search.store.infinispan;
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Iterator;
+
+import org.infinispan.manager.CacheManager;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.config.Configuration;
+import org.slf4j.Logger;
+
+import org.hibernate.search.SearchException;
+import org.hibernate.search.util.LoggerFactory;
+
+/**
+ * @author Lukasz Moren
+ */
+public class InfinispanCacheManagerFactory {
+	private static final Logger log = LoggerFactory.make();
+
+	public InfinispanCacheManagerFactory() {
+	}
+
+	public static CacheManager getInstance(InputStream configurationStream) {
+		CacheManager cacheManager;
+		try {
+
+			cacheManager = new DefaultCacheManager( configurationStream );
+		}
+		catch ( Exception e ) {
+			log.error( "Unable to create cache manager from provided configuration", e );
+			throw new SearchException( e );
+		}
+
+		return cacheManager;
+	}
+
+	public static CacheManager getInstance(InfinispanCacheManagerConfiguration conf) {
+		CacheManager cacheManager;
+		try {
+			cacheManager = new DefaultCacheManager( conf.getGlobalConfiguration() );
+
+			//now get configuration for caches and register it with cache manager
+			Map<String, Configuration> cachesConfiguration = conf.getCustomConfiguration();
+			Iterator<Map.Entry<String, Configuration>> it = cachesConfiguration.entrySet().iterator();
+			while ( it.hasNext() ) {
+				Map.Entry<String, Configuration> entry = it.next();
+				cacheManager.defineConfiguration( entry.getKey(), entry.getValue() );
+			}
+		}
+		catch ( Exception e ) {
+			log.error( "Unable to create cache manager from provided configuration", e );
+			throw new SearchException( e );
+		}
+
+		return cacheManager;
+	}
+}
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/configuration/LuceneIndexingParametersTest.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/configuration/LuceneIndexingParametersTest.java	(revision 15579)
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/configuration/LuceneIndexingParametersTest.java	Wed Sep 09 11:56:50 CEST 2009
@@ -4,6 +4,9 @@
 import java.io.IOException;
 import java.util.Properties;
 
+import org.apache.lucene.index.SerialMergeScheduler;
+import org.apache.lucene.index.ConcurrentMergeScheduler;
+
 import org.hibernate.search.backend.LuceneIndexingParameters;
 import org.hibernate.search.test.Document;
 import org.hibernate.search.test.SerializationTestHelper;
@@ -16,6 +19,7 @@
 import static org.hibernate.search.backend.configuration.IndexWriterSetting.RAM_BUFFER_SIZE;
 import static org.hibernate.search.backend.configuration.IndexWriterSetting.USE_COMPOUND_FILE;
 import static org.hibernate.search.backend.configuration.IndexWriterSetting.MAX_FIELD_LENGTH;
+import static org.hibernate.search.backend.configuration.IndexWriterSetting.MERGE_SCHEDULER;
 import static org.hibernate.search.test.configuration.ConfigurationReadTestCase.TransactionType.TRANSACTION;
 import static org.hibernate.search.test.configuration.ConfigurationReadTestCase.TransactionType.BATCH;
 
@@ -36,6 +40,8 @@
 		cfg.setProperty( "hibernate.search.default.transaction.max_merge_docs", "9" );
 //set by super : cfg.setProperty( "hibernate.search.default.transaction.merge_factor", "100" );
 		cfg.setProperty( "hibernate.search.default.transaction.max_buffered_docs", "11" );
+		//for InfinispanDirectoryProvider this property is hardcoded to SerialMergeScheduler 
+		cfg.setProperty( "hibernate.search.default.transaction.merge_scheduler", "org.apache.lucene.index.SerialMergeScheduler" );
 		
 		cfg.setProperty( "hibernate.search.Book.batch.max_merge_docs", "12" );
 		cfg.setProperty( "hibernate.search.Book.transaction.use_compound_file", "false" );
@@ -47,6 +53,7 @@
 		cfg.setProperty( "hibernate.search.Book.transaction.max_merge_docs", "15" );
 		cfg.setProperty( "hibernate.search.Book.transaction.merge_factor", "16" );
 		cfg.setProperty( "hibernate.search.Book.transaction.max_buffered_docs", "17" );
+		cfg.setProperty( "hibernate.search.Book.batch.merge_scheduler", "org.apache.lucene.index.ConcurrentMergeScheduler" );
 		
 		cfg.setProperty( "hibernate.search.Documents.transaction.ram_buffer_size", "default" );
 		cfg.setProperty( "hibernate.search.Documents.transaction.max_merge_docs", "5" );
@@ -55,6 +62,7 @@
 		cfg.setProperty( "hibernate.search.Documents.batch.max_merge_docs", "9" );
 		cfg.setProperty( "hibernate.search.Documents.transaction.max_field_length", "7" );
 		cfg.setProperty( "hibernate.search.Documents.batch.max_field_length", "9" );
+		cfg.setProperty( "hibernate.search.Documents.transaction.merge_scheduler", "org.apache.lucene.index.ConcurrentMergeScheduler" );
 	}
 	
 	public void testDefaultIndexProviderParameters() throws Exception {
@@ -63,6 +71,7 @@
 		assertValueIsSet( Author.class, TRANSACTION, MAX_MERGE_DOCS, 9 );
 		assertValueIsSet( Author.class, TRANSACTION, MAX_BUFFERED_DOCS,  11 );
 		assertValueIsSet( Author.class, TRANSACTION, MERGE_FACTOR,  100 );
+		assertValueIsSet( Author.class, TRANSACTION, MERGE_SCHEDULER, new SerialMergeScheduler());
 	}
 	
 	public void testBatchParametersGlobals() throws Exception {
@@ -70,11 +79,13 @@
 		assertValueIsSet( Author.class, BATCH, MAX_MERGE_DOCS, 9 );
 		assertValueIsSet( Author.class, BATCH, MAX_BUFFERED_DOCS, 1000 );
 		assertValueIsSet( Author.class, BATCH, MERGE_FACTOR, 100 );
+		assertValueIsSet( Author.class, BATCH, MERGE_SCHEDULER, new SerialMergeScheduler());
 	}
 	
 	public void testUnsetBatchValueTakesTransaction() throws Exception {
 		assertValueIsSet( Document.class, BATCH, MERGE_FACTOR, 6 );
 		assertValueIsSet( Document.class, BATCH, MAX_BUFFERED_DOCS, 1000 );
+		assertValueIsSet( Document.class, BATCH, MERGE_SCHEDULER, new ConcurrentMergeScheduler());
 	}
 	
 	public void testMaxFieldLength() throws Exception {
@@ -88,6 +99,7 @@
 		assertValueIsSet( Book.class, BATCH, MAX_BUFFERED_DOCS, 14 );
 		assertValueIsSet( Book.class, BATCH, MERGE_FACTOR, 13 );
 		assertValueIsSet( Book.class, TRANSACTION, USE_COMPOUND_FILE, 0 );
+		assertValueIsSet( Book.class, BATCH, MERGE_SCHEDULER, new ConcurrentMergeScheduler());
 	}
 	
 	public void testInheritedBatchParametersFromTranscation() throws Exception {
Index: ../HibernateSearch/src/main/java/org/hibernate/search/backend/LuceneIndexingParameters.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/backend/LuceneIndexingParameters.java	(revision 15579)
+++ ../HibernateSearch/src/main/java/org/hibernate/search/backend/LuceneIndexingParameters.java	Fri Aug 21 00:46:46 CEST 2009
@@ -29,17 +29,17 @@
 
 	private static final long serialVersionUID = 5424606407623591663L;
 	private static final Logger log = LoggerFactory.make();
-	
+
 	// value keyword
 	public static final String EXPLICIT_DEFAULT_VALUE = "default";
 	// property path keywords
 	public static final String BATCH = "batch";
 	public static final String TRANSACTION = "transaction";
 	public static final String PROP_GROUP = "indexwriter";
-	
+
 	private final ParameterSet transactionIndexParameters;
 	private final ParameterSet batchIndexParameters;
-	
+
 	public LuceneIndexingParameters( Properties sourceProps ) {
 		//prefer keys under "indexwriter" but fallback for backwards compatibility:
 		Properties indexingParameters = new MaskedProperty( sourceProps, PROP_GROUP, sourceProps );
@@ -54,14 +54,14 @@
 
 	private void doSanityChecks(ParameterSet transParams, ParameterSet batchParams) {
 		if ( log.isWarnEnabled() ) {
-			Integer maxFieldLengthTransaction = transParams.parameters.get( MAX_FIELD_LENGTH );
-			Integer maxFieldLengthBatch = batchParams.parameters.get( MAX_FIELD_LENGTH );
+			Object maxFieldLengthTransaction = transParams.parameters.get( MAX_FIELD_LENGTH );
+			Object maxFieldLengthBatch = batchParams.parameters.get( MAX_FIELD_LENGTH );
 			if ( notEquals( maxFieldLengthTransaction, maxFieldLengthBatch ) ) {
 				log.warn( "The max_field_length value configured for transaction is "
 						+ "different than the value configured for batch." );
 			}
-			Integer useCompoundTransaction = transParams.parameters.get( USE_COMPOUND_FILE );
-			Integer useCompoundBatch = batchParams.parameters.get( USE_COMPOUND_FILE );
+			Object useCompoundTransaction = transParams.parameters.get( USE_COMPOUND_FILE );
+			Object useCompoundBatch = batchParams.parameters.get( USE_COMPOUND_FILE );
 			if ( notEquals( useCompoundTransaction, useCompoundBatch ) ) {
 				log.warn( "The IndexWriter setting \"use_compound_file\" for batch "+
 						"mode can't be different from the transaction setting." );
@@ -69,11 +69,11 @@
 		}
 	}
 
-	private boolean notEquals(Integer a, Integer b) {
+	private boolean notEquals(Object a, Object b) {
 		if ( a==null && b==null ) return false;
 		if ( a==null && b!=null ) return true;
 		if ( a!=null && b==null ) return true;
-		return a.intValue() != b.intValue();
+		return !a.equals( b );
 	}
 
 	public ParameterSet getTransactionIndexParameters() {
@@ -85,11 +85,11 @@
 	}
 
 	public static class ParameterSet implements Serializable {
-		
+
 		private static final long serialVersionUID = -6121723702279869524L;
-		
+
-		final Map<IndexWriterSetting, Integer> parameters = new EnumMap<IndexWriterSetting, Integer>(IndexWriterSetting.class);
+		final Map<IndexWriterSetting, Object> parameters = new EnumMap<IndexWriterSetting, Object>(IndexWriterSetting.class);
-		
+
 		public ParameterSet(Properties prop, String paramName) {
 			//don't iterate on property entries as we know all the keys:
 			for ( IndexWriterSetting t : IndexWriterSetting.values() ) {
@@ -104,14 +104,14 @@
 				}
 			}
 		}
-		
+
 		/**
 		 * Applies the parameters represented by this to a writer.
 		 * Undefined parameters are not set, leaving the lucene default.
 		 * @param writer the IndexWriter whereto the parameters will be applied.
 		 */
 		public void applyToWriter(IndexWriter writer) {
-			for ( Map.Entry<IndexWriterSetting,Integer> entry : parameters.entrySet() ) {
+			for ( Map.Entry<IndexWriterSetting, Object> entry : parameters.entrySet() ) {
 				try {
 					entry.getKey().applySetting( writer, entry.getValue() );
 				} catch ( IllegalArgumentException e ) {
@@ -121,12 +121,12 @@
 				}
 			}
 		}
-		
+
-		public Integer getCurrentValueFor(IndexWriterSetting ws){
+		public Object getCurrentValueFor(IndexWriterSetting ws){
 			return parameters.get( ws );
 		}
 
-		public void setCurrentValueFor(IndexWriterSetting ws, Integer newValue){
+		public void setCurrentValueFor(IndexWriterSetting ws, Object newValue){
 			if ( newValue == null ) {
 				parameters.remove( ws );
 			} else {
@@ -159,7 +159,7 @@
 				return false;
 			return true;
 		}
-		
+
  	}
 
 	public void applyToWriter(IndexWriter writer, boolean batch) {
Index: ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/Genre.java
===================================================================
--- ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/Genre.java	Wed Sep 09 11:56:50 CEST 2009
+++ ../HibernateSearch/src/test/java/org/hibernate/search/test/directoryProvider/infinispan/Genre.java	Wed Sep 09 11:56:50 CEST 2009
@@ -0,0 +1,35 @@
+package org.hibernate.search.test.directoryProvider.infinispan;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+import org.hibernate.search.annotations.DocumentId;
+import org.hibernate.search.annotations.Field;
+import org.hibernate.search.annotations.Index;
+import org.hibernate.search.annotations.Indexed;
+
+/**
+ * @author Lukasz Moren
+ */
+
+@Entity
+@Indexed(index = "Genre")
+public class Genre {
+
+	@Id
+	@GeneratedValue
+	@DocumentId
+	private Long id;
+
+	@Field(index = Index.TOKENIZED)
+	private String name;
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/DefaultInfinispanCacheManagerConfiguration.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/DefaultInfinispanCacheManagerConfiguration.java	Wed Sep 23 14:19:28 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/DefaultInfinispanCacheManagerConfiguration.java	Wed Sep 23 14:19:28 CEST 2009
@@ -0,0 +1,43 @@
+package org.hibernate.search.store.infinispan;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.infinispan.config.Configuration;
+import org.infinispan.config.GlobalConfiguration;
+import org.infinispan.transaction.lookup.GenericTransactionManagerLookup;
+
+/**
+ * Default configuration for Infinispan cache and cache managers that are used to store indexed by Hibernate Search
+ *
+ * @author Lukasz Moren
+ */
+public class DefaultInfinispanCacheManagerConfiguration implements InfinispanCacheManagerConfiguration {
+
+	public GlobalConfiguration getGlobalConfiguration() {
+		GlobalConfiguration gc = GlobalConfiguration.getClusteredDefault();
+		gc.setClusterName( "Hibernate-Search-Cluster" );
+		return gc;
+	}
+
+	/**
+	 * Default caches configuration; one common cache is used to store all Lucene indexes
+	 *
+	 * @return configuration for Infinispan caches
+	 */
+	public Map<String, Configuration> getCustomConfiguration() {
+		Configuration c = new Configuration();
+		Map<String, Configuration> cacheConfiguration = new HashMap<String, Configuration>();
+
+		c.setCacheMode( Configuration.CacheMode.REPL_SYNC );
+		c.setSyncReplTimeout( 30000 );
+		c.setLockAcquisitionTimeout( 30000 );
+		c.setTransactionManagerLookupClass( GenericTransactionManagerLookup.class.getName() );
+		c.setUseLockStriping( false );
+		c.setSyncCommitPhase( true );
+		c.setSyncRollbackPhase( true );
+
+		cacheConfiguration.put( InfinispanDirectoryProvider.DEFAULT_CACHE_NAME, c );
+		return cacheConfiguration;
+	}
+}
Index: ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheEntryListener.java
===================================================================
--- ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheEntryListener.java	Mon Sep 14 19:17:38 CEST 2009
+++ ../HibernateSearch/src/main/java/org/hibernate/search/store/infinispan/InfinispanCacheEntryListener.java	Mon Sep 14 19:17:38 CEST 2009
@@ -0,0 +1,59 @@
+package org.hibernate.search.store.infinispan;
+
+import java.util.Map;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntryEvent;
+import org.infinispan.notifications.cachelistener.event.Event;
+
+
+/**
+ * Infinispan cache listener
+ *
+ * @author Lukasz Moren
+ */
+
+@Listener
+public class InfinispanCacheEntryListener {
+
+	private final String indexName;
+
+	public InfinispanCacheEntryListener(String indexName) {
+		this.indexName = indexName;
+	}
+
+	/**
+	 * Updates current Lucene file list in a cache - when cache entry is created or removed
+	 *
+	 * @param event Infinispan event
+	 */
+	@SuppressWarnings({ "unchecked" })
+	@CacheEntryCreated
+	@CacheEntryRemoved
+	public void cacheEntryCreatedListener(CacheEntryEvent event) {
+		Object ob = event.getKey();
+		//process only local events
+		if ( ob instanceof FileCacheKey && event.isOriginLocal() ) {
+			FileCacheKey key = ( FileCacheKey ) ob;
+
+			if ( key.getIndexName().equals( indexName ) && !key.isLockKey) {
+				FileListCacheKey fileListKey = new FileListCacheKey( key.getIndexName() );
+				Cache<CacheKey, Object> cache = event.getCache();
+				Map<String, String> fileList = ( Map<String, String> ) cache.get( fileListKey );
+
+				if ( event.getType().equals( Event.Type.CACHE_ENTRY_CREATED ) ) {
+					fileList.put( key.getFileName(), key.getFileName() );
+				}
+				else if ( event.getType().equals( Event.Type.CACHE_ENTRY_REMOVED ) ) {
+					fileList.remove( key.getFileName() );
+				}
+
+				cache.put( fileListKey, fileList );
+			}
+		}
+	}
+
+}
