/** * 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.plugins.ingest; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.roda.core.RodaCoreFactory; import org.roda.core.common.notifications.EmailNotificationProcessor; import org.roda.core.common.notifications.HTTPNotificationProcessor; 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.JobException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.v2.LiteOptionalWithCause; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.TransferredResource; import org.roda.core.data.v2.jobs.Job; import org.roda.core.data.v2.jobs.JobStats; import org.roda.core.data.v2.jobs.PluginParameter; import org.roda.core.data.v2.jobs.PluginParameter.PluginParameterType; import org.roda.core.data.v2.jobs.PluginType; import org.roda.core.data.v2.jobs.Report; import org.roda.core.data.v2.jobs.Report.PluginState; import org.roda.core.data.v2.notifications.Notification; import org.roda.core.data.v2.validation.ValidationException; import org.roda.core.index.IndexService; import org.roda.core.model.LiteRODAObjectFactory; import org.roda.core.model.ModelService; import org.roda.core.plugins.AbstractPlugin; import org.roda.core.plugins.Plugin; import org.roda.core.plugins.PluginException; import org.roda.core.plugins.orchestrate.IngestJobPluginInfo; import org.roda.core.plugins.plugins.PluginHelper; import org.roda.core.plugins.plugins.antivirus.AntivirusPlugin; import org.roda.core.plugins.plugins.base.DescriptiveMetadataValidationPlugin; import org.roda.core.plugins.plugins.base.ReplicationPlugin; import org.roda.core.plugins.plugins.characterization.PremisSkeletonPlugin; import org.roda.core.plugins.plugins.characterization.SiegfriedPlugin; import org.roda.core.storage.StorageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.CaseFormat; /*** * https://docs.google.com/spreadsheets/d/ * 1Ncu0My6tf19umSClIA6iXeYlJ4_FP6MygRwFCe0EzyM * * @author Hélder Silva <hsilva@keep.pt> */ public abstract class DefaultIngestPlugin extends AbstractPlugin<TransferredResource> { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIngestPlugin.class); public static final String START_MESSAGE = "The ingest process has started."; public static final PreservationEventType START_TYPE = PreservationEventType.INGEST_START; public static final String END_SUCCESS = "The ingest process has successfully ended."; public static final String END_FAILURE = "Failed to conclude the ingest process."; public static final String END_PARTIAL = "The ingest process ended, however, some of the SIPs could not be successfully ingested."; public static final String END_DESCRIPTION = "The ingest process has ended."; public static final PreservationEventType END_TYPE = PreservationEventType.INGEST_END; protected int totalSteps = 11; private String successMessage; private String failureMessage; private PreservationEventType eventType; private String eventDescription; @Override public void setParameterValues(Map<String, String> parameters) throws InvalidParameterException { super.setParameterValues(parameters); totalSteps = calculateEfectiveTotalSteps(); getParameterValues().put(RodaConstants.PLUGIN_PARAMS_TOTAL_STEPS, Integer.toString(getTotalSteps())); Boolean createSubmission = RodaCoreFactory.getRodaConfiguration() .getBoolean("core.ingest.sip2aip.create_submission", false); getParameterValues().put(RodaConstants.PLUGIN_PARAMS_CREATE_SUBMISSION, createSubmission.toString()); getParameterValues().put(RodaConstants.PLUGIN_PARAMS_REPORTING_CLASS, getClass().getName()); } public abstract PluginParameter getPluginParameter(String pluginParameterId); @Override public void init() throws PluginException { // do nothing } @Override public void shutdown() { // do nothing } @Override public Report beforeAllExecute(IndexService index, ModelService model, StorageService storage) throws PluginException { // do nothing LOGGER.debug("Doing nothing in beforeAllExecute"); return null; } @Override public Report execute(IndexService index, ModelService model, StorageService storage, List<LiteOptionalWithCause> liteList) throws PluginException { try { Date startDate = new Date(); Report report = PluginHelper.initPluginReport(this); Report pluginReport; final IngestJobPluginInfo jobPluginInfo = PluginHelper.getInitialJobInformation(this, IngestJobPluginInfo.class); PluginHelper.updateJobInformation(this, jobPluginInfo.setTotalSteps(getTotalSteps())); Job job = PluginHelper.getJob(this, model); List<TransferredResource> resources = PluginHelper.transformLitesIntoObjects(model, this, report, jobPluginInfo, liteList, job); // 0) process "parent id" and "force parent id" info. (because we might // need to fallback to default values) String parentId = PluginHelper.getStringFromParameters(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_PARENT_ID)); boolean forceParentId = PluginHelper.getBooleanFromParameters(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_FORCE_PARENT_ID)); getParameterValues().put(RodaConstants.PLUGIN_PARAMS_PARENT_ID, parentId); getParameterValues().put(RodaConstants.PLUGIN_PARAMS_FORCE_PARENT_ID, forceParentId ? "true" : "false"); // 1) unpacking & wellformedness check (transform TransferredResource into // an AIP) pluginReport = transformTransferredResourceIntoAnAIP(index, model, storage, resources); mergeReports(jobPluginInfo, pluginReport); final List<AIP> aips = getAIPsFromReports(model, jobPluginInfo); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); // this event can only be created after AIPs exist and that's why it is // performed here, after transformTransferredResourceIntoAnAIP createIngestStartedEvent(model, index, jobPluginInfo, startDate); // 2) virus check if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_VIRUS_CHECK))) { pluginReport = doVirusCheck(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, true); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 3) descriptive metadata validation if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_DESCRIPTIVE_METADATA_VALIDATION))) { pluginReport = doDescriptiveMetadataValidation(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, true); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 4) create file fixity information if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_CREATE_PREMIS_SKELETON))) { pluginReport = createFileFixityInformation(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, true); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 5) format identification (using Siegfried) if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_FILE_FORMAT_IDENTIFICATION))) { pluginReport = doFileFormatIdentification(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, false); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 6) Format validation - PDF/A format validator (using VeraPDF) if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_VERAPDF_CHECK))) { Map<String, String> params = new HashMap<>(); params.put("profile", "1b"); pluginReport = doVeraPDFCheck(index, model, storage, aips, params); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, false); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 7.1) feature extraction (using Apache Tika) // 7.2) full-text extraction (using Apache Tika) if (!aips.isEmpty() && (PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_FEATURE_EXTRACTION)) || PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_FULL_TEXT_EXTRACTION)))) { Map<String, String> params = new HashMap<>(); params.put(RodaConstants.PLUGIN_PARAMS_DO_FEATURE_EXTRACTION, PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_FEATURE_EXTRACTION)) ? "true" : "false"); params.put(RodaConstants.PLUGIN_PARAMS_DO_FULLTEXT_EXTRACTION, PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_FULL_TEXT_EXTRACTION)) ? "true" : "false"); pluginReport = doFeatureAndFullTextExtraction(index, model, storage, aips, params); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, false); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 8) validation of digital signature if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_DIGITAL_SIGNATURE_VALIDATION))) { pluginReport = doDigitalSignatureValidation(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, false); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 9) verify producer authorization if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_PRODUCER_AUTHORIZATION_CHECK))) { pluginReport = verifyProducerAuthorization(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, true); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } // 10) Auto accept if (!aips.isEmpty()) { if (PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_AUTO_ACCEPT))) { pluginReport = doAutoAccept(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); recalculateAIPsList(model, index, jobPluginInfo, aips, true); jobPluginInfo.incrementStepsCompletedByOne(); } else { updateAIPsToBeAppraised(model, aips, jobPluginInfo); } } // X) move SIPs to PROCESSED folder??? (default: false) if (PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_AUTO_ACCEPT)) && RodaCoreFactory.getRodaConfiguration().getBoolean("core.ingest.processed.move_when_autoaccept", false)) { PluginHelper.moveSIPs(this, model, index, resources, jobPluginInfo); } createIngestEndedEvent(model, index, aips); // 11) Replication if (!aips.isEmpty() && PluginHelper.verifyIfStepShouldBePerformed(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_DO_REPLICATION))) { pluginReport = doReplication(index, model, storage, aips); mergeReports(jobPluginInfo, pluginReport); PluginHelper.updateJobInformation(this, jobPluginInfo.incrementStepsCompletedByOne()); } getAfterExecute().ifPresent(e -> e.execute(jobPluginInfo, aips)); // X) final job info update jobPluginInfo.finalizeInfo(); PluginHelper.updateJobInformation(this, jobPluginInfo); return report; } catch (JobException | AuthorizationDeniedException | NotFoundException | GenericException | RequestNotValidException e) { throw new PluginException("A job exception has occurred", e); } } @Override public Report afterAllExecute(IndexService index, ModelService model, StorageService storage) throws PluginException { LOGGER.debug("Doing stuff in afterAllExecute"); try { sendNotification(model); index.commitAIPs(); PluginHelper.fixParents(index, model, Optional.ofNullable(PluginHelper.getJobId(this)), PluginHelper.getSearchScopeFromParameters(this, model)); } catch (GenericException | RequestNotValidException | NotFoundException | AuthorizationDeniedException e) { LOGGER.error("Could not send ingest notification"); } return null; } private Report transformTransferredResourceIntoAnAIP(IndexService index, ModelService model, StorageService storage, List<TransferredResource> transferredResources) { Report report = null; String pluginClassName = getParameterValues().getOrDefault( getPluginParameter(RodaConstants.PLUGIN_PARAMS_SIP_TO_AIP_CLASS).getId(), getPluginParameter(RodaConstants.PLUGIN_PARAMS_SIP_TO_AIP_CLASS).getDefaultValue()); Plugin<TransferredResource> plugin = RodaCoreFactory.getPluginManager().getPlugin(pluginClassName, TransferredResource.class); try { plugin.setParameterValues(getParameterValues()); List<LiteOptionalWithCause> lites = LiteRODAObjectFactory.transformIntoLiteWithCause(model, transferredResources); report = plugin.execute(index, model, storage, lites); } catch (PluginException | InvalidParameterException e) { // TODO handle failure LOGGER.error("Error executing plugin to transform transferred resource into AIP", e); } return report; } private void mergeReports(IngestJobPluginInfo jobPluginInfo, Report plugin) { Map<String, String> aipIdToTransferredResourceId = jobPluginInfo.getAipIdToTransferredResourceId(); if (plugin != null) { for (Report reportItem : plugin.getReports()) { if (TransferredResource.class.getName().equals(reportItem.getSourceObjectClass())) { Report report = new Report(reportItem); report.addReport(reportItem); jobPluginInfo.addReport(reportItem.getSourceObjectId(), reportItem.getOutcomeObjectId(), report); } else if (StringUtils.isNotBlank(reportItem.getOutcomeObjectId()) && aipIdToTransferredResourceId.get(reportItem.getOutcomeObjectId()) != null) { jobPluginInfo.addReport(reportItem.getOutcomeObjectId(), reportItem); } } } } private List<AIP> getAIPsFromReports(ModelService model, IngestJobPluginInfo jobPluginInfo) { List<AIP> aips = new ArrayList<>(); List<String> aipIds = jobPluginInfo.getAipIds(); LOGGER.debug("Getting AIPs: {}", aipIds); for (String aipId : aipIds) { try { aips.add(model.retrieveAIP(aipId)); } catch (RequestNotValidException | NotFoundException | GenericException | AuthorizationDeniedException e) { LOGGER.error("Error while retrieving AIP", e); } } LOGGER.debug("Done retrieving AIPs"); jobPluginInfo.updateCounters(); return aips; } private void sendNotification(ModelService model) throws GenericException, RequestNotValidException, NotFoundException, AuthorizationDeniedException { Job job = PluginHelper.getJob(this, model); JobStats jobStats = job.getJobStats(); String emails = PluginHelper.getStringFromParameters(this, getPluginParameter(RodaConstants.PLUGIN_PARAMS_EMAIL_NOTIFICATION)); if (StringUtils.isNotBlank(emails)) { List<String> emailList = new ArrayList<>(Arrays.asList(emails.split("\\s*,\\s*"))); Notification notification = new Notification(); String outcome = PluginState.SUCCESS.toString(); if (jobStats.getSourceObjectsProcessedWithFailure() > 0) { outcome = PluginState.FAILURE.toString(); } String subject = RodaCoreFactory.getRodaConfigurationAsString("core", "notification", "ingest_subject"); if (StringUtils.isNotBlank(subject)) { subject += outcome; } else { subject = outcome; } notification.setSubject(subject); notification.setFromUser(this.getClass().getSimpleName()); notification.setRecipientUsers(emailList); Map<String, Object> scopes = new HashMap<>(); scopes.put("outcome", outcome); scopes.put("type", CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, job.getPluginType().toString())); scopes.put("sips", jobStats.getSourceObjectsCount()); scopes.put("success", jobStats.getSourceObjectsProcessedWithSuccess()); scopes.put("failed", jobStats.getSourceObjectsProcessedWithFailure()); scopes.put("name", job.getName()); scopes.put("creator", job.getUsername()); SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); scopes.put("start", parser.format(job.getStartDate())); long duration = (new Date().getTime() - job.getStartDate().getTime()) / 1000; scopes.put("duration", duration + " seconds"); model.createNotification(notification, new EmailNotificationProcessor(RodaConstants.INGEST_EMAIL_TEMPLATE, scopes)); } String httpNotifications = RodaCoreFactory.getRodaConfiguration() .getString(RodaConstants.NOTIFICATION_HTTP_ENDPOINT, ""); if (StringUtils.isNotBlank(httpNotifications)) { Notification notification = new Notification(); String outcome = PluginState.SUCCESS.toString(); if (jobStats.getSourceObjectsProcessedWithFailure() > 0) { outcome = PluginState.FAILURE.toString(); } notification.setSubject("RODA ingest process finished - " + outcome); notification.setFromUser(this.getClass().getSimpleName()); notification.setRecipientUsers(Arrays.asList(httpNotifications)); Map<String, Object> scope = new HashMap<>(); scope.put(HTTPNotificationProcessor.JOB_KEY, job); model.createNotification(notification, new HTTPNotificationProcessor(httpNotifications, scope)); } } /** * Recalculates (if failures must be noticed) and updates AIP objects (by * obtaining them from model) */ private void recalculateAIPsList(ModelService model, IndexService index, IngestJobPluginInfo jobPluginInfo, List<AIP> aips, boolean removeAIPProcessingFailed) { aips.clear(); List<AIP> transferredResourceAips; List<String> transferredResourcesToRemoveFromjobPluginInfo = new ArrayList<>(); boolean oneTransferredResourceAipFailed; for (Entry<String, Map<String, Report>> transferredResourcejobPluginInfoEntry : jobPluginInfo .getReportsFromBeingProcessed().entrySet()) { String transferredResourceId = transferredResourcejobPluginInfoEntry.getKey(); transferredResourceAips = new ArrayList<>(); oneTransferredResourceAipFailed = false; if (jobPluginInfo.getAipIds(transferredResourceId) != null) { for (String aipId : jobPluginInfo.getAipIds(transferredResourceId)) { Report aipReport = transferredResourcejobPluginInfoEntry.getValue().get(aipId); if (removeAIPProcessingFailed && aipReport.getPluginState() == PluginState.FAILURE) { LOGGER.trace("Removing AIP {} from the list", aipReport.getOutcomeObjectId()); oneTransferredResourceAipFailed = true; break; } else { try { transferredResourceAips.add(model.retrieveAIP(aipId)); } catch (RequestNotValidException | NotFoundException | GenericException | AuthorizationDeniedException e) { LOGGER.error("Error while retrieving AIP", e); } } } if (oneTransferredResourceAipFailed) { LOGGER.trace( "Will not process AIPs from transferred resource '{}' any longer because at least one of them failed", transferredResourceId); jobPluginInfo.incrementObjectsProcessedWithFailure(); jobPluginInfo.failOtherTransferredResourceAIPs(model, index, transferredResourceId); transferredResourcesToRemoveFromjobPluginInfo.add(transferredResourceId); } else { aips.addAll(transferredResourceAips); } } } for (String transferredResourceId : transferredResourcesToRemoveFromjobPluginInfo) { jobPluginInfo.remove(transferredResourceId); } } private int calculateEfectiveTotalSteps() { List<String> parameterIdsToIgnore = Arrays.asList(RodaConstants.PLUGIN_PARAMS_FORCE_PARENT_ID, RodaConstants.PLUGIN_PARAMS_DO_FEATURE_EXTRACTION, RodaConstants.PLUGIN_PARAMS_DO_FULL_TEXT_EXTRACTION, RodaConstants.PLUGIN_PARAMS_DO_AUTO_ACCEPT); int effectiveTotalSteps = getTotalSteps(); boolean tikaParameters = false; boolean dontDoFeatureExtraction = false; boolean dontDoFulltext = false; for (PluginParameter pluginParameter : getParameters()) { if (pluginParameter.getType() == PluginParameterType.BOOLEAN && !parameterIdsToIgnore.contains(pluginParameter.getId()) && !PluginHelper.verifyIfStepShouldBePerformed(this, pluginParameter)) { effectiveTotalSteps--; } if (pluginParameter.getId().equals(RodaConstants.PLUGIN_PARAMS_DO_FEATURE_EXTRACTION)) { tikaParameters = true; if (!PluginHelper.verifyIfStepShouldBePerformed(this, pluginParameter)) { dontDoFeatureExtraction = true; } } if (pluginParameter.getId().equals(RodaConstants.PLUGIN_PARAMS_DO_FULL_TEXT_EXTRACTION)) { tikaParameters = true; if (!PluginHelper.verifyIfStepShouldBePerformed(this, pluginParameter)) { dontDoFulltext = true; } } } if (tikaParameters && (dontDoFeatureExtraction && dontDoFulltext)) { effectiveTotalSteps--; } return effectiveTotalSteps; } private void createIngestStartedEvent(ModelService model, IndexService index, IngestJobPluginInfo jobPluginInfo, Date startDate) { Map<String, String> aipIdToTransferredResourceId = jobPluginInfo.getAipIdToTransferredResourceId(); setPreservationEventType(START_TYPE); setPreservationSuccessMessage(START_MESSAGE); setPreservationFailureMessage(START_MESSAGE); setPreservationEventDescription(START_MESSAGE); for (Map.Entry<String, String> entry : aipIdToTransferredResourceId.entrySet()) { try { AIP aip = model.retrieveAIP(entry.getKey()); TransferredResource tr = index.retrieve(TransferredResource.class, entry.getValue(), Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.TRANSFERRED_RESOURCE_RELATIVEPATH)); boolean notify = true; PluginHelper.createPluginEvent(this, aip.getId(), model, index, tr, PluginState.SUCCESS, "", notify, startDate); } catch (NotFoundException | RequestNotValidException | GenericException | AuthorizationDeniedException | ValidationException | AlreadyExistsException e) { LOGGER.warn("Error creating ingest start event", e); } } } private void createIngestEndedEvent(ModelService model, IndexService index, List<AIP> aips) { setPreservationEventType(END_TYPE); setPreservationSuccessMessage(END_SUCCESS); setPreservationFailureMessage(END_FAILURE); setPreservationEventDescription(END_DESCRIPTION); for (AIP aip : aips) { try { boolean notify = true; // FIXME create event based on report (outcome & outcomeDetails) PluginHelper.createPluginEvent(this, aip.getId(), model, index, PluginState.SUCCESS, "", notify); } catch (NotFoundException | RequestNotValidException | GenericException | AuthorizationDeniedException | ValidationException | AlreadyExistsException e) { LOGGER.warn("Error creating ingest end event", e); } } } private Report createFileFixityInformation(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, PremisSkeletonPlugin.class.getName()); } private Report doVirusCheck(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, AntivirusPlugin.class.getName()); } private Report doVeraPDFCheck(IndexService index, ModelService model, StorageService storage, List<AIP> aips, Map<String, String> params) { return executePlugin(index, model, storage, aips, RodaConstants.PLUGIN_CLASS_VERAPDF, params); } private Report doDescriptiveMetadataValidation(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, DescriptiveMetadataValidationPlugin.class.getName()); } private Report verifyProducerAuthorization(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, VerifyUserAuthorizationPlugin.class.getName()); } private Report doFileFormatIdentification(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, SiegfriedPlugin.class.getName()); } private Report doDigitalSignatureValidation(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, RodaConstants.PLUGIN_CLASS_DIGITAL_SIGNATURE); } private Report doFeatureAndFullTextExtraction(IndexService index, ModelService model, StorageService storage, List<AIP> aips, Map<String, String> params) { return executePlugin(index, model, storage, aips, RodaConstants.PLUGIN_CLASS_TIKA_FULLTEXT, params); } private Report doAutoAccept(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, AutoAcceptSIPPlugin.class.getName()); } private Report doReplication(IndexService index, ModelService model, StorageService storage, List<AIP> aips) { return executePlugin(index, model, storage, aips, ReplicationPlugin.class.getName()); } private Report executePlugin(IndexService index, ModelService model, StorageService storage, List<AIP> aips, String pluginClassName) { return executePlugin(index, model, storage, aips, pluginClassName, null); } private Report executePlugin(IndexService index, ModelService model, StorageService storage, List<AIP> aips, String pluginClassName, Map<String, String> params) { Plugin<AIP> plugin = RodaCoreFactory.getPluginManager().getPlugin(pluginClassName, AIP.class); Map<String, String> mergedParams = new HashMap<>(getParameterValues()); if (params != null) { mergedParams.putAll(params); } try { plugin.setParameterValues(mergedParams); List<LiteOptionalWithCause> lites = LiteRODAObjectFactory.transformIntoLiteWithCause(model, aips); return plugin.execute(index, model, storage, lites); } catch (InvalidParameterException | PluginException | RuntimeException e) { LOGGER.error("Error executing plugin", e); } return null; } private void updateAIPsToBeAppraised(ModelService model, List<AIP> aips, IngestJobPluginInfo jobPluginInfo) { for (AIP aip : aips) { aip.setState(AIPState.UNDER_APPRAISAL); try { aip = model.updateAIPState(aip, PluginHelper.getJobUsername(this, model)); // update main report outcomeObjectState PluginHelper.updateJobReportState(this, model, aip.getId(), AIPState.UNDER_APPRAISAL); // update counters of manual intervention jobPluginInfo.incrementOutcomeObjectsWithManualIntervention(); } catch (GenericException | NotFoundException | RequestNotValidException | AuthorizationDeniedException e) { LOGGER.error("Error while updating AIP state to '{}'. Reason: {}", AIPState.UNDER_APPRAISAL, e.getMessage()); } } } @Override public PluginType getType() { return PluginType.INGEST; } @Override public boolean areParameterValuesValid() { boolean areValid = true; PluginParameter sipToAipClassPluginParameter = getPluginParameter(RodaConstants.PLUGIN_PARAMS_SIP_TO_AIP_CLASS); String sipToAipClass = getParameterValues().getOrDefault(sipToAipClassPluginParameter.getId(), sipToAipClassPluginParameter.getDefaultValue()); if (StringUtils.isNotBlank(sipToAipClass)) { Plugin<TransferredResource> plugin = RodaCoreFactory.getPluginManager().getPlugin(sipToAipClass, TransferredResource.class); if (plugin == null || plugin.getType() != PluginType.SIP_TO_AIP) { areValid = false; } } else { areValid = false; } return areValid; } @Override public PreservationEventType getPreservationEventType() { return eventType; } @Override public String getPreservationEventDescription() { return eventDescription; } @Override public String getPreservationEventSuccessMessage() { return successMessage; } @Override public String getPreservationEventFailureMessage() { return failureMessage; } public void setPreservationEventType(PreservationEventType t) { this.eventType = t; } public void setPreservationSuccessMessage(String message) { this.successMessage = message; } public void setPreservationFailureMessage(String message) { this.failureMessage = message; } public void setPreservationEventDescription(String description) { this.eventDescription = description; } public int getTotalSteps() { return totalSteps; } public abstract void setTotalSteps(); public abstract Optional<? extends AfterExecute> getAfterExecute(); @FunctionalInterface public interface AfterExecute { void execute(IngestJobPluginInfo jobPluginInfo, List<AIP> aips); } }