/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.content.packager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import mockit.NonStrictExpectations;
import org.apache.log4j.Logger;
import org.dspace.AbstractUnitTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.*;
import org.dspace.content.crosswalk.CrosswalkException;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.*;
import org.dspace.core.*;
import org.dspace.core.factory.CoreServiceFactory;
import org.dspace.core.service.PluginService;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.dspace.handle.factory.HandleServiceFactory;
import org.dspace.handle.service.HandleService;
import org.dspace.services.ConfigurationService;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.workflow.WorkflowException;
import org.junit.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import org.junit.rules.TemporaryFolder;
/**
* Basic integration testing for the AIP Backup and Restore feature
* https://wiki.duraspace.org/display/DSDOC5x/AIP+Backup+and+Restore
*
* @author Tim Donohue
*/
public class ITDSpaceAIP extends AbstractUnitTest
{
/** log4j category */
private static final Logger log = Logger.getLogger(ITDSpaceAIP.class);
protected CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
protected CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
protected ItemService itemService = ContentServiceFactory.getInstance().getItemService();
protected BundleService bundleService = ContentServiceFactory.getInstance().getBundleService();
protected BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
protected WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
protected InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
protected HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
protected PluginService pluginService = CoreServiceFactory.getInstance().getPluginService();
protected ConfigurationService configService = DSpaceServicesFactory.getInstance().getConfigurationService();
protected ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance().getResourcePolicyService();
protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService();
protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService();
/** InfoMap multiple value separator (see saveObjectInfo() and assertObject* methods) **/
private static final String valueseparator = "::";
/** Handles for Test objects initialized in setUpClass() and used in various tests below **/
private static String topCommunityHandle = null;
private static String testCollectionHandle = null;
private static String testItemHandle = null;
private static String testMappedItemHandle = null;
private static String submitterEmail = "aip-test@dspace.org";
private Context context;
/** Create a global temporary upload folder which will be cleaned up automatically by JUnit.
NOTE: As a ClassRule, this temp folder is shared by ALL tests below. **/
@ClassRule
public static final TemporaryFolder uploadTempFolder = new TemporaryFolder();
/** Create another temporary folder for AIPs. As a Rule, this one is *recreated* for each
test, in order to ensure each test is standalone with respect to AIPs. **/
@Rule
public final TemporaryFolder aipTempFolder = new TemporaryFolder();
/**
* This method will be run during class initialization. It will initialize
* shared resources required for all the tests. It is only run once.
*
* Other methods can be annotated with @Before here or in subclasses
* but no execution order is guaranteed
*/
@BeforeClass
public static void setUpClass()
{
try
{
Context context = new Context();
// Create a dummy Community hierarchy to test with
// Turn off authorization temporarily to create some test objects.
context.turnOffAuthorisationSystem();
CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
ItemService itemService = ContentServiceFactory.getInstance().getItemService();
BitstreamService bitstreamService = ContentServiceFactory.getInstance().getBitstreamService();
WorkspaceItemService workspaceItemService = ContentServiceFactory.getInstance().getWorkspaceItemService();
InstallItemService installItemService = ContentServiceFactory.getInstance().getInstallItemService();
log.info("setUpClass() - CREATE TEST HIERARCHY");
// Create a hierachy of sub-Communities and Collections and Items,
// which looks like this:
// "Top Community"
// - "Child Community"
// - "Grandchild Community"
// - "GreatGrandchild Collection"
// - "GreatGrandchild Collection Item #1"
// - "GreatGrandchild Collection Item #2"
// - "Mapped Item" (mapped collection)
// - "Grandchild Collection"
// - "Grandchild Collection Item #1"
// - "Mapped Item" (owning collection)
//
Community topCommunity = communityService.create(null, context);
communityService.addMetadata(context, topCommunity, MetadataSchema.DC_SCHEMA, "title", null, null, "Top Community");
communityService.update(context, topCommunity);
topCommunityHandle = topCommunity.getHandle();
Community child = communityService.createSubcommunity(context, topCommunity);
communityService.addMetadata(context, child, MetadataSchema.DC_SCHEMA, "title", null, null, "Child Community");
communityService.update(context, child);
Community grandchild = communityService.createSubcommunity(context, child);
communityService.addMetadata(context, grandchild, MetadataSchema.DC_SCHEMA, "title", null, null, "Grandchild Community");
communityService.update(context, grandchild);
// Create our primary Test Collection
Collection grandchildCol = collectionService.create(context, child);
collectionService.addMetadata(context, grandchildCol, "dc", "title", null, null, "Grandchild Collection");
collectionService.update(context, grandchildCol);
testCollectionHandle = grandchildCol.getHandle();
// Create an additional Test Collection
Collection greatgrandchildCol = collectionService.create(context, grandchild);
collectionService.addMetadata(context, greatgrandchildCol, "dc", "title", null, null, "GreatGrandchild Collection");
collectionService.update(context, greatgrandchildCol);
EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
EPerson submitter = ePersonService.create(context);
submitter.setEmail(submitterEmail);
ePersonService.update(context, submitter);
context.setCurrentUser(submitter);
// Create our primary Test Item
WorkspaceItem wsItem = workspaceItemService.create(context, grandchildCol, false);
Item item = installItemService.installItem(context, wsItem);
itemService.addMetadata(context, item, "dc", "title", null, null, "Grandchild Collection Item #1");
// For our primary test item, create a Bitstream in the ORIGINAL bundle
File f = new File(testProps.get("test.bitstream").toString());
Bitstream b = itemService.createSingleBitstream(context, new FileInputStream(f), item);
b.setName(context, "Test Bitstream");
bitstreamService.update(context, b);
itemService.update(context, item);
testItemHandle = item.getHandle();
// Create a Mapped Test Item (mapped to multiple collections
WorkspaceItem wsItem2 = workspaceItemService.create(context, grandchildCol, false);
Item item2 = installItemService.installItem(context, wsItem2);
itemService.addMetadata(context, item2, "dc", "title", null, null, "Mapped Item");
itemService.update(context, item2);
collectionService.addItem(context, greatgrandchildCol, item2);
testMappedItemHandle = item2.getHandle();
WorkspaceItem wsItem3 = workspaceItemService.create(context, greatgrandchildCol, false);
Item item3 = installItemService.installItem(context, wsItem3);
itemService.addMetadata(context, item3, "dc", "title", null, null, "GreatGrandchild Collection Item #1");
itemService.update(context, item3);
WorkspaceItem wsItem4 = workspaceItemService.create(context, greatgrandchildCol, false);
Item item4 = installItemService.installItem(context, wsItem4);
itemService.addMetadata(context, item4, "dc", "title", null, null, "GreatGrandchild Collection Item #2");
itemService.update(context, item4);
// Commit these changes to our DB
context.restoreAuthSystemState();
context.complete();
}
catch (AuthorizeException ex)
{
log.error("Authorization Error in setUpClass()", ex);
fail("Authorization Error in setUpClass(): " + ex.getMessage());
}
catch (IOException ex)
{
log.error("IO Error in setUpClass()", ex);
fail("IO Error in setUpClass(): " + ex.getMessage());
}
catch (SQLException ex)
{
log.error("SQL Error in setUpClass()", ex);
fail("SQL Error in setUpClass(): " + ex.getMessage());
}
}
/**
* This method will be run once at the very end
*/
@AfterClass
public static void tearDownClass()
{
try
{
Context context = new Context();
CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
HandleService handleService = HandleServiceFactory.getInstance().getHandleService();
Community topCommunity = (Community) handleService.resolveToObject(context, topCommunityHandle);
// Delete top level test community and test hierarchy under it
if(topCommunity!=null)
{
log.info("tearDownClass() - DESTROY TEST HIERARCHY");
context.turnOffAuthorisationSystem();
communityService.delete(context, topCommunity);
context.restoreAuthSystemState();
context.commit();
}
// Delete the Eperson created to submit test items
EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
EPerson eperson = ePersonService.findByEmail(context, submitterEmail);
if(eperson!=null)
{
log.info("tearDownClass() - DESTROY TEST EPERSON");
context.turnOffAuthorisationSystem();
ePersonService.delete(context, eperson);
context.restoreAuthSystemState();
context.commit();
}
if(context.isValid())
context.abort();
}
catch (Exception ex)
{
log.error("Error in tearDownClass()", ex);
}
}
/**
* Create an initial set of AIPs for the test content generated in setUpClass() above.
*/
@Before
@Override
public void init()
{
// call init() from AbstractUnitTest to initialize testing framework
super.init();
// Override default value of configured temp directory to point at our
// JUnit TemporaryFolder. This ensures Crosswalk classes like RoleCrosswalk
// store their temp files in a place where JUnit can clean them up automatically.
new NonStrictExpectations(configService.getClass()) {{
configService.getProperty("upload.temp.dir"); result = uploadTempFolder.getRoot().getAbsolutePath();
}};
try
{
context = new Context();
context.setCurrentUser(EPersonServiceFactory.getInstance().getEPersonService().findByEmail(context, submitterEmail));
}
catch (SQLException ex)
{
log.error("SQL Error in init()", ex);
fail("SQL Error in init(): " + ex.getMessage());
}
}
@After
@Override
public void destroy() {
context.abort();
super.destroy();
}
/**
* Test restoration from AIP of entire Community Hierarchy
*/
@Test
public void testRestoreCommunityHierarchy() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy you really need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testRestoreCommunityHierarchy() - BEGIN");
// Locate the top level community (from our test data)
Community topCommunity = (Community) handleService.resolveToObject(context, topCommunityHandle);
// Get parent object, so that we can restore to same parent later
DSpaceObject parent = communityService.getParentObject(context, topCommunity);
// Save basic info about top community (and children) to an infoMap
HashMap<String,String> infoMap = new HashMap<String,String>();
saveObjectInfo(topCommunity, infoMap);
// Export community & child AIPs
log.info("testRestoreCommunityHierarchy() - CREATE AIPs");
File aipFile = createAIP(topCommunity, null, true);
// Delete everything from parent community on down
log.info("testRestoreCommunityHierarchy() - DELETE Community Hierarchy");
communityService.delete(context, topCommunity);
// Assert all objects in infoMap no longer exist in DSpace
assertObjectsNotExist(infoMap);
// Restore this Community (recursively) from AIPs
log.info("testRestoreCommunityHierarchy() - RESTORE Community Hierarchy");
// Ensure "skipIfParentMissing" flag is set to true.
// As noted in the documentation, this is often needed for larger, hierarchical
// restores when you have Mapped Items (which we do in our test data)
PackageParameters pkgParams = new PackageParameters();
pkgParams.addProperty("skipIfParentMissing", "true");
restoreFromAIP(parent, aipFile, pkgParams, true);
// Assert all objects in infoMap now exist again!
assertObjectsExist(infoMap);
// SPECIAL CASE: Test Item Mapping restoration was successful
// In our community, we have one Item which should be in two Collections
Item mappedItem = (Item) handleService.resolveToObject(context, testMappedItemHandle);
assertEquals("testRestoreCommunityHierarchy() - Mapped Item's Collection mappings restored", 2, mappedItem.getCollections().size());
log.info("testRestoreCommunityHierarchy() - END");
}
/**
* Test restoration from AIP of an access restricted Community
*/
@Test
public void testRestoreRestrictedCommunity() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy (Items/Bundles/Bitstreams) you need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testRestoreRestrictedCommunity() - BEGIN");
// Locate the top-level Community (as a parent)
Community parent = (Community) handleService.resolveToObject(context, topCommunityHandle);
// Create a brand new (empty) Community to test with
Community community = communityService.createSubcommunity(context, parent);
communityService.addMetadata(context, community, "dc", "title", null, null, "Restricted Community");
communityService.update(context, community);
String communityHandle = community.getHandle();
// Create a new Group to access restrict to
Group group = groupService.create(context);
groupService.setName(group, "Special Users");
groupService.update(context, group);
// Create a custom resource policy for this community
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy policy = resourcePolicyService.create(context);
policy.setRpName("Special Read Only");
policy.setGroup(group);
policy.setAction(Constants.READ);
policies.add(policy);
// Replace default community policies with this new one
authorizeService.removeAllPolicies(context, community);
authorizeService.addPolicies(context, policies, community);
// Export collection AIP
log.info("testRestoreRestrictedCommunity() - CREATE Community AIP");
File aipFile = createAIP(community, null, false);
// Now, delete that Collection
log.info("testRestoreRestrictedCommunity() - DELETE Community");
communityService.removeSubcommunity(context, parent, community);
// Assert the deleted collection no longer exists
DSpaceObject obj = handleService.resolveToObject(context, communityHandle);
assertThat("testRestoreRestrictedCommunity() Community " + communityHandle + " doesn't exist", obj, nullValue());
// Restore Collection from AIP (non-recursive)
log.info("testRestoreRestrictedCommunity() - RESTORE Community");
restoreFromAIP(parent, aipFile, null, false);
// Assert the deleted Collection is RESTORED
DSpaceObject objRestored = handleService.resolveToObject(context, communityHandle);
assertThat("testRestoreRestrictedCommunity() Community " + communityHandle + " exists", objRestored, notNullValue());
// Assert the number of restored policies is equal
List<ResourcePolicy> policiesRestored = authorizeService.getPolicies(context, objRestored);
assertEquals("testRestoreRestrictedCommunity() restored policy count equal", policies.size(), policiesRestored.size());
// Assert the restored policy has same name, group and permission settings
ResourcePolicy restoredPolicy = policiesRestored.get(0);
assertEquals("testRestoreRestrictedCommunity() restored policy group successfully", policy.getGroup().getName(), restoredPolicy.getGroup().getName());
assertEquals("testRestoreRestrictedCommunity() restored policy action successfully", policy.getAction(), restoredPolicy.getAction());
assertEquals("testRestoreRestrictedCommunity() restored policy name successfully", policy.getRpName(), restoredPolicy.getRpName());
log.info("testRestoreRestrictedCommunity() - END");
}
/**
* Test replacement from AIP of entire Community Hierarchy
*/
@Test
public void testReplaceCommunityHierarchy() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy you really need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testReplaceCommunityHierarchy() - BEGIN");
// Locate the top level community (from our test data)
Community topCommunity = (Community) handleService.resolveToObject(context, topCommunityHandle);
// Get the count of collections under our Community or any Sub-Communities
int numberOfCollections = communityService.getAllCollections(context, topCommunity).size();
// Export community & child AIPs
log.info("testReplaceCommunityHierarchy() - CREATE AIPs");
File aipFile = createAIP(topCommunity, null, true);
// Get some basic info about Collection to be deleted
// In this scenario, we'll delete the test "Grandchild Collection"
// (which is initialized as being under the Top Community)
String deletedCollectionHandle = testCollectionHandle;
Collection collectionToDelete = (Collection) handleService.resolveToObject(context, deletedCollectionHandle);
Community parent = (Community) collectionService.getParentObject(context, collectionToDelete);
// How many items are in this Collection we are about to delete?
int numberOfItems = itemService.countItems(context, collectionToDelete);
// Get an Item that should be deleted when we delete this Collection
// (NOTE: This item is initialized to be a member of the deleted Collection)
String deletedItemHandle = testItemHandle;
// Now, delete that one collection
log.info("testReplaceCommunityHierarchy() - DELETE Collection");
communityService.removeCollection(context, parent, collectionToDelete);
// Assert the deleted collection no longer exists
DSpaceObject obj = handleService.resolveToObject(context, deletedCollectionHandle);
assertThat("testReplaceCommunityHierarchy() collection " + deletedCollectionHandle + " doesn't exist", obj, nullValue());
// Assert the child item no longer exists
DSpaceObject obj2 = handleService.resolveToObject(context, deletedItemHandle);
assertThat("testReplaceCommunityHierarchy() item " + deletedItemHandle + " doesn't exist", obj2, nullValue());
// Replace Community (and all child objects, recursively) from AIPs
log.info("testReplaceCommunityHierarchy() - REPLACE Community Hierarchy");
// Ensure "skipIfParentMissing" flag is set to true.
// As noted in the documentation, this is often needed for larger, hierarchical
// replacements when you have Mapped Items (which we do in our test data)
PackageParameters pkgParams = new PackageParameters();
pkgParams.addProperty("skipIfParentMissing", "true");
replaceFromAIP(topCommunity, aipFile, pkgParams, true);
// Assert the deleted collection is RESTORED
DSpaceObject objRestored = handleService.resolveToObject(context, deletedCollectionHandle);
assertThat("testReplaceCommunityHierarchy() collection " + deletedCollectionHandle + " exists", objRestored, notNullValue());
// Assert the deleted item is also RESTORED
DSpaceObject obj2Restored = handleService.resolveToObject(context, deletedItemHandle);
assertThat("testReplaceCommunityHierarchy() item " + deletedItemHandle + " exists", obj2Restored, notNullValue());
// Assert the Collection count and Item count are same as before
assertEquals("testReplaceCommunityHierarchy() collection count", numberOfCollections, communityService.getAllCollections(context, topCommunity).size());
assertEquals("testReplaceCommunityHierarchy() item count", numberOfItems, itemService.countItems(context, ((Collection) objRestored)));
log.info("testReplaceCommunityHierarchy() - END");
}
/**
* Test replacement from AIP of JUST a Community object
*/
@Test
public void testReplaceCommunityOnly() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Community WRITE perms
authorizeService.authorizeAction((Context) any, (Community) any,
Constants.WRITE,true); result = null;
}};
log.info("testReplaceCommunityOnly() - BEGIN");
// Locate the top level community (from our test data)
Community topCommunity = (Community) handleService.resolveToObject(context, topCommunityHandle);
// Get its current name / title
String oldName = topCommunity.getName();
// Export only community AIP
log.info("testReplaceCommunityOnly() - CREATE Community AIP");
File aipFile = createAIP(topCommunity, null, false);
// Change the Community name
String newName = "This is NOT my Community name!";
communityService.clearMetadata(context, topCommunity, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY);
communityService.addMetadata(context, topCommunity, MetadataSchema.DC_SCHEMA, "title", null, null, newName);
// Ensure name is changed
assertEquals("testReplaceCommunityOnly() new name", topCommunity.getName(), newName);
// Now, replace our Community from AIP (non-recursive)
replaceFromAIP(topCommunity, aipFile, null, false);
// Check if name reverted to previous value
assertEquals("testReplaceCommunityOnly() old name", topCommunity.getName(), oldName);
}
/**
* Test restoration from AIP of entire Collection Hierarchy
*/
@Test
public void testRestoreCollectionHierarchy() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy you really need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testRestoreCollectionHierarchy() - BEGIN");
// Locate the collection (from our test data)
Collection testCollection = (Collection) handleService.resolveToObject(context, testCollectionHandle);
// Get parent object, so that we can restore to same parent later
Community parent = (Community) collectionService.getParentObject(context, testCollection);
// Save basic info about collection (and children) to an infoMap
HashMap<String,String> infoMap = new HashMap<String,String>();
saveObjectInfo(testCollection, infoMap);
// Export collection & child AIPs
log.info("testRestoreCollectionHierarchy() - CREATE AIPs");
File aipFile = createAIP(testCollection, null, true);
// Delete everything from collection on down
log.info("testRestoreCollectionHierarchy() - DELETE Collection Hierarchy");
communityService.removeCollection(context, parent, testCollection);
// Assert all objects in infoMap no longer exist in DSpace
assertObjectsNotExist(infoMap);
// Restore this Collection (recursively) from AIPs
log.info("testRestoreCollectionHierarchy() - RESTORE Collection Hierarchy");
restoreFromAIP(parent, aipFile, null, true);
// Assert all objects in infoMap now exist again!
assertObjectsExist(infoMap);
log.info("testRestoreCollectionHierarchy() - END");
}
/**
* Test restoration from AIP of an access restricted Collection
*/
@Test
public void testRestoreRestrictedCollection() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy (Items/Bundles/Bitstreams) you need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testRestoreRestrictedCollection() - BEGIN");
// Locate the top-level Community (as a parent)
Community parent = (Community) handleService.resolveToObject(context, topCommunityHandle);
// Create a brand new (empty) Collection to test with
Collection collection = collectionService.create(context, parent);
collectionService.addMetadata(context, collection, "dc", "title", null, null, "Restricted Collection");
collectionService.update(context, collection);
String collectionHandle = collection.getHandle();
// Create a new Group to access restrict to
Group group = groupService.create(context);
groupService.setName(group, "Special Users");
groupService.update(context, group);
// Create a custom resource policy for this Collection
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy policy = resourcePolicyService.create(context);
policy.setRpName("Special Read Only");
policy.setGroup(group);
policy.setAction(Constants.READ);
policies.add(policy);
// Replace default Collection policies with this new one
authorizeService.removeAllPolicies(context, collection);
authorizeService.addPolicies(context, policies, collection);
// Export collection AIP
log.info("testRestoreRestrictedCollection() - CREATE Collection AIP");
File aipFile = createAIP(collection, null, false);
// Now, delete that Collection
log.info("testRestoreRestrictedCollection() - DELETE Collection");
communityService.removeCollection(context, parent, collection);
// Assert the deleted collection no longer exists
DSpaceObject obj = handleService.resolveToObject(context, collectionHandle);
assertThat("testRestoreRestrictedCollection() Collection " + collectionHandle + " doesn't exist", obj, nullValue());
// Restore Collection from AIP (non-recursive)
log.info("testRestoreRestrictedCollection() - RESTORE Collection");
restoreFromAIP(parent, aipFile, null, false);
// Assert the deleted Collection is RESTORED
DSpaceObject objRestored = handleService.resolveToObject(context, collectionHandle);
assertThat("testRestoreRestrictedCollection() Collection " + collectionHandle + " exists", objRestored, notNullValue());
// Assert the number of restored policies is equal
List<ResourcePolicy> policiesRestored = authorizeService.getPolicies(context, objRestored);
assertEquals("testRestoreRestrictedCollection() restored policy count equal", policies.size(), policiesRestored.size());
// Assert the restored policy has same name, group and permission settings
ResourcePolicy restoredPolicy = policiesRestored.get(0);
assertEquals("testRestoreRestrictedCollection() restored policy group successfully", policy.getGroup().getName(), restoredPolicy.getGroup().getName());
assertEquals("testRestoreRestrictedCollection() restored policy action successfully", policy.getAction(), restoredPolicy.getAction());
assertEquals("testRestoreRestrictedCollection() restored policy name successfully", policy.getRpName(), restoredPolicy.getRpName());
log.info("testRestoreRestrictedCollection() - END");
}
/**
* Test replacement from AIP of entire Collection (with Items)
*/
@Test
public void testReplaceCollectionHierarchy() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy you really need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testReplaceCollectionHierarchy() - BEGIN");
// Locate the collection (from our test data)
Collection testCollection = (Collection) handleService.resolveToObject(context, testCollectionHandle);
// How many items are in this Collection?
int numberOfItems = itemService.countItems(context, testCollection);
// Export collection & child AIPs
log.info("testReplaceCollectionHierarchy() - CREATE AIPs");
File aipFile = createAIP(testCollection, null, true);
// Get some basic info about Item to be deleted
// In this scenario, we'll delete the test "Grandchild Collection Item #1"
// (which is initialized as being an Item within this Collection)
String deletedItemHandle = testItemHandle;
Item itemToDelete = (Item) handleService.resolveToObject(context, deletedItemHandle);
Collection parent = (Collection) itemService.getParentObject(context, itemToDelete);
// Now, delete that one item
log.info("testReplaceCollectionHierarchy() - DELETE Item");
collectionService.removeItem(context, parent, itemToDelete);
// Assert the deleted item no longer exists
DSpaceObject obj = handleService.resolveToObject(context, deletedItemHandle);
assertThat("testReplaceCollectionHierarchy() item " + deletedItemHandle + " doesn't exist", obj, nullValue());
// Assert the item count is one less
assertEquals("testReplaceCollectionHierarchy() updated item count for collection " + testCollectionHandle, numberOfItems - 1, itemService.countItems(context, testCollection));
// Replace Collection (and all child objects, recursively) from AIPs
log.info("testReplaceCollectionHierarchy() - REPLACE Collection Hierarchy");
replaceFromAIP(testCollection, aipFile, null, true);
// Assert the deleted item is RESTORED
DSpaceObject objRestored = handleService.resolveToObject(context, deletedItemHandle);
assertThat("testReplaceCollectionHierarchy() item " + deletedItemHandle + " exists", objRestored, notNullValue());
// Assert the Item count is same as before
assertEquals("testReplaceCollectionHierarchy() restored item count for collection " + testCollectionHandle, numberOfItems, itemService.countItems(context, testCollection));
log.info("testReplaceCollectionHierarchy() - END");
}
/**
* Test replacement from AIP of JUST a Collection object
*/
@Test
public void testReplaceCollectionOnly() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Collection WRITE perms
authorizeService.authorizeAction((Context) any, (Collection) any,
Constants.WRITE,true); result = null;
}};
log.info("testReplaceCollectionOnly() - BEGIN");
// Locate the collection (from our test data)
Collection testCollection = (Collection) handleService.resolveToObject(context, testCollectionHandle);
// Get its current name / title
String oldName = testCollection.getName();
// Export only collection AIP
log.info("testReplaceCollectionOnly() - CREATE Collection AIP");
File aipFile = createAIP(testCollection, null, false);
// Change the Collection name
String newName = "This is NOT my Collection name!";
collectionService.clearMetadata(context, testCollection, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY);
collectionService.addMetadata(context, testCollection, MetadataSchema.DC_SCHEMA, "title", null, null, newName);
// Ensure name is changed
assertEquals("testReplaceCollectionOnly() new name", testCollection.getName(), newName);
// Now, replace our Collection from AIP (non-recursive)
replaceFromAIP(testCollection, aipFile, null, false);
// Check if name reverted to previous value
assertEquals("testReplaceCollectionOnly() old name", testCollection.getName(), oldName);
}
/**
* Test restoration from AIP of an Item
*/
@Test
public void testRestoreItem() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy (Items/Bundles/Bitstreams) you need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testRestoreItem() - BEGIN");
// Locate the item (from our test data)
Item testItem = (Item) handleService.resolveToObject(context, testItemHandle);
// Get information about the Item's Bitstreams
// (There should be one bitstream initialized above)
int bitstreamCount = 0;
String bitstreamName = null;
String bitstreamCheckSum = null;
List<Bundle> bundles = itemService.getBundles(testItem, Constants.CONTENT_BUNDLE_NAME);
if(bundles.size()>0)
{
List<Bitstream> bitstreams = bundles.get(0).getBitstreams();
bitstreamCount = bitstreams.size();
if(bitstreamCount>0)
{
bitstreamName = bitstreams.get(0).getName();
bitstreamCheckSum = bitstreams.get(0).getChecksum();
}
}
// We need a test bitstream to work with!
if(bitstreamCount<=0)
fail("No test bitstream found for Item in testRestoreItem()!");
// Export item AIP
log.info("testRestoreItem() - CREATE Item AIP");
File aipFile = createAIP(testItem, null, false);
// Get parent, so we can restore under the same parent
Collection parent = (Collection) itemService.getParentObject(context, testItem);
// Now, delete that item
log.info("testRestoreItem() - DELETE Item");
collectionService.removeItem(context, parent, testItem);
// Assert the deleted item no longer exists
DSpaceObject obj = handleService.resolveToObject(context, testItemHandle);
assertThat("testRestoreItem() item " + testItemHandle + " doesn't exist", obj, nullValue());
// Restore Item from AIP (non-recursive)
log.info("testRestoreItem() - RESTORE Item");
restoreFromAIP(parent, aipFile, null, false);
// Assert the deleted item is RESTORED
DSpaceObject objRestored = handleService.resolveToObject(context, testItemHandle);
assertThat("testRestoreItem() item " + testItemHandle + " exists", objRestored, notNullValue());
// Assert Bitstream exists again & is associated with restored item
List<Bundle> restoredBund = itemService.getBundles(((Item) objRestored), Constants.CONTENT_BUNDLE_NAME);
Bitstream restoredBitstream = bundleService.getBitstreamByName(restoredBund.get(0), bitstreamName);
assertThat("testRestoreItem() bitstream exists", restoredBitstream, notNullValue());
assertEquals("testRestoreItem() bitstream checksum", restoredBitstream.getChecksum(), bitstreamCheckSum);
log.info("testRestoreItem() - END");
}
/**
* Test restoration from AIP of an access restricted Item
*/
@Test
public void testRestoreRestrictedItem() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy (Items/Bundles/Bitstreams) you need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testRestoreRestrictedItem() - BEGIN");
// Locate the test Collection (as a parent)
Collection parent = (Collection) handleService.resolveToObject(context, testCollectionHandle);
// Create a brand new Item to test with (since we will be changing policies)
WorkspaceItem wsItem = workspaceItemService.create(context, parent, false);
Item item = installItemService.installItem(context, wsItem);
itemService.addMetadata(context, item, "dc", "title", null, null, "Test Restricted Item");
// Create a test Bitstream in the ORIGINAL bundle
File f = new File(testProps.get("test.bitstream").toString());
Bitstream b = itemService.createSingleBitstream(context, new FileInputStream(f), item);
b.setName(context, "Test Bitstream");
bitstreamService.update(context, b);
itemService.update(context, item);
// Create a custom resource policy for this Item
List<ResourcePolicy> policies = new ArrayList<>();
ResourcePolicy admin_policy = resourcePolicyService.create(context);
admin_policy.setRpName("Admin Read-Only");
Group adminGroup = groupService.findByName(context, Group.ADMIN);
admin_policy.setGroup(adminGroup);
admin_policy.setAction(Constants.READ);
policies.add(admin_policy);
itemService.replaceAllItemPolicies(context, item, policies);
// Export item AIP
log.info("testRestoreRestrictedItem() - CREATE Item AIP");
File aipFile = createAIP(item, null, false);
// Get item handle, so we can check that it is later restored properly
String itemHandle = item.getHandle();
// Now, delete that item
log.info("testRestoreRestrictedItem() - DELETE Item");
collectionService.removeItem(context, parent, item);
// Assert the deleted item no longer exists
DSpaceObject obj = handleService.resolveToObject(context, itemHandle);
assertThat("testRestoreRestrictedItem() item " + itemHandle + " doesn't exist", obj, nullValue());
// Restore Item from AIP (non-recursive)
log.info("testRestoreRestrictedItem() - RESTORE Item");
restoreFromAIP(parent, aipFile, null, false);
// Assert the deleted item is RESTORED
DSpaceObject objRestored = handleService.resolveToObject(context, itemHandle);
assertThat("testRestoreRestrictedItem() item " + itemHandle + " exists", objRestored, notNullValue());
// Assert the number of restored policies is equal
List<ResourcePolicy> policiesRestored = authorizeService.getPolicies(context, objRestored);
assertEquals("testRestoreRestrictedItem() restored policy count equal", policies.size(), policiesRestored.size());
// Assert the restored policy has same name, group and permission settings
ResourcePolicy restoredPolicy = policiesRestored.get(0);
assertEquals("testRestoreRestrictedItem() restored policy group successfully", admin_policy.getGroup().getName(), restoredPolicy.getGroup().getName());
assertEquals("testRestoreRestrictedItem() restored policy action successfully", admin_policy.getAction(), restoredPolicy.getAction());
assertEquals("testRestoreRestrictedItem() restored policy name successfully", admin_policy.getRpName(), restoredPolicy.getRpName());
log.info("testRestoreRestrictedItem() - END");
}
/**
* Test restoration from AIP of an Item that has no access policies associated with it.
*/
@Test
public void testRestoreItemNoPolicies() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy (Items/Bundles/Bitstreams) you need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testRestoreItemNoPolicies() - BEGIN");
// Locate the test Collection (as a parent)
Collection parent = (Collection) handleService.resolveToObject(context, testCollectionHandle);
// Create a brand new Item to test with (since we will be changing policies)
WorkspaceItem wsItem = workspaceItemService.create(context, parent, false);
Item item = installItemService.installItem(context, wsItem);
itemService.addMetadata(context, item, "dc", "title", null, null, "Test No Policies Item");
// Create a test Bitstream in the ORIGINAL bundle
File f = new File(testProps.get("test.bitstream").toString());
Bitstream b = itemService.createSingleBitstream(context, new FileInputStream(f), item);
b.setName(context, "Test Bitstream");
bitstreamService.update(context, b);
itemService.update(context, item);
// Remove all existing policies from the Item
authorizeService.removeAllPolicies(context, item);
// Export item AIP
log.info("testRestoreItemNoPolicies() - CREATE Item AIP");
File aipFile = createAIP(item, null, false);
// Get item handle, so we can check that it is later restored properly
String itemHandle = item.getHandle();
// Now, delete that item
log.info("testRestoreItemNoPolicies() - DELETE Item");
collectionService.removeItem(context, parent, item);
// Assert the deleted item no longer exists
DSpaceObject obj = handleService.resolveToObject(context, itemHandle);
assertThat("testRestoreItemNoPolicies() item " + itemHandle + " doesn't exist", obj, nullValue());
// Restore Item from AIP (non-recursive)
log.info("testRestoreItemNoPolicies() - RESTORE Item");
restoreFromAIP(parent, aipFile, null, false);
// Assert the deleted item is RESTORED
DSpaceObject objRestored = handleService.resolveToObject(context, itemHandle);
assertThat("testRestoreItemNoPolicies() item " + itemHandle + " exists", objRestored, notNullValue());
// Assert the restored item also has ZERO policies
List<ResourcePolicy> policiesRestored = authorizeService.getPolicies(context, objRestored);
assertEquals("testRestoreItemNoPolicies() restored policy count is zero", 0, policiesRestored.size());
log.info("testRestoreItemNoPolicies() - END");
}
/**
* Test replacement from AIP of an Item object
*/
@Test
public void testReplaceItem() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy (Items/Bundles/Bitstreams) you need full admin rights
authorizeService.isAdmin((Context) any); result = true;
}};
log.info("testReplaceItem() - BEGIN");
// Locate the item (from our test data)
Item testItem = (Item) handleService.resolveToObject(context, testItemHandle);
// Get its current name / title
String oldName = testItem.getName();
// Export item AIP
log.info("testReplaceItem() - CREATE Item AIP");
File aipFile = createAIP(testItem, null, false);
// Change the Item name
String newName = "This is NOT my Item name!";
itemService.clearMetadata(context, testItem, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY);
itemService.addMetadata(context, testItem, MetadataSchema.DC_SCHEMA, "title", null, null, newName);
// Ensure name is changed
assertEquals("testReplaceItem() new name", testItem.getName(), newName);
// Now, replace our Item from AIP (non-recursive)
replaceFromAIP(testItem, aipFile, null, false);
// Check if name reverted to previous value
assertEquals("testReplaceItem() old name", testItem.getName(), oldName);
}
/**
* Test restoration from AIP of an Item that is mapped to multiple Collections.
* This tests restoring the mapped Item FROM its own AIP
*/
@Test
public void testRestoreMappedItem() throws Exception
{
new NonStrictExpectations(authorizeService.getClass())
{{
// Allow Full Admin permissions. Since we are working with an object
// hierarchy (Items/Bundles/Bitstreams) you need full admin rights
authorizeService.isAdmin((Context) any); result = true;
authorizeService.authorizeAction(context, (Collection) any, Constants.REMOVE); result = null;
authorizeService.authorizeAction(context, (Collection) any, Constants.ADD); result = null;
}};
log.info("testRestoreMappedItem() - BEGIN");
// Get a reference to our test mapped Item
Item item = (Item) handleService.resolveToObject(context, testMappedItemHandle);
// Get owning Collection
Collection owner = item.getOwningCollection();
// Assert that it is in multiple collections
List<Collection> mappedCollections = item.getCollections();
assertEquals("testRestoreMappedItem() item " + testMappedItemHandle + " is mapped to multiple collections", 2, mappedCollections.size());
// Export mapped item AIP
log.info("testRestoreMappedItem() - CREATE Mapped Item AIP");
File aipFile = createAIP(item, null, false);
// Now, delete that item (must be removed from BOTH collections to delete it)
log.info("testRestoreMappedItem() - DELETE Item");
itemService.delete(context, item);
// Assert the deleted item no longer exists
DSpaceObject obj = handleService.resolveToObject(context, testMappedItemHandle);
assertThat("testRestoreMappedItem() item " + testMappedItemHandle + " doesn't exist", obj, nullValue());
// Restore Item from AIP (non-recursive) into its original parent collection
log.info("testRestoreMappedItem() - RESTORE Item");
restoreFromAIP(owner, aipFile, null, false);
// Commit these changes to our DB
// Assert the deleted item is RESTORED
Item itemRestored = (Item) handleService.resolveToObject(context, testMappedItemHandle);
assertThat("testRestoreMappedItem() item " + testMappedItemHandle + " exists", itemRestored, notNullValue());
// Test that this restored Item exists in multiple Collections
List<Collection> restoredMappings = itemRestored.getCollections();
assertEquals("testRestoreMappedItem() collection count", 2, restoredMappings.size());
log.info("testRestoreMappedItem() - END");
}
/**
* Create AIP(s) based on a given DSpaceObject. This is a simple utility method
* to avoid having to rewrite this code into several tests.
* @param dso DSpaceObject to create AIP(s) for
* @param pkParams any special PackageParameters to pass (if any)
* @param recursive whether to recursively create AIPs or just a single AIP
* @return exported root AIP file
*/
private File createAIP(DSpaceObject dso, PackageParameters pkgParams, boolean recursive)
throws PackageException, CrosswalkException, AuthorizeException, SQLException, IOException
{
// Get a reference to the configured "AIP" package disseminator
PackageDisseminator dip = (PackageDisseminator) pluginService
.getNamedPlugin(PackageDisseminator.class, "AIP");
if (dip == null)
{
fail("Could not find a disseminator for type 'AIP'");
return null;
}
else
{
// Export file (this is placed in JUnit's temporary folder, so that it can be cleaned up after tests complete)
File exportAIPFile = new File(aipTempFolder.getRoot().getAbsolutePath() + File.separator + PackageUtils.getPackageName(dso, "zip"));
// If unspecified, set default PackageParameters
if (pkgParams==null)
pkgParams = new PackageParameters();
// Actually disseminate the object(s) to AIPs
if(recursive)
dip.disseminateAll(context, dso, pkgParams, exportAIPFile);
else
dip.disseminate(context, dso, pkgParams, exportAIPFile);
return exportAIPFile;
}
}
/**
* Restore DSpaceObject(s) from AIP(s). This is a simple utility method
* to avoid having to rewrite this code into several tests.
* @param parent The DSpaceObject which will be the parent object of the newly restored object(s)
* @param aipFile AIP file to start restoration from
* @param pkParams any special PackageParameters to pass (if any)
* @param recursive whether to recursively restore AIPs or just a single AIP
*/
private void restoreFromAIP(DSpaceObject parent, File aipFile, PackageParameters pkgParams, boolean recursive)
throws PackageException, CrosswalkException, AuthorizeException, SQLException, IOException, WorkflowException {
// Get a reference to the configured "AIP" package ingestor
PackageIngester sip = (PackageIngester) pluginService
.getNamedPlugin(PackageIngester.class, "AIP");
if(sip == null)
{
fail("Could not find a ingestor for type 'AIP'");
}
else
{
if(!aipFile.exists())
{
fail("AIP Package File does NOT exist: " + aipFile.getAbsolutePath());
}
// If unspecified, set default PackageParameters
if(pkgParams==null)
pkgParams = new PackageParameters();
// Ensure restore mode is enabled
pkgParams.setRestoreModeEnabled(true);
// Actually ingest the object(s) from AIPs
if(recursive)
sip.ingestAll(context, parent, aipFile, pkgParams, null);
else
sip.ingest(context, parent, aipFile, pkgParams, null);
}
}
/**
* Replace DSpaceObject(s) from AIP(s). This is a simple utility method
* to avoid having to rewrite this code into several tests.
* @param dso The DSpaceObject to be replaced from AIP
* @param aipFile AIP file to start replacement from
* @param pkParams any special PackageParameters to pass (if any)
* @param recursive whether to recursively restore AIPs or just a single AIP
*/
private void replaceFromAIP(DSpaceObject dso, File aipFile, PackageParameters pkgParams, boolean recursive)
throws PackageException, CrosswalkException, AuthorizeException, SQLException, IOException, WorkflowException {
// Get a reference to the configured "AIP" package ingestor
PackageIngester sip = (PackageIngester) pluginService
.getNamedPlugin(PackageIngester.class, "AIP");
if (sip == null)
{
fail("Could not find a ingestor for type 'AIP'");
}
else
{
if(!aipFile.exists())
{
fail("AIP Package File does NOT exist: " + aipFile.getAbsolutePath());
}
// If unspecified, set default PackageParameters
if (pkgParams==null)
pkgParams = new PackageParameters();
// Ensure restore mode is enabled
pkgParams.setRestoreModeEnabled(true);
// Actually replace the object(s) from AIPs
if(recursive)
sip.replaceAll(context, dso, aipFile, pkgParams);
else
sip.replace(context, dso, aipFile, pkgParams);
}
}
/**
* Save Object hierarchy info to the given HashMap. This utility method can
* be used in conjunction with "assertObjectsExist" and "assertObjectsNotExist"
* methods below, in order to assert whether a restoration succeeded or not.
* <P>
* In HashMap, Key is the object handle, and Value is "[type-text]::[title]".
* @param dso DSpaceObject
* @param infoMap HashMap
* @throws SQLException if database error
*/
private void saveObjectInfo(DSpaceObject dso, HashMap<String,String> infoMap)
throws SQLException
{
// We need the HashMap to be non-null
if(infoMap==null)
return;
if(dso instanceof Community)
{
// Save this Community's info to the infoMap
Community community = (Community) dso;
infoMap.put(community.getHandle(), communityService.getTypeText(community) + valueseparator + community.getName());
// Recursively call method for each SubCommunity
List<Community> subCommunities = community.getSubcommunities();
for(Community c : subCommunities)
{
saveObjectInfo(c, infoMap);
}
// Recursively call method for each Collection
List<Collection> collections = community.getCollections();
for(Collection c : collections)
{
saveObjectInfo(c, infoMap);
}
}
else if(dso instanceof Collection)
{
// Save this Collection's info to the infoMap
Collection collection = (Collection) dso;
infoMap.put(collection.getHandle(), collectionService.getTypeText(collection) + valueseparator + collection.getName());
// Recursively call method for each Item in Collection
Iterator<Item> items = itemService.findByCollection(context, collection);
while(items.hasNext())
{
Item i = items.next();
saveObjectInfo(i, infoMap);
}
}
else if(dso instanceof Item)
{
// Save this Item's info to the infoMap
Item item = (Item) dso;
infoMap.put(item.getHandle(), itemService.getTypeText(item) + valueseparator + item.getName());
}
}
/**
* Assert the objects listed in a HashMap all exist in DSpace and have
* properties equal to HashMap value(s).
* <P>
* In HashMap, Key is the object handle, and Value is "[type-text]::[title]".
* @param infoMap HashMap of objects to check for
* @throws SQLException if database error
*/
private void assertObjectsExist(HashMap<String,String> infoMap)
throws SQLException
{
if(infoMap==null || infoMap.isEmpty())
fail("Cannot assert against an empty infoMap");
// Loop through everything in infoMap, and ensure it all exists
for(String key : infoMap.keySet())
{
// The Key is the Handle, so make sure this object exists
DSpaceObject obj = handleService.resolveToObject(context, key);
assertThat("assertObjectsExist object " + key + " (info=" + infoMap.get(key) + ") exists", obj, notNullValue());
// Get the typeText & name of this object from the values
String info = infoMap.get(key);
String[] values = info.split(valueseparator);
String typeText = values[0];
String name = values[1];
// Also assert type and name are correct
assertEquals("assertObjectsExist object " + key + " type", ContentServiceFactory.getInstance().getDSpaceObjectService(obj).getTypeText(obj), typeText);
assertEquals("assertObjectsExist object " + key + " name", obj.getName(), name);
}
}
/**
* Assert the objects listed in a HashMap do NOT exist in DSpace.
* @param infoMap HashMap of objects to check for
* @throws SQLException if database error
*/
public void assertObjectsNotExist(HashMap<String,String> infoMap)
throws SQLException
{
if(infoMap==null || infoMap.isEmpty())
fail("Cannot assert against an empty infoMap");
// Loop through everything in infoMap, and ensure it all exists
for(String key : infoMap.keySet())
{
// The key is the Handle, so make sure this object does NOT exist
DSpaceObject obj = handleService.resolveToObject(context, key);
assertThat("assertObjectsNotExist object " + key + " (info=" + infoMap.get(key) + ") doesn't exist", obj, nullValue());
}
}
}