/*
* Copyright (C) 2012 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cas.lib.proarc.common.fedora;
import com.yourmediashelf.fedora.client.FedoraClientException;
import cz.cas.lib.proarc.common.fedora.RemoteStorage.RemoteObject;
import cz.cas.lib.proarc.common.fedora.SearchView.Item;
import cz.cas.lib.proarc.common.fedora.relation.RelationEditor;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Deletes object or hierarchy of objects from the remote storage. It also updates
* RELS-EXT of objects referencing deleted PIDs.
* <p>There are 2 options to delete objects. The first is to
* {@link #purge(java.util.List, boolean, java.lang.String) purge} them. It removes
* all deleted objects from the repository. The second is to mark them as
* {@link #delete(java.util.List, boolean, java.lang.String) deleted}.
* The second option should be preferred to keep the history or in case of
* e.g. OAI usage.</p>
*
* @author Jan Pokorsky
*/
public final class PurgeFedoraObject {
private final RemoteStorage storage;
/**
* set of PIDs to purge
*/
private final Set<String> toPurge;
/**
* set of PIDs to update
*/
private final Set<String> toUpdate;
private String logMessage;
public PurgeFedoraObject(RemoteStorage storage) {
this.storage = storage;
this.toPurge = new LinkedHashSet<String>();
this.toUpdate = new HashSet<String>();
}
public void delete(String pid, boolean hierarchy, String message) throws PurgeException {
process(Collections.singletonList(pid), hierarchy, true, message);
}
public void delete(List<String> pids, boolean hierarchy, String message) throws PurgeException {
process(pids, hierarchy, true, message);
}
public void purge(String pid, boolean hierarchy, String message) throws PurgeException {
purge(Collections.singletonList(pid), hierarchy, message);
}
public void purge(List<String> pids, boolean hierarchy, String message) throws PurgeException {
process(pids, hierarchy, false, message);
}
private void process(List<String> pids, boolean hierarchy, boolean setDeleted, String message) throws PurgeException {
toPurge.clear();
toUpdate.clear();
this.logMessage = message;
for (String pid : pids) {
process(pid, hierarchy);
}
try {
updateRelations(toUpdate);
} catch (DigitalObjectException ex) {
throw new PurgeException(ex);
}
if (setDeleted) {
setDeleted(toPurge);
} else {
purge(toPurge);
}
}
private void process(String pid, boolean hierarchy) throws PurgeException {
if (!toPurge.add(pid)) {
return ;
}
if (hierarchy) {
List<Item> items = getHierarchy(pid);
for (Item item : items) {
toPurge.add(item.getPid());
}
}
// For now it presumes that objects are referenced by one parent thus
// only roots are queried for parent.
// Later it should ask for parents of each deleted object. It will be
// expensive operation that could be optimized with ITQL queries created
// for each ~20 items not to exceed query length (missing specification).
// Query:
//select $pid
//from <#ri>
//where
//$pid <info:fedora/fedora-system:def/relations-external#hasMember> <info:fedora/uuid:tree1-child1-child1-child1>
//or $pid <info:fedora/fedora-system:def/relations-external#hasMember> <info:fedora/uuid:tree1-child1-child1>
List<Item> parents = getParent(pid);
for (Item parent : parents) {
toUpdate.add(parent.getPid());
}
}
private List<Item> getHierarchy(String pid) throws PurgeException {
try {
List<Item> pids = storage.getSearch().findChildrenHierarchy(pid);
return pids;
} catch (FedoraClientException ex) {
throw new PurgeException(pid, ex);
} catch (IOException ex) {
throw new PurgeException(pid, ex);
}
}
private List<Item> getParent(String pid) throws PurgeException {
List<Item> pids = null;
try {
pids = storage.getSearch().findReferrers(pid);
} catch (IOException ex) {
throw new PurgeException(pid, ex);
} catch (FedoraClientException ex) {
throw new PurgeException(pid, ex);
}
return pids;
}
/**
* Removes all objects that will be deleted from PID's relations in RELS-EXT.
* @param pid object to update
*/
private void updateRelations(String pid) throws DigitalObjectException {
RemoteObject remote = storage.find(pid);
RelationEditor editor = new RelationEditor(remote);
List<String> members = editor.getMembers();
members.removeAll(toPurge);
editor.setMembers(members);
editor.write(editor.getLastModified(), logMessage);
remote.flush();
}
/**
* @see #updateRelations(java.lang.String)
*/
private void updateRelations(Iterable<String> pids) throws DigitalObjectException {
for (String pid : pids) {
updateRelations(pid);
}
}
private void purge(String pid) throws PurgeException {
try {
RemoteObject remote = storage.find(pid);
remote.purge(logMessage);
} catch (DigitalObjectException ex) {
throw new PurgeException(pid, ex);
}
}
private void purge(Set<String> pids) throws PurgeException {
for (String pid : pids) {
purge(pid);
}
}
private void setDeleted(Set<String> pids) throws PurgeException {
for (String pid : pids) {
setDeleted(pid);
}
}
private void setDeleted(String pid) throws PurgeException {
try {
RemoteObject remote = storage.find(pid);
remote.delete(logMessage);
} catch (DigitalObjectException ex) {
throw new PurgeException(pid, ex);
}
}
public static class PurgeException extends Exception {
public PurgeException(Throwable cause) {
super(cause);
}
public PurgeException(String message, Throwable cause) {
super(message, cause);
}
}
}