001/*
002 * The contents of this file are subject to the license and copyright
003 * detailed in the LICENSE and NOTICE files at the root of the source
004 * tree.
005 */
006package org.fcrepo.kernel.impl.services;
007
008import static java.lang.String.format;
009
010import java.util.Optional;
011import java.util.stream.Stream;
012
013import javax.inject.Inject;
014
015import org.fcrepo.kernel.api.Transaction;
016import org.fcrepo.kernel.api.auth.ACLHandle;
017import org.fcrepo.kernel.api.exception.PathNotFoundException;
018import org.fcrepo.kernel.api.exception.PathNotFoundRuntimeException;
019import org.fcrepo.kernel.api.exception.RepositoryRuntimeException;
020import org.fcrepo.kernel.api.identifiers.FedoraId;
021import org.fcrepo.kernel.api.models.Binary;
022import org.fcrepo.kernel.api.models.Container;
023import org.fcrepo.kernel.api.models.FedoraResource;
024import org.fcrepo.kernel.api.models.NonRdfSourceDescription;
025import org.fcrepo.kernel.api.models.ResourceFactory;
026import org.fcrepo.kernel.api.models.Tombstone;
027import org.fcrepo.persistence.api.PersistentStorageSession;
028import org.fcrepo.persistence.api.PersistentStorageSessionManager;
029import org.fcrepo.persistence.api.exceptions.PersistentStorageException;
030
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import com.github.benmanes.caffeine.cache.Cache;
035
036/**
037 * Shared delete/purge code.
038 * @author whikloj
039 */
040abstract public class AbstractDeleteResourceService extends AbstractService {
041
042    private final static Logger log = LoggerFactory.getLogger(AbstractDeleteResourceService.class);
043
044    @Inject
045    protected ResourceFactory resourceFactory;
046
047    @Inject
048    protected PersistentStorageSessionManager psManager;
049
050    @Inject
051    private Cache<String, Optional<ACLHandle>> authHandleCache;
052
053    /**
054     * The starts the service, does initial checks and setups for processing.
055     * @param tx the transaction.
056     * @param fedoraResource the resource to start delete/purging.
057     * @param userPrincipal the user performing the action.
058     */
059    public void perform(final Transaction tx, final FedoraResource fedoraResource, final String userPrincipal) {
060        final String fedoraResourceId = fedoraResource.getId();
061
062        if (fedoraResource instanceof NonRdfSourceDescription) {
063            throw new RepositoryRuntimeException(
064            format("A NonRdfSourceDescription cannot be deleted independently of the NonRDFSource:  %s",
065            fedoraResourceId));
066        }
067
068        try {
069            log.debug("operating on {}", fedoraResourceId);
070            final PersistentStorageSession pSession = this.psManager.getSession(tx);
071            deleteDepthFirst(tx, pSession, fedoraResource, userPrincipal);
072        } catch (final PersistentStorageException ex) {
073            throw new RepositoryRuntimeException(format("failed to delete/purge resource %s", fedoraResourceId), ex);
074        }
075    }
076
077    /**
078     * Code to perform the recursion of containers.
079     * @param tx the transaction
080     * @param pSession the persistent storage session
081     * @param fedoraResource the current resource to check for any children.
082     * @param userPrincipal the user performing the action.
083     * @throws PersistentStorageException any problems accessing the underlying storage.
084     */
085    private void deleteDepthFirst(final Transaction tx, final PersistentStorageSession pSession,
086                                  final FedoraResource fedoraResource, final String userPrincipal)
087            throws PersistentStorageException {
088
089        final FedoraId fedoraId = fedoraResource.getFedoraId();
090
091        if (fedoraResource instanceof Container) {
092            final Stream<String> children = getContained(tx, fedoraResource);
093            children.forEach(childResourceId -> {
094                try {
095
096                    final FedoraResource res = resourceFactory.getResource(tx, FedoraId.create(childResourceId));
097                    if (res instanceof Tombstone) {
098                        deleteDepthFirst(tx, pSession, ((Tombstone) res).getDeletedObject(), userPrincipal);
099                    } else {
100                        deleteDepthFirst(tx, pSession, res, userPrincipal);
101                    }
102                } catch (final PathNotFoundException ex) {
103                    log.error("Path not found for {}: {}", fedoraId.getFullId(), ex.getMessage());
104                    throw new PathNotFoundRuntimeException(ex.getMessage(), ex);
105                } catch (final PersistentStorageException ex) {
106                    throw new RepositoryRuntimeException(format("failed to delete resource %s", fedoraId.getFullId()),
107                            ex);
108                }
109            });
110        } else if (fedoraResource instanceof Binary) {
111            doAction(tx, pSession, fedoraResource.getDescription().getFedoraId(), userPrincipal);
112        }
113
114        //delete/purge the acl if this is not the acl
115        if (!fedoraResource.isAcl()) {
116            final FedoraResource acl = fedoraResource.getAcl();
117            if (acl != null) {
118                doAction(tx, pSession, acl.getFedoraId(), userPrincipal);
119                // Flush ACL cache on any ACL creation/update/deletion.
120                authHandleCache.invalidateAll();
121            }
122        } else {
123            // Flush ACL cache on any ACL creation/update/deletion.
124            authHandleCache.invalidateAll();
125        }
126
127        //delete/purge the resource itself
128        doAction(tx, pSession, fedoraId, userPrincipal);
129    }
130
131    /**
132     * Get the contained resources to act upon.
133     * @param tx the transaction this occurs in.
134     * @param resource the parent resource to find contained resources for.
135     * @return stream of child ids.
136     */
137    abstract protected Stream<String> getContained(final Transaction tx, final FedoraResource resource);
138
139    /**
140     * Perform the actual delete or purge action
141     * @param tx the transaction this occurs in.
142     * @param pSession the persistent storage session.
143     * @param resourceId the resource to perform the action on.
144     * @param userPrincipal the user performing the action
145     * @throws PersistentStorageException if problem performing the action.
146     */
147    abstract protected void doAction(final Transaction tx, final PersistentStorageSession pSession,
148                                     final FedoraId resourceId, final String userPrincipal)
149            throws PersistentStorageException;
150}