/**
* 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 java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.roda.core.RodaCoreFactory;
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.IsStillUpdatingException;
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.index.IndexResult;
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.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.IndexedFile;
import org.roda.core.data.v2.ip.TransferredResource;
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.index.IndexService;
import org.roda.core.model.ModelService;
import org.roda.core.model.utils.ModelUtils;
import org.roda.core.plugins.AbstractPlugin;
import org.roda.core.plugins.Plugin;
import org.roda.core.plugins.PluginException;
import org.roda.core.plugins.RODAObjectsProcessingLogic;
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 MovePlugin<T extends IsRODAObject> extends AbstractPlugin<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(MovePlugin.class);
private static final String EVENT_DESCRIPTION = "The process of updating an object of the repository";
private String destinationId = null;
private String details = null;
private static Map<String, PluginParameter> pluginParameters = new HashMap<>();
static {
pluginParameters.put(RodaConstants.PLUGIN_PARAMS_ID, new PluginParameter(RodaConstants.PLUGIN_PARAMS_ID,
"Destination object identifier", PluginParameterType.STRING, "", false, false, "Destination object identifier"));
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 "Move action over RODA entity";
}
@Override
public String getDescription() {
return "Executes move actions over RODA entity faster";
}
@Override
public String getVersionImpl() {
return "1.0";
}
@Override
public List<PluginParameter> getParameters() {
ArrayList<PluginParameter> parameters = new ArrayList<>();
parameters.add(pluginParameters.get(RodaConstants.PLUGIN_PARAMS_ID));
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_ID)) {
destinationId = parameters.get(RodaConstants.PLUGIN_PARAMS_ID);
}
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 RODAObjectsProcessingLogic<T>() {
@Override
public void process(IndexService index, ModelService model, StorageService storage, Report report, Job cachedJob,
SimpleJobPluginInfo jobPluginInfo, Plugin<T> plugin, List<T> objects) {
if (!objects.isEmpty()) {
if (objects.get(0) instanceof AIP) {
for (T object : objects) {
processAIP(model, index, report, jobPluginInfo, cachedJob, (AIP) object);
}
} else if (objects.get(0) instanceof File) {
for (T object : objects) {
processFile(index, model, report, jobPluginInfo, cachedJob, (File) object);
}
} else if (objects.get(0) instanceof TransferredResource) {
processTransferredResource(jobPluginInfo, (List<TransferredResource>) objects);
}
}
}
}, index, model, storage, liteList);
}
private void processAIP(ModelService model, IndexService index, Report report, SimpleJobPluginInfo jobPluginInfo,
Job job, AIP aip) {
PluginState state = PluginState.SUCCESS;
if (!aip.getId().equals(destinationId)) {
LOGGER.debug("Moving AIP {} under {}", aip.getId(), destinationId);
try {
IndexResult<IndexedAIP> result = new IndexResult<>();
if (destinationId != null) {
Filter filter = new Filter();
filter.add(new SimpleFilterParameter(RodaConstants.INDEX_UUID, destinationId));
filter.add(new SimpleFilterParameter(RodaConstants.AIP_ANCESTORS, aip.getId()));
result = index.find(IndexedAIP.class, filter, Sorter.NONE, new Sublist(0, 1),
Arrays.asList(RodaConstants.INDEX_UUID));
}
if (destinationId == null || result.getResults().isEmpty()) {
model.moveAIP(aip.getId(), destinationId);
} else {
state = PluginState.FAILURE;
Report reportItem = PluginHelper.initPluginReportItem(this, aip.getId(), AIP.class, AIPState.ACTIVE);
reportItem.addPluginDetails("Could not move AIP because the destination is a sublevel").setPluginState(state);
report.addReport(reportItem);
PluginHelper.updatePartialJobReport(this, model, reportItem, true, job);
}
} catch (GenericException | NotFoundException | RequestNotValidException | AuthorizationDeniedException e) {
state = PluginState.FAILURE;
Report reportItem = PluginHelper.initPluginReportItem(this, aip.getId(), AIP.class, AIPState.ACTIVE);
reportItem.addPluginDetails("Could not move AIP: " + e.getMessage()).setPluginState(state);
report.addReport(reportItem);
PluginHelper.updatePartialJobReport(this, model, reportItem, true, job);
}
}
String outcomeText = "";
try {
IndexedAIP item = index.retrieve(IndexedAIP.class, aip.getId(),
Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.AIP_TITLE));
if (state.equals(PluginState.SUCCESS)) {
outcomeText = PluginHelper.createOutcomeTextForAIP(item, "has been manually moved");
} else {
outcomeText = PluginHelper.createOutcomeTextForAIP(item, "has not been manually moved");
}
} catch (NotFoundException | GenericException e1) {
if (state.equals(PluginState.SUCCESS)) {
outcomeText = "Archival Information Package [id: " + aip.getId() + "] has been manually moved";
} else {
outcomeText = "Archival Information Package [id: " + aip.getId() + "] has not been manually moved";
}
}
jobPluginInfo.incrementObjectsProcessed(state);
model.createUpdateAIPEvent(aip.getId(), null, null, null, PreservationEventType.UPDATE, 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;
StringBuilder outcomeText = new StringBuilder();
try {
String toAIP;
String toRepresentation;
List<String> toPath = null;
if (StringUtils.isNotBlank(destinationId)) {
List<String> fileFields = Arrays.asList(RodaConstants.INDEX_UUID, RodaConstants.FILE_AIP_ID,
RodaConstants.FILE_REPRESENTATION_ID, RodaConstants.FILE_PATH, RodaConstants.FILE_FILE_ID);
IndexedFile toFolder = index.retrieve(IndexedFile.class, destinationId, fileFields);
LOGGER.debug("Moving File {} under {}", file.getId(), toFolder.getId());
toAIP = toFolder.getAipId();
toRepresentation = toFolder.getRepresentationId();
toPath = new ArrayList<>();
if (toFolder.getPath() != null) {
toPath.addAll(toFolder.getPath());
}
toPath.add(toFolder.getId());
} else {
toAIP = file.getAipId();
toRepresentation = file.getRepresentationId();
}
File movedFile = model.moveFile(file, toAIP, toRepresentation, toPath, file.getId(), true, true);
outcomeText.append("The file '").append(ModelUtils.getFileStoragePath(file).toString())
.append("' has been manually moved to '").append(ModelUtils.getFileStoragePath(movedFile).toString())
.append("'");
} catch (GenericException | AlreadyExistsException | NotFoundException | RequestNotValidException
| AuthorizationDeniedException e) {
state = PluginState.FAILURE;
Report reportItem = PluginHelper.initPluginReportItem(this, file.getId(), File.class);
reportItem.addPluginDetails("Could not move file: " + e.getMessage()).setPluginState(state);
report.addReport(reportItem);
PluginHelper.updatePartialJobReport(this, model, reportItem, true, job);
outcomeText.append("The file '").append(file.getId()).append("' has not been manually moved: [")
.append(e.getClass().getSimpleName()).append("] ").append(e.getMessage());
}
jobPluginInfo.incrementObjectsProcessed(state);
model.createUpdateAIPEvent(file.getAipId(), file.getRepresentationId(), null, null, PreservationEventType.UPDATE,
EVENT_DESCRIPTION, state, outcomeText.toString(), details, job.getUsername(), true);
}
private void processTransferredResource(SimpleJobPluginInfo jobPluginInfo, List<TransferredResource> resources) {
if (destinationId == null) {
destinationId = "";
}
try {
RodaCoreFactory.getTransferredResourcesScanner().moveTransferredResource(resources, destinationId, false, true);
jobPluginInfo.incrementObjectsProcessedWithSuccess(resources.size());
} catch (AlreadyExistsException | GenericException | IsStillUpdatingException | NotFoundException e) {
LOGGER.error("Could not move transferred resource list");
jobPluginInfo.incrementObjectsProcessedWithFailure(resources.size());
}
}
@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);
if (TransferredResource.class.getName().equals(job.getSourceObjects().getSelectedClass())) {
String relativePath = "";
if (getParameterValues().containsKey(RodaConstants.PLUGIN_PARAMS_ID)) {
relativePath = getParameterValues().get(RodaConstants.PLUGIN_PARAMS_ID);
}
RodaCoreFactory.getTransferredResourcesScanner().updateTransferredResources(Optional.of(relativePath), true);
}
index.commit((Class<? extends IsIndexed>) Class.forName(job.getSourceObjects().getSelectedClass()));
} catch (NotFoundException | GenericException | IsStillUpdatingException e) {
LOGGER.error("Could not update new resource parent folder");
} catch (ClassNotFoundException e) {
LOGGER.error("Error commiting after move operation");
}
return new Report();
}
@Override
public Plugin<T> cloneMe() {
return new MovePlugin<>();
}
@Override
public PluginType getType() {
return PluginType.INTERNAL;
}
@Override
public boolean areParameterValuesValid() {
return true;
}
@Override
public PreservationEventType getPreservationEventType() {
return PreservationEventType.MIGRATION;
}
@Override
public String getPreservationEventDescription() {
return "Moves objects to a destination";
}
@Override
public String getPreservationEventSuccessMessage() {
return "The objects were successfully moved";
}
@Override
public String getPreservationEventFailureMessage() {
return "The objects were not successfully moved";
}
@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(File.class);
list.add(TransferredResource.class);
return (List) list;
}
}