diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml
index 7f05732f0b..04321bc64c 100644
--- a/config/alfresco/ehcache-default.xml
+++ b/config/alfresco/ehcache-default.xml
@@ -363,4 +363,13 @@
timeToLiveSeconds="60"
/>
+
+
diff --git a/source/java/org/alfresco/repo/content/ContentStore.java b/source/java/org/alfresco/repo/content/ContentStore.java
index 3d7ed28a20..b1d9a3338f 100644
--- a/source/java/org/alfresco/repo/content/ContentStore.java
+++ b/source/java/org/alfresco/repo/content/ContentStore.java
@@ -37,7 +37,7 @@ import org.alfresco.service.cmr.repository.ContentWriter;
*
* 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 store://year/month/day/GUID.bin
+ * for file stores is store://year/month/day/hour/minute/GUID.bin
*
* - store://: prefix identifying an Alfresco content stores
* regardless of the persistence mechanism.
diff --git a/source/java/org/alfresco/repo/content/caching/CachingContentStore.java b/source/java/org/alfresco/repo/content/caching/CachingContentStore.java
index 18b83c7cd5..858b969a23 100644
--- a/source/java/org/alfresco/repo/content/caching/CachingContentStore.java
+++ b/source/java/org/alfresco/repo/content/caching/CachingContentStore.java
@@ -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;
+ }
+
+
}
diff --git a/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java b/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java
index 38e1ba7fef..99379a7c18 100644
--- a/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java
+++ b/source/java/org/alfresco/repo/content/caching/CachingContentStoreSpringTest.java
@@ -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 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 memoryStore = new EhCacheAdapter();
+ memoryStore.setCache(manager.getCache(EHCACHE_NAME));
+
+ return memoryStore;
+ }
+
+
public void testStoreWillReadFromCacheWhenAvailable()
{
final String content = "Content for " + getName() + " test.";
diff --git a/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java b/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java
index 63cdd594ba..3429065982 100644
--- a/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java
+++ b/source/java/org/alfresco/repo/content/caching/CachingContentStoreTest.java
@@ -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 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...
diff --git a/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java b/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java
index 62677004d7..5fcc7319ab 100644
--- a/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java
+++ b/source/java/org/alfresco/repo/content/caching/ContentCacheImpl.java
@@ -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 memoryStore;
- public ContentCacheImpl()
- {
- // TODO: Configuration to be moved out into Spring
- memoryStore = new EhCacheAdapter();
- 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 memoryStore)
+ {
+ this.memoryStore = memoryStore;
}
}
diff --git a/source/java/org/alfresco/repo/content/caching/FullTest.java b/source/java/org/alfresco/repo/content/caching/FullTest.java
new file mode 100644
index 0000000000..f662c9fa81
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/caching/FullTest.java
@@ -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 .
+ */
+
+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();
+ }
+}
diff --git a/source/test-resources/cachingstore/test-context.xml b/source/test-resources/cachingstore/test-context.xml
new file mode 100644
index 0000000000..4086d128dd
--- /dev/null
+++ b/source/test-resources/cachingstore/test-context.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.alfresco.cache.cachingContentStoreCache
+
+
+
+
+
+
+
+
+ ${dir.contentstore}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+