/**
* 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.plugins;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.xmlbeans.XmlException;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.hamcrest.core.Is;
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.monitor.TransferredResourcesScanner;
import org.roda.core.data.common.RodaConstants;
import org.roda.core.data.common.RodaConstants.PreservationEventType;
import org.roda.core.data.exceptions.AlreadyExistsException;
import org.roda.core.data.exceptions.AuthorizationDeniedException;
import org.roda.core.data.exceptions.GenericException;
import org.roda.core.data.exceptions.InvalidParameterException;
import org.roda.core.data.exceptions.NotFoundException;
import org.roda.core.data.exceptions.RODAException;
import org.roda.core.data.exceptions.RequestNotValidException;
import org.roda.core.data.v2.IsRODAObject;
import org.roda.core.data.v2.common.OptionalWithCause;
import org.roda.core.data.v2.index.IndexResult;
import org.roda.core.data.v2.index.IndexRunnable;
import org.roda.core.data.v2.index.filter.Filter;
import org.roda.core.data.v2.index.filter.SimpleFilterParameter;
import org.roda.core.data.v2.index.select.SelectedItemsList;
import org.roda.core.data.v2.index.sublist.Sublist;
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.IndexedAIP;
import org.roda.core.data.v2.ip.IndexedFile;
import org.roda.core.data.v2.ip.Permissions;
import org.roda.core.data.v2.ip.TransferredResource;
import org.roda.core.data.v2.ip.metadata.IndexedPreservationEvent;
import org.roda.core.data.v2.ip.metadata.PreservationMetadata;
import org.roda.core.data.v2.ip.metadata.PreservationMetadata.PreservationMetadataType;
import org.roda.core.data.v2.jobs.Job;
import org.roda.core.data.v2.jobs.PluginType;
import org.roda.core.index.IndexService;
import org.roda.core.model.ModelService;
import org.roda.core.plugins.plugins.PluginHelper;
import org.roda.core.plugins.plugins.antivirus.AntivirusPlugin;
import org.roda.core.plugins.plugins.characterization.PremisSkeletonPlugin;
import org.roda.core.plugins.plugins.characterization.SiegfriedPlugin;
import org.roda.core.plugins.plugins.ingest.AutoAcceptSIPPlugin;
import org.roda.core.plugins.plugins.ingest.TransferredResourceToAIPPlugin;
import org.roda.core.storage.Binary;
import org.roda.core.storage.fs.FSUtils;
import org.roda.core.util.IdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.w3c.util.InvalidDateException;
import com.google.common.collect.Iterables;
import gov.loc.premis.v3.EventComplexType;
import gov.loc.premis.v3.FormatComplexType;
import gov.loc.premis.v3.FormatRegistryComplexType;
import gov.loc.premis.v3.LinkingAgentIdentifierComplexType;
import gov.loc.premis.v3.ObjectCharacteristicsComplexType;
import gov.loc.premis.v3.Representation;
import jersey.repackaged.com.google.common.collect.Lists;
@Test(groups = {RodaConstants.TEST_GROUP_ALL, RodaConstants.TEST_GROUP_TRAVIS})
public class InternalPluginsTest {
private static final String FAKE_REPORTING_CLASS = "NONE";
private static final Logger LOGGER = LoggerFactory.getLogger(InternalPluginsTest.class);
private static final int CORPORA_FILES_COUNT = 13;
private static final int CORPORA_FOLDERS_COUNT = 3;
private static final String CORPORA_PDF = "test.docx";
private static final String CORPORA_TEST1 = "test1";
private static final String CORPORA_TEST1_TXT = "test1.txt";
private static final int GENERATED_FILE_SIZE = 100;
private static Path basePath;
private static ModelService model;
private static IndexService index;
@BeforeClass
public void setUp() throws Exception {
basePath = TestsHelper.createBaseTempDir(getClass(), true,
PosixFilePermissions
.asFileAttribute(new HashSet<>(Arrays.asList(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE,
PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_EXECUTE))));
boolean deploySolr = true;
boolean deployLdap = true;
boolean deployFolderMonitor = true;
boolean deployOrchestrator = true;
boolean deployPluginManager = true;
boolean deployDefaultResources = false;
RodaCoreFactory.instantiateTest(deploySolr, deployLdap, deployFolderMonitor, deployOrchestrator,
deployPluginManager, deployDefaultResources);
model = RodaCoreFactory.getModelService();
index = RodaCoreFactory.getIndexService();
LOGGER.info("Running internal plugins tests under storage {}", basePath);
}
@AfterClass
public void tearDown() throws Exception {
RodaCoreFactory.shutdown();
FSUtils.deletePath(basePath);
}
@AfterMethod
public void cleanUp() throws RODAException {
// delete all AIPs
index.execute(IndexedAIP.class, Filter.ALL, new ArrayList<>(), new IndexRunnable<IndexedAIP>() {
@Override
public void run(IndexedAIP item) throws GenericException, RequestNotValidException, AuthorizationDeniedException {
try {
model.deleteAIP(item.getId());
} catch (NotFoundException e) {
// do nothing
}
}
});
// delete all Transferred Resources
index.execute(TransferredResource.class, Filter.ALL, new ArrayList<>(), new IndexRunnable<TransferredResource>() {
@Override
public void run(TransferredResource item)
throws GenericException, RequestNotValidException, AuthorizationDeniedException {
model.deleteTransferredResource(item);
}
});
}
private ByteArrayInputStream generateContentData() {
return new ByteArrayInputStream(RandomStringUtils.randomAscii(GENERATED_FILE_SIZE).getBytes());
}
private TransferredResource createCorpora() throws InterruptedException, IOException, NotFoundException,
GenericException, RequestNotValidException, AlreadyExistsException {
TransferredResourcesScanner f = RodaCoreFactory.getTransferredResourcesScanner();
String parentUUID = f.createFolder(null, "test").getUUID();
String test1UUID = f.createFolder(parentUUID, CORPORA_TEST1).getUUID();
String test2UUID = f.createFolder(parentUUID, "test2").getUUID();
String test3UUID = f.createFolder(parentUUID, "test3").getUUID();
f.createFile(parentUUID, CORPORA_TEST1_TXT, generateContentData());
f.createFile(parentUUID, "test2.txt", generateContentData());
f.createFile(parentUUID, "test3.txt", generateContentData());
f.createFile(test1UUID, CORPORA_TEST1_TXT, generateContentData());
f.createFile(test1UUID, "test2.txt", generateContentData());
f.createFile(test1UUID, "test3.txt", generateContentData());
f.createFile(test2UUID, CORPORA_TEST1_TXT, generateContentData());
f.createFile(test2UUID, "test2.txt", generateContentData());
f.createFile(test2UUID, "test3.txt", generateContentData());
f.createFile(test3UUID, CORPORA_TEST1_TXT, generateContentData());
f.createFile(test3UUID, "test2.txt", generateContentData());
f.createFile(test3UUID, "test3.txt", generateContentData());
f.createFile(parentUUID, CORPORA_PDF, getClass().getResourceAsStream("/corpora/Media/" + CORPORA_PDF));
index.commit(TransferredResource.class);
TransferredResource transferredResource = index.retrieve(TransferredResource.class, IdUtils.createUUID("test"),
new ArrayList<>());
return transferredResource;
}
private AIP ingestCorpora()
throws RequestNotValidException, NotFoundException, GenericException, AlreadyExistsException,
AuthorizationDeniedException, InvalidParameterException, InterruptedException, IOException, SolrServerException {
String parentId = null;
String aipType = RodaConstants.AIP_TYPE_MIXED;
AIP root = model.createAIP(parentId, aipType, new Permissions(), RodaConstants.ADMIN);
Map<String, String> parameters = new HashMap<>();
parameters.put(RodaConstants.PLUGIN_PARAMS_PARENT_ID, root.getId());
TransferredResource transferredResource = createCorpora();
AssertJUnit.assertNotNull(transferredResource);
Job job = TestsHelper.executeJob(TransferredResourceToAIPPlugin.class, parameters, PluginType.SIP_TO_AIP,
SelectedItemsList.create(TransferredResource.class, transferredResource.getUUID()));
TestsHelper.getJobReports(index, job, true);
index.commitAIPs();
IndexResult<IndexedAIP> find = index.find(IndexedAIP.class,
new Filter(new SimpleFilterParameter(RodaConstants.AIP_PARENT_ID, root.getId())), null, new Sublist(0, 10),
new ArrayList<>());
AssertJUnit.assertEquals(1L, find.getTotalCount());
IndexedAIP indexedAIP = find.getResults().get(0);
AIP aip = model.retrieveAIP(indexedAIP.getId());
return aip;
}
@Test
public void testIngestTransferredResource()
throws IOException, InterruptedException, RODAException, SolrServerException {
AIP aip = ingestCorpora();
AssertJUnit.assertEquals(1, aip.getRepresentations().size());
CloseableIterable<OptionalWithCause<File>> allFiles = model.listFilesUnder(aip.getId(),
aip.getRepresentations().get(0).getId(), true);
List<File> reusableAllFiles = new ArrayList<>();
Iterables.addAll(reusableAllFiles,
Lists.newArrayList(allFiles).stream().filter(f -> f.isPresent()).map(f -> f.get()).collect(Collectors.toList()));
// All folders and files
AssertJUnit.assertEquals(CORPORA_FOLDERS_COUNT + CORPORA_FILES_COUNT, reusableAllFiles.size());
}
@Test
public void testVirusCheck()
throws RODAException, InterruptedException, IOException, SolrServerException, XmlException {
AIP aip = ingestCorpora();
Job job = TestsHelper.executeJob(AntivirusPlugin.class, PluginType.AIP_TO_AIP,
SelectedItemsList.create(AIP.class, aip.getId()));
TestsHelper.getJobReports(index, job, true);
aip = model.retrieveAIP(aip.getId());
Plugin<? extends IsRODAObject> plugin = RodaCoreFactory.getPluginManager()
.getPlugin(AntivirusPlugin.class.getName());
String agentID = PluginHelper.getPluginAgentId(plugin);
boolean found = false;
CloseableIterable<OptionalWithCause<PreservationMetadata>> preservationMetadataList = model
.listPreservationMetadata(aip.getId(), true);
for (OptionalWithCause<PreservationMetadata> opm : preservationMetadataList) {
if (opm.isPresent()) {
PreservationMetadata pm = opm.get();
if (pm.getType().equals(PreservationMetadataType.EVENT)) {
EventComplexType event = PremisV3Utils
.binaryToEvent(model.retrievePreservationEvent(pm.getAipId(), pm.getRepresentationId(),
pm.getFileDirectoryPath(), pm.getFileId(), pm.getId()).getContent().createInputStream());
if (event.getLinkingAgentIdentifierArray() != null && event.getLinkingAgentIdentifierArray().length > 0) {
for (LinkingAgentIdentifierComplexType laict : event.getLinkingAgentIdentifierArray()) {
if (laict.getLinkingAgentIdentifierValue() != null
&& laict.getLinkingAgentIdentifierValue().equalsIgnoreCase(agentID)) {
found = true;
break;
}
}
if (found) {
break;
}
}
}
}
}
IOUtils.closeQuietly(preservationMetadataList);
AssertJUnit.assertTrue(found);
index.commitAIPs();
Filter filter = new Filter();
filter.add(
new SimpleFilterParameter(RodaConstants.PRESERVATION_EVENT_TYPE, PreservationEventType.VIRUS_CHECK.toString()));
filter.add(new SimpleFilterParameter(RodaConstants.PRESERVATION_EVENT_AIP_ID, aip.getId()));
IndexResult<IndexedPreservationEvent> events = index.find(IndexedPreservationEvent.class, filter, null,
new Sublist(0, 10), new ArrayList<>());
AssertJUnit.assertEquals(1, events.getTotalCount());
}
@SuppressWarnings("unchecked")
@Test
public void testPremisSkeleton() throws RODAException, InterruptedException, IOException, SolrServerException {
AIP aip = ingestCorpora();
TestsHelper.executeJob(PremisSkeletonPlugin.class, PluginType.AIP_TO_AIP,
SelectedItemsList.create(AIP.class, aip.getId()));
aip = model.retrieveAIP(aip.getId());
CloseableIterable<OptionalWithCause<PreservationMetadata>> preservationMetadata = model
.listPreservationMetadata(aip.getId(), true);
// Files plus one representation + 2 SIP To AIP Event + 1 Premis Skeleton
// Event
AssertJUnit.assertEquals(CORPORA_FILES_COUNT + 4, Iterables.size(preservationMetadata));
preservationMetadata.close();
Binary rpo_bin = model.retrievePreservationRepresentation(aip.getId(), aip.getRepresentations().get(0).getId());
Representation rpo = PremisV3Utils.binaryToRepresentation(rpo_bin.getContent(), true);
// Relates to files
AssertJUnit.assertEquals(CORPORA_FILES_COUNT, rpo.getRelationshipArray().length);
Binary fpo_bin = model.retrievePreservationFile(aip.getId(), aip.getRepresentations().get(0).getId(),
Arrays.asList(CORPORA_TEST1), CORPORA_TEST1_TXT);
gov.loc.premis.v3.File fpo = PremisV3Utils.binaryToFile(fpo_bin.getContent(), true);
ObjectCharacteristicsComplexType fileCharacteristics = fpo.getObjectCharacteristicsArray(0);
// check a fixity was generated
AssertJUnit.assertTrue("No fixity checks", fileCharacteristics.getFixityArray().length > 0);
// check file size
long size = fileCharacteristics.getSize();
AssertJUnit.assertTrue("File size is zero", size > 0);
// check file original name
String originalName = fpo.getOriginalName().getStringValue();
AssertJUnit.assertEquals(CORPORA_TEST1_TXT, originalName);
}
@SuppressWarnings("unchecked")
@Test
public void testSiegfried()
throws RODAException, InterruptedException, IOException, SolrServerException, XmlException {
AIP aip = ingestCorpora();
// ensure PREMIS objects are created
TestsHelper.executeJob(PremisSkeletonPlugin.class, PluginType.AIP_TO_AIP,
SelectedItemsList.create(AIP.class, aip.getId()));
// run siegfried
Map<String, String> parameters = new HashMap<>();
parameters.put(RodaConstants.PLUGIN_PARAMS_REPORTING_CLASS, FAKE_REPORTING_CLASS);
Job job = TestsHelper.executeJob(SiegfriedPlugin.class, parameters, PluginType.AIP_TO_AIP,
SelectedItemsList.create(AIP.class, aip.getId()));
TestsHelper.getJobReports(index, job, true);
aip = model.retrieveAIP(aip.getId());
// Files with Siegfried output
AssertJUnit.assertEquals(CORPORA_FILES_COUNT,
Iterables.size(model.listOtherMetadata(aip.getId(), RodaConstants.OTHER_METADATA_TYPE_SIEGFRIED, true)));
Binary om = model.retrieveOtherMetadataBinary(aip.getId(), aip.getRepresentations().get(0).getId(),
Arrays.asList(CORPORA_TEST1), CORPORA_TEST1_TXT, SiegfriedPlugin.FILE_SUFFIX,
RodaConstants.OTHER_METADATA_TYPE_SIEGFRIED);
AssertJUnit.assertNotNull(om);
Binary fpo_bin = model.retrievePreservationFile(aip.getId(), aip.getRepresentations().get(0).getId(),
Arrays.asList(CORPORA_TEST1), CORPORA_TEST1_TXT);
gov.loc.premis.v3.File fpo = PremisV3Utils.binaryToFile(fpo_bin.getContent(), true);
FormatComplexType format = fpo.getObjectCharacteristicsArray(0).getFormatArray(0);
AssertJUnit.assertEquals("Plain Text File", format.getFormatDesignation().getFormatName().getStringValue());
FormatRegistryComplexType pronomRegistry = PremisV3Utils.getFormatRegistry(fpo,
RodaConstants.PRESERVATION_REGISTRY_PRONOM);
AssertJUnit.assertEquals(RodaConstants.PRESERVATION_REGISTRY_PRONOM,
pronomRegistry.getFormatRegistryName().getStringValue());
AssertJUnit.assertEquals("x-fmt/111", pronomRegistry.getFormatRegistryKey().getStringValue());
FormatRegistryComplexType mimeRegistry = PremisV3Utils.getFormatRegistry(fpo,
RodaConstants.PRESERVATION_REGISTRY_MIME);
String mimetype = "text/plain";
AssertJUnit.assertEquals(mimetype, mimeRegistry.getFormatRegistryKey().getStringValue());
index.commitAIPs();
IndexedFile indFile = index.retrieve(IndexedFile.class, IdUtils.getFileId(aip.getId(),
aip.getRepresentations().get(0).getId(), Arrays.asList(CORPORA_TEST1), CORPORA_TEST1_TXT), new ArrayList<>());
AssertJUnit.assertEquals(mimetype, indFile.getFileFormat().getMimeType());
AssertJUnit.assertEquals("x-fmt/111", indFile.getFileFormat().getPronom());
AssertJUnit.assertEquals("Plain Text File", indFile.getFileFormat().getFormatDesignationName());
List<String> suggest = index.suggest(IndexedFile.class, RodaConstants.FILE_FORMAT_MIMETYPE,
mimetype.substring(0, 1), null, false, false);
MatcherAssert.assertThat(suggest, Matchers.contains(mimetype));
Plugin<? extends IsRODAObject> plugin = RodaCoreFactory.getPluginManager()
.getPlugin(SiegfriedPlugin.class.getName());
String agentID = PluginHelper.getPluginAgentId(plugin);
boolean found = false;
CloseableIterable<OptionalWithCause<PreservationMetadata>> preservationMetadataList = model
.listPreservationMetadata(aip.getId(), true);
for (OptionalWithCause<PreservationMetadata> opm : preservationMetadataList) {
if (opm.isPresent()) {
PreservationMetadata pm = opm.get();
if (pm.getType().equals(PreservationMetadataType.EVENT)) {
EventComplexType event = PremisV3Utils
.binaryToEvent(model.retrievePreservationEvent(pm.getAipId(), pm.getRepresentationId(),
pm.getFileDirectoryPath(), pm.getFileId(), pm.getId()).getContent().createInputStream());
if (event.getLinkingAgentIdentifierArray() != null && event.getLinkingAgentIdentifierArray().length > 0) {
for (LinkingAgentIdentifierComplexType laict : event.getLinkingAgentIdentifierArray()) {
if (laict.getLinkingAgentIdentifierValue() != null
&& laict.getLinkingAgentIdentifierValue().equalsIgnoreCase(agentID)) {
found = true;
break;
}
}
if (found) {
break;
}
}
}
}
}
IOUtils.closeQuietly(preservationMetadataList);
AssertJUnit.assertTrue(found);
Filter filter = new Filter();
filter.add(new SimpleFilterParameter(RodaConstants.PRESERVATION_EVENT_TYPE,
PreservationEventType.FORMAT_IDENTIFICATION.toString()));
filter.add(new SimpleFilterParameter(RodaConstants.PRESERVATION_EVENT_AIP_ID, aip.getId()));
IndexResult<IndexedPreservationEvent> events = index.find(IndexedPreservationEvent.class, filter, null,
new Sublist(0, 10), new ArrayList<>());
AssertJUnit.assertEquals(1, events.getTotalCount());
}
@Test
public void testAutoAccept()
throws RODAException, InterruptedException, IOException, InvalidDateException, SolrServerException {
AIP aip = ingestCorpora();
TestsHelper.executeJob(AutoAcceptSIPPlugin.class, PluginType.AIP_TO_AIP,
SelectedItemsList.create(AIP.class, aip.getId()));
aip = model.retrieveAIP(aip.getId());
MatcherAssert.assertThat(aip.getState(), Is.is(AIPState.ACTIVE));
}
}