/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE file at the root of the source * tree and available online at * * https://github.com/keeps/roda */ package org.roda.core.model; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.testng.Assert.fail; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertNull; import static org.testng.AssertJUnit.assertTrue; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.apache.xmlbeans.XmlException; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.hamcrest.core.Is; import org.roda.core.CorporaConstants; import org.roda.core.RodaCoreFactory; import org.roda.core.TestsHelper; import org.roda.core.common.PremisV3Utils; import org.roda.core.common.iterables.CloseableIterable; import org.roda.core.common.iterables.CloseableIterables; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.exceptions.AlreadyExistsException; import org.roda.core.data.exceptions.GenericException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RODAException; import org.roda.core.data.v2.LiteOptionalWithCause; import org.roda.core.data.v2.LiteRODAObject; import org.roda.core.data.v2.common.OptionalWithCause; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.File; import org.roda.core.data.v2.ip.Representation; import org.roda.core.data.v2.ip.StoragePath; import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.data.v2.ip.metadata.DescriptiveMetadata; import org.roda.core.data.v2.log.LogEntry; import org.roda.core.data.v2.log.LogEntry.LOG_ENTRY_STATE; import org.roda.core.data.v2.log.LogEntryParameter; import org.roda.core.data.v2.user.Group; import org.roda.core.data.v2.user.User; import org.roda.core.model.utils.ModelUtils; import org.roda.core.storage.Binary; import org.roda.core.storage.DefaultStoragePath; import org.roda.core.storage.StorageService; import org.roda.core.storage.StorageTestUtils; import org.roda.core.storage.fs.FSUtils; import org.roda.core.storage.fs.FileStorageService; import org.roda.core.util.IdUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.google.common.collect.Iterables; import gov.loc.premis.v3.AgentComplexType; import gov.loc.premis.v3.EventComplexType; import gov.loc.premis.v3.ObjectCharacteristicsComplexType; import gov.loc.premis.v3.ObjectIdentifierComplexType; import jersey.repackaged.com.google.common.collect.Lists; /** * Unit tests for ModelService * * @author Hélder Silva <hsilva@keep.pt> * @author Luis Faria <lfaria@keep.pt> * * @see ModelService */ // @PrepareForTest({}) @Test(groups = {RodaConstants.TEST_GROUP_ALL, RodaConstants.TEST_GROUP_TRAVIS}) public class ModelServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(ModelServiceTest.class); private static final String ROLE1 = RodaConstants.REPOSITORY_PERMISSIONS_DESCRIPTIVE_METADATA_UPDATE; private static final String ROLE2 = RodaConstants.REPOSITORY_PERMISSIONS_LOG_ENTRY_READ; private static Path basePath; private static Path logPath; private static StorageService storage; private static ModelService model; private static Path corporaPath; private static StorageService corporaService; private static int fileCounter = 0; @BeforeClass public static void setUp() throws IOException, URISyntaxException, GenericException { URL corporaURL = ModelServiceTest.class.getResource("/corpora"); corporaPath = Paths.get(corporaURL.toURI()); corporaService = new FileStorageService(corporaPath); LOGGER.debug("Running model test under storage: {}", basePath); } // @BeforeMethod @BeforeClass public void init() throws IOException, GenericException { basePath = TestsHelper.createBaseTempDir(getClass(), true); boolean deploySolr = false; boolean deployLdap = true; boolean deployFolderMonitor = true; boolean deployOrchestrator = false; boolean deployPluginManager = false; boolean deployDefaultResources = false; RodaCoreFactory.instantiateTest(deploySolr, deployLdap, deployFolderMonitor, deployOrchestrator, deployPluginManager, deployDefaultResources); logPath = RodaCoreFactory.getLogPath(); storage = RodaCoreFactory.getStorageService(); model = RodaCoreFactory.getModelService(); } // @AfterMethod @AfterClass public void cleanup() throws NotFoundException, GenericException, IOException { RodaCoreFactory.shutdown(); FSUtils.deletePath(basePath); } @Test public void testCreateAIP() throws RODAException, ParseException, IOException, XmlException { // generate AIP ID final String aipId = CorporaConstants.SOURCE_AIP_ID; // testing AIP final AIP aip = model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); assertNotNull(aip); assertEquals(aipId, aip.getId()); assertNull("AIP_1 should not have a parent", aip.getParentId()); assertThat(aip.getState(), Is.is(AIPState.ACTIVE)); List<String> descriptiveMetadataIds = aip.getDescriptiveMetadata().stream().map(dm -> dm.getId()) .collect(Collectors.toList()); assertThat(descriptiveMetadataIds, containsInAnyOrder(CorporaConstants.DESCRIPTIVE_METADATA_ID)); List<String> representationIds = aip.getRepresentations().stream().map(rep -> rep.getId()) .collect(Collectors.toList()); assertThat(representationIds, containsInAnyOrder(CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_2_ID)); // testing descriptive metadata final DescriptiveMetadata descMetadata = model.retrieveDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID); assertEquals(aipId, descMetadata.getAipId()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_ID, descMetadata.getId()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_TYPE, descMetadata.getType()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_VERSION, descMetadata.getVersion()); StoragePath descriptiveMetadataPath = ModelUtils.getDescriptiveMetadataStoragePath(descMetadata.getAipId(), descMetadata.getId()); final Binary descMetadataBinary = storage.getBinary(descriptiveMetadataPath); assertTrue(descMetadataBinary.getSizeInBytes() > 0); assertEquals(descMetadataBinary.getSizeInBytes().intValue(), IOUtils.toByteArray(descMetadataBinary.getContent().createInputStream()).length); // testing representations final Representation representation1 = model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); assertEquals(aipId, representation1.getAipId()); assertEquals(CorporaConstants.REPRESENTATION_1_ID, representation1.getId()); assertEquals(CorporaConstants.REPRESENTATION_1_ORIGINAL, representation1.isOriginal()); CloseableIterable<OptionalWithCause<File>> allRep1Files = model.listFilesUnder(aipId, CorporaConstants.REPRESENTATION_1_ID, true); List<String> allRep1FileIds = Lists.newArrayList(allRep1Files).stream().filter(f -> f.isPresent()) .map(f -> f.get().getId()).collect(Collectors.toList()); allRep1Files.close(); assertThat(allRep1FileIds, containsInAnyOrder(CorporaConstants.REPRESENTATION_1_FILE_1_ID, CorporaConstants.REPRESENTATION_1_FILE_2_ID)); final Representation representation2 = model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_2_ID); assertEquals(aipId, representation2.getAipId()); assertEquals(CorporaConstants.REPRESENTATION_2_ID, representation2.getId()); assertEquals(CorporaConstants.REPRESENTATION_2_ORIGINAL, representation2.isOriginal()); CloseableIterable<OptionalWithCause<File>> allRep2Files = model.listFilesUnder(aipId, CorporaConstants.REPRESENTATION_2_ID, true); List<String> allRep2FileIds = Lists.newArrayList(allRep2Files).stream().filter(f -> f.isPresent()) .map(f -> f.get().getId()).collect(Collectors.toList()); allRep2Files.close(); assertThat(allRep2FileIds, containsInAnyOrder(CorporaConstants.REPRESENTATION_2_FILE_1_ID, CorporaConstants.REPRESENTATION_2_FILE_2_ID)); // testing files final File file_1_1 = model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID); assertEquals(aipId, file_1_1.getAipId()); assertEquals(CorporaConstants.REPRESENTATION_1_ID, file_1_1.getRepresentationId()); assertEquals(CorporaConstants.REPRESENTATION_1_FILE_1_ID, file_1_1.getId()); final Binary binary_1_1 = storage.getBinary(ModelUtils.getFileStoragePath(file_1_1)); assertTrue(binary_1_1.getSizeInBytes() > 0); assertEquals(binary_1_1.getSizeInBytes().intValue(), IOUtils.toByteArray(binary_1_1.getContent().createInputStream()).length); final File file_1_2 = model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_2_PATH, CorporaConstants.REPRESENTATION_1_FILE_2_ID); assertEquals(aipId, file_1_2.getAipId()); assertEquals(CorporaConstants.REPRESENTATION_1_ID, file_1_2.getRepresentationId()); assertEquals(CorporaConstants.REPRESENTATION_1_FILE_2_ID, file_1_2.getId()); final Binary binary_1_2 = storage.getBinary(ModelUtils.getFileStoragePath(file_1_2)); assertTrue(binary_1_2.getSizeInBytes() > 0); assertEquals(binary_1_2.getSizeInBytes().intValue(), IOUtils.toByteArray(binary_1_2.getContent().createInputStream()).length); final File file_2_1 = model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_2_ID, CorporaConstants.REPRESENTATION_2_FILE_1_PATH, CorporaConstants.REPRESENTATION_2_FILE_1_ID); assertEquals(aipId, file_2_1.getAipId()); assertEquals(CorporaConstants.REPRESENTATION_2_ID, file_2_1.getRepresentationId()); assertEquals(CorporaConstants.REPRESENTATION_2_FILE_1_ID, file_2_1.getId()); final Binary binary_2_1 = storage.getBinary(ModelUtils.getFileStoragePath(file_2_1)); assertTrue(binary_2_1.getSizeInBytes() > 0); assertEquals(binary_2_1.getSizeInBytes().intValue(), IOUtils.toByteArray(binary_2_1.getContent().createInputStream()).length); final File file_2_2 = model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_2_ID, CorporaConstants.REPRESENTATION_2_FILE_2_PATH, CorporaConstants.REPRESENTATION_2_FILE_2_ID); assertEquals(aipId, file_2_2.getAipId()); assertEquals(CorporaConstants.REPRESENTATION_2_ID, file_2_2.getRepresentationId()); assertEquals(CorporaConstants.REPRESENTATION_2_FILE_2_ID, file_2_2.getId()); final Binary binary_2_2 = storage.getBinary(ModelUtils.getFileStoragePath(file_2_2)); assertTrue(binary_2_2.getSizeInBytes() > 0); assertEquals(binary_2_2.getSizeInBytes().intValue(), IOUtils.toByteArray(binary_2_2.getContent().createInputStream()).length); // test preservation metadata Binary preservationObject = model.retrievePreservationRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); gov.loc.premis.v3.Representation rpo = PremisV3Utils.binaryToRepresentation(preservationObject.getContent(), true); ObjectIdentifierComplexType[] objectIdentifierArray = rpo.getObjectIdentifierArray(); assertEquals(RodaConstants.PREMIS_IDENTIFIER_TYPE_URN, objectIdentifierArray[0].getObjectIdentifierType().getStringValue()); assertEquals(CorporaConstants.REPRESENTATION_1_URN, rpo.getObjectIdentifierArray()[0].getObjectIdentifierValue()); assertEquals(CorporaConstants.PRESERVATION_LEVEL_FULL, rpo.getPreservationLevelArray(0).getPreservationLevelValue().getStringValue()); Binary f0_premis_bin = model.retrievePreservationFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID); gov.loc.premis.v3.File f0_premis_file = PremisV3Utils.binaryToFile(f0_premis_bin.getContent(), true); ObjectCharacteristicsComplexType f0_characteristics = f0_premis_file.getObjectCharacteristicsArray(0); assertEquals(0, f0_characteristics.getCompositionLevel().getBigIntegerValue().intValue()); assertEquals(0, f0_characteristics.getCompositionLevel().getBigIntegerValue().intValue()); assertEquals(f0_characteristics.getFormatArray(0).getFormatDesignation().getFormatName().getStringValue(), CorporaConstants.TEXT_XML); Binary event_premis_bin = model.retrievePreservationEvent(aipId, CorporaConstants.REPRESENTATION_1_ID, null, null, CorporaConstants.REPRESENTATION_1_PREMIS_EVENT_ID); EventComplexType event_premis = PremisV3Utils.binaryToEvent(event_premis_bin.getContent(), true); assertEquals(CorporaConstants.INGESTION, event_premis.getEventType().getStringValue()); assertEquals(CorporaConstants.SUCCESS, event_premis.getEventOutcomeInformationArray(0).getEventOutcome().getStringValue()); // cleanup model.deleteAIP(aipId); } @Test public void testCreateAIPVersionEAD3() throws RODAException, ParseException, IOException, XmlException { // generate AIP ID final String aipId = IdUtils.createUUID(); // testing AIP final AIP aip = model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_VERSION_EAD_3), RodaConstants.ADMIN); assertNotNull(aip); assertEquals(aipId, aip.getId()); assertNull("AIP_1 should not have a parent", aip.getParentId()); assertThat(aip.getState(), Is.is(AIPState.ACTIVE)); List<String> descriptiveMetadataIds = aip.getDescriptiveMetadata().stream().map(dm -> dm.getId()) .collect(Collectors.toList()); assertThat(descriptiveMetadataIds, containsInAnyOrder(CorporaConstants.DESCRIPTIVE_METADATA_ID_EAD3)); List<String> representationIds = aip.getRepresentations().stream().map(rep -> rep.getId()) .collect(Collectors.toList()); assertThat(representationIds, containsInAnyOrder(CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_2_ID)); // testing descriptive metadata final DescriptiveMetadata descMetadata = model.retrieveDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID_EAD3); assertEquals(aipId, descMetadata.getAipId()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_ID_EAD3, descMetadata.getId()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_TYPE_EAD, descMetadata.getType()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_TYPE_EAD_VERSION3, descMetadata.getVersion()); StoragePath descriptiveMetadataPath = ModelUtils.getDescriptiveMetadataStoragePath(descMetadata.getAipId(), descMetadata.getId()); final Binary descMetadataBinary = storage.getBinary(descriptiveMetadataPath); assertTrue(descMetadataBinary.getSizeInBytes() > 0); assertEquals(descMetadataBinary.getSizeInBytes().intValue(), IOUtils.toByteArray(descMetadataBinary.getContent().createInputStream()).length); // cleanup model.deleteAIP(aipId); } @Test public void testCreateAIPVersionUnknown() throws RODAException, ParseException, IOException, XmlException { // generate AIP ID final String aipId = IdUtils.createUUID(); // testing AIP final AIP aip = model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_VERSION_EAD_UNKNOWN), RodaConstants.ADMIN); assertNotNull(aip); assertEquals(aipId, aip.getId()); assertNull("AIP_1 should not have a parent", aip.getParentId()); assertThat(aip.getState(), Is.is(AIPState.ACTIVE)); List<String> descriptiveMetadataIds = aip.getDescriptiveMetadata().stream().map(dm -> dm.getId()) .collect(Collectors.toList()); assertThat(descriptiveMetadataIds, containsInAnyOrder(CorporaConstants.DESCRIPTIVE_METADATA_ID_EADUNKNOWN)); List<String> representationIds = aip.getRepresentations().stream().map(rep -> rep.getId()) .collect(Collectors.toList()); assertThat(representationIds, containsInAnyOrder(CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_2_ID)); // testing descriptive metadata final DescriptiveMetadata descMetadata = model.retrieveDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID_EADUNKNOWN); assertEquals(aipId, descMetadata.getAipId()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_ID_EADUNKNOWN, descMetadata.getId()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_TYPE_EAD, descMetadata.getType()); assertEquals(CorporaConstants.DESCRIPTIVE_METADATA_TYPE_EAD_VERSIONUNKNOWN, descMetadata.getVersion()); StoragePath descriptiveMetadataPath = ModelUtils.getDescriptiveMetadataStoragePath(descMetadata.getAipId(), descMetadata.getId()); final Binary descMetadataBinary = storage.getBinary(descriptiveMetadataPath); assertTrue(descMetadataBinary.getSizeInBytes() > 0); assertEquals(descMetadataBinary.getSizeInBytes().intValue(), IOUtils.toByteArray(descMetadataBinary.getContent().createInputStream()).length); // cleanup model.deleteAIP(aipId); } @Test public void testCreateAIPWithSubFolders() throws RODAException, ParseException, IOException { // generate AIP ID final String aipId = IdUtils.createUUID(); // testing AIP model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_REP_WITH_SUBFOLDERS), RodaConstants.ADMIN); CloseableIterable<OptionalWithCause<File>> allFiles = model.listFilesUnder(aipId, CorporaConstants.REPRESENTATION_1_ID, true); List<File> reusableList = new ArrayList<>(); Iterables.addAll(reusableList, Lists.newArrayList(allFiles).stream().filter(f -> f.isPresent()).map(f -> f.get()).collect(Collectors.toList())); allFiles.close(); assertTrue(reusableList.contains( new File("2012-roda-promo-en.pdf", aipId, CorporaConstants.REPRESENTATION_1_ID, new ArrayList<>(), false))); assertTrue( reusableList.contains(new File("folder", aipId, CorporaConstants.REPRESENTATION_1_ID, new ArrayList<>(), true))); assertTrue(reusableList .contains(new File("subfolder", aipId, CorporaConstants.REPRESENTATION_1_ID, Arrays.asList("folder"), true))); assertTrue(reusableList.contains(new File("RODA 2 logo.svg", aipId, CorporaConstants.REPRESENTATION_1_ID, Arrays.asList("folder", "subfolder"), false))); assertTrue(reusableList.contains(new File("RODA 2 logo-circle-black.svg", aipId, CorporaConstants.REPRESENTATION_1_ID, Arrays.asList("folder"), false))); assertTrue(reusableList.contains(new File("RODA 2 logo-circle-white.svg", aipId, CorporaConstants.REPRESENTATION_1_ID, Arrays.asList("folder"), false))); // cleanup model.deleteAIP(aipId); } @Test public void testDeleteAIP() throws RODAException { // generate AIP ID final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); model.deleteAIP(aipId); // check if AIP was indeed deleted try { model.retrieveAIP(aipId); Assert.fail("AIP should have been deleted, but yet was retrieved."); } catch (NotFoundException e) { // do nothing as it was expected } // check if AIP sub-resources were recursively deleted try { model.retrieveDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID); Assert.fail("Descriptive metadata binary should have been deleted, but yet was retrieved."); } catch (NotFoundException e) { // do nothing as it was expected } try { model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); Assert.fail("Descriptive metadata binary should have been deleted, but yet was retrieved."); } catch (NotFoundException e) { // do nothing as it was expected } try { model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_2_ID); Assert.fail("Representation should have been deleted, but yet was retrieved."); } catch (NotFoundException e) { // do nothing as it was expected } try { model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID); Assert.fail("File should have been deleted, but yet was retrieved."); } catch (NotFoundException e) { // do nothing as it was expected } } // TODO test if deleting AIP also deletes all sub-AIPs @Test public void testListAIPs() throws RODAException { // generate AIP ID final String aip1Id = IdUtils.createUUID(); final String aip2Id = IdUtils.createUUID(); final String aip3Id = IdUtils.createUUID(); final AIP aip1 = model.createAIP(aip1Id, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final AIP aip2 = model.createAIP(aip2Id, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final AIP aip3 = model.createAIP(aip3Id, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); Iterable<OptionalWithCause<AIP>> listAIPs = model.listAIPs(); List<AIP> reusableList = new ArrayList<>(); Iterables.addAll(reusableList, Lists.newArrayList(listAIPs).stream().filter(f -> f.isPresent()).map(f -> f.get()).collect(Collectors.toList())); assertThat(reusableList, containsInAnyOrder(aip1, aip2, aip3)); // cleanup model.deleteAIP(aip1Id); model.deleteAIP(aip2Id); model.deleteAIP(aip3Id); } @Test public void testCreateAIPWithExistingId() throws RODAException { // generate AIP ID final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); try { model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); Assert.fail("AIP shouldn't have been created and yet it was."); } catch (AlreadyExistsException e) { // do nothing as it was expected } // cleanup model.deleteAIP(aipId); } @Test public void testUpdateAIP() throws RODAException, IOException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); StoragePath otherAipPath = DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.OTHER_AIP_ID); AIP updatedAIP = model.updateAIP(aipId, corporaService, otherAipPath, RodaConstants.ADMIN); // check it is connected AIP retrievedAIP = model.retrieveAIP(aipId); assertEquals(updatedAIP, retrievedAIP); // check content is correct StorageTestUtils.testEntityEqualRecursively(corporaService, otherAipPath, storage, ModelUtils.getAIPStoragePath(aipId)); // cleanup model.deleteAIP(aipId); } @Test public void testListDescriptiveMetadata() throws RODAException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); Iterable<DescriptiveMetadata> list = model.retrieveAIP(aipId).getDescriptiveMetadata(); DescriptiveMetadata descriptiveMetadata1 = model.retrieveDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID); assertThat(list, containsInAnyOrder(descriptiveMetadata1)); // cleanup model.deleteAIP(aipId); } @Test public void testCreateDescriptiveMetadata() throws RODAException, IOException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final String newDescriptiveMetadataId = IdUtils.createUUID(); final Binary binary = corporaService .getBinary(DefaultStoragePath.parse(CorporaConstants.OTHER_DESCRIPTIVE_METADATA_STORAGEPATH)); final DescriptiveMetadata newDescriptiveMetadata = model.createDescriptiveMetadata(aipId, newDescriptiveMetadataId, binary.getContent(), CorporaConstants.OTHER_DESCRIPTIVE_METADATA_TYPE, CorporaConstants.OTHER_DESCRIPTIVE_METADATA_VERSION); // check if it is connected DescriptiveMetadata retrievedDescriptiveMetadata = model.retrieveDescriptiveMetadata(aipId, newDescriptiveMetadataId); assertEquals(newDescriptiveMetadata, retrievedDescriptiveMetadata); // check content Binary newDescriptiveMetadataBinary = storage .getBinary(ModelUtils.getDescriptiveMetadataStoragePath(newDescriptiveMetadata)); assertTrue(IOUtils.contentEquals(binary.getContent().createInputStream(), newDescriptiveMetadataBinary.getContent().createInputStream())); // cleanup model.deleteAIP(aipId); } @Test public void testUpdateDescriptiveMetadata() throws RODAException, IOException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final Binary binary = corporaService .getBinary(DefaultStoragePath.parse(CorporaConstants.OTHER_DESCRIPTIVE_METADATA_STORAGEPATH)); Map<String, String> properties = new HashMap<>(); properties.put(RodaConstants.VERSION_ACTION, RodaConstants.VersionAction.UPDATED.toString()); final DescriptiveMetadata updatedDescriptiveMetadata = model.updateDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID, binary.getContent(), CorporaConstants.OTHER_DESCRIPTIVE_METADATA_TYPE, CorporaConstants.OTHER_DESCRIPTIVE_METADATA_VERSION, properties); // check if it is connected DescriptiveMetadata retrievedDescriptiveMetadata = model.retrieveDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID); assertEquals(updatedDescriptiveMetadata, retrievedDescriptiveMetadata); // check content StoragePath storagePath = ModelUtils.getDescriptiveMetadataStoragePath(updatedDescriptiveMetadata); Binary updatedDescriptiveMetadataBinary = storage.getBinary(storagePath); assertTrue(IOUtils.contentEquals(binary.getContent().createInputStream(), updatedDescriptiveMetadataBinary.getContent().createInputStream())); // check if binary version was created assertEquals(1, Iterables.size(storage.listBinaryVersions(storagePath))); // check if binary version message collisions are well treated model.updateDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID, binary.getContent(), CorporaConstants.OTHER_DESCRIPTIVE_METADATA_TYPE, CorporaConstants.OTHER_DESCRIPTIVE_METADATA_VERSION, properties); model.updateDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID, binary.getContent(), CorporaConstants.OTHER_DESCRIPTIVE_METADATA_TYPE, CorporaConstants.OTHER_DESCRIPTIVE_METADATA_VERSION, properties); model.updateDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID, binary.getContent(), CorporaConstants.OTHER_DESCRIPTIVE_METADATA_TYPE, CorporaConstants.OTHER_DESCRIPTIVE_METADATA_VERSION, properties); assertEquals(4, Iterables.size(storage.listBinaryVersions(storagePath))); // cleanup model.deleteAIP(aipId); } @Test public void testDeleteDescriptiveMetadata() throws RODAException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); model.deleteDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID); // check if it deleted try { model.retrieveDescriptiveMetadata(aipId, CorporaConstants.DESCRIPTIVE_METADATA_ID); Assert.fail("Descriptive metadata deleted and yet exists."); } catch (NotFoundException e) { // do nothing as it was expected } // cleanup model.deleteAIP(aipId); } @Test public void testListRepresentations() throws RODAException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final Representation representation1 = model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); final Representation representation2 = model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_2_ID); final Iterable<Representation> list = model.retrieveAIP(aipId).getRepresentations(); assertThat(list, containsInAnyOrder(representation1, representation2)); CloseableIterable<OptionalWithCause<Representation>> list2 = model.list(Representation.class); assertThat(Iterables.transform(list2, i -> i.get()), containsInAnyOrder(representation1, representation2)); // cleanup model.deleteAIP(aipId); } @Test public void testCreateRepresentation() throws RODAException, IOException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final String newRepresentationId = IdUtils.createUUID(); final StoragePath corporaRepresentationPath = DefaultStoragePath .parse(CorporaConstants.OTHER_REPRESENTATION_STORAGEPATH); Representation createdRepresentation = model.createRepresentation(aipId, newRepresentationId, CorporaConstants.REPRESENTATION_1_ORIGINAL, CorporaConstants.REPRESENTATION_1_TYPE, corporaService, DefaultStoragePath.parse(CorporaConstants.OTHER_REPRESENTATION_STORAGEPATH), false); // check if it is connected Representation retrievedRepresentation = model.retrieveRepresentation(aipId, newRepresentationId); assertEquals(createdRepresentation, retrievedRepresentation); // check content StorageTestUtils.testEntityEqualRecursively(corporaService, corporaRepresentationPath, storage, ModelUtils.getRepresentationStoragePath(aipId, newRepresentationId)); // cleanup model.deleteAIP(aipId); } @Test public void testUpdateRepresentation() throws RODAException, IOException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final StoragePath corporaRepresentationPath = DefaultStoragePath .parse(CorporaConstants.OTHER_REPRESENTATION_STORAGEPATH); Representation updatedRepresentation = model.updateRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_ORIGINAL, CorporaConstants.REPRESENTATION_1_TYPE, corporaService, DefaultStoragePath.parse(CorporaConstants.OTHER_REPRESENTATION_STORAGEPATH)); // check if it is connected Representation retrievedRepresentation = model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); assertEquals(updatedRepresentation, retrievedRepresentation); // check content StorageTestUtils.testEntityEqualRecursively(corporaService, corporaRepresentationPath, storage, ModelUtils.getRepresentationStoragePath(aipId, CorporaConstants.REPRESENTATION_1_ID)); // cleanup model.deleteAIP(aipId); } @Test public void testDeleteRepresentation() throws RODAException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); model.deleteRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); // check if it deleted try { model.retrieveRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); Assert.fail("Representation deleted and yet exists."); } catch (NotFoundException e) { // do nothing as it was expected } // check if file under representation was deleted try { model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID); Assert.fail("Representation deleted and yet one of its files still exist."); } catch (NotFoundException e) { // do nothing as it was expected } // cleanup model.deleteAIP(aipId); } @Test public void testCreateFile() throws RODAException, IOException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final String newFileId = IdUtils.createUUID(); final List<String> newFileDirectoryPath = new ArrayList<>(); final StoragePath corporaFilePath = DefaultStoragePath.parse(CorporaConstants.OTHER_FILE_STORAGEPATH); final Binary binary = corporaService.getBinary(corporaFilePath); File createdFile = model.createFile(aipId, CorporaConstants.REPRESENTATION_1_ID, newFileDirectoryPath, newFileId, binary.getContent(), true); // check if it is connected File retrievedFile = model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_1_ID, newFileDirectoryPath, newFileId); assertEquals(createdFile, retrievedFile); // check content Binary createdFileBinary = storage.getBinary(ModelUtils.getFileStoragePath(createdFile)); assertTrue(IOUtils.contentEquals(binary.getContent().createInputStream(), createdFileBinary.getContent().createInputStream())); // cleanup model.deleteAIP(aipId); } @Test public void testUpdateFile() throws RODAException, IOException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); final StoragePath corporaFilePath = DefaultStoragePath.parse(CorporaConstants.OTHER_FILE_STORAGEPATH); final Binary binary = corporaService.getBinary(corporaFilePath); final boolean createIfNotExists = false; final boolean notify = false; File createdFile = model.updateFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID, binary.getContent(), createIfNotExists, notify); // check if it is connected File retrievedFile = model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID); assertEquals(createdFile, retrievedFile); // check content Binary createdFileBinary = storage.getBinary(ModelUtils.getFileStoragePath(createdFile)); assertTrue(IOUtils.contentEquals(binary.getContent().createInputStream(), createdFileBinary.getContent().createInputStream())); // cleanup model.deleteAIP(aipId); } @Test public void testDeleteFile() throws RODAException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); model.deleteFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID, true); // check if it deleted try { model.retrieveFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID); Assert.fail("File deleted and yet exists."); } catch (NotFoundException e) { // do nothing } // cleanup model.deleteAIP(aipId); } @Test public void testRetrieveEventPreservationObject() throws RODAException { // set up final String aipId = IdUtils.createUUID(); model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); Binary event_bin = model.retrievePreservationEvent(aipId, CorporaConstants.REPRESENTATION_1_ID, null, null, CorporaConstants.REPRESENTATION_1_PREMIS_EVENT_ID); EventComplexType event = PremisV3Utils.binaryToEvent(event_bin.getContent(), true); assertEquals(CorporaConstants.AGENT_RODA_8, event.getLinkingAgentIdentifierArray(0).getLinkingAgentIdentifierValue()); assertEquals(CorporaConstants.INGESTION, event.getEventType().getStringValue()); } @Test public void testRepresentationFileObject() throws RODAException { // set up final String aipId = CorporaConstants.SOURCE_AIP_ID; model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); Binary file_bin = model.retrievePreservationFile(aipId, CorporaConstants.REPRESENTATION_1_ID, CorporaConstants.REPRESENTATION_1_FILE_1_PATH, CorporaConstants.REPRESENTATION_1_FILE_1_ID); gov.loc.premis.v3.File file = PremisV3Utils.binaryToFile(file_bin.getContent(), true); ObjectCharacteristicsComplexType file_characteristics = file.getObjectCharacteristicsArray(0); assertEquals(2, file_characteristics.getFixityArray().length); assertEquals(CorporaConstants.METS_XML, file.getOriginalName().getStringValue()); // cleanup model.deleteAIP(aipId); } @Test public void testRepresentationPreservationObject() throws RODAException { // set up final String aipId = CorporaConstants.SOURCE_AIP_ID; model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); Binary representation_bin = model.retrievePreservationRepresentation(aipId, CorporaConstants.REPRESENTATION_1_ID); gov.loc.premis.v3.Representation representation = PremisV3Utils .binaryToRepresentation(representation_bin.getContent(), true); assertEquals(representation.getPreservationLevelArray(0).getPreservationLevelValue().getStringValue(), CorporaConstants.PRESERVATION_LEVEL_FULL); // cleanup model.deleteAIP(aipId); } @Test public void getAgentPreservationObject() throws RODAException { // pre-load the preservation container data DefaultStoragePath preservationContainerPath = DefaultStoragePath .parse(CorporaConstants.SOURCE_PRESERVATION_CONTAINER); storage.deleteContainer(preservationContainerPath); storage.copy(corporaService, preservationContainerPath, DefaultStoragePath.parse(CorporaConstants.SOURCE_PRESERVATION_CONTAINER)); Binary agent_bin = model.retrievePreservationAgent(CorporaConstants.AGENT_RODA_8); AgentComplexType agent = PremisV3Utils.binaryToAgent(agent_bin.getContent(), true); assertEquals(CorporaConstants.AGENT_RODA_8, agent.getAgentIdentifierArray(0).getAgentIdentifierValue()); assertEquals(CorporaConstants.SOFTWARE_INGEST_TASK, agent.getAgentType().getStringValue()); assertEquals(CorporaConstants.INGEST_CREATE_AIP, agent.getAgentNameArray(0).getStringValue()); } @Test public void createLogEntry() throws RODAException { // setup createLogActionDirectory(); LogEntry entry = new LogEntry(); entry.setActionComponent("Action"); entry.setActionMethod("Method"); entry.setAddress("Address"); entry.setId("ID"); entry.setDatetime(new Date()); entry.setState(LOG_ENTRY_STATE.SUCCESS); List<LogEntryParameter> parameters = new ArrayList<>(); parameters.add(new LogEntryParameter("NAME1", "VALUE1")); parameters.add(new LogEntryParameter("NAME2", "VALUE2")); entry.setParameters(parameters); model.addLogEntry(entry, logPath); } private void createLogActionDirectory() { try { Files.createDirectories(logPath); } catch (IOException e) { } } @Test public void testMemberInheritance() throws RODAException { // create group 1 Group group1 = new Group("group-1"); group1.setActive(true); group1.setFullName("NAMEGROUP1"); group1.setDirectRoles(new HashSet<>(Arrays.asList(ROLE1))); model.createGroup(group1, true); // gen. asserts for group 1 Group retrievedGroup1 = model.retrieveGroup(group1.getId()); Assert.assertNotNull(retrievedGroup1); // create group 2 Group group2 = new Group("group2"); group2.setActive(true); group2.setFullName("NAMEGROUP2"); group2.setDirectRoles(new HashSet<>(Arrays.asList(ROLE2))); model.createGroup(group2, true); // gen. asserts for group 2 Group retrievedGroup2 = model.retrieveGroup(group2.getId()); Assert.assertNotNull(retrievedGroup2); MatcherAssert.assertThat(retrievedGroup2.getAllRoles(), Matchers.containsInAnyOrder(ROLE2)); // create user 1 User user = new User("user_1"); user.setActive(true); user.setEmail("user1@example.com"); user.setGuest(false); user.setFullName("user1"); user.addGroup(group1.getId()); user.addGroup(group2.getId()); model.createUser(user, true); // gen. asserts for user 1 User retrievedUser = model.retrieveUserByName(user.getId()); Assert.assertNotNull(retrievedUser); MatcherAssert.assertThat(retrievedUser.getGroups(), Matchers.containsInAnyOrder(group1.getId(), group2.getId())); MatcherAssert.assertThat(retrievedUser.getAllRoles(), Matchers.containsInAnyOrder(ROLE1, ROLE2)); // modify group 2 groups model.deleteGroup(group1.getId(), false); try { model.retrieveGroup(group1.getId()); Assert.fail("should have not found exception"); } catch (NotFoundException e) { // expected } User retrievedUser2 = model.retrieveUserByName(user.getId()); Assert.assertNotNull(retrievedUser2); MatcherAssert.assertThat(retrievedUser2.getGroups(), Matchers.containsInAnyOrder(group2.getId())); MatcherAssert.assertThat(retrievedUser2.getGroups(), Matchers.not(Matchers.containsInAnyOrder(group1.getId()))); MatcherAssert.assertThat(retrievedUser2.getAllRoles(), Matchers.containsInAnyOrder(ROLE2)); MatcherAssert.assertThat(retrievedUser2.getAllRoles(), Matchers.not(Matchers.containsInAnyOrder(ROLE1))); } @Test public void testListing() throws RODAException, IOException { populate(RodaCoreFactory.getTransferredResourcesScanner().getBasePath()); CloseableIterable<OptionalWithCause<LiteRODAObject>> list = model.listLite(TransferredResource.class); int size = CloseableIterables.size(list); assertEquals(fileCounter, size); } @Test public void testLiteOptionalWithCauseSerialization() throws RODAException, IOException, ClassNotFoundException { final String aipId = CorporaConstants.SOURCE_AIP_ID; AIP aip = model.createAIP(aipId, corporaService, DefaultStoragePath.parse(CorporaConstants.SOURCE_AIP_CONTAINER, CorporaConstants.SOURCE_AIP_ID), RodaConstants.ADMIN); Optional<LiteRODAObject> lightAIP = model.retrieveLiteFromObject(aip); // cleanup model.deleteAIP(aipId); if (lightAIP.isPresent()) { LiteOptionalWithCause test = LiteOptionalWithCause.of(lightAIP.get()); Path tempFile = Files.createTempFile(basePath, "test", ".tmp"); try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(tempFile))) { oos.writeObject(test); } try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(tempFile))) { LiteOptionalWithCause test2 = (LiteOptionalWithCause) ois.readObject(); assertEquals(test, test2); } } else { fail("Should be present"); } } private static void populate(Path basePath) throws IOException { Random randomno = new Random(); int numberOfItemsByLevel = nextIntInRange(2, 3, randomno); int numberOfLevels = nextIntInRange(2, 3, randomno); populate(basePath, numberOfItemsByLevel, numberOfLevels, 0, randomno); } private static void populate(Path path, int numberOfItemsByLevel, int numberOfLevels, int currentLevel, Random randomno) throws IOException { currentLevel++; for (int i = 0; i < numberOfItemsByLevel; i++) { Path p; if (i % 2 == 0) { if (currentLevel > 1) { p = Files.createFile(path.resolve(IdUtils.createUUID() + ".txt")); Files.write(p, "NUNCAMAISACABA".getBytes()); fileCounter++; } } else { p = Files.createDirectory(path.resolve(IdUtils.createUUID())); fileCounter++; if (currentLevel <= numberOfLevels) { populate(p, numberOfItemsByLevel, numberOfLevels, currentLevel, randomno); } else { if (currentLevel > 1) { for (int j = 0; j < numberOfItemsByLevel; j++) { Path temp = Files.createFile(p.resolve(IdUtils.createUUID() + ".txt")); Files.write(temp, "NUNCAMAISACABA".getBytes()); fileCounter++; } } } } } } static int nextIntInRange(int min, int max, Random rng) { if (min > max) { throw new IllegalArgumentException("Cannot draw random int from invalid range [" + min + ", " + max + "]."); } int diff = max - min; if (diff >= 0 && diff != Integer.MAX_VALUE) { return (min + rng.nextInt(diff + 1)); } int i; do { i = rng.nextInt(); } while (i < min || i > max); return i; } }