mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
ALF-9613: configuration changes for ContentCachingStore, bug fixes, more tests.
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29803 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -363,4 +363,13 @@
|
||||
timeToLiveSeconds="60"
|
||||
/>
|
||||
|
||||
<!-- Caching Content Store -->
|
||||
<cache
|
||||
name="org.alfresco.cache.cachingContentStoreCache"
|
||||
maxElementsInMemory="10000"
|
||||
eternal="false"
|
||||
timeToLiveSeconds="86400"
|
||||
overflowToDisk="false"
|
||||
statistics="false"
|
||||
/>
|
||||
</ehcache>
|
||||
|
@@ -37,7 +37,7 @@ import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
* <p>
|
||||
* Content URLs must consist of a prefix or protocol followed by an
|
||||
* implementation-specific identifier. For example, the content URL format
|
||||
* for file stores is <b>store://year/month/day/GUID.bin</b> <br>
|
||||
* for file stores is <b>store://year/month/day/hour/minute/GUID.bin</b> <br>
|
||||
* <ul>
|
||||
* <li> <b>store://</b>: prefix identifying an Alfresco content stores
|
||||
* regardless of the persistence mechanism. </li>
|
||||
|
@@ -26,6 +26,7 @@ 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.springframework.beans.factory.annotation.Required;
|
||||
|
||||
/**
|
||||
* Implementation of ContentStore that wraps any other ContentStore (the backing store)
|
||||
@@ -41,10 +42,14 @@ import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
*/
|
||||
public class CachingContentStore implements ContentStore
|
||||
{
|
||||
private final ContentStore backingStore;
|
||||
private final ContentCache cache;
|
||||
private final boolean cacheOnInbound;
|
||||
private ContentStore backingStore;
|
||||
private ContentCache cache;
|
||||
private boolean cacheOnInbound;
|
||||
|
||||
|
||||
public CachingContentStore()
|
||||
{
|
||||
}
|
||||
|
||||
public CachingContentStore(ContentStore backingStore, ContentCache cache, boolean cacheOnInbound)
|
||||
{
|
||||
@@ -158,19 +163,23 @@ public class CachingContentStore implements ContentStore
|
||||
final ContentWriter bsWriter = backingStore.getWriter(context);
|
||||
|
||||
// write to cache
|
||||
final ContentWriter writer = cache.getWriter(bsWriter.getContentUrl());
|
||||
final ContentWriter cacheWriter = cache.getWriter(bsWriter.getContentUrl());
|
||||
|
||||
writer.addListener(new ContentStreamListener()
|
||||
cacheWriter.addListener(new ContentStreamListener()
|
||||
{
|
||||
@Override
|
||||
public void contentStreamClosed() throws ContentIOException
|
||||
{
|
||||
// copy from the cache to the backing store
|
||||
bsWriter.putContent(writer.getReader());
|
||||
// Finished writing to the cache, so copy to the backing store -
|
||||
// ensuring that the encoding attributes are set to the same as for the cache writer.
|
||||
bsWriter.setEncoding(cacheWriter.getEncoding());
|
||||
bsWriter.setLocale(cacheWriter.getLocale());
|
||||
bsWriter.setMimetype(cacheWriter.getMimetype());
|
||||
bsWriter.putContent(cacheWriter.getReader());
|
||||
}
|
||||
});
|
||||
|
||||
return writer;
|
||||
return cacheWriter;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -186,7 +195,8 @@ public class CachingContentStore implements ContentStore
|
||||
@Override
|
||||
public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl)
|
||||
{
|
||||
return backingStore.getWriter(existingContentReader, newContentUrl);
|
||||
ContentContext ctx = new ContentContext(existingContentReader, newContentUrl);
|
||||
return getWriter(ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -219,4 +229,24 @@ public class CachingContentStore implements ContentStore
|
||||
|
||||
return backingStore.delete(contentUrl);
|
||||
}
|
||||
|
||||
|
||||
@Required
|
||||
public void setBackingStore(ContentStore backingStore)
|
||||
{
|
||||
this.backingStore = backingStore;
|
||||
}
|
||||
|
||||
@Required
|
||||
public void setCache(ContentCache cache)
|
||||
{
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
public void setCacheOnInbound(boolean cacheOnInbound)
|
||||
{
|
||||
this.cacheOnInbound = cacheOnInbound;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -20,6 +20,9 @@ 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.AbstractWritableContentStoreTest;
|
||||
import org.alfresco.repo.content.ContentContext;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
@@ -28,15 +31,18 @@ import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
|
||||
/**
|
||||
* Tests for the CachingContentStore that use a full spring context.
|
||||
* Tests for the CachingContentStore that benefit from a full set of tests
|
||||
* defined in AbstractWritableContentStoreTest.
|
||||
*
|
||||
* @author Matt Ward
|
||||
*/
|
||||
public class CachingContentStoreSpringTest extends AbstractWritableContentStoreTest
|
||||
{
|
||||
private static final String EHCACHE_NAME = "cache.test.cachingContentStoreCache";
|
||||
private static final int T24_HOURS = 86400;
|
||||
private CachingContentStore store;
|
||||
private FileContentStore backingStore;
|
||||
private ContentCache cache;
|
||||
private ContentCacheImpl cache;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
@@ -51,10 +57,31 @@ public class CachingContentStoreSpringTest extends AbstractWritableContentStoreT
|
||||
getName());
|
||||
|
||||
cache = new ContentCacheImpl();
|
||||
cache.setMemoryStore(createMemoryStore());
|
||||
store = new CachingContentStore(backingStore, cache, false);
|
||||
}
|
||||
|
||||
|
||||
private EhCacheAdapter<String, String> createMemoryStore()
|
||||
{
|
||||
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, 50, false, false, T24_HOURS, T24_HOURS);
|
||||
|
||||
manager.addCache(memoryOnlyCache);
|
||||
}
|
||||
|
||||
EhCacheAdapter<String, String> memoryStore = new EhCacheAdapter<String, String>();
|
||||
memoryStore.setCache(manager.getCache(EHCACHE_NAME));
|
||||
|
||||
return memoryStore;
|
||||
}
|
||||
|
||||
|
||||
public void testStoreWillReadFromCacheWhenAvailable()
|
||||
{
|
||||
final String content = "Content for " + getName() + " test.";
|
||||
|
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.alfresco.repo.content.ContentContext;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
@@ -137,6 +138,35 @@ public class CachingContentStoreTest
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void encodingAttrsCopiedToBackingStoreWriter()
|
||||
{
|
||||
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);
|
||||
|
||||
when(cacheWriter.getEncoding()).thenReturn("UTF-8");
|
||||
when(cacheWriter.getLocale()).thenReturn(Locale.UK);
|
||||
when(cacheWriter.getMimetype()).thenReturn("not/real/mimetype");
|
||||
|
||||
cachingStore.getWriter(ctx);
|
||||
|
||||
// Get the stream listener and trigger it
|
||||
ArgumentCaptor<ContentStreamListener> arg = ArgumentCaptor.forClass(ContentStreamListener.class);
|
||||
verify(cacheWriter).addListener(arg.capture());
|
||||
// Simulate a stream close
|
||||
arg.getValue().contentStreamClosed();
|
||||
|
||||
verify(bsWriter).setEncoding("UTF-8");
|
||||
verify(bsWriter).setLocale(Locale.UK);
|
||||
verify(bsWriter).setMimetype("not/real/mimetype");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Tests for delegated methods follow...
|
||||
|
@@ -20,9 +20,8 @@ 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.ContentStore;
|
||||
import org.alfresco.repo.content.filestore.FileContentReader;
|
||||
import org.alfresco.repo.content.filestore.FileContentWriter;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
@@ -45,33 +44,9 @@ 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
|
||||
@@ -153,7 +128,7 @@ public class ContentCacheImpl implements ContentCache
|
||||
{
|
||||
// Get a writer to a cache file.
|
||||
final File cacheFile = createCacheFile(url);
|
||||
ContentWriter writer = new FileContentWriter(cacheFile);
|
||||
ContentWriter writer = new FileContentWriter(cacheFile, url, null);
|
||||
|
||||
// Attach a listener to populate the in-memory store when done writing.
|
||||
writer.addListener(new ContentStreamListener()
|
||||
@@ -184,6 +159,18 @@ public class ContentCacheImpl implements ContentCache
|
||||
*/
|
||||
private String pathFromUrl(String contentUrl)
|
||||
{
|
||||
return contentUrl.replaceFirst("://", "/");
|
||||
return contentUrl.replaceFirst(ContentStore.PROTOCOL_DELIMITER, "/");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Configure ContentCache with a memory store - an EhCacheAdapter.
|
||||
*
|
||||
* @param memoryStore the memoryStore to set
|
||||
*/
|
||||
public void setMemoryStore(EhCacheAdapter<String, String> memoryStore)
|
||||
{
|
||||
this.memoryStore = memoryStore;
|
||||
}
|
||||
}
|
||||
|
117
source/java/org/alfresco/repo/content/caching/FullTest.java
Normal file
117
source/java/org/alfresco/repo/content/caching/FullTest.java
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 java.util.Locale;
|
||||
|
||||
import org.alfresco.repo.content.ContentContext;
|
||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
||||
import org.alfresco.service.cmr.repository.ContentAccessor;
|
||||
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.ApplicationContextHelper;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
* Tests for CachingContentStore where all the main collaborators are defined as Spring beans.
|
||||
*
|
||||
* @author Matt Ward
|
||||
*/
|
||||
public class FullTest
|
||||
{
|
||||
private ApplicationContext ctx;
|
||||
private CachingContentStore store;
|
||||
|
||||
@Before
|
||||
public void setUp()
|
||||
{
|
||||
String conf = "classpath:cachingstore/test-context.xml";
|
||||
ctx = ApplicationContextHelper.getApplicationContext(new String[] { conf });
|
||||
|
||||
store = (CachingContentStore) ctx.getBean("cachingContentStore");
|
||||
store.setCacheOnInbound(true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void canUseCachingContentStore()
|
||||
{
|
||||
// Write through the caching content store - cache during the process.
|
||||
ContentWriter writer = store.getWriter(ContentContext.NULL_CONTEXT);
|
||||
final String content = makeContent();
|
||||
writer.putContent(content);
|
||||
|
||||
ContentReader reader = store.getReader(writer.getContentUrl());
|
||||
assertEquals("Reader and writer should have same URLs", writer.getContentUrl(), reader.getContentUrl());
|
||||
assertEquals("Reader should get correct content", content, reader.getContentString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void writeToCacheWithContentContext()
|
||||
{
|
||||
// Write through the caching content store - cache during the process.
|
||||
final String proposedUrl = FileContentStore.createNewFileStoreUrl();
|
||||
ContentWriter writer = store.getWriter(new ContentContext(null, proposedUrl));
|
||||
final String content = makeContent();
|
||||
writer.putContent(content);
|
||||
assertEquals("Writer should have correct URL", proposedUrl, writer.getContentUrl());
|
||||
|
||||
ContentReader reader = store.getReader(writer.getContentUrl());
|
||||
assertEquals("Reader and writer should have same URLs", writer.getContentUrl(), reader.getContentUrl());
|
||||
assertEquals("Reader should get correct content", content, reader.getContentString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void writeToCacheWithExistingReader()
|
||||
{
|
||||
ContentWriter oldWriter = store.getWriter(ContentContext.NULL_CONTEXT);
|
||||
oldWriter.putContent("Old content for " + getClass().getSimpleName());
|
||||
ContentReader existingReader = oldWriter.getReader();
|
||||
|
||||
// Write through the caching content store - cache during the process.
|
||||
final String proposedUrl = FileContentStore.createNewFileStoreUrl();
|
||||
ContentWriter writer = store.getWriter(new ContentContext(existingReader, proposedUrl));
|
||||
final String content = makeContent();
|
||||
writer.putContent(content);
|
||||
assertEquals("Writer should have correct URL", proposedUrl, writer.getContentUrl());
|
||||
|
||||
assertFalse("Old and new writers must have different URLs",
|
||||
oldWriter.getContentUrl().equals(writer.getContentUrl()));
|
||||
|
||||
ContentReader reader = store.getReader(writer.getContentUrl());
|
||||
assertEquals("Reader and writer should have same URLs", writer.getContentUrl(), reader.getContentUrl());
|
||||
assertEquals("Reader should get correct content", content, reader.getContentString());
|
||||
}
|
||||
|
||||
|
||||
private String makeContent()
|
||||
{
|
||||
return "Example content for " + getClass().getSimpleName();
|
||||
}
|
||||
}
|
39
source/test-resources/cachingstore/test-context.xml
Normal file
39
source/test-resources/cachingstore/test-context.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
|
||||
|
||||
<beans>
|
||||
|
||||
<import resource="classpath:alfresco/application-context.xml" />
|
||||
|
||||
<bean id="cachingContentStoreCache" class="org.alfresco.repo.cache.EhCacheAdapter">
|
||||
<property name="cache">
|
||||
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
|
||||
<property name="cacheManager">
|
||||
<ref bean="internalEHCacheManager" />
|
||||
</property>
|
||||
<property name="cacheName">
|
||||
<value>org.alfresco.cache.cachingContentStoreCache</value>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="backingStore" class="org.alfresco.repo.content.filestore.FileContentStore">
|
||||
<constructor-arg>
|
||||
<value>${dir.contentstore}</value>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="contentCache" class="org.alfresco.repo.content.caching.ContentCacheImpl">
|
||||
<property name="memoryStore" ref="cachingContentStoreCache"/>
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="cachingContentStore" class="org.alfresco.repo.content.caching.CachingContentStore">
|
||||
<property name="backingStore" ref="backingStore"/>
|
||||
<property name="cache" ref="contentCache"/>
|
||||
<property name="cacheOnInbound" value="true"/>
|
||||
</bean>
|
||||
</beans>
|
Reference in New Issue
Block a user