/** * 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.base; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.apache.commons.lang.StringUtils; import org.roda.core.data.common.RodaConstants; import org.roda.core.data.common.RodaConstants.PreservationEventType; import org.roda.core.data.exceptions.AuthorizationDeniedException; import org.roda.core.data.exceptions.GenericException; 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.Void; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.filter.NotSimpleFilterParameter; import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.ip.AIP; import org.roda.core.data.v2.ip.AIPState; import org.roda.core.data.v2.ip.IndexedAIP; import org.roda.core.data.v2.jobs.Job; 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.index.IndexService; import org.roda.core.index.utils.IterableIndexResult; 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.RODAProcessingLogic; import org.roda.core.plugins.orchestrate.SimpleJobPluginInfo; import org.roda.core.plugins.plugins.PluginHelper; import org.roda.core.storage.StorageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CleanupFailedIngestAIPsPlugin extends AbstractPlugin<Void> { private static final Logger LOGGER = LoggerFactory.getLogger(CleanupFailedIngestAIPsPlugin.class); @Override public void init() throws PluginException { // do nothing } @Override public void shutdown() { // do nothing } @Override public String getName() { return "Remove AIP(s) whose ingest failed"; } @Override public String getDescription() { return "Permanently removes AIP(s) from the repository whose ingest failed (state=" + AIPState.INGEST_PROCESSING.toString() + "). Data, metadata and event history will be deleted permanently. WARNING: This operation cannot be undone. Use with extreme caution."; } @Override public String getVersionImpl() { return "1.0"; } @Override public Report execute(IndexService index, ModelService model, StorageService storage, List<LiteOptionalWithCause> objects) throws PluginException { return PluginHelper.processVoids(this, new RODAProcessingLogic<Void>() { @Override public void process(IndexService index, ModelService model, StorageService storage, Report report, Job cachedJob, SimpleJobPluginInfo jobPluginInfo, Plugin<Void> plugin) { try { processAIPs(index, model, report, cachedJob, jobPluginInfo); } catch (NotFoundException | GenericException | RequestNotValidException | AuthorizationDeniedException e) { LOGGER.error("Could not update Job information"); } } }, index, model, storage); } private void processAIPs(IndexService index, ModelService model, Report report, Job job, SimpleJobPluginInfo jobPluginInfo) throws NotFoundException, GenericException, RequestNotValidException, AuthorizationDeniedException { // jobs that are running List<String> activeJobsIds = findActiveJobs(index); // remove self activeJobsIds.remove(job.getId()); // find & delete aips IterableIndexResult<IndexedAIP> aipsToDelete = findAipsToDelete(index, activeJobsIds); for (IndexedAIP indexedAIP : aipsToDelete) { String error = ""; try { LOGGER.debug("Removing unwanted AIP {}", indexedAIP.getId()); model.deleteAIP(indexedAIP.getId()); } catch (RequestNotValidException | NotFoundException | GenericException | AuthorizationDeniedException e) { error = e.getMessage(); } Report reportItem = PluginHelper.initPluginReportItem(this, indexedAIP.getId(), AIP.class, AIPState.INGEST_PROCESSING); if (StringUtils.isNotBlank(error)) { reportItem.setPluginState(PluginState.FAILURE) .setPluginDetails("Removal of AIP " + indexedAIP.getId() + " did not end successfully: " + error); jobPluginInfo.incrementObjectsProcessedWithFailure(); } else { reportItem.setPluginState(PluginState.SUCCESS) .setPluginDetails("Removal of AIP " + indexedAIP.getId() + " ended successfully"); jobPluginInfo.incrementObjectsProcessedWithSuccess(); } report.addReport(reportItem); PluginHelper.updatePartialJobReport(this, model, reportItem, true, job); } jobPluginInfo.setSourceObjectsCount((int) aipsToDelete.getTotalObjects()); } private List<String> findActiveJobs(IndexService index) { Filter activeJobsViaStateFilter = new Filter(); for (Job.JOB_STATE jobState : Job.JOB_STATE.values()) { if (Job.isFinalState(jobState)) { activeJobsViaStateFilter.add(new NotSimpleFilterParameter(RodaConstants.JOB_STATE, jobState.toString())); } } List<String> activeJobsIds = new ArrayList<>(); index.findAll(Job.class, activeJobsViaStateFilter, Arrays.asList(RodaConstants.INDEX_UUID)) .forEach(e -> activeJobsIds.add(e.getId())); return activeJobsIds; } private IterableIndexResult<IndexedAIP> findAipsToDelete(IndexService index, List<String> activeJobsIds) { Filter aipsFilter = new Filter(); // all aips whose job id is not one of the active job ids activeJobsIds.forEach(e -> aipsFilter.add(new NotSimpleFilterParameter(RodaConstants.INGEST_JOB_ID, e))); // aip state is INGEST_PROCESSING aipsFilter.add(new SimpleFilterParameter(RodaConstants.AIP_STATE, AIPState.INGEST_PROCESSING.toString())); return index.findAll(IndexedAIP.class, aipsFilter, false, Arrays.asList(RodaConstants.INDEX_UUID)); } @Override public Report beforeAllExecute(IndexService index, ModelService model, StorageService storage) throws PluginException { return new Report(); } @Override public Report afterAllExecute(IndexService index, ModelService model, StorageService storage) throws PluginException { return new Report(); } @Override public Plugin<Void> cloneMe() { return new CleanupFailedIngestAIPsPlugin(); } @Override public PluginType getType() { return PluginType.MISC; } @Override public boolean areParameterValuesValid() { return true; } @Override public PreservationEventType getPreservationEventType() { return PreservationEventType.DELETION; } @Override public String getPreservationEventDescription() { return "Removes AIP from the system"; } @Override public String getPreservationEventSuccessMessage() { return "The AIPs were successfully removed"; } @Override public String getPreservationEventFailureMessage() { return "The AIPs were not removed"; } @Override public List<String> getCategories() { return Arrays.asList(RodaConstants.PLUGIN_CATEGORY_MANAGEMENT); } @Override public List<Class<Void>> getObjectClasses() { return Arrays.asList(Void.class); } }