diff --git a/source/java/org/alfresco/repo/avm/AVMServiceConcurrentTest.java b/source/java/org/alfresco/repo/avm/AVMServiceConcurrentTest.java
new file mode 100644
index 0000000000..478d498526
--- /dev/null
+++ b/source/java/org/alfresco/repo/avm/AVMServiceConcurrentTest.java
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ */
+
+package org.alfresco.repo.avm;
+
+import java.util.List;
+import java.util.SortedMap;
+
+import javax.transaction.UserTransaction;
+
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
+import org.alfresco.service.cmr.avmsync.AVMDifference;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.ResultSetRow;
+import org.alfresco.service.cmr.search.SearchService;
+
+/**
+ * AVM concurrency and search
+ *
+ * @author andyh, janv
+ */
+public class AVMServiceConcurrentTest extends AVMServiceTestBase
+{
+ public void testSetup() throws Exception
+ {
+ super.testSetup();
+ }
+
+ public void test_CreateDelete() throws Exception
+ {
+ int threads= 4;
+
+ int loops = 10;
+
+ //int snapshotsPerLoop = 4;
+ int snapshotsPerLoop = 1;
+
+ fService.createDirectory("main:/", "test");
+
+ int startVersion = fService.createSnapshot("main", null, null).get("main");
+
+ assertEquals(0, fService.getDirectoryListing(-1, "main:/test").size());
+
+ UserTransaction testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ StoreRef storeRef = AVMNodeConverter.ToStoreRef("main");
+ SearchService searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ ResultSet results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ assertEquals(0, results.length());
+ results.close();
+
+ testTX.commit();
+
+ Thread runner = null;
+ for (int i = 0; i < threads; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, false, snapshotsPerLoop, Nester.Mode.CREATE, loops);
+ }
+
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ SortedMap listing = fService.getDirectoryListing(-1, "main:/test");
+ assertEquals(loops, listing.size());
+
+ for(AVMNodeDescriptor node : listing.values())
+ {
+ System.out.println("Listed: "+node.getPath()+" "+node.getVersionID());
+ }
+ List diffs = fSyncService.compare(startVersion, "main:/", -1, "main:/", null);
+ assertEquals(loops, diffs.size());
+ for(AVMDifference diff : diffs)
+ {
+ AVMNodeDescriptor desc = fService.lookup(diff.getDestinationVersion(), diff.getDestinationPath(), true);
+ assertFalse(desc.isDeleted());
+ }
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(loops, results.length());
+ results.close();
+
+ testTX.commit();
+
+
+ // delete
+
+ runner = null;
+ for (int i = 0; i < threads; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, false, snapshotsPerLoop, Nester.Mode.DELETE, loops);
+ }
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ assertEquals(0, fService.getDirectoryListing(-1, "main:/test").size());
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(0, results.length());
+ results.close();
+
+ testTX.commit();
+ }
+
+ public void test_ALF_786() throws Exception
+ {
+ //int threads= 4;
+ int threads= 2;
+
+ int loops = 10;
+
+ //int snapshotsPerLoop = 4;
+ int snapshotsPerLoop = 1;
+
+ fService.createDirectory("main:/", "test");
+
+ int startVersion = fService.createSnapshot("main", null, null).get("main");
+
+ assertEquals(0, fService.getDirectoryListing(-1, "main:/test").size());
+
+ UserTransaction testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ StoreRef storeRef = AVMNodeConverter.ToStoreRef("main");
+ SearchService searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ ResultSet results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ assertEquals(0, results.length());
+ results.close();
+
+ testTX.commit();
+
+ Thread runner = null;
+ for (int i = 0; i < threads; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, false, snapshotsPerLoop, Nester.Mode.CREATE, loops);
+ }
+
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ SortedMap listing = fService.getDirectoryListing(-1, "main:/test");
+ assertEquals(loops, listing.size());
+
+ for(AVMNodeDescriptor node : listing.values())
+ {
+ System.out.println("Listed: "+node.getPath()+" "+node.getVersionID());
+ }
+ List diffs = fSyncService.compare(startVersion, "main:/", -1, "main:/", null);
+ assertEquals(loops, diffs.size());
+ for(AVMDifference diff : diffs)
+ {
+ AVMNodeDescriptor desc = fService.lookup(diff.getDestinationVersion(), diff.getDestinationPath(), true);
+ assertFalse(desc.isDeleted());
+ }
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(loops, results.length());
+ results.close();
+
+ testTX.commit();
+
+ // update
+
+ runner = null;
+ for (int i = 0; i < threads; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, false, snapshotsPerLoop, Nester.Mode.UPDATE, loops);
+ }
+
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(loops, results.length());
+
+ results.close();
+ testTX.commit();
+
+ // delete
+
+ runner = null;
+ for (int i = 0; i < threads; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, false, snapshotsPerLoop, Nester.Mode.DELETE, loops);
+ }
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ assertEquals(0, fService.getDirectoryListing(-1, "main:/test").size());
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(0, results.length());
+ results.close();
+
+ testTX.commit();
+
+ // recreate
+
+ runner = null;
+ for (int i = 0; i < threads; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, false, snapshotsPerLoop, Nester.Mode.CREATE, loops);
+ }
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(loops, results.length());
+ results.close();
+
+ testTX.commit();
+
+ //move
+
+ runner = null;
+ for (int i = 0; i < threads; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, false, snapshotsPerLoop, Nester.Mode.MOVE, loops);
+ }
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(loops, results.length());
+ results.close();
+
+ testTX.commit();
+ }
+
+ public void xtest_ALF_786_PLUS() throws Exception
+ {
+ int startVersion;
+ UserTransaction testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+ fService.createDirectory("main:/", "test");
+ startVersion = fService.createSnapshot("main", null, null).get("main");
+
+ testTX.commit();
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+
+
+ StoreRef storeRef = AVMNodeConverter.ToStoreRef("main");
+ SearchService searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ ResultSet results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ assertEquals(0, results.length());
+ results.close();
+ testTX.commit();
+
+ Thread runner = null;
+
+ for (int i = 0; i < 10; i++)
+ {
+ runner = new Nester("Concurrent-" + i, runner, true, 10, Nester.Mode.CREATE, 10 );
+ }
+ if (runner != null)
+ {
+ runner.start();
+
+ try
+ {
+ runner.join();
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();
+ // snap
+ testTX.commit();
+ testTX = fTransactionService.getUserTransaction();
+ testTX.begin();;
+ SortedMap listing = fService.getDirectoryListing(-1, "main:/test");
+ assertEquals(100, listing.size());
+ for(AVMNodeDescriptor node : listing.values())
+ {
+ System.out.println("Listed: "+node.getPath()+" "+node.getVersionID());
+ }
+ List diffs = fSyncService.compare(startVersion, "main:/", -1, "main:/", null);
+ assertEquals(100, diffs.size());
+ for(AVMDifference diff : diffs)
+ {
+ AVMNodeDescriptor desc = fService.lookup(diff.getDestinationVersion(), diff.getDestinationPath(), true);
+ assertFalse(desc.isDeleted());
+ }
+
+
+
+ searchService = fIndexerAndSearcher.getSearcher(AVMNodeConverter.ToStoreRef("main"), true);
+ results = searchService.query(storeRef, "lucene", "PATH:\"/test/*\"");
+ for(ResultSetRow row : results)
+ {
+ System.out.println("Found: "+row.getNodeRef());
+ }
+ assertEquals(100, results.length());
+ results.close();
+ testTX.commit();
+ }
+
+ static class Nester extends Thread
+ {
+ enum Mode {CREATE, UPDATE, DELETE, MOVE};
+
+ Thread waiter;
+
+ int i;
+
+ boolean multiThread;
+
+ int snapshotCount;
+
+ Mode mode;
+
+ int loopCount;
+
+ Nester(String name, Thread waiter, boolean multiThread, int snapshotCount, Mode mode, int loopCount)
+ {
+ super(name);
+ this.setDaemon(true);
+ this.waiter = waiter;
+ this.multiThread = multiThread;
+ this.snapshotCount = snapshotCount;
+ this.mode = mode;
+ this.loopCount = loopCount;
+ }
+
+ public void run()
+ {
+ fAuthenticationComponent.setSystemUserAsCurrentUser();
+ if (waiter != null)
+ {
+ waiter.start();
+ }
+ try
+ {
+ System.out.println("Start " + this.getName());
+ for(i = 0; i < loopCount; i++)
+ {
+ RetryingTransactionCallback create = new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ fService.createFile("main:/test", getName()+"-"+i).close();
+
+ return null;
+ }
+ };
+ RetryingTransactionCallback update = new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ fService.setMimeType("main:/test/"+getName()+"-"+i, "text/plain");
+
+ return null;
+ }
+ };
+ RetryingTransactionCallback delete = new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ fService.removeNode("main:/test/"+getName()+"-"+i);
+
+ return null;
+ }
+ };
+ RetryingTransactionCallback move = new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ fService.rename("main:/test/", getName()+"-"+i, "main:/test/", "MOVED-"+getName()+"-"+i);
+
+ return null;
+ }
+ };
+ if(multiThread || (waiter == null))
+ {
+ // only one thread creates for 786
+ switch(mode)
+ {
+ case CREATE:
+ fRetryingTransactionHelper.doInTransaction(create);
+ System.out.println(getName()+i);
+ break;
+ case UPDATE:
+ fRetryingTransactionHelper.doInTransaction(update);
+ break;
+ case DELETE:
+ fRetryingTransactionHelper.doInTransaction(delete);
+ break;
+ case MOVE:
+ fRetryingTransactionHelper.doInTransaction(move);
+ break;
+ default:
+ }
+
+ }
+
+ RetryingTransactionCallback snap = new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ fService.createSnapshot("main", null, null);
+
+ return null;
+ }
+ };
+ for(int s = 0; s < snapshotCount; s++)
+ {
+ fRetryingTransactionHelper.doInTransaction(snap);
+ }
+ }
+ System.out.println("End " + this.getName());
+ }
+ catch (Exception e)
+ {
+ System.out.println("End " + this.getName() + " with error " + e.getMessage());
+ e.printStackTrace();
+ }
+ finally
+ {
+ fAuthenticationComponent.clearCurrentSecurityContext();
+ }
+ if (waiter != null)
+ {
+ try
+ {
+ waiter.join();
+ System.out.println("Waited for " + waiter.getName()+" by "+this.getName());
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+ }
+ }
+}