diff --git a/config/alfresco/cache-context.xml b/config/alfresco/cache-context.xml index 4115f4905a..fede969a64 100644 --- a/config/alfresco/cache-context.xml +++ b/config/alfresco/cache-context.xml @@ -45,8 +45,8 @@ org.alfresco.cache.propertyValueTransactionalCache - - + + @@ -77,8 +77,9 @@ org.alfresco.cache.contentDataTransactionalCache - + + @@ -111,8 +112,8 @@ org.alfresco.cache.immutableEntityTransactionalCache - - + + @@ -144,8 +145,8 @@ org.alfresco.cache.node.rootNodesTransactionalCache - - + + @@ -173,8 +174,8 @@ org.alfresco.cache.node.allRootNodesTransactionalCache - - + + @@ -207,6 +208,7 @@ + @@ -455,6 +457,7 @@ org.alfresco.authorityTransactionalCache + @@ -1272,8 +1275,8 @@ org.alfresco.cache.tagscopeSummaryTransactionalCache - - + + @@ -1305,7 +1308,7 @@ org.alfresco.cache.imapMessageTransactionalCache - + diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 183444854a..9a74dbece9 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -148,7 +148,7 @@ /> - - - - - - - - - - - - - - - - - - - - - - - - + parent="search.abstractLuceneIndexerAndSearcherFactory" + class="org.alfresco.repo.search.impl.lucene.ADMLuceneIndexerAndSearcherFactory"> + + + + + + + + - + diff --git a/source/java/org/alfresco/repo/cache/CacheTest.java b/source/java/org/alfresco/repo/cache/CacheTest.java index 5e9426050f..ab634d6a23 100644 --- a/source/java/org/alfresco/repo/cache/CacheTest.java +++ b/source/java/org/alfresco/repo/cache/CacheTest.java @@ -29,11 +29,13 @@ import net.sf.ehcache.CacheManager; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.lang.mutable.MutableLong; import org.springframework.context.ApplicationContext; /** @@ -58,14 +60,23 @@ public class CacheTest extends TestCase @Override public void setUp() throws Exception { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + fail("A transaction is still running"); + } + serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); standaloneCache = (SimpleCache) ctx.getBean("ehCache1"); backingCache = (SimpleCache) ctx.getBean("backingCache"); transactionalCache = (TransactionalCache) ctx.getBean("transactionalCache"); objectCache = (SimpleCache) ctx.getBean("objectCache"); + // Make sure that the backing cache is empty + backingCache.clear(); + // Make the cache mutable (default) transactionalCache.setMutable(true); + transactionalCache.setAllowEqualsChecks(false); } @Override @@ -331,6 +342,53 @@ public class CacheTest extends TestCase } } + public void testTransactionalCacheDisableSharedCaches() throws Throwable + { + // add item to global cache + backingCache.put(NEW_GLOBAL_ONE, NEW_GLOBAL_ONE); + backingCache.put(NEW_GLOBAL_TWO, NEW_GLOBAL_TWO); + backingCache.put(NEW_GLOBAL_THREE, NEW_GLOBAL_THREE); + + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + try + { + // begin a transaction + txn.begin(); + + // Go directly past ALL shared caches + transactionalCache.setDisableSharedCacheReadForTransaction(true); + + // Try to get results in shared caches + assertNull("Read of mutable shared cache MUST NOT use backing cache", transactionalCache.get(NEW_GLOBAL_ONE)); + assertNull("Value should not be in any cache", transactionalCache.get(UPDATE_TXN_THREE)); + + // Update the transactional caches + transactionalCache.put(NEW_GLOBAL_TWO, "An update"); + transactionalCache.put(UPDATE_TXN_THREE, UPDATE_TXN_THREE); + + // Try to get results in shared caches + assertNull("Read of mutable shared cache MUST NOT use backing cache", transactionalCache.get(NEW_GLOBAL_ONE)); + assertEquals("Value should be in transactional cache", "An update", transactionalCache.get(NEW_GLOBAL_TWO)); + assertEquals("Value should be in transactional cache", UPDATE_TXN_THREE, transactionalCache.get(UPDATE_TXN_THREE)); + + txn.commit(); + + // Now check that values were not written through for any caches + assertEquals("Out-of-txn read must return shared value", NEW_GLOBAL_ONE, transactionalCache.get(NEW_GLOBAL_ONE)); + assertNull("Value should be removed from shared cache", transactionalCache.get(NEW_GLOBAL_TWO)); + assertEquals("New values must be written to shared cache", UPDATE_TXN_THREE, transactionalCache.get(UPDATE_TXN_THREE)); + } + catch (Throwable e) + { + if (txn.getStatus() == Status.STATUS_ACTIVE) + { + txn.rollback(); + } + throw e; + } + } + /** * Preloads the cache, then performs a simultaneous addition of N new values and * removal of the N preloaded values. @@ -417,7 +475,7 @@ public class CacheTest extends TestCase } finally { - try { txn.rollback(); } catch (Throwable ee) {} + try { txn.rollback(); } catch (Throwable ee) {ee.printStackTrace();} } } long end = System.nanoTime(); @@ -462,22 +520,24 @@ public class CacheTest extends TestCase * Starts off with a null in the backing cache and adds a value to the * transactional cache. There should be no problem with this. */ - public void testNullValue() throws Exception + public void testNullValue() throws Throwable { TransactionService transactionService = serviceRegistry.getTransactionService(); UserTransaction txn = transactionService.getUserTransaction(); + + txn.begin(); + + backingCache.put("A", null); + transactionalCache.put("A", "AAA"); + try { - txn.begin(); - - backingCache.put("A", null); - transactionalCache.put("A", "AAA"); - txn.commit(); } - finally + catch (Throwable e) { - try { txn.rollback(); } catch (Throwable ee) {} + try {txn.rollback();} catch (Throwable ee) {} + throw e; } } @@ -540,9 +600,15 @@ public class CacheTest extends TestCase } Object actualValue = backingCache.get(key); assertEquals("Backing cache value was not correct", expectedValue, actualValue); + + // Clear the backing cache to ensure that subsequent tests don't run into existing data + backingCache.clear(); } private static final String COMMON_KEY = "A"; + private static final MutableLong VALUE_ONE_A = new MutableLong(1L); + private static final MutableLong VALUE_ONE_B = new MutableLong(1L); + private static final MutableLong VALUE_TWO_A = new MutableLong(2L); /** *
    *
  • Add to the transaction cache
  • @@ -556,17 +622,26 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - transactionalCache.put(COMMON_KEY, "AAA"); - backingCache.put(COMMON_KEY, "aaa"); + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); + backingCache.put(COMMON_KEY, VALUE_ONE_B); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, "aaa"); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, "AAA"); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct } /** *
      @@ -577,22 +652,30 @@ public class CacheTest extends TestCase */ public void testConcurrentAddAgainstAddSame()throws Throwable { - final Object commonValue = "AAA"; RetryingTransactionCallback callback = new RetryingTransactionCallback() { public Object execute() throws Throwable { - transactionalCache.put(COMMON_KEY, commonValue); - backingCache.put(COMMON_KEY, commonValue); + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); + backingCache.put(COMMON_KEY, VALUE_ONE_A); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, "AAA"); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, commonValue); - executeAndCheck(callback, true, COMMON_KEY, commonValue); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match } /** *
        @@ -607,17 +690,26 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - transactionalCache.put(COMMON_KEY, "AAA"); + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); backingCache.clear(); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, "AAA"); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache } /** *
          @@ -633,18 +725,27 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); - transactionalCache.put(COMMON_KEY, "AAA"); - backingCache.put(COMMON_KEY, "aaa2"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); + backingCache.put(COMMON_KEY, VALUE_TWO_A); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, "aaa2"); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, "AAA"); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Shared cache value checked failed + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Shared cache value checked failed + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct } /** *
            @@ -660,18 +761,27 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); - transactionalCache.put(COMMON_KEY, "AAA"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); backingCache.put(COMMON_KEY, null); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, "AAA"); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache } /** *
              @@ -687,18 +797,27 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); transactionalCache.put(COMMON_KEY, null); - backingCache.put(COMMON_KEY, "aaa2"); + backingCache.put(COMMON_KEY, VALUE_ONE_B); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, "aaa2"); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct } /** *
                @@ -714,18 +833,27 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); - transactionalCache.put(COMMON_KEY, "AAA"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); backingCache.remove(COMMON_KEY); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, "AAA"); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache } /** *
                  @@ -741,18 +869,27 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); - transactionalCache.put(COMMON_KEY, "AAA"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); backingCache.clear(); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, "AAA"); - executeAndCheck(callback, true, COMMON_KEY, "AAA"); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache } /** *
                    @@ -770,16 +907,25 @@ public class CacheTest extends TestCase { backingCache.remove(COMMON_KEY); transactionalCache.remove(COMMON_KEY); - backingCache.put(COMMON_KEY, "aaa2"); + backingCache.put(COMMON_KEY, VALUE_ONE_B); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache } /** *
                      @@ -796,17 +942,26 @@ public class CacheTest extends TestCase public Object execute() throws Throwable { backingCache.remove(COMMON_KEY); - transactionalCache.put(COMMON_KEY, "aaa2-x"); - backingCache.put(COMMON_KEY, "aaa2"); + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); + backingCache.put(COMMON_KEY, VALUE_ONE_B); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, "aaa2"); // Doesn't write through + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, "aaa2-x"); // Always overwrites - executeAndCheck(callback, true, COMMON_KEY, "aaa2-x"); // Always overwrites + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct } /** *
                        @@ -822,18 +977,27 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); transactionalCache.remove(COMMON_KEY); - backingCache.put(COMMON_KEY, "aaa2"); + backingCache.put(COMMON_KEY, VALUE_ONE_B); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache } /** *
                          @@ -849,18 +1013,27 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); transactionalCache.remove(COMMON_KEY); backingCache.remove(COMMON_KEY); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Remove from backing cache transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Remove from backing cache + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache } /** *
                            @@ -876,17 +1049,26 @@ public class CacheTest extends TestCase { public Object execute() throws Throwable { - backingCache.put(COMMON_KEY, "aaa1"); + backingCache.put(COMMON_KEY, VALUE_ONE_A); transactionalCache.remove(COMMON_KEY); backingCache.clear(); return null; } }; + transactionalCache.setAllowEqualsChecks(false); transactionalCache.setMutable(true); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Nothing to do transactionalCache.setMutable(false); - executeAndCheck(callback, false, COMMON_KEY, null); - executeAndCheck(callback, true, COMMON_KEY, null); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Nothing to do + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Nothing to do + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Nothing to do } } diff --git a/source/java/org/alfresco/repo/cache/NullCache.java b/source/java/org/alfresco/repo/cache/NullCache.java index 5a22b9f8a6..89eef2999b 100644 --- a/source/java/org/alfresco/repo/cache/NullCache.java +++ b/source/java/org/alfresco/repo/cache/NullCache.java @@ -33,6 +33,18 @@ import java.util.Collections; */ public class NullCache implements SimpleCache { + /** Singleton for retrieval via {@link #getInstance() } */ + private static final NullCache INSTANCE = new NullCache(); + + /** + * @return Returns a singleton that can be used in any way - all operations are stateless + */ + @SuppressWarnings("unchecked") + public static final NullCache getInstance() + { + return (NullCache) INSTANCE; + } + public NullCache() { } diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java index 7b9b4c2be8..cf184105fd 100644 --- a/source/java/org/alfresco/repo/cache/TransactionalCache.java +++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java @@ -72,7 +72,7 @@ import org.springframework.beans.factory.InitializingBean; public class TransactionalCache implements SimpleCache, TransactionListener, InitializingBean { - private static final String RESOURCE_KEY_TXN_DATA = "TransactionalCache.TxnData"; + private static final String RESOURCE_KEY_TXN_DATA = "TransactionalCache.TxnData"; private Log logger; private boolean isDebugEnabled; @@ -85,6 +85,8 @@ public class TransactionalCache private SimpleCache sharedCache; /** can the cached values be modified */ private boolean isMutable; + /** can values be compared using full equality checking */ + private boolean allowEqualsChecks; /** the maximum number of elements to be contained in the cache */ private int maxCacheSize = 500; /** a unique string identifying this instance when binding resources */ @@ -99,6 +101,7 @@ public class TransactionalCache isDebugEnabled = logger.isDebugEnabled(); disableSharedCache = false; isMutable = true; + allowEqualsChecks = false; } /** @@ -163,6 +166,21 @@ public class TransactionalCache this.isMutable = isMutable; } + /** + * Allow equality checking of values before they are written to the shared cache on + * commit. This allows some caches to bypass unnecessary cache updates when the + * values remain unchanged. Typically, this setting should be applied only to mutable + * caches and only where the values being stored have a fast and reliable equality check. + * + * @param allowEqualsChecks true if value comparisons can be made between values + * stored in the transactional cache and those stored in the + * shared cache + */ + public void setAllowEqualsChecks(boolean allowEqualsChecks) + { + this.allowEqualsChecks = allowEqualsChecks; + } + /** * Set the maximum number of elements to store in the update and remove caches. * The maximum number of elements stored in the transaction will be twice the @@ -171,7 +189,7 @@ public class TransactionalCache * The removed list will overflow to disk in order to ensure that deletions are * not lost. * - * @param maxCacheSize + * @param maxCacheSize maximum number of items to be held in-transaction */ public void setMaxCacheSize(int maxCacheSize) { @@ -179,9 +197,7 @@ public class TransactionalCache } /** - * Set the name that identifies this cache from other instances. This is optional. - * - * @param name + * Set the name that identifies this cache from other instances. */ public void setName(String name) { @@ -205,7 +221,7 @@ public class TransactionalCache // Assign a 'null' cache if write-through is disabled if (disableSharedCache) { - sharedCache = new NullCache(); + sharedCache = NullCache.getInstance(); } } @@ -232,6 +248,23 @@ public class TransactionalCache return data; } + /** + * Transaction-long setting to force all the share cache to be bypassed for the current transaction. + *

                            + * This setting is like having a {@link NullCache null} {@link #setSharedCache(SimpleCache) shared cache}, + * but only lasts for the transaction. + *

                            + * Use this when a read transaction must see consistent and current data i.e. go to the database. + * While this is active, write operations will also not be committed to the shared cache. + * + * @param noSharedCacheRead true to avoid reading from the shared cache for the transaction + */ + public void setDisableSharedCacheReadForTransaction(boolean noSharedCacheRead) + { + TransactionData txnData = getTransactionData(); + txnData.noSharedCacheRead = noSharedCacheRead; + } + /** * Checks the transactional removed and updated caches before checking the shared cache. */ @@ -347,6 +380,11 @@ public class TransactionalCache // Can't store values in the current txn any more ignoreSharedCache = true; } + else if (txnData.noSharedCacheRead) + { + // Explicitly told to ignore shared cache + ignoreSharedCache = true; + } else { // There is no in-txn entry for the key @@ -356,8 +394,6 @@ public class TransactionalCache txnData.updatedItemsCache.put(key, bucket); return value; } - // check if the cleared flag has been set - cleared flag means ignore shared as unreliable - ignoreSharedCache = txnData.isClearOn; } } // no value found - must we ignore the shared cache? @@ -440,7 +476,7 @@ public class TransactionalCache txnData.haveIssuedFullWarning = true; } } - Object existingValueObj = sharedCache.get(key); + Object existingValueObj = txnData.noSharedCacheRead ? null : sharedCache.get(key); CacheBucket bucket = null; if (existingValueObj == null) { @@ -650,7 +686,9 @@ public class TransactionalCache { K key = entry.getKey(); CacheBucket bucket = entry.getValue(); - bucket.doPreCommit(sharedCache, key, this.isMutable, txnData.isReadOnly); + bucket.doPreCommit( + sharedCache, + key, this.isMutable, this.allowEqualsChecks, txnData.isReadOnly); } if (isDebugEnabled) { @@ -709,7 +747,9 @@ public class TransactionalCache { K key = entry.getKey(); CacheBucket bucket = entry.getValue(); - bucket.doPostCommit(sharedCache, key, this.isMutable, txnData.isReadOnly); + bucket.doPostCommit( + sharedCache, + key, this.isMutable, this.allowEqualsChecks, txnData.isReadOnly); } if (isDebugEnabled) { @@ -800,7 +840,7 @@ public class TransactionalCache public void doPreCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly); + boolean mutable, boolean allowEqualsCheck, boolean readOnly); /** * Flush the current bucket to the shared cache as far as possible. * @@ -810,7 +850,7 @@ public class TransactionalCache public void doPostCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly); + boolean mutable, boolean allowEqualsCheck, boolean readOnly); } /** @@ -834,41 +874,42 @@ public class TransactionalCache public void doPreCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly) { } public void doPostCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly) { Object sharedObj = sharedCache.get(key); - if (!mutable) + if (sharedObj == null) { - // The value can't change so we can write through on the assumption - // that the value is always correct + // Nothing has changed, write it through sharedCache.put(key, value); } - else if (readOnly) + else if (!mutable) { - // Only add if nothing else has been added in the interim - if (sharedObj == null) - { - sharedCache.put(key, value); - } + // Someone else put the object there + // The assumption is that the value will be correct because the values are immutable + // Don't write it unnecessarily. + } + else if (sharedObj == value) + { + // Someone else put exactly the same value into the cache + // Don't write it unnecessarily. + } + else if (allowEqualsCheck && EqualsHelper.nullSafeEquals(value, sharedObj)) + { + // Someone else added a value but we have validated that it is the same + // as the new one that we where going to add. + // Don't write it unnecessarily. } else { - // Mutable, read-write - if (sharedObj == null) - { - sharedCache.put(key, value); - } - else - { - // Remove new value in the cache - sharedCache.remove(key); - } + // The shared value moved on in a way that was not possible to + // validate. We pessimistically remove the entry. + sharedCache.remove(key); } } } @@ -896,43 +937,52 @@ public class TransactionalCache public void doPreCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly) { } public void doPostCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly) { Object sharedObj = sharedCache.get(key); - if (!mutable) + if (sharedObj == null) { - // Not normally required as mutable objects don't change, - // but we can write it straight through as it should represent - // unchanging values - sharedCache.put(key, value); - } - else if (readOnly) - { - // Only add if value has not changed in the interim - if (sharedObj == originalValue) - { - sharedCache.put(key, value); - } - } - else - { - // Mutable, read-write - if (sharedObj == originalValue) + // Someone removed the value + if (!mutable) { + // We can assume that our value is correct because it's immutable sharedCache.put(key, value); } else { - // The value changed - sharedCache.remove(key); + // The value is mutable, so we must behave pessimistically } } + else if (!mutable) + { + // Someone else has already updated the value. + // This is not normally seen for immutable values. The assumption is that the values + // are equal. + // Don't write it unnecessarily. + } + else if (sharedObj == originalValue) + { + // Nothing has changed, write it through + sharedCache.put(key, value); + } + else if (allowEqualsCheck && EqualsHelper.nullSafeEquals(value, sharedObj)) + { + // Someone else updated the value but we have validated that it is the same + // as the one that we where going to update. + // Don't write it unnecessarily. + } + else + { + // The shared value moved on in a way that was not possible to + // validate. We pessimistically remove the entry. + sharedCache.remove(key); + } } } @@ -956,13 +1006,13 @@ public class TransactionalCache public void doPreCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly) { } public void doPostCommit( SimpleCache sharedCache, Serializable key, - boolean mutable, boolean readOnly) + boolean mutable, boolean allowEqualsCheck, boolean readOnly) { } } @@ -976,6 +1026,7 @@ public class TransactionalCache private boolean isClearOn; private boolean isClosed; private boolean isReadOnly; + private boolean noSharedCacheRead; } /** diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 40b5549093..b23d4cd761 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -44,6 +44,7 @@ import org.alfresco.ibatis.RetryingCallbackHelper.RetryingCallback; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.NullCache; import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.cache.TransactionalCache; import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAOAdaptor; import org.alfresco.repo.domain.contentdata.ContentDataDAO; @@ -152,6 +153,10 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO * VALUE KEY: The Node's NodeRef
                            */ private EntityLookupCache nodesCache; + /** + * Backing transactional cache to allow read-through requests to be honoured + */ + private TransactionalCache nodesTransactionalCache; /** * Cache for the QName values:
                            * KEY: NodeVersionKey
                            @@ -311,7 +316,10 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO cache, CACHE_REGION_NODES, new NodesCacheCallbackDAO()); - + if (cache instanceof TransactionalCache) + { + this.nodesTransactionalCache = (TransactionalCache) cache; + } } /** @@ -629,9 +637,17 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return txn; } - public Long getCurrentTransactionId() + public Long getCurrentTransactionId(boolean ensureNew) { - TransactionEntity txn = AlfrescoTransactionSupport.getResource(KEY_TRANSACTION); + TransactionEntity txn; + if (ensureNew) + { + txn = getCurrentTransaction(); + } + else + { + txn = AlfrescoTransactionSupport.getResource(KEY_TRANSACTION); + } return txn == null ? null : txn.getId(); } @@ -815,8 +831,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO /** * Callback to cache nodes by ID and {@link NodeRef}. When looking up objects based on the - * value key, only the referencing properties need be populated. ONLY live nodes are - * cached. + * value key, only the referencing properties need be populated. ALL nodes are cached, + * not just live nodes. * * @see NodeEntity * @@ -838,7 +854,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO */ public Pair findByKey(Long nodeId) { - NodeEntity node = selectNodeById(nodeId, Boolean.FALSE); + NodeEntity node = selectNodeById(nodeId, null); if (node != null) { // Lock it to prevent 'accidental' modification @@ -867,7 +883,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO public Pair findByValue(Node node) { NodeRef nodeRef = node.getNodeRef(); - node = selectNodeByNodeRef(nodeRef, Boolean.FALSE); + node = selectNodeByNodeRef(nodeRef, null); if (node != null) { // Lock it to prevent 'accidental' modification @@ -897,7 +913,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO @Override public boolean isInCurrentTxn(Long nodeId) { - Long currentTxnId = getCurrentTransactionId(); + Long currentTxnId = getCurrentTransactionId(false); if (currentTxnId == null) { // No transactional changes have been made to any nodes, therefore the node cannot @@ -909,59 +925,19 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return nodeTxnId.equals(currentTxnId); } - // TODO: Restore to simple version - // TODO: Add read-through option for caches public Status getNodeRefStatus(NodeRef nodeRef) { - Node node = null; - - // Stage 1: check the cache without reading through - Long nodeId = nodesCache.getKey(nodeRef); - if (nodeId != null) - { - node = nodesCache.getValue(nodeId); - // If the node isn't for the current transaction, we are probably reindexing. So invalidate the cache, - // forcing a read through and a repeatable read on this noderef - if (node == null || AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_READ_WRITE - || !getCurrentTransaction().getId().equals(node.getTransaction().getId()) - || !node.getNodeRef().equals(nodeRef)) - { - invalidateNodeCaches(nodeId); - node = null; - } - } - - // Stage 2, read through to the database, caching results if appropriate - if (node == null) - { - Node nodeEntity = new NodeEntity(nodeRef); - // Explicitly remove this noderef from the cache, forcing a 'repeatable read' on this noderef from now on. - nodesCache.removeByValue(nodeEntity); - Pair pair = nodesCache.getByValue(nodeEntity); - if (pair == null) - { - // It's not there, so select ignoring the 'deleted' flag - node = selectNodeByNodeRef(nodeRef, null); - if (node != null) - { - // Invalidate anything cached for this node ID, just in case it has moved store, etc. - invalidateNodeCaches(node.getId()); - } - } - else - { - // We have successfully populated the cache - node = pair.getSecond(); - } - } - - if (node == null) + Node node = new NodeEntity(nodeRef); + Pair nodePair = nodesCache.getByValue(node); + // The nodesCache gets both live and deleted nodes. + if (nodePair == null) { return null; } - - Transaction txn = node.getTransaction(); - return new NodeRef.Status(nodeRef, txn.getChangeTxnId(), txn.getId(), node.getDeleted()); + else + { + return nodePair.getSecond().getNodeStatus(); + } } public Pair getNodePair(NodeRef nodeRef) @@ -989,6 +965,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO Pair pair = nodesCache.getByKey(nodeId); if (pair == null || pair.getSecond().getDeleted()) { + // Force a removal from the cache + nodesCache.removeByKey(nodeId); // Go back to the database and get what is there NodeEntity dbNode = selectNodeById(nodeId, null); if (pair == null) @@ -3679,6 +3657,16 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO * Bulk caching */ + @Override + public void setCheckNodeConsistency() + { + if (nodesTransactionalCache != null) + { + nodesTransactionalCache.setDisableSharedCacheReadForTransaction(true); + } + } + + @Override public void cacheNodesById(List nodeIds) { /* diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index e198d6d8bf..b7087b39b9 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -68,12 +68,13 @@ public interface NodeDAO extends NodeBulkLoader */ /** - * + * @param forceNew true to ensure that a new transaction entry is created + * if the current transaction does not have one. * @return Returns the ID of the current transaction entry or null if * there have not been any modifications to nodes registered in the - * transaction + * transaction and forceNew is false */ - public Long getCurrentTransactionId(); + public Long getCurrentTransactionId(boolean ensureNew); /* * Store diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAOTest.java b/source/java/org/alfresco/repo/domain/node/NodeDAOTest.java index e56ac893c4..19e2a96299 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAOTest.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAOTest.java @@ -65,11 +65,12 @@ public class NodeDAOTest extends TestCase public void testTransaction() throws Throwable { + final boolean[] newTxn = new boolean[] {false}; RetryingTransactionCallback getTxnIdCallback = new RetryingTransactionCallback() { public Long execute() throws Throwable { - return nodeDAO.getCurrentTransactionId(); + return nodeDAO.getCurrentTransactionId(newTxn[0]); } }; // No txn @@ -87,6 +88,10 @@ public class NodeDAOTest extends TestCase // First success Long txnId1 = txnHelper.doInTransaction(getTxnIdCallback); assertNull("No Txn ID should be present in untouched txn", txnId1); + // Second success + newTxn[0] = true; + Long txnId2 = txnHelper.doInTransaction(getTxnIdCallback); + assertNotNull("Txn ID should be present by forcing it", txnId2); } public void testGetNodesWithAspects() throws Throwable diff --git a/source/java/org/alfresco/repo/domain/node/NodeEntity.java b/source/java/org/alfresco/repo/domain/node/NodeEntity.java index 03cc48ce31..3bbfdc9840 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeEntity.java +++ b/source/java/org/alfresco/repo/domain/node/NodeEntity.java @@ -80,6 +80,25 @@ public class NodeEntity implements Node, PermissionCheckValue this.auditableProperties = node.getAuditableProperties(); } + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) return false; + if (!(obj instanceof NodeEntity)) return false; + NodeEntity that = (NodeEntity) obj; + return this.id.equals(that.id) && this.version.equals(that.version); + } + @Override public String toString() { diff --git a/source/java/org/alfresco/repo/domain/node/NodeVersionKey.java b/source/java/org/alfresco/repo/domain/node/NodeVersionKey.java index 1668b17e34..7646910d60 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeVersionKey.java +++ b/source/java/org/alfresco/repo/domain/node/NodeVersionKey.java @@ -63,7 +63,7 @@ public class NodeVersionKey implements Serializable @Override public int hashCode() { - return nodeId.hashCode() + version.hashCode(); + return nodeId.hashCode() + version.hashCode()*37; } @Override diff --git a/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java b/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java index aa8a693952..aeab9db27d 100644 --- a/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/ConcurrentNodeServiceTest.java @@ -29,6 +29,7 @@ import junit.framework.TestCase; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; +import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -65,6 +66,7 @@ public class ConcurrentNodeServiceTest extends TestCase static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private NodeService nodeService; + private NodeDAO nodeDAO; private TransactionService transactionService; private NodeRef rootNodeRef; private AuthenticationComponent authenticationComponent; @@ -91,6 +93,7 @@ public class ConcurrentNodeServiceTest extends TestCase ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); nodeService = serviceRegistry.getNodeService(); + nodeDAO = (NodeDAO) ctx.getBean("nodeDAO"); transactionService = serviceRegistry.getTransactionService(); authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); @@ -182,6 +185,7 @@ public class ConcurrentNodeServiceTest extends TestCase @Override public Integer execute() throws Throwable { + nodeDAO.setCheckNodeConsistency(); // Grab the current value int current = 0; Object obj = (Object) nodeService.getProperty(rootNodeRef, property); @@ -248,7 +252,7 @@ public class ConcurrentNodeServiceTest extends TestCase { errors.add("\n Prop " + properties[i] + " : " + value); } - if (!value.equals(new Integer(loops))) + else if (!value.equals(new Integer(loops))) { errors.add("\n Prop " + properties[i] + " : " + value); } diff --git a/source/java/org/alfresco/repo/node/NodeBulkLoader.java b/source/java/org/alfresco/repo/node/NodeBulkLoader.java index 2fbe90264f..a63a0f1272 100644 --- a/source/java/org/alfresco/repo/node/NodeBulkLoader.java +++ b/source/java/org/alfresco/repo/node/NodeBulkLoader.java @@ -32,6 +32,12 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public interface NodeBulkLoader { + /** + * Transaction-scope setting to make the Node loader to guarantee the validity of all + * caches: some cache data will be reloaded; some cache data will be considered safe. + */ + public void setCheckNodeConsistency(); + /** * Pre-cache data relevant to the given nodes. There is no need to split the collection * up before calling this method; it is up to the implementations to ensure that batching diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index a536ee885a..3e119d98d2 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -2767,7 +2767,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl "Touched cm:modified date for node " + parentNodeId + " (" + modifiedDate + ")" + (useCurrentTxn ? " in txn " : " in new txn ") + - nodeDAO.getCurrentTransactionId()); + nodeDAO.getCurrentTransactionId(false)); } } catch (Throwable e) diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java index d7ac74c972..cc813b6553 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java @@ -34,26 +34,18 @@ import org.alfresco.service.transaction.TransactionService; /** * Factory for ADM indxers and searchers * @author andyh - * */ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAndSearcherFactory implements SupportsBackgroundIndexing { protected DictionaryService dictionaryService; - private NamespaceService nameSpaceService; - protected NodeService nodeService; - protected FullTextSearchIndexer fullTextSearchIndexer; - protected ContentService contentService; - protected TransactionService transactionService; /** * Set the dictinary service - * - * @param dictionaryService */ public void setDictionaryService(DictionaryService dictionaryService) { @@ -62,7 +54,6 @@ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd /** * Set the name space service - * @param nameSpaceService */ public void setNameSpaceService(NamespaceService nameSpaceService) { @@ -71,7 +62,6 @@ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd /** * Set the node service - * @param nodeService */ public void setNodeService(NodeService nodeService) { @@ -85,13 +75,15 @@ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd /** * Set the content service - * @param contentService */ public void setContentService(ContentService contentService) { this.contentService = contentService; } + /** + * Set the transaction service + */ public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; @@ -103,6 +95,7 @@ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd ADMLuceneIndexerImpl indexer = ADMLuceneIndexerImpl.getUpdateIndexer(storeRef, deltaId, this); indexer.setNodeService(nodeService); + indexer.setBulkLoader(getBulkLoader()); indexer.setTenantService(tenantService); indexer.setDictionaryService(dictionaryService); // indexer.setLuceneIndexLock(luceneIndexLock); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java index 77fd3372ad..cf26e509a4 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -642,7 +642,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp final IndexReader mainReader) { final NodeRef nodeRef = new NodeRef(stringNodeRef); - final NodeRef.Status nodeStatus = nodeService.getNodeStatus(nodeRef); // DH: Let me know if this field gets dropped (performance) + final NodeRef.Status nodeStatus = nodeService.getNodeStatus(nodeRef); final List docs = new LinkedList(); if (nodeStatus == null) { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneUnIndexedIndexAndSearcherFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneUnIndexedIndexAndSearcherFactory.java index 9d81fe9601..0432c71f0c 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneUnIndexedIndexAndSearcherFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneUnIndexedIndexAndSearcherFactory.java @@ -28,6 +28,7 @@ public class ADMLuceneUnIndexedIndexAndSearcherFactory extends ADMLuceneIndexerA { ADMLuceneNoActionIndexerImpl indexer = ADMLuceneIndexerImpl.getNoActionIndexer(storeRef, deltaId, this); indexer.setNodeService(nodeService); + indexer.setBulkLoader(getBulkLoader()); indexer.setTenantService(tenantService); indexer.setDictionaryService(dictionaryService); // indexer.setLuceneIndexLock(luceneIndexLock); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java index 4ca27b46ba..61139ed7a4 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AbstractLuceneIndexerImpl.java @@ -30,6 +30,7 @@ import java.util.Set; import javax.transaction.Status; import javax.transaction.xa.XAResource; +import org.alfresco.repo.node.NodeBulkLoader; import org.alfresco.repo.search.Indexer; import org.alfresco.repo.search.IndexerException; import org.alfresco.repo.search.impl.lucene.index.TransactionStatus; @@ -102,6 +103,7 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase im private boolean isReadThrough; protected TransactionService transactionService; + protected NodeBulkLoader bulkLoader; public void setReadThrough(boolean isReadThrough) { @@ -113,6 +115,14 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase im this.transactionService = transactionService; } + /** + * @param bulkLoader object to provide node loading options + */ + public void setBulkLoader(NodeBulkLoader bulkLoader) + { + this.bulkLoader = bulkLoader; + } + protected static class Command { S ref; @@ -695,6 +705,11 @@ public abstract class AbstractLuceneIndexerImpl extends AbstractLuceneBase im @Override public T2 execute() throws Throwable { + // Request clean node data + if (bulkLoader != null) + { + bulkLoader.setCheckNodeConsistency(); + } try { return callback.execute(); diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java index cc418f383a..27114936f5 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponent.java @@ -20,7 +20,6 @@ package org.alfresco.repo.solr; import java.util.List; import java.util.Map; -import java.util.Set; import org.alfresco.repo.domain.node.Node; import org.alfresco.service.namespace.QName; diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java index 9ff793fcf4..88e583ad2d 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentImpl.java @@ -510,6 +510,9 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent return; } + // Ensure that we get fresh node references + nodeDAO.setCheckNodeConsistency(); + NodeMetaDataQueryRowHandler rowHandler = new NodeMetaDataQueryRowHandler(callback); boolean includeType = (resultFilter == null ? true : resultFilter.getIncludeType()); boolean includeProperties = (resultFilter == null ? true : resultFilter.getIncludeProperties()); @@ -868,9 +871,6 @@ public class SOLRTrackingComponentImpl implements SOLRTrackingComponent } } - /* (non-Javadoc) - * @see org.alfresco.repo.solr.SOLRTrackingComponent#getLastTransactionTimestamp() - */ @Override public Long getMaxTxnCommitTime() { diff --git a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java index 39c8294f75..3559e0aeb6 100644 --- a/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java +++ b/source/java/org/alfresco/repo/solr/SOLRTrackingComponentTest.java @@ -576,12 +576,20 @@ public class SOLRTrackingComponentTest extends TestCase logger.debug("Got " + bt.getActualNodeCount() + " nodes in " + (endTime - startTime) + " ms"); } - private void getNodeMetaData(NodeMetaDataParameters params, MetaDataResultsFilter filter, SOLRTest bt) + private void getNodeMetaData(final NodeMetaDataParameters params, final MetaDataResultsFilter filter, final SOLRTest bt) { bt.clearNodesMetaData(); long startTime = System.currentTimeMillis(); - solrTrackingComponent.getNodesMetadata(params, filter, bt); + txnHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + solrTrackingComponent.getNodesMetadata(params, filter, bt); + return null; + } + }, true, true); long endTime = System.currentTimeMillis(); bt.runNodeMetaDataChecks(params.getMaxResults());