mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +00:00
ACS-406: S3 Connector: support for content direct access urls throws … (#1080)
- added implementation for CachingContentStore and AggregatingContentStore - added tests
This commit is contained in:
@@ -23,8 +23,9 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.content.caching;
|
package org.alfresco.repo.content.caching;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||||
@@ -39,440 +40,451 @@ import org.alfresco.service.cmr.repository.ContentIOException;
|
|||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
import org.alfresco.service.cmr.repository.ContentStreamListener;
|
import org.alfresco.service.cmr.repository.ContentStreamListener;
|
||||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
|
import org.alfresco.service.cmr.repository.DirectAccessUrl;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.BeanNameAware;
|
import org.springframework.beans.factory.BeanNameAware;
|
||||||
import org.springframework.beans.factory.annotation.Required;
|
import org.springframework.beans.factory.annotation.Required;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.context.ApplicationEventPublisherAware;
|
import org.springframework.context.ApplicationEventPublisherAware;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of ContentStore that wraps any other ContentStore (the backing store)
|
* Implementation of ContentStore that wraps any other ContentStore (the backing store)
|
||||||
* transparently providing caching of content in that backing store.
|
* transparently providing caching of content in that backing store.
|
||||||
* <p>
|
* <p>
|
||||||
* CachingContentStore should only be used to wrap content stores that are significantly
|
* CachingContentStore should only be used to wrap content stores that are significantly
|
||||||
* slower that FileContentStore - otherwise performance may actually degrade from its use.
|
* slower that FileContentStore - otherwise performance may actually degrade from its use.
|
||||||
* <p>
|
* <p>
|
||||||
* It is important that cacheOnInbound is set to true for exceptionally slow backing stores.
|
* It is important that cacheOnInbound is set to true for exceptionally slow backing stores.
|
||||||
* <p>
|
* <p>
|
||||||
* This store handles the {@link FileContentStore#SPOOF_PROTOCOL} and can be used to wrap stores
|
* This store handles the {@link FileContentStore#SPOOF_PROTOCOL} and can be used to wrap stores
|
||||||
* that do not handle the protocol out of the box e.g. the S3 connector's store.
|
* that do not handle the protocol out of the box e.g. the S3 connector's store.
|
||||||
*
|
*
|
||||||
* @author Matt Ward
|
* @author Matt Ward
|
||||||
*/
|
*/
|
||||||
public class CachingContentStore implements ContentStore, ApplicationEventPublisherAware, BeanNameAware
|
public class CachingContentStore implements ContentStore, ApplicationEventPublisherAware, BeanNameAware
|
||||||
{
|
{
|
||||||
private final static Log log = LogFactory.getLog(CachingContentStore.class);
|
private final static Log log = LogFactory.getLog(CachingContentStore.class);
|
||||||
// NUM_LOCKS absolutely must be a power of 2 for the use of locks to be evenly balanced
|
// NUM_LOCKS absolutely must be a power of 2 for the use of locks to be evenly balanced
|
||||||
private final static int numLocks = 256;
|
private final static int numLocks = 256;
|
||||||
private final static ReentrantReadWriteLock[] locks;
|
private final static ReentrantReadWriteLock[] locks;
|
||||||
private ContentStore backingStore;
|
private ContentStore backingStore;
|
||||||
private ContentCache cache;
|
private ContentCache cache;
|
||||||
private QuotaManagerStrategy quota = new UnlimitedQuotaStrategy();
|
private QuotaManagerStrategy quota = new UnlimitedQuotaStrategy();
|
||||||
private boolean cacheOnInbound;
|
private boolean cacheOnInbound;
|
||||||
private int maxCacheTries = 2;
|
private int maxCacheTries = 2;
|
||||||
private ApplicationEventPublisher eventPublisher;
|
private ApplicationEventPublisher eventPublisher;
|
||||||
private String beanName;
|
private String beanName;
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
locks = new ReentrantReadWriteLock[numLocks];
|
locks = new ReentrantReadWriteLock[numLocks];
|
||||||
for (int i = 0; i < numLocks; i++)
|
for (int i = 0; i < numLocks; i++)
|
||||||
{
|
{
|
||||||
locks[i] = new ReentrantReadWriteLock();
|
locks[i] = new ReentrantReadWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CachingContentStore()
|
public CachingContentStore()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public CachingContentStore(ContentStore backingStore, ContentCache cache, boolean cacheOnInbound)
|
public CachingContentStore(ContentStore backingStore, ContentCache cache, boolean cacheOnInbound)
|
||||||
{
|
{
|
||||||
this.backingStore = backingStore;
|
this.backingStore = backingStore;
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.cacheOnInbound = cacheOnInbound;
|
this.cacheOnInbound = cacheOnInbound;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialisation method, should be called once the CachingContentStore has been constructed.
|
* Initialisation method, should be called once the CachingContentStore has been constructed.
|
||||||
*/
|
*/
|
||||||
public void init()
|
public void init()
|
||||||
{
|
{
|
||||||
eventPublisher.publishEvent(new CachingContentStoreCreatedEvent(this));
|
eventPublisher.publishEvent(new CachingContentStoreCreatedEvent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isContentUrlSupported(String contentUrl)
|
public boolean isContentUrlSupported(String contentUrl)
|
||||||
{
|
{
|
||||||
return backingStore.isContentUrlSupported(contentUrl);
|
return backingStore.isContentUrlSupported(contentUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isWriteSupported()
|
public boolean isWriteSupported()
|
||||||
{
|
{
|
||||||
return backingStore.isWriteSupported();
|
return backingStore.isWriteSupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getSpaceFree()
|
public long getSpaceFree()
|
||||||
{
|
{
|
||||||
return backingStore.getSpaceFree();
|
return backingStore.getSpaceFree();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getSpaceTotal()
|
public long getSpaceTotal()
|
||||||
{
|
{
|
||||||
return backingStore.getSpaceTotal();
|
return backingStore.getSpaceTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRootLocation()
|
public String getRootLocation()
|
||||||
{
|
{
|
||||||
return backingStore.getRootLocation();
|
return backingStore.getRootLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
* <p>
|
* <p>
|
||||||
* For {@link #SPOOF_PROTOCOL spoofed} URLs, the URL always exists.
|
* For {@link #SPOOF_PROTOCOL spoofed} URLs, the URL always exists.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean exists(String contentUrl)
|
public boolean exists(String contentUrl)
|
||||||
{
|
{
|
||||||
if (contentUrl.startsWith(FileContentStore.SPOOF_PROTOCOL))
|
if (contentUrl.startsWith(FileContentStore.SPOOF_PROTOCOL))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return backingStore.exists(contentUrl);
|
return backingStore.exists(contentUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
* <p>
|
* <p>
|
||||||
* This store handles the {@link FileContentStore#SPOOF_PROTOCOL} so that underlying stores do not need
|
* This store handles the {@link FileContentStore#SPOOF_PROTOCOL} so that underlying stores do not need
|
||||||
* to implement anything <a href="https://issues.alfresco.com/jira/browse/ACE-4516">related to spoofing</a>.
|
* to implement anything <a href="https://issues.alfresco.com/jira/browse/ACE-4516">related to spoofing</a>.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ContentReader getReader(String contentUrl)
|
public ContentReader getReader(String contentUrl)
|
||||||
{
|
{
|
||||||
// Handle the spoofed URL
|
// Handle the spoofed URL
|
||||||
if (contentUrl.startsWith(FileContentStore.SPOOF_PROTOCOL))
|
if (contentUrl.startsWith(FileContentStore.SPOOF_PROTOCOL))
|
||||||
{
|
{
|
||||||
return new SpoofedTextContentReader(contentUrl);
|
return new SpoofedTextContentReader(contentUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use pool of locks - which one is determined by a hash of the URL.
|
// Use pool of locks - which one is determined by a hash of the URL.
|
||||||
// This will stop the content from being read/cached multiple times from the backing store
|
// This will stop the content from being read/cached multiple times from the backing store
|
||||||
// when it should only be read once - cached versions should be returned after that.
|
// when it should only be read once - cached versions should be returned after that.
|
||||||
ReadLock readLock = readWriteLock(contentUrl).readLock();
|
ReadLock readLock = readWriteLock(contentUrl).readLock();
|
||||||
readLock.lock();
|
readLock.lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (cache.contains(contentUrl))
|
if (cache.contains(contentUrl))
|
||||||
{
|
{
|
||||||
return cache.getReader(contentUrl);
|
return cache.getReader(contentUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(CacheMissException e)
|
catch(CacheMissException e)
|
||||||
{
|
{
|
||||||
// Fall through to cacheAndRead(url);
|
// Fall through to cacheAndRead(url);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
readLock.unlock();
|
readLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
return cacheAndRead(contentUrl);
|
return cacheAndRead(contentUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private ContentReader cacheAndRead(String url)
|
private ContentReader cacheAndRead(String url)
|
||||||
{
|
{
|
||||||
WriteLock writeLock = readWriteLock(url).writeLock();
|
WriteLock writeLock = readWriteLock(url).writeLock();
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < maxCacheTries; i++)
|
for (int i = 0; i < maxCacheTries; i++)
|
||||||
{
|
{
|
||||||
ContentReader backingStoreReader = backingStore.getReader(url);
|
ContentReader backingStoreReader = backingStore.getReader(url);
|
||||||
long contentSize = backingStoreReader.getSize();
|
long contentSize = backingStoreReader.getSize();
|
||||||
|
|
||||||
if (!quota.beforeWritingCacheFile(contentSize))
|
if (!quota.beforeWritingCacheFile(contentSize))
|
||||||
{
|
{
|
||||||
return backingStoreReader;
|
return backingStoreReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentReader reader = attemptCacheAndRead(url, backingStoreReader);
|
ContentReader reader = attemptCacheAndRead(url, backingStoreReader);
|
||||||
|
|
||||||
if (reader != null)
|
if (reader != null)
|
||||||
{
|
{
|
||||||
boolean keepCacheFile = quota.afterWritingCacheFile(contentSize);
|
boolean keepCacheFile = quota.afterWritingCacheFile(contentSize);
|
||||||
if (keepCacheFile)
|
if (keepCacheFile)
|
||||||
{
|
{
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Quota strategy has requested cache file not to be kept.
|
// Quota strategy has requested cache file not to be kept.
|
||||||
cache.deleteFile(url);
|
cache.deleteFile(url);
|
||||||
cache.remove(url);
|
cache.remove(url);
|
||||||
return backingStore.getReader(url);
|
return backingStore.getReader(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Have tried multiple times to cache the item and read it back from the cache
|
// Have tried multiple times to cache the item and read it back from the cache
|
||||||
// but there is a recurring problem - give up and return the item from the backing store.
|
// but there is a recurring problem - give up and return the item from the backing store.
|
||||||
if (log.isWarnEnabled())
|
if (log.isWarnEnabled())
|
||||||
{
|
{
|
||||||
log.warn("Attempted " + maxCacheTries + " times to cache content item and failed - "
|
log.warn("Attempted " + maxCacheTries + " times to cache content item and failed - "
|
||||||
+ "returning reader from backing store instead [" +
|
+ "returning reader from backing store instead [" +
|
||||||
"backingStore=" + backingStore +
|
"backingStore=" + backingStore +
|
||||||
", url=" + url +
|
", url=" + url +
|
||||||
"]");
|
"]");
|
||||||
}
|
}
|
||||||
return backingStore.getReader(url);
|
return backingStore.getReader(url);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to read content into a cached file and return a reader onto it. If the content is
|
* Attempt to read content into a cached file and return a reader onto it. If the content is
|
||||||
* already in the cache (possibly due to a race condition between the read/write locks) then
|
* already in the cache (possibly due to a race condition between the read/write locks) then
|
||||||
* a reader onto that content is returned.
|
* a reader onto that content is returned.
|
||||||
* <p>
|
* <p>
|
||||||
* If it is not possible to cache the content and/or get a reader onto the cached content then
|
* If it is not possible to cache the content and/or get a reader onto the cached content then
|
||||||
* <code>null</code> is returned and the method ensure that the URL is not stored in the cache.
|
* <code>null</code> is returned and the method ensure that the URL is not stored in the cache.
|
||||||
*
|
*
|
||||||
* @param url URL to cache.
|
* @param url URL to cache.
|
||||||
* @return A reader onto the cached content file or null if unable to provide one.
|
* @return A reader onto the cached content file or null if unable to provide one.
|
||||||
*/
|
*/
|
||||||
private ContentReader attemptCacheAndRead(String url, ContentReader backingStoreReader)
|
private ContentReader attemptCacheAndRead(String url, ContentReader backingStoreReader)
|
||||||
{
|
{
|
||||||
ContentReader reader = null;
|
ContentReader reader = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!cache.contains(url))
|
if (!cache.contains(url))
|
||||||
{
|
{
|
||||||
if (cache.put(url, backingStoreReader))
|
if (cache.put(url, backingStoreReader))
|
||||||
{
|
{
|
||||||
reader = cache.getReader(url);
|
reader = cache.getReader(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader = cache.getReader(url);
|
reader = cache.getReader(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(CacheMissException e)
|
catch(CacheMissException e)
|
||||||
{
|
{
|
||||||
cache.remove(url);
|
cache.remove(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ContentWriter getWriter(final ContentContext context)
|
public ContentWriter getWriter(final ContentContext context)
|
||||||
{
|
{
|
||||||
if (cacheOnInbound)
|
if (cacheOnInbound)
|
||||||
{
|
{
|
||||||
final ContentWriter bsWriter = backingStore.getWriter(context);
|
final ContentWriter bsWriter = backingStore.getWriter(context);
|
||||||
|
|
||||||
if (!quota.beforeWritingCacheFile(0))
|
if (!quota.beforeWritingCacheFile(0))
|
||||||
{
|
{
|
||||||
return bsWriter;
|
return bsWriter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writing will be performed straight to the cache.
|
// Writing will be performed straight to the cache.
|
||||||
final String url = bsWriter.getContentUrl();
|
final String url = bsWriter.getContentUrl();
|
||||||
final BackingStoreAwareCacheWriter cacheWriter = new BackingStoreAwareCacheWriter(cache.getWriter(url), bsWriter);
|
final BackingStoreAwareCacheWriter cacheWriter = new BackingStoreAwareCacheWriter(cache.getWriter(url), bsWriter);
|
||||||
|
|
||||||
// When finished writing perform these actions.
|
// When finished writing perform these actions.
|
||||||
cacheWriter.addListener(new ContentStreamListener()
|
cacheWriter.addListener(new ContentStreamListener()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void contentStreamClosed() throws ContentIOException
|
public void contentStreamClosed() throws ContentIOException
|
||||||
{
|
{
|
||||||
// Finished writing to the cache, so copy to the backing store -
|
// 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.
|
// ensuring that the encoding attributes are set to the same as for the cache writer.
|
||||||
bsWriter.setEncoding(cacheWriter.getEncoding());
|
bsWriter.setEncoding(cacheWriter.getEncoding());
|
||||||
bsWriter.setLocale(cacheWriter.getLocale());
|
bsWriter.setLocale(cacheWriter.getLocale());
|
||||||
bsWriter.setMimetype(cacheWriter.getMimetype());
|
bsWriter.setMimetype(cacheWriter.getMimetype());
|
||||||
bsWriter.putContent(cacheWriter.getReader());
|
bsWriter.putContent(cacheWriter.getReader());
|
||||||
boolean contentUrlChanged = !url.equals(bsWriter.getContentUrl());
|
boolean contentUrlChanged = !url.equals(bsWriter.getContentUrl());
|
||||||
|
|
||||||
// MNT-11758 fix, re-cache files for which content url has changed after write to backing store (e.g. XAM, Centera)
|
// MNT-11758 fix, re-cache files for which content url has changed after write to backing store (e.g. XAM, Centera)
|
||||||
if (!quota.afterWritingCacheFile(cacheWriter.getSize()) || contentUrlChanged)
|
if (!quota.afterWritingCacheFile(cacheWriter.getSize()) || contentUrlChanged)
|
||||||
{
|
{
|
||||||
if (contentUrlChanged)
|
if (contentUrlChanged)
|
||||||
{
|
{
|
||||||
// MNT-11758 fix, cache file with new and correct contentUrl after write operation to backing store completed
|
// MNT-11758 fix, cache file with new and correct contentUrl after write operation to backing store completed
|
||||||
cache.put(bsWriter.getContentUrl(), cacheWriter.getReader());
|
cache.put(bsWriter.getContentUrl(), cacheWriter.getReader());
|
||||||
}
|
}
|
||||||
// Quota manager has requested that the new cache file is not kept.
|
// Quota manager has requested that the new cache file is not kept.
|
||||||
cache.deleteFile(url);
|
cache.deleteFile(url);
|
||||||
cache.remove(url);
|
cache.remove(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return cacheWriter;
|
return cacheWriter;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No need to invalidate the cache for this content URL, since a content URL
|
// No need to invalidate the cache for this content URL, since a content URL
|
||||||
// is only ever written to once.
|
// is only ever written to once.
|
||||||
return backingStore.getWriter(context);
|
return backingStore.getWriter(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean delete(String contentUrl)
|
public boolean delete(String contentUrl)
|
||||||
{
|
{
|
||||||
if (contentUrl.startsWith(FileContentStore.SPOOF_PROTOCOL))
|
if (contentUrl.startsWith(FileContentStore.SPOOF_PROTOCOL))
|
||||||
{
|
{
|
||||||
// This is not a failure but the content can never actually be deleted
|
// This is not a failure but the content can never actually be deleted
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReentrantReadWriteLock readWriteLock = readWriteLock(contentUrl);
|
ReentrantReadWriteLock readWriteLock = readWriteLock(contentUrl);
|
||||||
ReadLock readLock = readWriteLock.readLock();
|
ReadLock readLock = readWriteLock.readLock();
|
||||||
readLock.lock();
|
readLock.lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!cache.contains(contentUrl))
|
if (!cache.contains(contentUrl))
|
||||||
{
|
{
|
||||||
// The item isn't in the cache, so simply delete from the backing store
|
// The item isn't in the cache, so simply delete from the backing store
|
||||||
return backingStore.delete(contentUrl);
|
return backingStore.delete(contentUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
readLock.unlock();
|
readLock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLock writeLock = readWriteLock.writeLock();
|
WriteLock writeLock = readWriteLock.writeLock();
|
||||||
writeLock.lock();
|
writeLock.lock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Double check the content still exists in the cache
|
// Double check the content still exists in the cache
|
||||||
if (cache.contains(contentUrl))
|
if (cache.contains(contentUrl))
|
||||||
{
|
{
|
||||||
// The item is in the cache, so remove.
|
// The item is in the cache, so remove.
|
||||||
cache.remove(contentUrl);
|
cache.remove(contentUrl);
|
||||||
|
|
||||||
}
|
}
|
||||||
// Whether the item was in the cache or not, it must still be deleted from the backing store.
|
// Whether the item was in the cache or not, it must still be deleted from the backing store.
|
||||||
return backingStore.delete(contentUrl);
|
return backingStore.delete(contentUrl);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
writeLock.unlock();
|
writeLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a ReentrantReadWriteLock for a given URL. The lock is from a pool rather than
|
* Get a ReentrantReadWriteLock for a given URL. The lock is from a pool rather than
|
||||||
* per URL, so some contention is expected.
|
* per URL, so some contention is expected.
|
||||||
*
|
*
|
||||||
* @param url String
|
* @param url String
|
||||||
* @return ReentrantReadWriteLock
|
* @return ReentrantReadWriteLock
|
||||||
*/
|
*/
|
||||||
public ReentrantReadWriteLock readWriteLock(String url)
|
public ReentrantReadWriteLock readWriteLock(String url)
|
||||||
{
|
{
|
||||||
return locks[lockIndex(url)];
|
return locks[lockIndex(url)];
|
||||||
}
|
}
|
||||||
|
|
||||||
private int lockIndex(String url)
|
private int lockIndex(String url)
|
||||||
{
|
{
|
||||||
return url.hashCode() & (numLocks - 1);
|
return url.hashCode() & (numLocks - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Required
|
@Required
|
||||||
public void setBackingStore(ContentStore backingStore)
|
public void setBackingStore(ContentStore backingStore)
|
||||||
{
|
{
|
||||||
this.backingStore = backingStore;
|
this.backingStore = backingStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBackingStoreType()
|
public String getBackingStoreType()
|
||||||
{
|
{
|
||||||
return backingStore.getClass().getName();
|
return backingStore.getClass().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBackingStoreDescription()
|
public String getBackingStoreDescription()
|
||||||
{
|
{
|
||||||
return backingStore.toString();
|
return backingStore.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Required
|
@Required
|
||||||
public void setCache(ContentCache cache)
|
public void setCache(ContentCache cache)
|
||||||
{
|
{
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentCache getCache()
|
public ContentCache getCache()
|
||||||
{
|
{
|
||||||
return this.cache;
|
return this.cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCacheOnInbound(boolean cacheOnInbound)
|
public void setCacheOnInbound(boolean cacheOnInbound)
|
||||||
{
|
{
|
||||||
this.cacheOnInbound = cacheOnInbound;
|
this.cacheOnInbound = cacheOnInbound;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCacheOnInbound()
|
public boolean isCacheOnInbound()
|
||||||
{
|
{
|
||||||
return this.cacheOnInbound;
|
return this.cacheOnInbound;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxCacheTries()
|
public int getMaxCacheTries()
|
||||||
{
|
{
|
||||||
return this.maxCacheTries;
|
return this.maxCacheTries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMaxCacheTries(int maxCacheTries)
|
public void setMaxCacheTries(int maxCacheTries)
|
||||||
{
|
{
|
||||||
this.maxCacheTries = maxCacheTries;
|
this.maxCacheTries = maxCacheTries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the QuotaManagerStrategy that will be used.
|
* Sets the QuotaManagerStrategy that will be used.
|
||||||
*
|
*
|
||||||
* @param quota QuotaManagerStrategy
|
* @param quota QuotaManagerStrategy
|
||||||
*/
|
*/
|
||||||
@Required
|
@Required
|
||||||
public void setQuota(QuotaManagerStrategy quota)
|
public void setQuota(QuotaManagerStrategy quota)
|
||||||
{
|
{
|
||||||
this.quota = quota;
|
this.quota = quota;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QuotaManagerStrategy getQuota()
|
public QuotaManagerStrategy getQuota()
|
||||||
{
|
{
|
||||||
return this.quota;
|
return this.quota;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher)
|
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher)
|
||||||
{
|
{
|
||||||
this.eventPublisher = applicationEventPublisher;
|
this.eventPublisher = applicationEventPublisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setBeanName(String name)
|
public void setBeanName(String name)
|
||||||
{
|
{
|
||||||
this.beanName = name;
|
this.beanName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBeanName()
|
public String getBeanName()
|
||||||
{
|
{
|
||||||
return this.beanName;
|
return this.beanName;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public boolean isDirectAccessSupported()
|
||||||
|
{
|
||||||
|
return backingStore.isDirectAccessSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt)
|
||||||
|
{
|
||||||
|
return backingStore.getDirectAccessUrl(contentUrl, expiresAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.content.replication;
|
package org.alfresco.repo.content.replication;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
@@ -39,6 +40,7 @@ import org.alfresco.repo.content.caching.CachingContentStore;
|
|||||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
|
import org.alfresco.service.cmr.repository.DirectAccessUrl;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
@@ -262,4 +264,115 @@ public class AggregatingContentStore extends AbstractContentStore
|
|||||||
}
|
}
|
||||||
return deleted;
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns <tt>true</tt> if at least one store supports direct access
|
||||||
|
*/
|
||||||
|
public boolean isDirectAccessSupported()
|
||||||
|
{
|
||||||
|
// Check the primary store
|
||||||
|
boolean isDirectAccessSupported = primaryStore.isDirectAccessSupported();
|
||||||
|
|
||||||
|
if (!isDirectAccessSupported)
|
||||||
|
{
|
||||||
|
// Direct access is not supported by the primary store so we have to check the
|
||||||
|
// other stores
|
||||||
|
for (ContentStore store : secondaryStores)
|
||||||
|
{
|
||||||
|
|
||||||
|
isDirectAccessSupported = store.isDirectAccessSupported();
|
||||||
|
|
||||||
|
if (isDirectAccessSupported)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isDirectAccessSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt)
|
||||||
|
{
|
||||||
|
if (primaryStore == null)
|
||||||
|
{
|
||||||
|
throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a read lock so that we are sure that no replication is underway
|
||||||
|
readLock.lock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Keep track of the unsupported state of the content URL - it might be a rubbish URL
|
||||||
|
boolean contentUrlSupported = true;
|
||||||
|
boolean directAccessUrlSupported = true;
|
||||||
|
|
||||||
|
DirectAccessUrl directAccessUrl = null;
|
||||||
|
|
||||||
|
// Check the primary store
|
||||||
|
try
|
||||||
|
{
|
||||||
|
directAccessUrl = primaryStore.getDirectAccessUrl(contentUrl, expiresAt);
|
||||||
|
}
|
||||||
|
catch (UnsupportedOperationException e)
|
||||||
|
{
|
||||||
|
// The store does not support direct access URL
|
||||||
|
directAccessUrlSupported = false;
|
||||||
|
}
|
||||||
|
catch (UnsupportedContentUrlException e)
|
||||||
|
{
|
||||||
|
// The store can't handle the content URL
|
||||||
|
contentUrlSupported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directAccessUrl != null)
|
||||||
|
{
|
||||||
|
return directAccessUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the content is not in the primary store so we have to go looking for it
|
||||||
|
for (ContentStore store : secondaryStores)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
directAccessUrl = store.getDirectAccessUrl(contentUrl, expiresAt);
|
||||||
|
}
|
||||||
|
catch (UnsupportedOperationException e)
|
||||||
|
{
|
||||||
|
// The store does not support direct access URL
|
||||||
|
directAccessUrlSupported = false;
|
||||||
|
}
|
||||||
|
catch (UnsupportedContentUrlException e)
|
||||||
|
{
|
||||||
|
// The store can't handle the content URL
|
||||||
|
contentUrlSupported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directAccessUrl != null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directAccessUrl == null)
|
||||||
|
{
|
||||||
|
if (!directAccessUrlSupported)
|
||||||
|
{
|
||||||
|
// The direct access URL was not supported
|
||||||
|
throw new UnsupportedOperationException("Retrieving direct access URLs is not supported by this content store.");
|
||||||
|
}
|
||||||
|
else if (!contentUrlSupported)
|
||||||
|
{
|
||||||
|
// The content URL was not supported
|
||||||
|
throw new UnsupportedContentUrlException(this, contentUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return directAccessUrl;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
readLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -23,8 +23,8 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.content.replication;
|
package org.alfresco.repo.content.replication;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -32,136 +32,280 @@ import java.util.List;
|
|||||||
import org.alfresco.repo.content.AbstractWritableContentStoreTest;
|
import org.alfresco.repo.content.AbstractWritableContentStoreTest;
|
||||||
import org.alfresco.repo.content.ContentContext;
|
import org.alfresco.repo.content.ContentContext;
|
||||||
import org.alfresco.repo.content.ContentStore;
|
import org.alfresco.repo.content.ContentStore;
|
||||||
|
import org.alfresco.repo.content.UnsupportedContentUrlException;
|
||||||
import org.alfresco.repo.content.filestore.FileContentStore;
|
import org.alfresco.repo.content.filestore.FileContentStore;
|
||||||
import org.alfresco.service.cmr.repository.ContentReader;
|
import org.alfresco.service.cmr.repository.ContentReader;
|
||||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||||
|
import org.alfresco.service.cmr.repository.DirectAccessUrl;
|
||||||
import org.alfresco.test_category.OwnJVMTestsCategory;
|
import org.alfresco.test_category.OwnJVMTestsCategory;
|
||||||
import org.alfresco.util.GUID;
|
import org.alfresco.util.GUID;
|
||||||
import org.alfresco.util.TempFileProvider;
|
import org.alfresco.util.TempFileProvider;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
import org.junit.experimental.categories.Category;
|
import org.junit.experimental.categories.Category;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.MockitoJUnit;
|
||||||
|
import org.mockito.junit.MockitoRule;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
/**
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
* Tests read and write functionality for the aggregating store.
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
* <p>
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
*
|
import static org.mockito.Mockito.verify;
|
||||||
* @see org.alfresco.repo.content.replication.AggregatingContentStore
|
import static org.mockito.Mockito.when;
|
||||||
*
|
|
||||||
* @author Derek Hulley
|
/**
|
||||||
* @author Mark Rogers
|
* Tests read and write functionality for the aggregating store.
|
||||||
*/
|
* <p>
|
||||||
@Category(OwnJVMTestsCategory.class)
|
*
|
||||||
public class AggregatingContentStoreTest extends AbstractWritableContentStoreTest
|
* @see org.alfresco.repo.content.replication.AggregatingContentStore
|
||||||
{
|
*
|
||||||
private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency";
|
* @author Derek Hulley
|
||||||
|
* @author Mark Rogers
|
||||||
private AggregatingContentStore aggregatingStore;
|
*/
|
||||||
private ContentStore primaryStore;
|
@Category(OwnJVMTestsCategory.class)
|
||||||
private List<ContentStore> secondaryStores;
|
public class AggregatingContentStoreTest extends AbstractWritableContentStoreTest
|
||||||
|
{
|
||||||
@Before
|
private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency";
|
||||||
public void before() throws Exception
|
|
||||||
{
|
private AggregatingContentStore aggregatingStore;
|
||||||
File tempDir = TempFileProvider.getTempDir();
|
private ContentStore primaryStore;
|
||||||
// create a primary file store
|
private List<ContentStore> secondaryStores;
|
||||||
String storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate();
|
|
||||||
primaryStore = new FileContentStore(ctx, storeDir);
|
@Mock
|
||||||
// create some secondary file stores
|
ContentStore primaryStoreMock;
|
||||||
secondaryStores = new ArrayList<ContentStore>(3);
|
@Mock
|
||||||
for (int i = 0; i < 4; i++)
|
ContentStore secondaryStoreMock;
|
||||||
{
|
@Mock
|
||||||
storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate();
|
AggregatingContentStore aggregatingContentStoreMock;
|
||||||
FileContentStore store = new FileContentStore(ctx, storeDir);
|
|
||||||
secondaryStores.add(store);
|
@Rule
|
||||||
}
|
public MockitoRule rule = MockitoJUnit.rule();
|
||||||
// Create the aggregating store
|
|
||||||
aggregatingStore = new AggregatingContentStore();
|
@Before
|
||||||
aggregatingStore.setPrimaryStore(primaryStore);
|
public void before() throws Exception
|
||||||
aggregatingStore.setSecondaryStores(secondaryStores);
|
{
|
||||||
}
|
File tempDir = TempFileProvider.getTempDir();
|
||||||
|
// create a primary file store
|
||||||
@Override
|
String storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate();
|
||||||
public ContentStore getStore()
|
primaryStore = new FileContentStore(ctx, storeDir);
|
||||||
{
|
// create some secondary file stores
|
||||||
return aggregatingStore;
|
secondaryStores = new ArrayList<ContentStore>(3);
|
||||||
}
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
/**
|
storeDir = tempDir.getAbsolutePath() + File.separatorChar + GUID.generate();
|
||||||
* Get a writer into the store. This test class assumes that the store is writable and
|
FileContentStore store = new FileContentStore(ctx, storeDir);
|
||||||
* that it therefore supports the ability to write content.
|
secondaryStores.add(store);
|
||||||
*
|
}
|
||||||
* @return
|
// Create the aggregating store
|
||||||
* Returns a writer for new content
|
aggregatingStore = new AggregatingContentStore();
|
||||||
*/
|
aggregatingStore.setPrimaryStore(primaryStore);
|
||||||
protected ContentWriter getWriter()
|
aggregatingStore.setSecondaryStores(secondaryStores);
|
||||||
{
|
}
|
||||||
ContentStore store = getStore();
|
|
||||||
return store.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
|
@Override
|
||||||
}
|
public ContentStore getStore()
|
||||||
|
{
|
||||||
|
return aggregatingStore;
|
||||||
/**
|
}
|
||||||
* {@inheritDoc}
|
|
||||||
* <p>
|
/**
|
||||||
* This implementation creates some content in the store and returns the new content URL.
|
* Get a writer into the store. This test class assumes that the store is writable and
|
||||||
*/
|
* that it therefore supports the ability to write content.
|
||||||
protected String getExistingContentUrl()
|
*
|
||||||
{
|
* @return
|
||||||
ContentWriter writer = getWriter();
|
* Returns a writer for new content
|
||||||
writer.putContent("Content for getExistingContentUrl");
|
*/
|
||||||
return writer.getContentUrl();
|
protected ContentWriter getWriter()
|
||||||
}
|
{
|
||||||
|
ContentStore store = getStore();
|
||||||
public void testAddContent() throws Exception
|
return store.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
|
||||||
{
|
}
|
||||||
ContentWriter writer = getWriter();
|
|
||||||
writer.putContent(SOME_CONTENT);
|
|
||||||
String contentUrl = writer.getContentUrl();
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
checkForUrl(contentUrl, true);
|
* <p>
|
||||||
}
|
* This implementation creates some content in the store and returns the new content URL.
|
||||||
|
*/
|
||||||
/**
|
protected String getExistingContentUrl()
|
||||||
* Checks that the url is present in each of the stores
|
{
|
||||||
*
|
ContentWriter writer = getWriter();
|
||||||
* @param contentUrl String
|
writer.putContent("Content for getExistingContentUrl");
|
||||||
* @param mustExist true if the content must exist, false if it must <b>not</b> exist
|
return writer.getContentUrl();
|
||||||
*/
|
}
|
||||||
private void checkForUrl(String contentUrl, boolean mustExist)
|
|
||||||
|
public void testAddContent() throws Exception
|
||||||
|
{
|
||||||
|
ContentWriter writer = getWriter();
|
||||||
|
writer.putContent(SOME_CONTENT);
|
||||||
|
String contentUrl = writer.getContentUrl();
|
||||||
|
|
||||||
|
checkForUrl(contentUrl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the url is present in each of the stores
|
||||||
|
*
|
||||||
|
* @param contentUrl String
|
||||||
|
* @param mustExist true if the content must exist, false if it must <b>not</b> exist
|
||||||
|
*/
|
||||||
|
private void checkForUrl(String contentUrl, boolean mustExist)
|
||||||
{
|
{
|
||||||
ContentReader reader = getReader(contentUrl);
|
ContentReader reader = getReader(contentUrl);
|
||||||
assertEquals("Reader state differs from expected: " + reader, mustExist, reader.exists());
|
assertEquals("Reader state differs from expected: " + reader, mustExist, reader.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDelete() throws Exception
|
public void testDelete() throws Exception
|
||||||
{
|
{
|
||||||
|
|
||||||
// write some content
|
// write some content
|
||||||
ContentWriter writer = getWriter();
|
ContentWriter writer = getWriter();
|
||||||
writer.putContent(SOME_CONTENT);
|
writer.putContent(SOME_CONTENT);
|
||||||
String contentUrl = writer.getContentUrl();
|
String contentUrl = writer.getContentUrl();
|
||||||
|
|
||||||
ContentReader reader = primaryStore.getReader(contentUrl);
|
ContentReader reader = primaryStore.getReader(contentUrl);
|
||||||
assertTrue("Content was not in the primary store", reader.exists());
|
assertTrue("Content was not in the primary store", reader.exists());
|
||||||
assertEquals("The content was incorrect", SOME_CONTENT, reader.getContentString());
|
assertEquals("The content was incorrect", SOME_CONTENT, reader.getContentString());
|
||||||
|
|
||||||
getStore().delete(contentUrl);
|
getStore().delete(contentUrl);
|
||||||
checkForUrl(contentUrl, false);
|
checkForUrl(contentUrl, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadFromSecondaryStore()
|
public void testReadFromSecondaryStore()
|
||||||
{
|
{
|
||||||
// pick a secondary store and write some content to it
|
// pick a secondary store and write some content to it
|
||||||
ContentStore secondaryStore = secondaryStores.get(2);
|
ContentStore secondaryStore = secondaryStores.get(2);
|
||||||
ContentWriter writer = secondaryStore.getWriter(ContentContext.NULL_CONTEXT);
|
ContentWriter writer = secondaryStore.getWriter(ContentContext.NULL_CONTEXT);
|
||||||
writer.putContent(SOME_CONTENT);
|
writer.putContent(SOME_CONTENT);
|
||||||
String contentUrl = writer.getContentUrl();
|
String contentUrl = writer.getContentUrl();
|
||||||
|
|
||||||
checkForUrl(contentUrl, true);
|
checkForUrl(contentUrl, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
}
|
public void testIsDirectAccessSupported()
|
||||||
|
{
|
||||||
|
// Create the aggregating store
|
||||||
|
AggregatingContentStore aggStore = new AggregatingContentStore();
|
||||||
|
aggStore.setPrimaryStore(primaryStoreMock);
|
||||||
|
aggStore.setSecondaryStores(List.of(secondaryStoreMock));
|
||||||
|
|
||||||
|
// By default it is unsupported
|
||||||
|
assertFalse(aggStore.isDirectAccessSupported());
|
||||||
|
|
||||||
|
// Supported if at least one store supports direct access
|
||||||
|
{
|
||||||
|
when(primaryStoreMock.isDirectAccessSupported()).thenReturn(false);
|
||||||
|
when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(true);
|
||||||
|
assertTrue(aggStore.isDirectAccessSupported());
|
||||||
|
|
||||||
|
when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true);
|
||||||
|
when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(true);
|
||||||
|
assertTrue(aggStore.isDirectAccessSupported());
|
||||||
|
|
||||||
|
when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true);
|
||||||
|
when(secondaryStoreMock.isDirectAccessSupported()).thenReturn(false);
|
||||||
|
assertTrue(aggStore.isDirectAccessSupported());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDirectAccessUrl()
|
||||||
|
{
|
||||||
|
// Create the aggregating store
|
||||||
|
AggregatingContentStore aggStore = new AggregatingContentStore();
|
||||||
|
aggStore.setPrimaryStore(primaryStoreMock);
|
||||||
|
aggStore.setSecondaryStores(List.of(secondaryStoreMock));
|
||||||
|
|
||||||
|
UnsupportedOperationException unsupportedExc = new UnsupportedOperationException();
|
||||||
|
UnsupportedContentUrlException unsupportedContentUrlExc = new UnsupportedContentUrlException(aggStore, "");
|
||||||
|
|
||||||
|
// By default it is unsupported
|
||||||
|
DirectAccessUrl directAccessUrl = aggStore.getDirectAccessUrl("url", null);
|
||||||
|
assertNull(directAccessUrl);
|
||||||
|
|
||||||
|
// Direct access not supported
|
||||||
|
try
|
||||||
|
{
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
|
||||||
|
aggStore.getDirectAccessUrl("urlDANotSupported", null);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (UnsupportedOperationException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc);
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
|
||||||
|
aggStore.getDirectAccessUrl("urlDANotSupported", null);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (UnsupportedOperationException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc);
|
||||||
|
aggStore.getDirectAccessUrl("urlDANotSupported", null);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (UnsupportedOperationException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content url not supported
|
||||||
|
try
|
||||||
|
{
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc);
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc);
|
||||||
|
aggStore.getDirectAccessUrl("urlNotSupported", null);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (UnsupportedContentUrlException e)
|
||||||
|
{
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl());
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenThrow(unsupportedExc);
|
||||||
|
directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null);
|
||||||
|
assertNotNull(directAccessUrl);
|
||||||
|
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl());
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenThrow(unsupportedContentUrlExc);
|
||||||
|
directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null);
|
||||||
|
assertNotNull(directAccessUrl);
|
||||||
|
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedExc);
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl());
|
||||||
|
directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null);
|
||||||
|
assertNotNull(directAccessUrl);
|
||||||
|
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenThrow(unsupportedContentUrlExc);
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl());
|
||||||
|
directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null);
|
||||||
|
assertNotNull(directAccessUrl);
|
||||||
|
|
||||||
|
when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl());
|
||||||
|
when(secondaryStoreMock.getDirectAccessUrl(eq("urlSecSupported"), any())).thenReturn(new DirectAccessUrl());
|
||||||
|
directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null);
|
||||||
|
assertNotNull(directAccessUrl);
|
||||||
|
directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null);
|
||||||
|
assertNotNull(directAccessUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user