/** * 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.internal; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.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.InvalidParameterException; import org.roda.core.data.exceptions.NotFoundException; import org.roda.core.data.exceptions.RequestNotValidException; import org.roda.core.data.v2.IsRODAObject; import org.roda.core.data.v2.LiteOptionalWithCause; import org.roda.core.data.v2.formats.Format; import org.roda.core.data.v2.index.IndexResult; import org.roda.core.data.v2.index.IndexRunnable; import org.roda.core.data.v2.index.IsIndexed; import org.roda.core.data.v2.index.filter.Filter; import org.roda.core.data.v2.index.filter.OneOfManyFilterParameter; import org.roda.core.data.v2.index.filter.SimpleFilterParameter; import org.roda.core.data.v2.index.sort.Sorter; 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.Representation; import org.roda.core.data.v2.ip.metadata.LinkingIdentifier; import org.roda.core.data.v2.jobs.Job; 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.risks.Risk; import org.roda.core.data.v2.risks.RiskIncidence; import org.roda.core.index.IndexService; 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.RODAObjectProcessingLogic; 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 DeleteRODAObjectPlugin<T extends IsRODAObject> extends AbstractPlugin<T> { private static final Logger LOGGER = LoggerFactory.getLogger(DeleteRODAObjectPlugin.class); private static final String EVENT_DESCRIPTION = "The process of deleting an object of the repository"; private String details = null; private static Map<String, PluginParameter> pluginParameters = new HashMap<>(); static { pluginParameters.put(RodaConstants.PLUGIN_PARAMS_DETAILS, new PluginParameter(RodaConstants.PLUGIN_PARAMS_DETAILS, "Event details", PluginParameterType.STRING, "", false, false, "Details that will be used when creating event")); } @Override public void init() throws PluginException { // do nothing } @Override public void shutdown() { // do nothing } @Override public String getName() { return "Delete RODA entities"; } @Override public String getDescription() { return "Delete any removable type of RODA entities"; } @Override public String getVersionImpl() { return "1.0"; } @Override public List<PluginParameter> getParameters() { ArrayList<PluginParameter> parameters = new ArrayList<>(); parameters.add(pluginParameters.get(RodaConstants.PLUGIN_PARAMS_DETAILS)); return parameters; } @Override public void setParameterValues(Map<String, String> parameters) throws InvalidParameterException { super.setParameterValues(parameters); if (parameters.containsKey(RodaConstants.PLUGIN_PARAMS_DETAILS)) { details = parameters.get(RodaConstants.PLUGIN_PARAMS_DETAILS); } } @Override public Report execute(IndexService index, ModelService model, StorageService storage, List<LiteOptionalWithCause> liteList) throws PluginException { return PluginHelper.processObjects(this, new RODAObjectProcessingLogic<T>() { @Override public void process(IndexService index, ModelService model, StorageService storage, Report report, Job cachedJob, SimpleJobPluginInfo jobPluginInfo, Plugin<T> plugin, T object) { if (object instanceof AIP) { processAIP(index, model, report, jobPluginInfo, cachedJob, (AIP) object); } else if (object instanceof File) { processFile(index, model, report, jobPluginInfo, cachedJob, (File) object); } else if (object instanceof Representation) { processRepresentation(index, model, report, jobPluginInfo, cachedJob, (Representation) object); } else if (object instanceof Risk) { processRisk(index, model, report, jobPluginInfo, cachedJob, (Risk) object); } else if (object instanceof Format) { processFormat(model, report, jobPluginInfo, cachedJob, (Format) object); } } }, index, model, storage, liteList); } private void processAIP(IndexService index, ModelService model, Report report, SimpleJobPluginInfo jobPluginInfo, Job job, AIP aip) { PluginState state = PluginState.SUCCESS; Report reportItem = PluginHelper.initPluginReportItem(this, aip.getId(), AIP.class, AIPState.ACTIVE); final List<String> aipsDeleted = new ArrayList<>(); try { Filter filter = new Filter(new SimpleFilterParameter(RodaConstants.AIP_ANCESTORS, aip.getId())); index.execute(IndexedAIP.class, filter, Arrays.asList(RodaConstants.INDEX_UUID), new IndexRunnable<IndexedAIP>() { @Override public void run(IndexedAIP item) throws GenericException, RequestNotValidException, AuthorizationDeniedException { PluginState state = PluginState.SUCCESS; try { model.deleteAIP(item.getId()); aipsDeleted.add(item.getId()); } catch (NotFoundException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete AIP: " + e.getMessage()); } String outcomeText; if (state.equals(PluginState.SUCCESS)) { outcomeText = PluginHelper.createOutcomeTextForAIP(item, "has been manually deleted"); } else { outcomeText = PluginHelper.createOutcomeTextForAIP(item, "has not been manually deleted"); } model.createRepositoryEvent(PreservationEventType.DELETION, EVENT_DESCRIPTION, state, outcomeText, details, job.getUsername(), true); } }); } catch (GenericException | RequestNotValidException | AuthorizationDeniedException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete sublevel AIPs: " + e.getMessage()); } try { // removing related risk incidences Filter incidenceFilter = new Filter( new OneOfManyFilterParameter(RodaConstants.RISK_INCIDENCE_AIP_ID, aipsDeleted)); deleteRelatedIncidences(model, index, incidenceFilter); } catch (NotFoundException | GenericException | RequestNotValidException | AuthorizationDeniedException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete AIP related incidences: " + e.getMessage()); } report.addReport(reportItem.setPluginState(state)); PluginHelper.updatePartialJobReport(this, model, reportItem, true, job); jobPluginInfo.incrementObjectsProcessed(state); IndexedAIP item = null; String outcomeText; try { item = index.retrieve(IndexedAIP.class, aip.getId(), Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.AIP_TITLE)); } catch (NotFoundException | GenericException e) { // do nothing } try { model.deleteAIP(aip.getId()); aipsDeleted.add(aip.getId()); if (item != null) { outcomeText = PluginHelper.createOutcomeTextForAIP(item, "has been manually deleted"); } else { outcomeText = "Archival Information Package [id: " + aip.getId() + "] has been manually deleted"; } } catch (NotFoundException | GenericException | RequestNotValidException | AuthorizationDeniedException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete AIP: " + e.getMessage()); if (item != null) { outcomeText = PluginHelper.createOutcomeTextForAIP(item, "has not been manually deleted"); } else { outcomeText = "Archival Information Package [id: " + aip.getId() + "] has not been manually deleted"; } } model.createRepositoryEvent(PreservationEventType.DELETION, EVENT_DESCRIPTION, state, outcomeText, details, job.getUsername(), true); } private void processFile(IndexService index, ModelService model, Report report, SimpleJobPluginInfo jobPluginInfo, Job job, File file) { PluginState state = PluginState.SUCCESS; Report reportItem = PluginHelper.initPluginReportItem(this, file.getId(), File.class); try { model.deleteFile(file.getAipId(), file.getRepresentationId(), file.getPath(), file.getId(), true); } catch (NotFoundException | GenericException | RequestNotValidException | AuthorizationDeniedException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete File: " + e.getMessage()); } try { // removing related risk incidences Filter incidenceFilter = new Filter( new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_AIP_ID, file.getAipId()), new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_REPRESENTATION_ID, file.getRepresentationId()), new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_FILE_PATH_COMPUTED, StringUtils.join(file.getPath(), RodaConstants.RISK_INCIDENCE_FILE_PATH_COMPUTED_SEPARATOR)), new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_FILE_ID, file.getId())); deleteRelatedIncidences(model, index, incidenceFilter); } catch (NotFoundException | GenericException | RequestNotValidException | AuthorizationDeniedException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete file related incidences: " + e.getMessage()); } report.addReport(reportItem.setPluginState(state)); PluginHelper.updatePartialJobReport(this, model, reportItem, true, job); jobPluginInfo.incrementObjectsProcessed(state); String outcomeText; if (state.equals(PluginState.SUCCESS)) { outcomeText = "The file '" + file.getId() + "' has been manually deleted."; } else { outcomeText = "The file '" + file.getId() + "' has not been manually deleted."; } List<LinkingIdentifier> sources = new ArrayList<>(); sources.add(PluginHelper.getLinkingIdentifier(file.getAipId(), file.getRepresentationId(), RodaConstants.PRESERVATION_LINKING_OBJECT_SOURCE)); model.createRepositoryEvent(PreservationEventType.DELETION, EVENT_DESCRIPTION, sources, null, state, outcomeText, details, job.getUsername(), true); } private void processRepresentation(IndexService index, ModelService model, Report report, SimpleJobPluginInfo jobPluginInfo, Job job, Representation representation) { PluginState state = PluginState.SUCCESS; Report reportItem = PluginHelper.initPluginReportItem(this, representation.getId(), Representation.class); try { model.deleteRepresentation(representation.getAipId(), representation.getId()); } catch (NotFoundException | GenericException | RequestNotValidException | AuthorizationDeniedException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete representation: " + e.getMessage()); } try { // removing related risk incidences Filter incidenceFilter = new Filter( new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_AIP_ID, representation.getAipId()), new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_REPRESENTATION_ID, representation.getId())); deleteRelatedIncidences(model, index, incidenceFilter); } catch (NotFoundException | GenericException | RequestNotValidException | AuthorizationDeniedException e) { state = PluginState.FAILURE; reportItem.addPluginDetails("Could not delete representation related incidences: " + e.getMessage()); } report.addReport(reportItem.setPluginState(state)); PluginHelper.updatePartialJobReport(this, model, reportItem, true, job); jobPluginInfo.incrementObjectsProcessed(state); String outcomeText; if (state.equals(PluginState.SUCCESS)) { outcomeText = "The representation '" + representation.getId() + "' has been manually deleted."; } else { outcomeText = "The representation '" + representation.getId() + "' has not been manually deleted."; } List<LinkingIdentifier> sources = new ArrayList<>(); sources.add( PluginHelper.getLinkingIdentifier(representation.getAipId(), RodaConstants.PRESERVATION_LINKING_OBJECT_SOURCE)); model.createRepositoryEvent(PreservationEventType.DELETION, EVENT_DESCRIPTION, sources, null, state, outcomeText, details, job.getUsername(), true); } private void processRisk(IndexService index, ModelService model, Report report, SimpleJobPluginInfo jobPluginInfo, Job job, Risk risk) { Report reportItem = PluginHelper.initPluginReportItem(this, risk.getId(), Risk.class); PluginState state = PluginState.SUCCESS; try { Filter incidenceFilter = new Filter( new SimpleFilterParameter(RodaConstants.RISK_INCIDENCE_RISK_ID, risk.getId())); deleteRelatedIncidences(model, index, incidenceFilter); } catch (GenericException | RequestNotValidException | NotFoundException | AuthorizationDeniedException e) { reportItem.addPluginDetails("Could not delete representation related incidences: " + e.getMessage()); state = PluginState.FAILURE; } try { model.deleteRisk(risk.getId(), true); } catch (GenericException | NotFoundException | AuthorizationDeniedException | RequestNotValidException e) { state = PluginState.FAILURE; } report.addReport(reportItem.setPluginState(state)); PluginHelper.updatePartialJobReport(this, model, reportItem, true, job); jobPluginInfo.incrementObjectsProcessed(state); } private void processFormat(ModelService model, Report report, SimpleJobPluginInfo jobPluginInfo, Job job, Format format) { Report reportItem = PluginHelper.initPluginReportItem(this, format.getId(), Format.class); PluginState state = PluginState.SUCCESS; try { model.deleteFormat(format.getId(), true); } catch (GenericException | NotFoundException | AuthorizationDeniedException | RequestNotValidException e) { state = PluginState.FAILURE; } report.addReport(reportItem.setPluginState(state)); PluginHelper.updatePartialJobReport(this, model, reportItem, true, job); jobPluginInfo.incrementObjectsProcessed(state); } private void deleteRelatedIncidences(ModelService model, IndexService index, Filter incidenceFilter) throws GenericException, RequestNotValidException, NotFoundException, AuthorizationDeniedException { Long incidenceCounter = index.count(RiskIncidence.class, incidenceFilter); IndexResult<RiskIncidence> incidences = index.find(RiskIncidence.class, incidenceFilter, Sorter.NONE, new Sublist(0, incidenceCounter.intValue()), Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.RISK_INCIDENCE_ID)); for (RiskIncidence incidence : incidences.getResults()) { model.deleteRiskIncidence(incidence.getId(), false); } } @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 { try { Job job = PluginHelper.getJob(this, index); index.commit((Class<? extends IsIndexed>) Class.forName(job.getSourceObjects().getSelectedClass())); } catch (NotFoundException | GenericException | ClassNotFoundException e) { LOGGER.error("Could not commit after delete operation"); } return new Report(); } @Override public Plugin<T> cloneMe() { return new DeleteRODAObjectPlugin<>(); } @Override public PluginType getType() { return PluginType.INTERNAL; } @Override public boolean areParameterValuesValid() { return true; } @Override public PreservationEventType getPreservationEventType() { return PreservationEventType.DELETION; } @Override public String getPreservationEventDescription() { return "Deletes RODA entities"; } @Override public String getPreservationEventSuccessMessage() { return "RODA entities were successfully removed"; } @Override public String getPreservationEventFailureMessage() { return "RODA entities were not successfully removed"; } @Override public List<String> getCategories() { return Arrays.asList(RodaConstants.PLUGIN_CATEGORY_NOT_LISTABLE); } @SuppressWarnings({"unchecked", "rawtypes"}) @Override public List<Class<T>> getObjectClasses() { List<Class<? extends IsRODAObject>> list = new ArrayList<>(); list.add(AIP.class); list.add(Representation.class); list.add(File.class); list.add(Risk.class); list.add(Format.class); return (List) list; } }