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.persistence.ocfl.impl;
007
008import org.fcrepo.config.OcflPropsConfig;
009import org.fcrepo.kernel.api.ReadOnlyTransaction;
010import org.fcrepo.kernel.api.Transaction;
011import org.fcrepo.persistence.api.PersistentStorageSession;
012import org.fcrepo.persistence.api.PersistentStorageSessionManager;
013import org.fcrepo.persistence.ocfl.api.FedoraToOcflObjectIndex;
014import org.fcrepo.storage.ocfl.OcflObjectSessionFactory;
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017import org.springframework.beans.factory.annotation.Autowired;
018import org.springframework.stereotype.Component;
019
020import javax.inject.Inject;
021import java.io.IOException;
022import java.nio.file.FileVisitResult;
023import java.nio.file.Files;
024import java.nio.file.Path;
025import java.nio.file.SimpleFileVisitor;
026import java.nio.file.attribute.BasicFileAttributes;
027import java.util.Map;
028import java.util.concurrent.ConcurrentHashMap;
029
030/**
031 * OCFL implementation of PersistentStorageSessionManager
032 *
033 * @author whikloj
034 * @author dbernstein
035 * @since 2019-09-20
036 */
037@Component
038public class OcflPersistentSessionManager implements PersistentStorageSessionManager {
039
040    private static final Logger LOGGER = LoggerFactory.getLogger(OcflPersistentSessionManager.class);
041
042    private volatile PersistentStorageSession readOnlySession;
043
044    private Map<String, PersistentStorageSession> sessionMap;
045
046    @Inject
047    private OcflObjectSessionFactory objectSessionFactory;
048
049    @Inject
050    private FedoraToOcflObjectIndex ocflIndex;
051
052    @Inject
053    private ReindexService reindexService;
054
055    @Inject
056    private OcflPropsConfig ocflPropsConfig;
057
058    /**
059     * Default constructor
060     */
061    @Autowired
062    public OcflPersistentSessionManager() {
063        this.sessionMap = new ConcurrentHashMap<>();
064    }
065
066    @Override
067    public PersistentStorageSession getSession(final Transaction transaction) {
068        if (transaction == null) {
069            throw new IllegalArgumentException("session id must be non-null");
070        }
071
072        return sessionMap.computeIfAbsent(transaction.getId(), key -> {
073            LOGGER.debug("Creating storage session {}", transaction);
074            return new OcflPersistentStorageSessionMetrics(
075                    new OcflPersistentStorageSession(
076                            transaction,
077                            ocflIndex,
078                            objectSessionFactory,
079                            reindexService));
080        });
081    }
082
083    @Override
084    public PersistentStorageSession getReadOnlySession() {
085        var localSession = this.readOnlySession;
086
087        if (localSession == null) {
088            synchronized (this) {
089                localSession = this.readOnlySession;
090                if (localSession == null) {
091                    this.readOnlySession = new OcflPersistentStorageSessionMetrics(
092                            new OcflPersistentStorageSession(ReadOnlyTransaction.INSTANCE,
093                                    ocflIndex, objectSessionFactory, reindexService));
094                    localSession = this.readOnlySession;
095                }
096            }
097        }
098
099        return localSession;
100    }
101
102    @Override
103    public PersistentStorageSession removeSession(final String sessionId) {
104        LOGGER.debug("Removing storage session {}", sessionId);
105        return sessionMap.remove(sessionId);
106    }
107
108    @Override
109    public void clearAllSessions() {
110        LOGGER.debug("Clearing all storage sessions");
111        sessionMap.clear();
112        ocflIndex.clearAllTransactions();
113        try {
114            deleteStagingDirectories();
115        } catch (IOException e) {
116            LOGGER.error("Failed to delete OCFL staging directories", e);
117        }
118    }
119
120    /**
121     * Deletes all of the staging directories within the root staging directory
122     * @throws IOException
123     */
124    private void deleteStagingDirectories() throws IOException {
125        // Delete
126        Files.walkFileTree(ocflPropsConfig.getFedoraOcflStaging(), new SimpleFileVisitor<>() {
127            @Override
128            public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
129                // Delete the file
130                Files.delete(file);
131                return FileVisitResult.CONTINUE;
132            }
133
134            @Override
135            public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
136                // Delete the directory after its contents have been deleted, excluding the root staging directory
137                if (!dir.equals(ocflPropsConfig.getFedoraOcflStaging())) {
138                    Files.delete(dir);
139                }
140                return FileVisitResult.CONTINUE;
141            }
142        });
143    }
144}