diff --git a/source/java/org/alfresco/repo/content/caching/CachingContentStore.java b/source/java/org/alfresco/repo/content/caching/CachingContentStore.java index b8c0f9721c..7ad255ad3f 100644 --- a/source/java/org/alfresco/repo/content/caching/CachingContentStore.java +++ b/source/java/org/alfresco/repo/content/caching/CachingContentStore.java @@ -49,7 +49,8 @@ public class CachingContentStore implements ContentStore private ContentCache cache; private boolean cacheOnInbound; - static { + static + { locks = new Object[numLocks]; for (int i = 0; i < numLocks; i++) { @@ -152,22 +153,42 @@ public class CachingContentStore implements ContentStore // when it should only be read once and cached versions should be returned after that. synchronized(lock(contentUrl)) { - if (!cache.contains(contentUrl)) + return retryingCacheRead(contentUrl); + } + } + + private ContentReader retryingCacheRead(String url) + { + int triesLeft = 15; + + while (triesLeft > 0) + { + try { - ContentReader bsReader = backingStore.getReader(contentUrl); - if (!cache.put(contentUrl, bsReader)) + return cache.getReader(url); + } + catch (CacheMissException e) + { + // Cached content is missing either from memory or disk + // so try and populate it and retry reading it. + ContentReader bsReader = backingStore.getReader(url); + if (!cache.put(url, bsReader)) { - // Content wasn't put into cache successfully. + // Content was empty - probably hasn't been written yet. return bsReader.getReader(); } - } + else + { + triesLeft--; + } + } } - - // TODO: what if, in the meantime this item has been deleted from the disk cache? - return cache.getReader(contentUrl); + + // Give up and use the backing store directly + return backingStore.getReader(url); } - - + + /* * @see org.alfresco.repo.content.ContentStore#getWriter(org.alfresco.repo.content.ContentContext) */ diff --git a/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java b/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java index 99379a7c18..c9d81a8209 100644 --- a/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java +++ b/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java @@ -19,6 +19,7 @@ package org.alfresco.repo.content.caching; import java.io.File; +import java.io.FilenameFilter; import net.sf.ehcache.CacheManager; @@ -137,6 +138,30 @@ public class CachingContentStoreSpringTest extends AbstractWritableContentStoreT } + public void testStoreWillRecoverFromDeletedCacheFile() + { + final String content = "Content for " + getName() + " test."; + + // Write some content to the backing store. + ContentWriter writer = backingStore.getWriter(ContentContext.NULL_CONTEXT); + writer.putContent(content); + final String contentUrl = writer.getContentUrl(); + + // Read content using the CachingContentStore - will cause content to be cached. + String retrievedContent = store.getReader(contentUrl).getContentString(); + assertEquals(content, retrievedContent); + + // Remove the cached disk file + File cacheFile = new File(cache.cacheFileLocation(contentUrl)); + cacheFile.delete(); + assertTrue("Cached content should have been deleted", !cacheFile.exists()); + + // Should still be able to ask for this content, even though the cache file was + // deleted and the record of the cache is still in the in-memory cache/lookup. + String contentAfterDelete = store.getReader(contentUrl).getContentString(); + assertEquals(content, contentAfterDelete); + } + /* * @see org.alfresco.repo.content.AbstractReadOnlyContentStoreTest#getStore() diff --git a/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java b/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java index 3429065982..eb58c7dd26 100644 --- a/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java +++ b/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java @@ -75,7 +75,6 @@ public class CachingContentStoreTest public void getReaderForItemInCache() { ContentReader cachedContentReader = mock(ContentReader.class); - when(cache.contains("url")).thenReturn(true); when(cache.getReader("url")).thenReturn(cachedContentReader); ContentReader returnedReader = cachingStore.getReader("url"); @@ -86,16 +85,25 @@ public class CachingContentStoreTest @Test - public void getReadForItemMissingFromCache() + public void getReaderForItemMissingFromCache() { ContentReader sourceContent = mock(ContentReader.class); - when(cache.contains("url")).thenReturn(false); + when(cache.getReader("url")).thenThrow(new CacheMissException("url")); when(backingStore.getReader("url")).thenReturn(sourceContent); + when(cache.put("url", sourceContent)).thenReturn(true); cachingStore.getReader("url"); + } + + @Test + public void getReaderForItemMissingFromCacheButNoContentToCache() + { + ContentReader sourceContent = mock(ContentReader.class); + when(cache.getReader("url")).thenThrow(new CacheMissException("url")); + when(backingStore.getReader("url")).thenReturn(sourceContent); + when(cache.put("url", sourceContent)).thenReturn(false); - verify(backingStore).getReader("url"); - verify(cache).put("url", sourceContent); + cachingStore.getReader("url"); } diff --git a/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java b/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java index 5b76fde2f9..84b3d5db80 100644 --- a/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java +++ b/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java @@ -63,7 +63,11 @@ public class ContentCacheImpl implements ContentCache if (memoryStore.contains(contentUrl)) { String path = memoryStore.get(contentUrl); - return new FileContentReader(new File(path), contentUrl); + File cacheFile = new File(path); + if (cacheFile.exists()) + { + return new FileContentReader(cacheFile, contentUrl); + } } throw new CacheMissException(contentUrl); @@ -173,4 +177,13 @@ public class ContentCacheImpl implements ContentCache { this.memoryStore = memoryStore; } + + + // Not part of the ContentCache interface as this breaks encapsulation. + // Handy method for tests though, since it allows us to find out where + // the content was cached. + protected String cacheFileLocation(String url) + { + return memoryStore.get(url); + } }