ACS-1604 : Update & tests for AggregatingContentStore (#486)

This commit is contained in:
Denis Ungureanu
2021-05-25 10:01:50 +03:00
committed by Andrea Ligios
parent 0b840643f9
commit 57dbd4d12f
2 changed files with 254 additions and 105 deletions

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.content.replication;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
@@ -66,7 +67,8 @@ import org.apache.commons.logging.LogFactory;
*/
public class AggregatingContentStore extends AbstractContentStore
{
private static Log logger = LogFactory.getLog(AggregatingContentStore.class);
private static final Log logger = LogFactory.getLog(AggregatingContentStore.class);
private static final String REPLICATING_CONTENT_STORE_NOT_INITIALISED = "ReplicatingContentStore not initialised";
private ContentStore primaryStore;
private List<ContentStore> secondaryStores;
@@ -135,7 +137,7 @@ public class AggregatingContentStore extends AbstractContentStore
{
if (primaryStore == null)
{
throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised");
throw new AlfrescoRuntimeException(REPLICATING_CONTENT_STORE_NOT_INITIALISED);
}
// get a read lock so that we are sure that no replication is underway
@@ -170,11 +172,12 @@ public class AggregatingContentStore extends AbstractContentStore
}
}
@Override
public boolean exists(String contentUrl)
{
if (primaryStore == null)
{
throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised");
throw new AlfrescoRuntimeException(REPLICATING_CONTENT_STORE_NOT_INITIALISED);
}
// get a read lock so that we are sure that no replication is underway
@@ -240,6 +243,7 @@ public class AggregatingContentStore extends AbstractContentStore
}
}
@Override
public ContentWriter getWriter(ContentContext ctx)
{
// get the writer
@@ -254,6 +258,7 @@ public class AggregatingContentStore extends AbstractContentStore
*
* @return Returns the value returned by the delete on the primary store.
*/
@Override
public boolean delete(String contentUrl) throws ContentIOException
{
// delete on the primary store
@@ -269,6 +274,7 @@ public class AggregatingContentStore extends AbstractContentStore
/**
* @return Returns <tt>true</tt> if at least one store supports direct access
*/
@Override
public boolean isDirectAccessSupported()
{
// Check the primary store
@@ -293,11 +299,12 @@ public class AggregatingContentStore extends AbstractContentStore
return isDirectAccessSupported;
}
@Override
public DirectAccessUrl getDirectAccessUrl(String contentUrl, Date expiresAt)
{
if (primaryStore == null)
{
throw new AlfrescoRuntimeException("ReplicatingContentStore not initialised");
throw new AlfrescoRuntimeException(REPLICATING_CONTENT_STORE_NOT_INITIALISED);
}
// get a read lock so that we are sure that no replication is underway
@@ -380,25 +387,9 @@ public class AggregatingContentStore extends AbstractContentStore
@Override
public boolean isStorageClassesSupported(Set<String> storageClasses)
{
// Check the primary store
boolean isStorageClassesSupported = primaryStore.isStorageClassesSupported(storageClasses);
if (!isStorageClassesSupported)
{
// Storage class is not supported by the primary store so we have to check the
// other stores
for (ContentStore store : secondaryStores)
{
isStorageClassesSupported = store.isDirectAccessSupported();
if (isStorageClassesSupported)
{
break;
}
}
}
return isStorageClassesSupported;
// We only need to provide info about the primary store,
// because the aggregating CS only allows to be written in the primary
return primaryStore.isStorageClassesSupported(storageClasses);
}
@Override
@@ -408,4 +399,91 @@ public class AggregatingContentStore extends AbstractContentStore
// because the aggregating CS only allows to be written in the primary
return primaryStore.getSupportedStorageClasses();
}
@Override
public void updateStorageClasses(String contentUrl, Set<String> storageClasses, Map<String, Object> parameters)
{
primaryStore.updateStorageClasses(contentUrl, storageClasses, parameters);
}
@Override
public Set<String> findStorageClasses(String contentUrl)
{
if (primaryStore == null)
{
throw new AlfrescoRuntimeException(REPLICATING_CONTENT_STORE_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;
Set<String> storageClasses = null;
// Check the primary store
try
{
storageClasses = primaryStore.findStorageClasses(contentUrl);
}
catch (UnsupportedContentUrlException e)
{
// The store can't handle the content URL
contentUrlSupported = false;
}
if (storageClasses != null)
{
return storageClasses;
}
// the content is not in the primary store so we have to go looking for it
for (ContentStore store : secondaryStores)
{
try
{
storageClasses = store.findStorageClasses(contentUrl);
}
catch (UnsupportedContentUrlException e)
{
// The store can't handle the content URL
contentUrlSupported = false;
}
if (storageClasses != null)
{
break;
}
}
if (storageClasses == null && !contentUrlSupported)
{
// The content URL was not supported
throw new UnsupportedContentUrlException(this, contentUrl);
}
return storageClasses;
}
finally
{
readLock.unlock();
}
}
@Override
public Map<Set<String>, Set<Set<String>>> getStorageClassesTransitions()
{
// We only need to provide info about the primary store,
// because the aggregating CS only allows to be written in the primary
return primaryStore.getStorageClassesTransitions();
}
@Override
public Map<Set<String>, Set<Set<String>>> findStorageClassesTransitions(String contentUrl)
{
// We only need to provide info about the primary store,
// because the aggregating CS only allows to be written in the primary
return primaryStore.findStorageClassesTransitions(contentUrl);
}
}

View File

@@ -25,6 +25,8 @@
*/
package org.alfresco.repo.content.replication;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -32,15 +34,15 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -57,12 +59,11 @@ import org.alfresco.util.GUID;
import org.alfresco.util.TempFileProvider;
import org.alfresco.util.testing.category.NeverRunsTests;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.junit.MockitoJUnitRunner;
/**
* Tests read and write functionality for the aggregating store.
@@ -74,6 +75,7 @@ import org.mockito.junit.MockitoRule;
* @author Mark Rogers
*/
@Category({OwnJVMTestsCategory.class, NeverRunsTests.class})
@RunWith(MockitoJUnitRunner.class)
public class AggregatingContentStoreTest extends AbstractWritableContentStoreTest
{
private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency";
@@ -86,11 +88,8 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
ContentStore primaryStoreMock;
@Mock
ContentStore secondaryStoreMock;
@Mock
AggregatingContentStore aggregatingContentStoreMock;
@Rule
public MockitoRule rule = MockitoJUnit.rule();
AggregatingContentStore aggregatingContentStoreMock;
@Before
public void before() throws Exception
@@ -107,10 +106,15 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
FileContentStore store = new FileContentStore(ctx, storeDir);
secondaryStores.add(store);
}
// Create the aggregating store
// Create the aggregating store for Spring tests
aggregatingStore = new AggregatingContentStore();
aggregatingStore.setPrimaryStore(primaryStore);
aggregatingStore.setSecondaryStores(secondaryStores);
// Create a mocked aggregating store
aggregatingContentStoreMock = new AggregatingContentStore();
aggregatingContentStoreMock.setPrimaryStore(primaryStoreMock);
aggregatingContentStoreMock.setSecondaryStores(List.of(secondaryStoreMock));
}
@Override
@@ -145,6 +149,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
return writer.getContentUrl();
}
@Test
public void testAddContent() throws Exception
{
ContentWriter writer = getWriter();
@@ -166,6 +171,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
assertEquals("Reader state differs from expected: " + reader, mustExist, reader.exists());
}
@Test
public void testDelete() throws Exception
{
@@ -182,6 +188,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
checkForUrl(contentUrl, false);
}
@Test
public void testReadFromSecondaryStore()
{
// pick a secondary store and write some content to it
@@ -194,45 +201,43 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
}
@Test
public void testIsDirectAccessSupported()
public void testDirectAccessUnsupportedByDefault()
{
// Create the aggregating store
AggregatingContentStore aggStore = new AggregatingContentStore();
aggStore.setPrimaryStore(primaryStoreMock);
aggStore.setSecondaryStores(List.of(secondaryStoreMock));
// By default it is unsupported
assertFalse(aggStore.isDirectAccessSupported());
assertFalse(aggregatingContentStoreMock.isDirectAccessSupported());
verify(primaryStoreMock, times(1)).isDirectAccessSupported();
verify(secondaryStoreMock, times(1)).isDirectAccessSupported();
}
// Supported if at least one store supports direct access
@Test
public void testIsDirectAccessSupportedByPrimaryStore()
{
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());
assertTrue(aggregatingContentStoreMock.isDirectAccessSupported());
verify(primaryStoreMock, times(1)).isDirectAccessSupported();
verify(secondaryStoreMock, times(1)).isDirectAccessSupported();
}
@Test
public void testIsDirectAccessSupportedBySecondaryStore()
{
when(primaryStoreMock.isDirectAccessSupported()).thenReturn(true);
assertTrue(aggregatingContentStoreMock.isDirectAccessSupported());
verify(primaryStoreMock, times(1)).isDirectAccessSupported();
verifyNoInteractions(secondaryStoreMock);
}
@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, "");
UnsupportedContentUrlException unsupportedContentUrlExc = new UnsupportedContentUrlException(aggregatingContentStoreMock, "");
// By default it is unsupported
DirectAccessUrl directAccessUrl = aggStore.getDirectAccessUrl("url", null);
DirectAccessUrl directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("url", null);
assertNull(directAccessUrl);
// Direct access not supported
@@ -240,7 +245,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
{
when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
aggStore.getDirectAccessUrl("urlDANotSupported", null);
aggregatingContentStoreMock.getDirectAccessUrl("urlDANotSupported", null);
fail();
}
catch (UnsupportedOperationException e)
@@ -252,7 +257,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
{
when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc);
when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
aggStore.getDirectAccessUrl("urlDANotSupported", null);
aggregatingContentStoreMock.getDirectAccessUrl("urlDANotSupported", null);
fail();
}
catch (UnsupportedOperationException e)
@@ -264,7 +269,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
{
when(primaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedExc);
when(secondaryStoreMock.getDirectAccessUrl(eq("urlDANotSupported"), any())).thenThrow(unsupportedContentUrlExc);
aggStore.getDirectAccessUrl("urlDANotSupported", null);
aggregatingContentStoreMock.getDirectAccessUrl("urlDANotSupported", null);
fail();
}
catch (UnsupportedOperationException e)
@@ -277,7 +282,7 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
{
when(primaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc);
when(secondaryStoreMock.getDirectAccessUrl(eq("urlNotSupported"), any())).thenThrow(unsupportedContentUrlExc);
aggStore.getDirectAccessUrl("urlNotSupported", null);
aggregatingContentStoreMock.getDirectAccessUrl("urlNotSupported", null);
fail();
}
catch (UnsupportedContentUrlException e)
@@ -286,30 +291,28 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
}
when(primaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenReturn(new DirectAccessUrl());
when(secondaryStoreMock.getDirectAccessUrl(eq("urlPriSupported"), any())).thenThrow(unsupportedExc);
directAccessUrl = aggStore.getDirectAccessUrl("urlPriSupported", null);
directAccessUrl = aggregatingContentStoreMock.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);
directAccessUrl = aggregatingContentStoreMock.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);
directAccessUrl = aggregatingContentStoreMock.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);
directAccessUrl = aggregatingContentStoreMock.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);
directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlPriSupported", null);
assertNotNull(directAccessUrl);
directAccessUrl = aggStore.getDirectAccessUrl("urlSecSupported", null);
directAccessUrl = aggregatingContentStoreMock.getDirectAccessUrl("urlSecSupported", null);
assertNotNull(directAccessUrl);
}
@@ -317,46 +320,114 @@ public class AggregatingContentStoreTest extends AbstractWritableContentStoreTes
public void testIsStorageClassesSupported()
{
Set<String> sc = Set.of("a-certain-storage-class");
// Create the aggregating store
AggregatingContentStore aggStore = new AggregatingContentStore();
aggStore.setPrimaryStore(primaryStoreMock);
aggStore.setSecondaryStores(List.of(secondaryStoreMock));
// SC supported by the primary store
when(primaryStoreMock.isStorageClassesSupported(sc)).thenReturn(true);
assertTrue(aggStore.isStorageClassesSupported(sc));
verify(primaryStoreMock, times(1)).isStorageClassesSupported(sc);
verify(secondaryStoreMock, never()).isStorageClassesSupported(sc);
// SC supported by the secondary store
when(primaryStoreMock.isStorageClassesSupported(sc)).thenReturn(false);
when(secondaryStoreMock.isStorageClassesSupported(sc)).thenReturn(true);
assertTrue(aggStore.isStorageClassesSupported(sc));
assertTrue(aggregatingContentStoreMock.isStorageClassesSupported(sc));
verify(primaryStoreMock, times(1)).isStorageClassesSupported(sc);
verify(secondaryStoreMock, times(1)).isStorageClassesSupported(sc);
verifyNoInteractions(secondaryStoreMock);
}
// SC not supported by the stores
@Test
public void testStorageClassesIsNotSupported()
{
Set<String> sc = Set.of("a-certain-storage-class");
when(primaryStoreMock.isStorageClassesSupported(sc)).thenReturn(false);
when(secondaryStoreMock.isStorageClassesSupported(sc)).thenReturn(false);
assertFalse(aggStore.isStorageClassesSupported(sc));
assertFalse(aggregatingContentStoreMock.isStorageClassesSupported(sc));
verify(primaryStoreMock, times(1)).isStorageClassesSupported(sc);
verify(secondaryStoreMock, times(1)).isStorageClassesSupported(sc);
verifyNoInteractions(secondaryStoreMock);
}
@Test
public void testGetSupportedStorageClasses()
{
Set<String> sc = Collections.emptySet();
// Create the aggregating store
AggregatingContentStore aggStore = new AggregatingContentStore();
aggStore.setPrimaryStore(primaryStoreMock);
aggStore.setSecondaryStores(List.of(secondaryStoreMock));
when(primaryStoreMock.getSupportedStorageClasses()).thenReturn(emptySet());
when(primaryStoreMock.getSupportedStorageClasses()).thenReturn(sc);
final Set<String> supportedStorageClasses = aggStore.getSupportedStorageClasses();
assertTrue(supportedStorageClasses.isEmpty());
assertTrue(aggregatingContentStoreMock.getSupportedStorageClasses().isEmpty());
verify(primaryStoreMock, times(1)).getSupportedStorageClasses();
verify(secondaryStoreMock, times(0)).getSupportedStorageClasses();
verifyNoInteractions(secondaryStoreMock);
}
@Test
public void testUpdateStorageClassesForGivenContentUrl()
{
String contentUrl = "contentUrl";
final Set<String> storageClasses = Set.of("a-certain-storage-class");
aggregatingContentStoreMock.updateStorageClasses(contentUrl, storageClasses, null);
verify(primaryStoreMock, times(1)).updateStorageClasses(contentUrl, storageClasses, null);
verifyNoInteractions(secondaryStoreMock);
}
@Test
public void testFindStorageClassesForGivenContentUrlInPrimaryStore()
{
when(primaryStoreMock.findStorageClasses(anyString())).thenReturn(emptySet());
assertTrue(aggregatingContentStoreMock.findStorageClasses("a-contentUrl").isEmpty());
verify(primaryStoreMock, times(1)).findStorageClasses("a-contentUrl");
verifyNoInteractions(secondaryStoreMock);
}
@Test
public void testFindStorageClassesForGivenContentUrlInSecondaryStore()
{
UnsupportedContentUrlException unsupportedContentUrlExc = new UnsupportedContentUrlException(
aggregatingContentStoreMock, "");
when(primaryStoreMock.findStorageClasses(anyString())).thenThrow(unsupportedContentUrlExc);
when(secondaryStoreMock.findStorageClasses(anyString())).thenReturn(emptySet());
assertTrue(aggregatingContentStoreMock.findStorageClasses("a-contentUrl").isEmpty());
verify(primaryStoreMock, times(1)).findStorageClasses("a-contentUrl");
verify(secondaryStoreMock, times(1)).findStorageClasses("a-contentUrl");
}
@Test(expected = UnsupportedContentUrlException.class)
public void testFindStorageClassesForInvalidContentUrl()
{
when(primaryStoreMock.findStorageClasses(anyString()))
.thenThrow(new UnsupportedContentUrlException(aggregatingContentStoreMock, ""));
when(secondaryStoreMock.findStorageClasses(anyString()))
.thenThrow(new UnsupportedContentUrlException(aggregatingContentStoreMock, ""));
aggregatingContentStoreMock.findStorageClasses("a-contentUrl");
verify(primaryStoreMock, times(1)).findStorageClasses("a-contentUrl");
verify(secondaryStoreMock, times(1)).findStorageClasses("a-contentUrl");
}
@Test
public void testGetStorageClassesTransitions()
{
when(primaryStoreMock.getStorageClassesTransitions()).thenReturn(emptyMap());
assertTrue(aggregatingContentStoreMock.getStorageClassesTransitions().isEmpty());
verify(primaryStoreMock, times(1)).getStorageClassesTransitions();
verifyNoInteractions(secondaryStoreMock);
}
@Test
public void testFindStorageClassesTransitionsForGivenContentUrl()
{
when(primaryStoreMock.findStorageClassesTransitions(anyString())).thenReturn(emptyMap());
assertTrue(
aggregatingContentStoreMock.findStorageClassesTransitions("contentUrl").isEmpty());
verify(primaryStoreMock, times(1)).findStorageClassesTransitions("contentUrl");
verifyNoInteractions(secondaryStoreMock);
}
@Test(expected = UnsupportedContentUrlException.class)
public void testFindStorageClassesTransitionsForUnsupportedContentUrl()
{
when(primaryStoreMock.findStorageClassesTransitions(anyString()))
.thenThrow(new UnsupportedContentUrlException(aggregatingContentStoreMock, ""));
aggregatingContentStoreMock.findStorageClassesTransitions("contentUrl");
verify(primaryStoreMock, times(1)).findStorageClassesTransitions("contentUrl");
verifyNoInteractions(secondaryStoreMock);
}
}