ALF-9613: caching content store

http://issues.alfresco.com/jira/browse/ALF-9613


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29662 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Matt Ward
2011-08-10 14:27:29 +00:00
parent 6ce1995a93
commit 065229b36b
6 changed files with 924 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2005-2011 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.content.caching;
/**
* CacheMissException will be thrown if an attempt is made to read
* content from the ContentCache when it is not in the cache.
*
* @author Matt Ward
*/
public class CacheMissException extends RuntimeException
{
private static final long serialVersionUID = -410818899455752655L;
/**
* @param contentUrl URL of content that was attempted to be retrieved.
*/
public CacheMissException(String contentUrl)
{
super("Content not found in cache [URL=" + contentUrl + "]");
}
}

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2005-2011 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.content.caching;
import java.util.Date;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentStreamListener;
import org.alfresco.service.cmr.repository.ContentWriter;
/**
* Implementation of ContentStore that wraps any other ContentStore (the backing store)
* transparently providing caching of content in that backing store.
* <p>
* CachingContentStore should only be used to wrap content stores that are significantly
* slower that FileContentStore - otherwise performance may actually degrade from its use.
* <p>
* It is important that cacheOnInbound is set to true for exceptionally slow backing stores,
* e.g. {@link org.alfresco.enterprise.repo.content.xam.XAMContentStore}
*
* @author Matt Ward
*/
public class CachingContentStore implements ContentStore
{
private final ContentStore backingStore;
private final ContentCache cache;
private final boolean cacheOnInbound;
public CachingContentStore(ContentStore backingStore, ContentCache cache, boolean cacheOnInbound)
{
this.backingStore = backingStore;
this.cache = cache;
this.cacheOnInbound = cacheOnInbound;
}
/*
* @see org.alfresco.repo.content.ContentStore#isContentUrlSupported(java.lang.String)
*/
@Override
public boolean isContentUrlSupported(String contentUrl)
{
return backingStore.isContentUrlSupported(contentUrl);
}
/*
* @see org.alfresco.repo.content.ContentStore#isWriteSupported()
*/
@Override
public boolean isWriteSupported()
{
return backingStore.isWriteSupported();
}
/*
* @see org.alfresco.repo.content.ContentStore#getTotalSize()
*/
@Override
public long getTotalSize()
{
return backingStore.getTotalSize();
}
/*
* @see org.alfresco.repo.content.ContentStore#getSpaceUsed()
*/
@Override
public long getSpaceUsed()
{
return backingStore.getSpaceUsed();
}
/*
* @see org.alfresco.repo.content.ContentStore#getSpaceFree()
*/
@Override
public long getSpaceFree()
{
return backingStore.getSpaceFree();
}
/*
* @see org.alfresco.repo.content.ContentStore#getSpaceTotal()
*/
@Override
public long getSpaceTotal()
{
return backingStore.getSpaceTotal();
}
/*
* @see org.alfresco.repo.content.ContentStore#getRootLocation()
*/
@Override
public String getRootLocation()
{
return backingStore.getRootLocation();
}
/*
* @see org.alfresco.repo.content.ContentStore#exists(java.lang.String)
*/
@Override
public boolean exists(String contentUrl)
{
return backingStore.exists(contentUrl);
}
/*
* @see org.alfresco.repo.content.ContentStore#getReader(java.lang.String)
*/
@Override
public ContentReader getReader(String contentUrl)
{
if (!cache.contains(contentUrl))
{
ContentReader bsReader = backingStore.getReader(contentUrl);
if (!cache.put(contentUrl, bsReader))
{
// Content wasn't put into cache successfully.
return bsReader.getReader();
}
}
// TODO: what if, in the meantime this item has been deleted from the disk cache?
return cache.getReader(contentUrl);
}
/*
* @see org.alfresco.repo.content.ContentStore#getWriter(org.alfresco.repo.content.ContentContext)
*/
@Override
public ContentWriter getWriter(final ContentContext context)
{
if (cacheOnInbound)
{
final ContentWriter bsWriter = backingStore.getWriter(context);
// write to cache
final ContentWriter writer = cache.getWriter(bsWriter.getContentUrl());
writer.addListener(new ContentStreamListener()
{
@Override
public void contentStreamClosed() throws ContentIOException
{
// copy from the cache to the backing store
bsWriter.putContent(writer.getReader());
}
});
return writer;
}
else
{
// No need to invalidate the cache for this content URL, since a content URL
// is only ever written to once.
return backingStore.getWriter(context);
}
}
/*
* @see org.alfresco.repo.content.ContentStore#getWriter(org.alfresco.service.cmr.repository.ContentReader, java.lang.String)
*/
@Override
public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl)
{
return backingStore.getWriter(existingContentReader, newContentUrl);
}
/*
* @see org.alfresco.repo.content.ContentStore#getUrls(org.alfresco.repo.content.ContentStore.ContentUrlHandler)
*/
@Override
public void getUrls(ContentUrlHandler handler) throws ContentIOException
{
backingStore.getUrls(handler);
}
/*
* @see org.alfresco.repo.content.ContentStore#getUrls(java.util.Date, java.util.Date, org.alfresco.repo.content.ContentStore.ContentUrlHandler)
*/
@Override
public void getUrls(Date createdAfter, Date createdBefore, ContentUrlHandler handler)
throws ContentIOException
{
backingStore.getUrls(createdAfter, createdBefore, handler);
}
/*
* @see org.alfresco.repo.content.ContentStore#delete(java.lang.String)
*/
@Override
public boolean delete(String contentUrl)
{
if (cache.contains(contentUrl))
cache.remove(contentUrl);
return backingStore.delete(contentUrl);
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright (C) 2005-2011 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.content.caching;
import java.io.File;
import org.alfresco.repo.content.AbstractWritableContentStoreTest;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.TempFileProvider;
/**
* Tests for the CachingContentStore that use a full spring context.
*
* @author Matt Ward
*/
public class CachingContentStoreSpringTest extends AbstractWritableContentStoreTest
{
private CachingContentStore store;
private FileContentStore backingStore;
private ContentCache cache;
@Override
public void setUp() throws Exception
{
super.setUp();
File tempDir = TempFileProvider.getTempDir();
backingStore = new FileContentStore(ctx,
tempDir.getAbsolutePath() +
File.separatorChar +
getName());
cache = new ContentCacheImpl();
store = new CachingContentStore(backingStore, cache, false);
}
public void testStoreWillReadFromCacheWhenAvailable()
{
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 original content from the backing store.
backingStore.delete(contentUrl);
assertFalse("Original content should have been deleted", backingStore.exists(contentUrl));
// The cached version is still available.
String contentAfterDelete = store.getReader(contentUrl).getContentString();
assertEquals(content, contentAfterDelete);
}
public void testCacheOnInbound()
{
store = new CachingContentStore(backingStore, cache, true);
final String content = "Content for " + getName() + " test.";
final String contentUrl = FileContentStore.createNewFileStoreUrl();
assertFalse("Content shouldn't be cached yet", cache.contains(contentUrl));
// Write some content using the caching store
ContentWriter writer = store.getWriter(new ContentContext(null, contentUrl));
writer.putContent(content);
assertTrue("Cache should contain content after write", cache.contains(contentUrl));
// Check DIRECTLY with the cache, since a getReader() from the CachingContentStore would result
// in caching, but we're checking that caching was caused by the write operation.
String retrievedContent = cache.getReader(contentUrl).getContentString();
assertEquals(content, retrievedContent);
// The content should have been written through to the backing store.
String fromBackingStore = backingStore.getReader(contentUrl).getContentString();
assertEquals("Content should be in backing store", content, fromBackingStore);
// Remove the original content from the backing store.
backingStore.delete(contentUrl);
assertFalse("Original content should have been deleted", backingStore.exists(contentUrl));
// The cached version is still available
String contentAfterDelete = store.getReader(contentUrl).getContentString();
assertEquals(content, contentAfterDelete);
}
/*
* @see org.alfresco.repo.content.AbstractReadOnlyContentStoreTest#getStore()
*/
@Override
protected ContentStore getStore()
{
return store;
}
}

View File

@@ -0,0 +1,269 @@
/*
* Copyright (C) 2005-2011 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.content.caching;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.Date;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.ContentStore.ContentUrlHandler;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentStreamListener;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Tests for the CachingContentStore class. Tests use mock backing store and cache.
*
* @author Matt Ward
*/
@RunWith(MockitoJUnitRunner.class)
public class CachingContentStoreTest
{
private CachingContentStore cachingStore;
@Mock
private ContentStore backingStore;
@Mock
private ContentCache cache;
@Before
public void setUp() throws Exception
{
cachingStore = new CachingContentStore(backingStore, cache, false);
}
@Test
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");
assertSame(returnedReader, cachedContentReader);
verify(backingStore, never()).getReader(anyString());
}
@Test
public void getReadForItemMissingFromCache()
{
ContentReader sourceContent = mock(ContentReader.class);
when(cache.contains("url")).thenReturn(false);
when(backingStore.getReader("url")).thenReturn(sourceContent);
cachingStore.getReader("url");
verify(backingStore).getReader("url");
verify(cache).put("url", sourceContent);
}
@Test
public void getWriterWhenNotCacheOnInbound()
{
ContentContext ctx = ContentContext.NULL_CONTEXT;
cachingStore.getWriter(ctx);
verify(backingStore).getWriter(ctx);
}
@Test
public void getWriterWhenCacheOnInbound() throws ContentIOException, IOException
{
cachingStore = new CachingContentStore(backingStore, cache, true);
ContentContext ctx = ContentContext.NULL_CONTEXT;
ContentWriter bsWriter = mock(ContentWriter.class);
when(backingStore.getWriter(ctx)).thenReturn(bsWriter);
when(bsWriter.getContentUrl()).thenReturn("url");
ContentWriter cacheWriter = mock(ContentWriter.class);
when(cache.getWriter("url")).thenReturn(cacheWriter);
ContentReader readerFromCacheWriter = mock(ContentReader.class);
when(cacheWriter.getReader()).thenReturn(readerFromCacheWriter);
cachingStore.getWriter(ctx);
// Check that a listener was attached to cacheWriter with the correct behaviour
ArgumentCaptor<ContentStreamListener> arg = ArgumentCaptor.forClass(ContentStreamListener.class);
verify(cacheWriter).addListener(arg.capture());
// Simulate a stream close
arg.getValue().contentStreamClosed();
// Check behaviour of the listener
verify(bsWriter).putContent(readerFromCacheWriter);
verify(backingStore).getWriter(ctx);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Tests for delegated methods follow...
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void delegatedIsContentUrlSupported()
{
when(backingStore.isContentUrlSupported("url")).thenReturn(true);
assertTrue(cachingStore.isContentUrlSupported("url"));
when(backingStore.isContentUrlSupported("url")).thenReturn(false);
assertFalse(cachingStore.isContentUrlSupported("url"));
}
@Test
public void delegatedIsWriteSupported()
{
when(backingStore.isWriteSupported()).thenReturn(true);
assertTrue(cachingStore.isWriteSupported());
when(backingStore.isWriteSupported()).thenReturn(false);
assertFalse(cachingStore.isWriteSupported());
}
@Test
public void delegatedGetTotalSize()
{
when(backingStore.getTotalSize()).thenReturn(234L);
assertEquals(234L, cachingStore.getTotalSize());
}
@Test
public void delegatedGetSpaceUsed()
{
when(backingStore.getSpaceUsed()).thenReturn(453L);
assertEquals(453L, cachingStore.getSpaceUsed());
}
@Test
public void delegatedGetSpaceFree()
{
when(backingStore.getSpaceFree()).thenReturn(124L);
assertEquals(124L, cachingStore.getSpaceFree());
}
@Test
public void delegatedGetSpaceTotal()
{
when(backingStore.getSpaceTotal()).thenReturn(4234L);
assertEquals(4234L, cachingStore.getSpaceTotal());
}
@Test
public void delegatedGetRootLocation()
{
when(backingStore.getRootLocation()).thenReturn("/random/root/dir");
assertEquals("/random/root/dir", cachingStore.getRootLocation());
}
@Test
public void delegatedExists()
{
when(backingStore.exists("url")).thenReturn(true);
assertTrue(cachingStore.exists("url"));
when(backingStore.exists("url")).thenReturn(false);
assertFalse(cachingStore.exists("url"));
}
@Test
public void delegatedGetUrls1()
{
ContentUrlHandler handler = createDummyUrlHandler();
cachingStore.getUrls(handler);
verify(backingStore).getUrls(handler);
}
@Test
public void delegatedGetUrls2()
{
ContentUrlHandler handler = createDummyUrlHandler();
Date after = new Date(123L);
Date before = new Date(456L);
cachingStore.getUrls(after, before, handler);
verify(backingStore).getUrls(after, before, handler);
}
@Test
public void delegatedDelete()
{
when(backingStore.delete("url")).thenReturn(true);
assertTrue(cachingStore.delete("url"));
when(backingStore.delete("url")).thenReturn(false);
assertFalse(cachingStore.delete("url"));
}
/**
* Create a stub handler - just so we can check it has been passed around correctly.
*
* @return ContentUrlHandler
*/
private ContentUrlHandler createDummyUrlHandler()
{
ContentUrlHandler handler = new ContentUrlHandler()
{
@Override
public void handle(String contentUrl)
{
}
};
return handler;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2005-2011 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.content.caching;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
/**
* A cache designed to operate on content and split between memory and disk.
* The binary content data itself is stored on disk but the references to
* those files are stored in memory.
*
* @author Matt Ward
*/
public interface ContentCache
{
/**
* Check to see if the content - specified by URL - exists in the cache.
* <p>
* Note that just because the in-memory cache has a record of the content item having been placed
* into the cache, it does not mean that the disk item is guaranteed to be there. The temp file
* clean-up process, for example, may have removed it.
* <p>
* @param contentUrl
* @return true if the URL exists in the in-memory cache. It <em>may</em> therefore be cached on disk.
*/
boolean contains(String contentUrl);
/**
* Retrieve a ContentReader for the cached content specified by URL.
*
* @param contentUrl
* @return ContentReader
* @throws org.alfresco.repo.content.caching.CacheMissException
* If the cache does not contain the specified content.
*/
ContentReader getReader(String contentUrl);
/**
* Put an item into cache - this will populate both a disk file (with content) and
* the in-memory lookup table (with the URL and cache file location).
*
* Empty content will NOT be cached - in which case false is returned.
*
* @param contentUrl
* @param reader
* @return true if the content was cached, false otherwise.
*/
boolean put(String contentUrl, ContentReader reader);
/**
* Remove a cached item from the in-memory lookup table. Implementation should not remove
* the actual cached content (file) - this should be left to the clean-up process.
*
* @param contentUrl
*/
void remove(String contentUrl);
/**
* Retrieve a ContentWriter to write content to a cache file. Upon closing the stream
* a listener will add the new content file to the in-memory lookup table.
*
* @param context
* @return ContentWriter
*/
ContentWriter getWriter(String url);
}

View File

@@ -0,0 +1,189 @@
/*
* Copyright (C) 2005-2011 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.content.caching;
import java.io.File;
import net.sf.ehcache.CacheManager;
import org.alfresco.repo.cache.EhCacheAdapter;
import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.repo.content.filestore.FileContentWriter;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentStreamListener;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.TempFileProvider;
/**
* The one and only implementation of the ContentCache class.
* <p>
* Binary content data itself is stored on disk in temporary files managed
* by Alfresco (see {@link org.alfresco.util.TempFileProvider}).
* <p>
* The in-memory lookup table is provided by Ehcache.
*
* @author Matt Ward
*/
public class ContentCacheImpl implements ContentCache
{
private static final String CACHE_DIR = "caching_cs";
private static final String TMP_FILE_EXTENSION = ".tmp";
private static final String EHCACHE_NAME = "contentStoreCache";
private static final long T24_HOURS = 86400;
private final File cacheRoot = TempFileProvider.getLongLifeTempDir(CACHE_DIR);
private EhCacheAdapter<String, String> memoryStore;
public ContentCacheImpl()
{
// TODO: Configuration to be moved out into Spring
memoryStore = new EhCacheAdapter<String, String>();
configureMemoryStore();
}
private void configureMemoryStore()
{
CacheManager manager = CacheManager.getInstance();
// Create the cache if it hasn't already been created.
if (!manager.cacheExists(EHCACHE_NAME))
{
net.sf.ehcache.Cache memoryOnlyCache =
new net.sf.ehcache.Cache(EHCACHE_NAME, 10000, false, false, T24_HOURS, T24_HOURS);
manager.addCache(memoryOnlyCache);
}
memoryStore.setCache(manager.getCache(EHCACHE_NAME));
}
@Override
public boolean contains(String contentUrl)
{
return memoryStore.contains(contentUrl);
}
@Override
public ContentReader getReader(String contentUrl)
{
if (memoryStore.contains(contentUrl))
{
String path = memoryStore.get(contentUrl);
return new FileContentReader(new File(path), contentUrl);
}
throw new CacheMissException(contentUrl);
}
@Override
public boolean put(String contentUrl, ContentReader source)
{
File cacheFile = createCacheFile(contentUrl);
// Copy the content from the source into a cache file
if (source.getSize() > 0L)
{
source.getContent(cacheFile);
// Add a record of the cached file to the in-memory cache.
memoryStore.put(contentUrl, cacheFile.getAbsolutePath());
return true;
}
return false;
}
/**
* Create a File object and makes any intermediate directories in the path.
*
* @param contentUrl
* @return File
*/
private File createCacheFile(String contentUrl)
{
File path = new File(cacheRoot, pathFromUrl(contentUrl));
File parentDir = path.getParentFile();
parentDir.mkdirs();
File cacheFile = TempFileProvider.createTempFile(path.getName(), TMP_FILE_EXTENSION, parentDir);
return cacheFile;
}
/*
* @see org.alfresco.repo.content.caching.ContentCache#remove(java.lang.String)
*/
@Override
public void remove(String contentUrl)
{
// Remove from the in-memory cache, but not from disk. Let the clean-up process do this asynchronously.
memoryStore.remove(contentUrl);
}
/*
* @see org.alfresco.repo.content.caching.ContentCache#getWriter(org.alfresco.repo.content.ContentContext)
*/
@Override
public ContentWriter getWriter(final String url)
{
// Get a writer to a cache file.
final File cacheFile = createCacheFile(url);
ContentWriter writer = new FileContentWriter(cacheFile);
// Attach a listener to populate the in-memory store when done writing.
writer.addListener(new ContentStreamListener()
{
@Override
public void contentStreamClosed() throws ContentIOException
{
memoryStore.put(url, cacheFile.getAbsolutePath());
}
});
return writer;
}
/**
* Converts a content URL to a relative path name where the protocol will
* be the name of a subdirectory. For example:
* <p>
* store://2011/8/5/15/4/386595e0-3b52-4d5c-a32d-df9d0b9fd56e.bin
* <p>
* will become:
* <p>
* store/2011/8/5/15/4/386595e0-3b52-4d5c-a32d-df9d0b9fd56e.bin
*
* @param contentUrl
* @return String representation of relative path to file.
*/
private String pathFromUrl(String contentUrl)
{
return contentUrl.replaceFirst("://", "/");
}
}