/** * 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.HashMap; import mockit.NonStrictExpectations; import org.apache.log4j.Logger; import org.dspace.AbstractUnitTest; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeManager; import org.dspace.content.Bitstream; import org.dspace.content.Bundle; import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DSpaceObject; import org.dspace.content.InstallItem; import org.dspace.content.Item; import org.dspace.content.ItemIterator; import org.dspace.content.MetadataSchema; import org.dspace.content.WorkspaceItem; import org.dspace.content.crosswalk.CrosswalkException; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.MockConfigurationManager; import org.dspace.core.PluginManager; import org.dspace.handle.HandleManager; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; 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 DSpaceAIPIntegrationTest extends AbstractUnitTest { /** log4j category */ private static final Logger log = Logger.getLogger(DSpaceAIPIntegrationTest.class); /** 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; /** Create a temporary folder which will be cleaned up automatically by JUnit. NOTE: As a ClassRule, this temp folder is shared by ALL tests below. Its AIP contents are initialized in init() below. **/ @ClassRule public static final TemporaryFolder testFolder = 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() { // Initialize MockConfigurationManager, and tell it to load properties by default new MockConfigurationManager(true); // 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. MockConfigurationManager.setProperty("upload.temp.dir", testFolder.getRoot().getAbsolutePath()); try { Context context = new Context(); // Create a dummy Community hierarchy to test with // Turn off authorization temporarily to create some test objects. context.turnOffAuthorisationSystem(); 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 = Community.create(null,context); topCommunity.addMetadata(MetadataSchema.DC_SCHEMA, "title", null, null, "Top Community"); topCommunity.update(); topCommunityHandle = topCommunity.getHandle(); Community child = topCommunity.createSubcommunity(); child.addMetadata(MetadataSchema.DC_SCHEMA, "title", null, null, "Child Community"); child.update(); Community grandchild = child.createSubcommunity(); grandchild.addMetadata(MetadataSchema.DC_SCHEMA, "title", null, null, "Grandchild Community"); grandchild.update(); // Create our primary Test Collection Collection grandchildCol = child.createCollection(); grandchildCol.addMetadata("dc", "title", null, null, "Grandchild Collection"); grandchildCol.update(); testCollectionHandle = grandchildCol.getHandle(); // Create an additional Test Collection Collection greatgrandchildCol = grandchild.createCollection(); greatgrandchildCol.addMetadata("dc", "title", null, null, "GreatGrandchild Collection"); greatgrandchildCol.update(); // Create our primary Test Item WorkspaceItem wsItem = WorkspaceItem.create(context, grandchildCol, false); Item item = InstallItem.installItem(context, wsItem); item.addMetadata("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 = item.createSingleBitstream(new FileInputStream(f)); b.setName("Test Bitstream"); b.update(); item.update(); testItemHandle = item.getHandle(); // Create a Mapped Test Item (mapped to multiple collections WorkspaceItem wsItem2 = WorkspaceItem.create(context, grandchildCol, false); Item item2 = InstallItem.installItem(context, wsItem2); item2.addMetadata("dc", "title", null, null, "Mapped Item"); item2.update(); greatgrandchildCol.addItem(item2); testMappedItemHandle = item2.getHandle(); WorkspaceItem wsItem3 = WorkspaceItem.create(context, greatgrandchildCol, false); Item item3 = InstallItem.installItem(context, wsItem3); item3.addMetadata("dc", "title", null, null, "GreatGrandchild Collection Item #1"); item3.update(); WorkspaceItem wsItem4 = WorkspaceItem.create(context, greatgrandchildCol, false); Item item4 = InstallItem.installItem(context, wsItem4); item4.addMetadata("dc", "title", null, null, "GreatGrandchild Collection Item #2"); item4.update(); // 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(); Community topCommunity = (Community) HandleManager.resolveToObject(context, topCommunityHandle); // Delete top level test community and test hierarchy under it if(topCommunity!=null) { log.info("tearDownClass() - DESTROY TEST HIERARCHY"); context.turnOffAuthorisationSystem(); topCommunity.delete(); context.restoreAuthSystemState(); context.complete(); } 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(); try { // Locate the top level community (from our test data) Community topCommunity = (Community) HandleManager.resolveToObject(context, topCommunityHandle); log.info("init() - CREATE TEST AIPS"); // NOTE: This will not overwrite the AIPs if they already exist. // But, it does ensure they are created PRIOR to running any of the below tests. // (So, essentially, this runs ONCE...after that, it'll be ignored since AIPs already exist) // While ideally, you don't want to share data between tests, generating AIPs is VERY timeconsuming. createAIP(topCommunity, null, true, false); } catch(PackageException|CrosswalkException ex) { log.error("Packaging Error in init()", ex); fail("Packaging Error in init(): " + ex.getMessage()); } catch (AuthorizeException ex) { log.error("Authorization Error in init()", ex); fail("Authorization Error in init(): " + ex.getMessage()); } catch (IOException ex) { log.error("IO Error in init()", ex); fail("IO Error in init(): " + ex.getMessage()); } catch (SQLException ex) { log.error("SQL Error in init()", ex); fail("SQL Error in init(): " + ex.getMessage()); } } /** * Test restoration from AIP of entire Community Hierarchy */ @Test public void testRestoreCommunityHierarchy() throws Exception { new NonStrictExpectations(AuthorizeManager.class) {{ // Allow Full Admin permissions. Since we are working with an object // hierarchy you really need full admin rights AuthorizeManager.isAdmin((Context) any); result = true; }}; log.info("testRestoreCommunityHierarchy() - BEGIN"); // Locate the top level community (from our test data) Community topCommunity = (Community) HandleManager.resolveToObject(context, topCommunityHandle); // Get parent object, so that we can restore to same parent later DSpaceObject parent = topCommunity.getParentObject(); // Save basic info about top community (and children) to an infoMap HashMap<String,String> infoMap = new HashMap<String,String>(); saveObjectInfo(topCommunity, infoMap); // Ensure community & child AIPs are exported (but don't overwrite) log.info("testRestoreCommunityHierarchy() - CREATE AIPs"); File aipFile = createAIP(topCommunity, null, true, false); // Delete everything from parent community on down log.info("testRestoreCommunityHierarchy() - DELETE Community Hierarchy"); topCommunity.delete(); // Commit these changes to our DB context.commit(); // 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); // Commit these changes to our DB context.commit(); // 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) HandleManager.resolveToObject(context, testMappedItemHandle); assertEquals("testRestoreCommunityHierarchy() - Mapped Item's Collection mappings restored", 2, mappedItem.getCollections().length); log.info("testRestoreCommunityHierarchy() - END"); } /** * Test replacement from AIP of entire Community Hierarchy */ @Test public void testReplaceCommunityHierarchy() throws Exception { new NonStrictExpectations(AuthorizeManager.class) {{ // Allow Full Admin permissions. Since we are working with an object // hierarchy you really need full admin rights AuthorizeManager.isAdmin((Context) any); result = true; }}; log.info("testReplaceCommunityHierarchy() - BEGIN"); // Locate the top level community (from our test data) Community topCommunity = (Community) HandleManager.resolveToObject(context, topCommunityHandle); // Get the count of collections under our Community or any Sub-Communities int numberOfCollections = topCommunity.getAllCollections().length; // Ensure community & child AIPs are exported (but don't overwrite) log.info("testReplaceCommunityHierarchy() - CREATE AIPs"); File aipFile = createAIP(topCommunity, null, true, false); // 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) HandleManager.resolveToObject(context, deletedCollectionHandle); Community parent = (Community) collectionToDelete.getParentObject(); // How many items are in this Collection we are about to delete? int numberOfItems = collectionToDelete.countItems(); // 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"); parent.removeCollection(collectionToDelete); context.commit(); // Assert the deleted collection no longer exists DSpaceObject obj = HandleManager.resolveToObject(context, deletedCollectionHandle); assertThat("testReplaceCommunityHierarchy() collection " + deletedCollectionHandle + " doesn't exist", obj, nullValue()); // Assert the child item no longer exists DSpaceObject obj2 = HandleManager.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); // Commit these changes to our DB context.commit(); // Assert the deleted collection is RESTORED DSpaceObject objRestored = HandleManager.resolveToObject(context, deletedCollectionHandle); assertThat("testReplaceCommunityHierarchy() collection " + deletedCollectionHandle + " exists", objRestored, notNullValue()); // Assert the deleted item is also RESTORED DSpaceObject obj2Restored = HandleManager.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, topCommunity.getAllCollections().length); assertEquals("testReplaceCommunityHierarchy() item count", numberOfItems, ((Collection)objRestored).countItems()); log.info("testReplaceCommunityHierarchy() - END"); } /** * Test replacement from AIP of JUST a Community object */ @Test public void testReplaceCommunityOnly() throws Exception { new NonStrictExpectations(AuthorizeManager.class) {{ // Allow Community WRITE perms AuthorizeManager.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) HandleManager.resolveToObject(context, topCommunityHandle); // Get its current name / title String oldName = topCommunity.getName(); // Ensure only community AIP is exported (but don't overwrite) log.info("testReplaceCommunityOnly() - CREATE Community AIP"); File aipFile = createAIP(topCommunity, null, false, false); // Change the Community name String newName = "This is NOT my Community name!"; topCommunity.clearMetadata(MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); topCommunity.addMetadata(MetadataSchema.DC_SCHEMA, "title", null, null, newName); // Commit these changes to our DB context.commit(); // Ensure name is changed assertEquals("testReplaceCommunityOnly() new name", topCommunity.getName(), newName); // Now, replace our Community from AIP (non-recursive) replaceFromAIP(topCommunity, aipFile, null, false); // Commit these changes to our DB context.commit(); // 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(AuthorizeManager.class) {{ // Allow Full Admin permissions. Since we are working with an object // hierarchy you really need full admin rights AuthorizeManager.isAdmin((Context) any); result = true; }}; log.info("testRestoreCollectionHierarchy() - BEGIN"); // Locate the collection (from our test data) Collection testCollection = (Collection) HandleManager.resolveToObject(context, testCollectionHandle); // Get parent object, so that we can restore to same parent later Community parent = (Community) testCollection.getParentObject(); // Save basic info about collection (and children) to an infoMap HashMap<String,String> infoMap = new HashMap<String,String>(); saveObjectInfo(testCollection, infoMap); // Ensure collection & child AIPs are exported (but don't overwrite) log.info("testRestoreCollectionHierarchy() - CREATE AIPs"); File aipFile = createAIP(testCollection, null, true, false); // Delete everything from collection on down log.info("testRestoreCollectionHierarchy() - DELETE Collection Hierarchy"); parent.removeCollection(testCollection); // Commit these changes to our DB context.commit(); // 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); // Commit these changes to our DB context.commit(); // Assert all objects in infoMap now exist again! assertObjectsExist(infoMap); log.info("testRestoreCollectionHierarchy() - END"); } /** * Test replacement from AIP of entire Collection (with Items) */ @Test public void testReplaceCollectionHierarchy() throws Exception { new NonStrictExpectations(AuthorizeManager.class) {{ // Allow Full Admin permissions. Since we are working with an object // hierarchy you really need full admin rights AuthorizeManager.isAdmin((Context) any); result = true; }}; log.info("testReplaceCollectionHierarchy() - BEGIN"); // Locate the collection (from our test data) Collection testCollection = (Collection) HandleManager.resolveToObject(context, testCollectionHandle); // How many items are in this Collection? int numberOfItems = testCollection.countItems(); // Ensure collection & child AIPs are exported (but don't overwrite) log.info("testReplaceCollectionHierarchy() - CREATE AIPs"); File aipFile = createAIP(testCollection, null, true, false); // 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) HandleManager.resolveToObject(context, deletedItemHandle); Collection parent = (Collection) itemToDelete.getParentObject(); // Now, delete that one item log.info("testReplaceCollectionHierarchy() - DELETE Item"); parent.removeItem(itemToDelete); context.commit(); // Assert the deleted item no longer exists DSpaceObject obj = HandleManager.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, testCollection.countItems()); // Replace Collection (and all child objects, recursively) from AIPs log.info("testReplaceCollectionHierarchy() - REPLACE Collection Hierarchy"); replaceFromAIP(testCollection, aipFile, null, true); // Commit these changes to our DB context.commit(); // Assert the deleted item is RESTORED DSpaceObject objRestored = HandleManager.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, testCollection.countItems()); log.info("testReplaceCollectionHierarchy() - END"); } /** * Test replacement from AIP of JUST a Collection object */ @Test public void testReplaceCollectionOnly() throws Exception { new NonStrictExpectations(AuthorizeManager.class) {{ // Allow Collection WRITE perms AuthorizeManager.authorizeAction((Context) any, (Collection) any, Constants.WRITE,true); result = null; }}; log.info("testReplaceCollectionOnly() - BEGIN"); // Locate the collection (from our test data) Collection testCollection = (Collection) HandleManager.resolveToObject(context, testCollectionHandle); // Get its current name / title String oldName = testCollection.getName(); // Ensure only collection AIP is exported (but don't overwrite) log.info("testReplaceCollectionOnly() - CREATE Collection AIP"); File aipFile = createAIP(testCollection, null, false, false); // Change the Collection name String newName = "This is NOT my Collection name!"; testCollection.clearMetadata(MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); testCollection.addMetadata(MetadataSchema.DC_SCHEMA, "title", null, null, newName); // Commit these changes to our DB context.commit(); // Ensure name is changed assertEquals("testReplaceCollectionOnly() new name", testCollection.getName(), newName); // Now, replace our Collection from AIP (non-recursive) replaceFromAIP(testCollection, aipFile, null, false); // Commit these changes to our DB context.commit(); // 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(AuthorizeManager.class) {{ // Allow Full Admin permissions. Since we are working with an object // hierarchy (Items/Bundles/Bitstreams) you need full admin rights AuthorizeManager.isAdmin((Context) any); result = true; }}; log.info("testRestoreItem() - BEGIN"); // Locate the item (from our test data) Item testItem = (Item) HandleManager.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; Bundle[] bundles = testItem.getBundles(Constants.CONTENT_BUNDLE_NAME); if(bundles.length>0) { Bitstream[] bitstreams = bundles[0].getBitstreams(); bitstreamCount = bitstreams.length; if(bitstreamCount>0) { bitstreamName = bitstreams[0].getName(); bitstreamCheckSum = bitstreams[0].getChecksum(); } } // We need a test bitstream to work with! if(bitstreamCount<=0) fail("No test bitstream found for Item in testRestoreItem()!"); // Ensure item AIP is exported (but don't overwrite) log.info("testRestoreItem() - CREATE Item AIP"); File aipFile = createAIP(testItem, null, false, false); // Get parent, so we can restore under the same parent Collection parent = (Collection) testItem.getParentObject(); // Now, delete that item log.info("testRestoreItem() - DELETE Item"); parent.removeItem(testItem); context.commit(); // Assert the deleted item no longer exists DSpaceObject obj = HandleManager.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); // Commit these changes to our DB context.commit(); // Assert the deleted item is RESTORED DSpaceObject objRestored = HandleManager.resolveToObject(context, testItemHandle); assertThat("testRestoreItem() item " + testItemHandle + " exists", objRestored, notNullValue()); // Assert Bitstream exists again & is associated with restored item Bundle[] restoredBund = ((Item) objRestored).getBundles(Constants.CONTENT_BUNDLE_NAME); Bitstream restoredBitstream = restoredBund[0].getBitstreamByName(bitstreamName); assertThat("testRestoreItem() bitstream exists", restoredBitstream, notNullValue()); assertEquals("testRestoreItem() bitstream checksum", restoredBitstream.getChecksum(), bitstreamCheckSum); log.info("testRestoreItem() - END"); } /** * Test replacement from AIP of an Item object */ @Test public void testReplaceItem() throws Exception { new NonStrictExpectations(AuthorizeManager.class) {{ // Allow Full Admin permissions. Since we are working with an object // hierarchy (Items/Bundles/Bitstreams) you need full admin rights AuthorizeManager.isAdmin((Context) any); result = true; }}; log.info("testReplaceItem() - BEGIN"); // Locate the item (from our test data) Item testItem = (Item) HandleManager.resolveToObject(context, testItemHandle); // Get its current name / title String oldName = testItem.getName(); // Ensure item AIP is exported (but don't overwrite) log.info("testReplaceItem() - CREATE Item AIP"); File aipFile = createAIP(testItem, null, false, false); // Change the Item name String newName = "This is NOT my Item name!"; testItem.clearMetadata(MetadataSchema.DC_SCHEMA, "title", null, Item.ANY); testItem.addMetadata(MetadataSchema.DC_SCHEMA, "title", null, null, newName); // Commit these changes to our DB context.commit(); // Ensure name is changed assertEquals("testReplaceItem() new name", testItem.getName(), newName); // Now, replace our Item from AIP (non-recursive) replaceFromAIP(testItem, aipFile, null, false); // Commit these changes to our DB context.commit(); // 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(AuthorizeManager.class) {{ // Allow Full Admin permissions. Since we are working with an object // hierarchy (Items/Bundles/Bitstreams) you need full admin rights AuthorizeManager.isAdmin((Context) any); result = true; }}; log.info("testRestoreMappedItem() - BEGIN"); // Get a reference to our test mapped Item Item item = (Item) HandleManager.resolveToObject(context, testMappedItemHandle); // Get owning Collection Collection owner = item.getOwningCollection(); // Assert that it is in multiple collections Collection[] mappedCollections = item.getCollections(); assertEquals("testRestoreMappedItem() item " + testMappedItemHandle + " is mapped to multiple collections", 2, mappedCollections.length); // Ensure mapped item AIP is exported (but don't overwrite) log.info("testRestoreMappedItem() - CREATE Mapped Item AIP"); File aipFile = createAIP(item, null, false, false); // Now, delete that item (must be removed from BOTH collections to delete it) log.info("testRestoreMappedItem() - DELETE Item"); for(Collection c : mappedCollections) { c.removeItem(item); } context.commit(); // Assert the deleted item no longer exists DSpaceObject obj = HandleManager.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 context.commit(); // Assert the deleted item is RESTORED Item itemRestored = (Item) HandleManager.resolveToObject(context, testMappedItemHandle); assertThat("testRestoreMappedItem() item " + testMappedItemHandle + " exists", itemRestored, notNullValue()); // Test that this restored Item exists in multiple Collections Collection[] restoredMappings = itemRestored.getCollections(); assertEquals("testRestoreMappedItem() collection count", 2, restoredMappings.length); 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 * @param overwrite whether to overwrite the local AIP file if it is found * @return exported root AIP file */ private File createAIP(DSpaceObject dso, PackageParameters pkgParams, boolean recursive, boolean overwrite) throws PackageException, CrosswalkException, AuthorizeException, SQLException, IOException { // Get a reference to the configured "AIP" package disseminator PackageDisseminator dip = (PackageDisseminator) PluginManager .getNamedPlugin(PackageDisseminator.class, "AIP"); if (dip == null) { fail("Could not find a disseminator for type 'AIP'"); } // Export file (this is placed in JUnit's temporary folder, so that it can be cleaned up after tests complete) File exportAIPFile = new File(testFolder.getRoot().getAbsolutePath() + File.separator + PackageUtils.getPackageName(dso, "zip")); // To save time, we'll skip re-exporting AIPs, unless overwrite == true if(!exportAIPFile.exists() || overwrite) { // 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 { // Get a reference to the configured "AIP" package ingestor PackageIngester sip = (PackageIngester) PluginManager .getNamedPlugin(PackageIngester.class, "AIP"); if(sip == null) { fail("Could not find a ingestor for type 'AIP'"); } 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 { // Get a reference to the configured "AIP" package ingestor PackageIngester sip = (PackageIngester) PluginManager .getNamedPlugin(PackageIngester.class, "AIP"); if (sip == null) { fail("Could not find a ingestor for type 'AIP'"); } 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 */ 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(), community.getTypeText() + valueseparator + community.getName()); // Recursively call method for each SubCommunity Community[] subCommunities = community.getSubcommunities(); for(Community c : subCommunities) { saveObjectInfo(c, infoMap); } // Recursively call method for each Collection 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(), collection.getTypeText() + valueseparator + collection.getName()); // Recursively call method for each Item in Collection ItemIterator items = collection.getItems(); 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(), item.getTypeText() + 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 */ 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 = HandleManager.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", obj.getTypeText(), 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 */ 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 = HandleManager.resolveToObject(context, key); assertThat("assertObjectsNotExist object " + key + " (info=" + infoMap.get(key) + ") doesn't exist", obj, nullValue()); } } }