mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Added a raw download servlet at URL http://.../alfresco/dr?contentUrl=...?ticket=...
Added ContentService.getRawReader to get content directly using a content URL. To access this, you need to be admin. Fixed EHCacheAdapter to handle non-Serializable values. Added tests for above and for AbstractRoutingContentStore. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5841 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -409,6 +409,7 @@
|
|||||||
<property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
|
<property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
|
||||||
<property name="objectDefinitionSource">
|
<property name="objectDefinitionSource">
|
||||||
<value>
|
<value>
|
||||||
|
org.alfresco.service.cmr.repository.ContentService.getRawReader=ACL_METHOD.ROLE_ADMINISTRATOR
|
||||||
org.alfresco.service.cmr.repository.ContentService.getReader=ACL_NODE.0.sys:base.ReadContent
|
org.alfresco.service.cmr.repository.ContentService.getReader=ACL_NODE.0.sys:base.ReadContent
|
||||||
org.alfresco.service.cmr.repository.ContentService.getWriter=ACL_NODE.0.sys:base.WriteContent
|
org.alfresco.service.cmr.repository.ContentService.getWriter=ACL_NODE.0.sys:base.WriteContent
|
||||||
org.alfresco.service.cmr.repository.ContentService.isTransformable=ACL_ALLOW
|
org.alfresco.service.cmr.repository.ContentService.isTransformable=ACL_ALLOW
|
||||||
|
@@ -55,6 +55,7 @@ public class CacheTest extends TestCase
|
|||||||
private SimpleCache<String, Serializable> standaloneCache;
|
private SimpleCache<String, Serializable> standaloneCache;
|
||||||
private SimpleCache<String, Serializable> backingCache;
|
private SimpleCache<String, Serializable> backingCache;
|
||||||
private SimpleCache<String, Serializable> transactionalCache;
|
private SimpleCache<String, Serializable> transactionalCache;
|
||||||
|
private SimpleCache<String, Object> objectCache;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
@@ -64,6 +65,7 @@ public class CacheTest extends TestCase
|
|||||||
standaloneCache = (SimpleCache<String, Serializable>) ctx.getBean("ehCache1");
|
standaloneCache = (SimpleCache<String, Serializable>) ctx.getBean("ehCache1");
|
||||||
backingCache = (SimpleCache<String, Serializable>) ctx.getBean("backingCache");
|
backingCache = (SimpleCache<String, Serializable>) ctx.getBean("backingCache");
|
||||||
transactionalCache = (SimpleCache<String, Serializable>) ctx.getBean("transactionalCache");
|
transactionalCache = (SimpleCache<String, Serializable>) ctx.getBean("transactionalCache");
|
||||||
|
objectCache = (SimpleCache<String, Object>) ctx.getBean("objectCache");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,6 +88,14 @@ public class CacheTest extends TestCase
|
|||||||
assertNotNull(backingCache);
|
assertNotNull(backingCache);
|
||||||
assertNotNull(standaloneCache);
|
assertNotNull(standaloneCache);
|
||||||
assertNotNull(transactionalCache);
|
assertNotNull(transactionalCache);
|
||||||
|
assertNotNull(objectCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testObjectCache() throws Exception
|
||||||
|
{
|
||||||
|
objectCache.put("A", this);
|
||||||
|
Object obj = objectCache.get("A");
|
||||||
|
assertTrue("Object not cached properly", this == obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEhcacheAdaptors() throws Exception
|
public void testEhcacheAdaptors() throws Exception
|
||||||
|
@@ -87,7 +87,7 @@ public class EhCacheAdapter<K extends Serializable, V extends Object>
|
|||||||
Element element = cache.get(key);
|
Element element = cache.get(key);
|
||||||
if (element != null)
|
if (element != null)
|
||||||
{
|
{
|
||||||
return (V) element.getValue();
|
return (V) element.getObjectValue();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -97,7 +97,8 @@ public class EhCacheAdapter<K extends Serializable, V extends Object>
|
|||||||
catch (CacheException e)
|
catch (CacheException e)
|
||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException("Failed to get from EhCache: \n" +
|
throw new AlfrescoRuntimeException("Failed to get from EhCache: \n" +
|
||||||
" key: " + key);
|
" key: " + key,
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -102,7 +102,8 @@ public abstract class AbstractContentReadWriteTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
protected final ContentWriter getWriter()
|
protected final ContentWriter getWriter()
|
||||||
{
|
{
|
||||||
return getStore().getWriter(null, contentUrl);
|
ContentContext contentCtx = new ContentContext(null, contentUrl);
|
||||||
|
return getStore().getWriter(contentCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -187,7 +188,8 @@ public abstract class AbstractContentReadWriteTest extends TestCase
|
|||||||
assertFalse("Reader exists failure", reader.exists());
|
assertFalse("Reader exists failure", reader.exists());
|
||||||
|
|
||||||
// write something
|
// write something
|
||||||
ContentWriter writer = store.getWriter(null, contentUrl);
|
ContentContext contentContext = new ContentContext(null, contentUrl);
|
||||||
|
ContentWriter writer = store.getWriter(contentContext);
|
||||||
writer.putContent("ABC");
|
writer.putContent("ABC");
|
||||||
|
|
||||||
assertTrue("Store exists should show URL to be present", store.exists(contentUrl));
|
assertTrue("Store exists should show URL to be present", store.exists(contentUrl));
|
||||||
@@ -298,10 +300,11 @@ public abstract class AbstractContentReadWriteTest extends TestCase
|
|||||||
String contentUrl = AbstractContentStore.createNewUrl();
|
String contentUrl = AbstractContentStore.createNewUrl();
|
||||||
ContentStore store = getStore();
|
ContentStore store = getStore();
|
||||||
|
|
||||||
ContentWriter firstWriter = store.getWriter(null, contentUrl);
|
ContentContext contentCtx = new ContentContext(null, contentUrl);
|
||||||
|
ContentWriter firstWriter = store.getWriter(contentCtx);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ContentWriter secondWriter = store.getWriter(null, contentUrl);
|
ContentWriter secondWriter = store.getWriter(contentCtx);
|
||||||
fail("Store issued two writers for the same URL: " + store);
|
fail("Store issued two writers for the same URL: " + store);
|
||||||
}
|
}
|
||||||
catch (ContentIOException e)
|
catch (ContentIOException e)
|
||||||
@@ -620,7 +623,8 @@ public abstract class AbstractContentReadWriteTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get a new writer from the store, using the existing content and perform a truncation check
|
// get a new writer from the store, using the existing content and perform a truncation check
|
||||||
ContentWriter writerTruncate = getStore().getWriter(writer.getReader(), AbstractContentStore.createNewUrl());
|
ContentContext writerTruncateCtx = new ContentContext(writer.getReader(), AbstractContentStore.createNewUrl());
|
||||||
|
ContentWriter writerTruncate = getStore().getWriter(writerTruncateCtx);
|
||||||
assertEquals("Content size incorrect", 0, writerTruncate.getSize());
|
assertEquals("Content size incorrect", 0, writerTruncate.getSize());
|
||||||
// get the channel with truncation
|
// get the channel with truncation
|
||||||
FileChannel fcTruncate = writerTruncate.getFileChannel(true);
|
FileChannel fcTruncate = writerTruncate.getFileChannel(true);
|
||||||
@@ -628,7 +632,8 @@ public abstract class AbstractContentReadWriteTest extends TestCase
|
|||||||
assertEquals("Content not truncated", 0, writerTruncate.getSize());
|
assertEquals("Content not truncated", 0, writerTruncate.getSize());
|
||||||
|
|
||||||
// get a new writer from the store, using the existing content and perform a non-truncation check
|
// get a new writer from the store, using the existing content and perform a non-truncation check
|
||||||
ContentWriter writerNoTruncate = getStore().getWriter(writer.getReader(), AbstractContentStore.createNewUrl());
|
ContentContext writerNoTruncateCtx = new ContentContext(writer.getReader(), AbstractContentStore.createNewUrl());
|
||||||
|
ContentWriter writerNoTruncate = getStore().getWriter(writerNoTruncateCtx);
|
||||||
assertEquals("Content size incorrect", 0, writerNoTruncate.getSize());
|
assertEquals("Content size incorrect", 0, writerNoTruncate.getSize());
|
||||||
// get the channel without truncation
|
// get the channel without truncation
|
||||||
FileChannel fcNoTruncate = writerNoTruncate.getFileChannel(false);
|
FileChannel fcNoTruncate = writerNoTruncate.getFileChannel(false);
|
||||||
|
@@ -86,6 +86,10 @@ public abstract class AbstractContentStore implements ContentStore
|
|||||||
* @param contentUrl a URL of the content to check
|
* @param contentUrl a URL of the content to check
|
||||||
* @return Returns the relative part of the URL
|
* @return Returns the relative part of the URL
|
||||||
* @throws RuntimeException if the URL is not correct
|
* @throws RuntimeException if the URL is not correct
|
||||||
|
*
|
||||||
|
* @deprecated Stores can really have any prefix in the URL. This method was
|
||||||
|
* really specific to the FileContentStore and has been moved into
|
||||||
|
* it.
|
||||||
*/
|
*/
|
||||||
public static String getRelativePart(String contentUrl) throws RuntimeException
|
public static String getRelativePart(String contentUrl) throws RuntimeException
|
||||||
{
|
{
|
||||||
|
@@ -83,7 +83,8 @@ public abstract class AbstractRoutingContentStore implements ContentStore
|
|||||||
* to make a decision.
|
* to make a decision.
|
||||||
*
|
*
|
||||||
* @param ctx the context to use to make the choice
|
* @param ctx the context to use to make the choice
|
||||||
* @return Returns the store most appropriate for the given context
|
* @return Returns the store most appropriate for the given context and
|
||||||
|
* <b>never <tt>null</tt></b>
|
||||||
*/
|
*/
|
||||||
protected abstract ContentStore selectWriteStore(ContentContext ctx);
|
protected abstract ContentStore selectWriteStore(ContentContext ctx);
|
||||||
|
|
||||||
@@ -132,9 +133,19 @@ public abstract class AbstractRoutingContentStore implements ContentStore
|
|||||||
List<ContentStore> stores = getAllStores();
|
List<ContentStore> stores = getAllStores();
|
||||||
for (ContentStore storeInList : stores)
|
for (ContentStore storeInList : stores)
|
||||||
{
|
{
|
||||||
if (!store.exists(contentUrl))
|
boolean exists = false;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// It is not in the store
|
exists = storeInList.exists(contentUrl);
|
||||||
|
if (!exists)
|
||||||
|
{
|
||||||
|
// It is not in the store
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// The API used to allow failure when the URL wasn't there
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// We found one
|
// We found one
|
||||||
@@ -264,6 +275,10 @@ public abstract class AbstractRoutingContentStore implements ContentStore
|
|||||||
{
|
{
|
||||||
// Select the store for writing
|
// Select the store for writing
|
||||||
ContentStore store = selectWriteStore(context);
|
ContentStore store = selectWriteStore(context);
|
||||||
|
if (store == null)
|
||||||
|
{
|
||||||
|
throw new NullPointerException("Unable to find a writer. 'selectWriteStore' may not return null.");
|
||||||
|
}
|
||||||
ContentWriter writer = store.getWriter(context);
|
ContentWriter writer = store.getWriter(context);
|
||||||
// Cache the store against the URL
|
// Cache the store against the URL
|
||||||
storesCacheWriteLock.lock();
|
storesCacheWriteLock.lock();
|
||||||
|
@@ -71,8 +71,10 @@ public interface ContentStore
|
|||||||
* reader to {@link ContentReader#exists() check for existence}, although
|
* reader to {@link ContentReader#exists() check for existence}, although
|
||||||
* that check should also be performed.
|
* that check should also be performed.
|
||||||
*
|
*
|
||||||
* @param contentUrl the path to the content
|
* @param contentUrl the path to the content
|
||||||
* @return Returns true if the content exists.
|
* @return Returns true if the content exists, otherwise
|
||||||
|
* false if the content doesn't exist or if the URL
|
||||||
|
* is not applicable to this store.
|
||||||
* @throws ContentIOException
|
* @throws ContentIOException
|
||||||
*
|
*
|
||||||
* @see ContentReader#exists()
|
* @see ContentReader#exists()
|
||||||
@@ -90,6 +92,7 @@ public interface ContentStore
|
|||||||
*
|
*
|
||||||
* @see #exists(String)
|
* @see #exists(String)
|
||||||
* @see ContentReader#exists()
|
* @see ContentReader#exists()
|
||||||
|
* @see EmptyContentReader
|
||||||
*/
|
*/
|
||||||
public ContentReader getReader(String contentUrl) throws ContentIOException;
|
public ContentReader getReader(String contentUrl) throws ContentIOException;
|
||||||
|
|
||||||
|
@@ -84,6 +84,7 @@ public class ContentTestSuite extends TestSuite
|
|||||||
suite.addTestSuite(ContentDataTest.class);
|
suite.addTestSuite(ContentDataTest.class);
|
||||||
suite.addTestSuite(MimetypeMapTest.class);
|
suite.addTestSuite(MimetypeMapTest.class);
|
||||||
suite.addTestSuite(RoutingContentServiceTest.class);
|
suite.addTestSuite(RoutingContentServiceTest.class);
|
||||||
|
suite.addTestSuite(RoutingContentStoreTest.class);
|
||||||
|
|
||||||
return suite;
|
return suite;
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
import org.alfresco.i18n.I18NUtil;
|
||||||
import org.alfresco.repo.avm.AVMNodeConverter;
|
import org.alfresco.repo.avm.AVMNodeConverter;
|
||||||
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy;
|
||||||
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
|
import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy;
|
||||||
@@ -268,6 +269,30 @@ public class RoutingContentService implements ContentService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
public ContentReader getRawReader(String contentUrl)
|
||||||
|
{
|
||||||
|
ContentReader reader = store.getReader(contentUrl);
|
||||||
|
if (reader == null)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("ContentStore implementations may not return null ContentReaders");
|
||||||
|
}
|
||||||
|
// set extra data on the reader
|
||||||
|
reader.setMimetype(MimetypeMap.MIMETYPE_BINARY);
|
||||||
|
reader.setEncoding("UTF-8");
|
||||||
|
reader.setLocale(I18NUtil.getLocale());
|
||||||
|
|
||||||
|
// Done
|
||||||
|
if (logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug(
|
||||||
|
"Direct request for reader: \n" +
|
||||||
|
" Content URL: " + contentUrl + "\n" +
|
||||||
|
" Reader: " + reader);
|
||||||
|
}
|
||||||
|
return reader;
|
||||||
|
}
|
||||||
|
|
||||||
public ContentReader getReader(NodeRef nodeRef, QName propertyQName)
|
public ContentReader getReader(NodeRef nodeRef, QName propertyQName)
|
||||||
{
|
{
|
||||||
return getReader(nodeRef, propertyQName, true);
|
return getReader(nodeRef, propertyQName, true);
|
||||||
@@ -318,6 +343,10 @@ public class RoutingContentService implements ContentService
|
|||||||
|
|
||||||
// The context of the read is entirely described by the URL
|
// The context of the read is entirely described by the URL
|
||||||
ContentReader reader = store.getReader(contentUrl);
|
ContentReader reader = store.getReader(contentUrl);
|
||||||
|
if (reader == null)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("ContentStore implementations may not return null ContentReaders");
|
||||||
|
}
|
||||||
|
|
||||||
// set extra data on the reader
|
// set extra data on the reader
|
||||||
reader.setMimetype(contentData.getMimetype());
|
reader.setMimetype(contentData.getMimetype());
|
||||||
|
@@ -269,6 +269,21 @@ public class RoutingContentServiceTest extends TestCase
|
|||||||
assertNull("Reader must be null if the content URL is null", reader);
|
assertNull("Reader must be null if the content URL is null", reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetRawReader() throws Exception
|
||||||
|
{
|
||||||
|
ContentReader reader = contentService.getRawReader("blah");
|
||||||
|
assertNotNull("A reader is expected with content URL referencing no content", reader);
|
||||||
|
assertFalse("Reader should not have any content", reader.exists());
|
||||||
|
// Now write something
|
||||||
|
ContentWriter writer = contentService.getWriter(contentNodeRef, ContentModel.PROP_CONTENT, false);
|
||||||
|
writer.putContent("ABC from " + getName());
|
||||||
|
// Try again
|
||||||
|
String contentUrl = writer.getContentUrl();
|
||||||
|
reader = contentService.getRawReader(contentUrl);
|
||||||
|
assertNotNull("Expected reader for live, raw content", reader);
|
||||||
|
assertEquals("Content sizes don't match", writer.getSize(), reader.getSize());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks what happens when the physical content disappears
|
* Checks what happens when the physical content disappears
|
||||||
*/
|
*/
|
||||||
|
@@ -0,0 +1,181 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2005-2007 Alfresco Software Limited.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
* This program 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 General Public License for more details.
|
||||||
|
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
* As a special exception to the terms and conditions of version 2.0 of
|
||||||
|
* the GPL, you may redistribute this Program in connection with Free/Libre
|
||||||
|
* and Open Source Software ("FLOSS") applications as described in Alfresco's
|
||||||
|
* FLOSS exception. You should have recieved a copy of the text describing
|
||||||
|
* the FLOSS exception, and it is also available here:
|
||||||
|
* http://www.alfresco.com/legal/licensing"
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.content;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import net.sf.ehcache.Cache;
|
||||||
|
import net.sf.ehcache.CacheManager;
|
||||||
|
|
||||||
|
import org.alfresco.repo.cache.EhCacheAdapter;
|
||||||
|
import org.alfresco.repo.content.filestore.FileContentStore;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
|
import org.alfresco.util.TempFileProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the routing of URLs based on context is working.
|
||||||
|
*
|
||||||
|
* @see AbstractRoutingContentStore
|
||||||
|
* @since 2.1
|
||||||
|
*
|
||||||
|
* @author Derek Hulley
|
||||||
|
*/
|
||||||
|
public class RoutingContentStoreTest extends TestCase
|
||||||
|
{
|
||||||
|
private ContentStore storeA;
|
||||||
|
private ContentStore storeB;
|
||||||
|
private ContentStore routingStore;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUp() throws Exception
|
||||||
|
{
|
||||||
|
File tempDir = TempFileProvider.getTempDir();
|
||||||
|
// Create a subdirectory for A
|
||||||
|
File storeADir = new File(tempDir, "A");
|
||||||
|
storeA = new FileContentStore(storeADir);
|
||||||
|
// Create a subdirectory for B
|
||||||
|
File storeBDir = new File(tempDir, "B");
|
||||||
|
storeB = new FileContentStore(storeBDir);
|
||||||
|
// Create the routing store
|
||||||
|
routingStore = new RandomRoutingContentStore(storeA, storeB);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSetUp() throws Exception
|
||||||
|
{
|
||||||
|
assertNotNull(storeA);
|
||||||
|
assertNotNull(storeB);
|
||||||
|
assertNotNull(routingStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkForContent(String contentUrl, String content)
|
||||||
|
{
|
||||||
|
for (ContentStore store : new ContentStore[] {storeA, storeB})
|
||||||
|
{
|
||||||
|
// Does the store have it
|
||||||
|
if (store.exists(contentUrl))
|
||||||
|
{
|
||||||
|
// Check it
|
||||||
|
ContentReader reader = store.getReader(contentUrl);
|
||||||
|
String checkContent = reader.getContentString();
|
||||||
|
assertEquals("Content found but is incorrect", content, checkContent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail("Content not found in any of the stores: " + contentUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that requests for missing content URLs are served.
|
||||||
|
*/
|
||||||
|
public void testMissingUrl()
|
||||||
|
{
|
||||||
|
ContentReader reader = routingStore.getReader("blah");
|
||||||
|
assertNotNull("Missing URL should not return null", reader);
|
||||||
|
assertFalse("Empty reader should say content doesn't exist.", reader.exists());
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reader.getContentString();
|
||||||
|
fail("Empty reader cannot return content.");
|
||||||
|
}
|
||||||
|
catch (Throwable e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testHandlingInCache()
|
||||||
|
{
|
||||||
|
for (int i = 0 ; i < 20; i++)
|
||||||
|
{
|
||||||
|
ContentContext contentContext = new ContentContext(null, null);
|
||||||
|
ContentWriter writer = routingStore.getWriter(contentContext);
|
||||||
|
String content = "This was generated by " + this.getClass().getName() + "#" + getName() + " number " + i;
|
||||||
|
writer.putContent(content);
|
||||||
|
// Check that it exists
|
||||||
|
String contentUrl = writer.getContentUrl();
|
||||||
|
checkForContent(contentUrl, content);
|
||||||
|
|
||||||
|
// Now go direct to the routing store and check that it is able to find the appropriate URLs
|
||||||
|
ContentReader reader = routingStore.getReader(contentUrl);
|
||||||
|
assertNotNull("Null reader returned", reader);
|
||||||
|
assertTrue("Reader should be onto live content", reader.exists());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that content URLs are matched to the appropriate stores when in the cache limit.
|
||||||
|
*/
|
||||||
|
public void testReadFindInCache()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test routing store that directs content writes to a randomly-chosen store.
|
||||||
|
* Matching of content URLs back to the stores is handled by the base class.
|
||||||
|
*
|
||||||
|
* @author Derek Hulley
|
||||||
|
*/
|
||||||
|
private static class RandomRoutingContentStore extends AbstractRoutingContentStore
|
||||||
|
{
|
||||||
|
private List<ContentStore> stores;
|
||||||
|
private Random random;
|
||||||
|
|
||||||
|
public RandomRoutingContentStore(ContentStore ... stores)
|
||||||
|
{
|
||||||
|
this.random = new Random();
|
||||||
|
this.stores = new ArrayList<ContentStore>(5);
|
||||||
|
for (ContentStore store : stores)
|
||||||
|
{
|
||||||
|
this.stores.add(store);
|
||||||
|
}
|
||||||
|
Cache ehCache = new Cache("RandomRoutingContentStore", 50, false, true, 0L, 0L);
|
||||||
|
CacheManager cacheManager = new CacheManager();
|
||||||
|
cacheManager.addCache(ehCache);
|
||||||
|
EhCacheAdapter<String, ContentStore> cache = new EhCacheAdapter<String, ContentStore>();
|
||||||
|
cache.setCache(ehCache);
|
||||||
|
super.setStoresCache(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<ContentStore> getAllStores()
|
||||||
|
{
|
||||||
|
return stores;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ContentStore selectWriteStore(ContentContext ctx)
|
||||||
|
{
|
||||||
|
int size = stores.size();
|
||||||
|
int index = (int) Math.floor(random.nextDouble() * (double) size);
|
||||||
|
return stores.get(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -57,12 +57,22 @@ public class FileContentStore extends AbstractContentStore
|
|||||||
private boolean allowRandomAccess;
|
private boolean allowRandomAccess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param rootDirectory the root under which files will be stored. The
|
* @param rootDirectoryStr the root under which files will be stored.
|
||||||
* directory will be created if it does not exist.
|
* The directory will be created if it does not exist.
|
||||||
|
*
|
||||||
|
* @see FileContentStore#FileContentStore(File)
|
||||||
*/
|
*/
|
||||||
public FileContentStore(String rootDirectoryStr)
|
public FileContentStore(String rootDirectoryStr)
|
||||||
{
|
{
|
||||||
rootDirectory = new File(rootDirectoryStr);
|
this(new File(rootDirectoryStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param rootDirectory the root under which files will be stored.
|
||||||
|
* The directory will be created if it does not exist.
|
||||||
|
*/
|
||||||
|
public FileContentStore(File rootDirectory)
|
||||||
|
{
|
||||||
if (!rootDirectory.exists())
|
if (!rootDirectory.exists())
|
||||||
{
|
{
|
||||||
if (!rootDirectory.mkdirs())
|
if (!rootDirectory.mkdirs())
|
||||||
@@ -70,7 +80,7 @@ public class FileContentStore extends AbstractContentStore
|
|||||||
throw new ContentIOException("Failed to create store root: " + rootDirectory, null);
|
throw new ContentIOException("Failed to create store root: " + rootDirectory, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootDirectory = rootDirectory.getAbsoluteFile();
|
this.rootDirectory = rootDirectory.getAbsoluteFile();
|
||||||
rootAbsolutePath = rootDirectory.getAbsolutePath();
|
rootAbsolutePath = rootDirectory.getAbsolutePath();
|
||||||
allowRandomAccess = true;
|
allowRandomAccess = true;
|
||||||
}
|
}
|
||||||
@@ -183,8 +193,7 @@ public class FileContentStore extends AbstractContentStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a file from the given relative URL. The URL must start with
|
* Creates a file from the given relative URL.
|
||||||
* the required {@link FileContentStore#STORE_PROTOCOL protocol prefix}.
|
|
||||||
*
|
*
|
||||||
* @param contentUrl the content URL including the protocol prefix
|
* @param contentUrl the content URL including the protocol prefix
|
||||||
* @return Returns a file representing the URL - the file may or may not
|
* @return Returns a file representing the URL - the file may or may not
|
||||||
@@ -195,7 +204,14 @@ public class FileContentStore extends AbstractContentStore
|
|||||||
private File makeFile(String contentUrl)
|
private File makeFile(String contentUrl)
|
||||||
{
|
{
|
||||||
// take just the part after the protocol
|
// take just the part after the protocol
|
||||||
String relativeUrl = getRelativePart(contentUrl);
|
String relativeUrl = FileContentStore.getRelativePart(contentUrl);
|
||||||
|
if (relativeUrl == null)
|
||||||
|
{
|
||||||
|
throw new ContentIOException(
|
||||||
|
"The content URL is not valid for this store: \n" +
|
||||||
|
" Store: " + this + "\n" +
|
||||||
|
" Content URL: " + contentUrl);
|
||||||
|
}
|
||||||
// get the file
|
// get the file
|
||||||
File file = new File(rootDirectory, relativeUrl);
|
File file = new File(rootDirectory, relativeUrl);
|
||||||
// done
|
// done
|
||||||
@@ -366,4 +382,47 @@ public class FileContentStore extends AbstractContentStore
|
|||||||
}
|
}
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method can be used to ensure that URLs conform to the required format.
|
||||||
|
* If subclasses have to parse the URL, then a call to this may not be required -
|
||||||
|
* provided that the format is checked.
|
||||||
|
* <p>
|
||||||
|
* The protocol part of the URL (including legacy protocols)
|
||||||
|
* is stripped out and just the relative path is returned. If no known prefix is
|
||||||
|
* found, or if the relative part is empty, then <tt>null</tt> is returned.
|
||||||
|
*
|
||||||
|
* @param contentUrl a URL of the content to check
|
||||||
|
* @return Returns the relative part of the URL. If there is no
|
||||||
|
* prefix, then the URL is assumed to be the relative part.
|
||||||
|
*/
|
||||||
|
public static String getRelativePart(String contentUrl)
|
||||||
|
{
|
||||||
|
int index = 0;
|
||||||
|
if (contentUrl.startsWith(STORE_PROTOCOL))
|
||||||
|
{
|
||||||
|
index = 8;
|
||||||
|
}
|
||||||
|
else if (contentUrl.startsWith("file://"))
|
||||||
|
{
|
||||||
|
index = 7;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (contentUrl.length() == 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Invalid FileStore content URL: " + contentUrl);
|
||||||
|
}
|
||||||
|
return contentUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the relative part of the URL
|
||||||
|
String path = contentUrl.substring(index);
|
||||||
|
// more extensive checks can be added in, but it seems overkill
|
||||||
|
if (path.length() == 0)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Invalid FileStore content URL: " + contentUrl);
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -54,6 +54,21 @@ import org.alfresco.service.namespace.QName;
|
|||||||
@PublicService
|
@PublicService
|
||||||
public interface ContentService
|
public interface ContentService
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Fetch content from the low-level stores using a content URL. None of the
|
||||||
|
* metadata associated with the content will be populated. This method should
|
||||||
|
* be used only to stream the binary data out when no other metadata is
|
||||||
|
* required.
|
||||||
|
* <p>
|
||||||
|
* <tt>null</tt> is never returned, but the reader should always be checked for
|
||||||
|
* {@link ContentReader#exists() existence}.
|
||||||
|
*
|
||||||
|
* @param contentUrl a content store URL
|
||||||
|
* @return Returns a reader for the URL that needs to be checked.
|
||||||
|
*/
|
||||||
|
@Auditable(key = Auditable.Key.ARG_0, parameters = {"contentUrl"})
|
||||||
|
public ContentReader getRawReader(String contentUrl);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a reader for the content associated with the given node property.
|
* Gets a reader for the content associated with the given node property.
|
||||||
* <p>
|
* <p>
|
||||||
|
@@ -45,4 +45,14 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean name="objectCache" class="org.alfresco.repo.cache.EhCacheAdapter">
|
||||||
|
<property name="cache">
|
||||||
|
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean" >
|
||||||
|
<property name="cacheName">
|
||||||
|
<value>objectCache</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
</beans>
|
</beans>
|
Reference in New Issue
Block a user