org.silverpeas.core.process.io.file.AbstractFileHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.silverpeas.core.process.io.file.AbstractFileHandler.java

Source

/*
 * Copyright (C) 2000 - 2018 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.silverpeas.core.process.io.file;

import org.silverpeas.core.process.io.IOAccess;
import org.silverpeas.core.process.io.file.exception.FileHandlerException;
import org.silverpeas.core.process.management.ProcessManagement;
import org.silverpeas.core.process.session.ProcessSession;
import org.silverpeas.core.util.MapUtil;
import org.silverpeas.core.util.file.FileRepositoryManager;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.*;

/**
 * Bases of file handler functionnalities whose a lot of these are protected and only usable by
 * extended classes and by <code>ProcessManagement</code> services (@see {@link ProcessManagement}).
 * This class contains the transactionnal mechanism of file system manipulations.
 * @author Yohann Chastagnier
 */
public abstract class AbstractFileHandler {

    private static final Set<FileBasePath> handledBasePath = new HashSet<>();
    static {
        handledBasePath.add(FileBasePath.UPLOAD_PATH);
    }

    private final String SESSION_TEMP_NODE = "@#@work@#@";

    private final File sessionRootPath = new File(FileRepositoryManager.getTemporaryPath());
    private final ProcessSession session;
    private final Map<FileBasePath, Set<File>> toDelete = new HashMap<>();
    private final Map<String, Set<DummyHandledFile>> dummyHandledFiles = new HashMap<>();
    private IOAccess ioAccess = IOAccess.READ_ONLY;

    /**
     * Default constructor
     * @param session
     */
    protected AbstractFileHandler(final ProcessSession session) {
        this.session = session;
    }

    /**
     * @return the session
     */
    private ProcessSession getSession() {
        return session;
    }

    /**
     * Mark the given file to be deleted.
     * @param basePath
     * @param file
     * @return true if the file exists and when it is the first time that it is registred to be
     * deleted
     */
    protected boolean markToDelete(final FileBasePath basePath, File file) throws Exception {
        if (isHandledPath(basePath)) {
            file = translateToRealPath(basePath, file);
            if (file.exists()) {
                if (getIoAccess().equals(IOAccess.READ_ONLY)) {
                    setIoAccess(IOAccess.DELETE_ONLY);
                }
                final Set<File> filesMarkedToBeDeleted = getMarkedToDelete(basePath);
                boolean markedToDelete = false;
                if (file.isFile()) {
                    boolean addOk = true;
                    for (final File curFile : filesMarkedToBeDeleted) {
                        if (curFile.isDirectory() && FileUtils.directoryContains(curFile, file)) {
                            addOk = false;
                            break;
                        }
                    }
                    if (addOk) {
                        markedToDelete = filesMarkedToBeDeleted.add(file);
                    }
                } else {
                    File curFile;
                    final Iterator<File> it = filesMarkedToBeDeleted.iterator();
                    while (it.hasNext()) {
                        curFile = it.next();
                        if (FileUtils.directoryContains(file, curFile)) {
                            it.remove();
                        }
                    }
                    markedToDelete = filesMarkedToBeDeleted.add(file);
                }
                return markedToDelete;
            }
        }
        return false;
    }

    /**
     * Indicates if the file will be deleted
     * @param basePath
     * @param file
     * @return
     */
    protected boolean isMarkedToDelete(final FileBasePath basePath, File file) {
        if (isHandledPath(basePath)) {
            file = translateToRealPath(basePath, file);
            for (final File fileToDelete : getMarkedToDelete(basePath)) {
                if (file.getPath().startsWith(fileToDelete.getPath())) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Gets the container that holds references on files to delete
     * @param basePath
     * @return
     */
    Set<File> getMarkedToDelete(final FileBasePath basePath) {
        Set<File> toDeleteContainer = toDelete.get(basePath);
        if (toDeleteContainer == null) {
            toDeleteContainer = new HashSet<>();
            toDelete.put(basePath, toDeleteContainer);
        }
        return toDeleteContainer;
    }

    /**
     * Translate to real.
     * Attention please : verify has to be called before.
     * @param basePath
     * @param file
     * @return
     */
    protected File translateToRealPath(final FileBasePath basePath, File file) {
        if (isHandledPath(basePath)) {
            final File sessionPath = getSessionPath(basePath);
            if (file.getPath().startsWith(sessionPath.getPath())) {
                final String endOfFileName = file.getPath().substring(sessionPath.getPath().length());
                file = new File(basePath.getPath() + endOfFileName);
            }
        }
        return file;
    }

    /**
     * Translate to session path.
     * Attention please : verify has to be called before.
     * @param basePath
     * @param file
     * @return
     */
    protected File translateToSessionPath(final FileBasePath basePath, File file) {
        if (isHandledPath(basePath) && file.getPath().startsWith(basePath.getPath())) {
            final String endOfFileName = file.getPath().substring(basePath.getPath().length());
            file = new File(getSessionPath(basePath).getPath() + endOfFileName);
        }
        return file;
    }

    /**
     * If the given file doesn't exist in session path, then the file existing in real path is
     * returned.
     * If the given file doesn't exist in real path, then the new session file is returned.
     * @param basePath
     * @param file
     * @return
     */
    protected File getExistingFile(final FileBasePath basePath, final File file) {
        final File sessionFile = translateToSessionPath(basePath, file);
        final File realFile = translateToRealPath(basePath, file);
        return (!sessionFile.exists() && realFile.exists() && !isMarkedToDelete(basePath, realFile)) ? realFile
                : sessionFile;
    }

    /**
     * If the given file doesn't exist in session path, then the file existing in real path is
     * returned.
     * If the given file doesn't exist in real path, then the new session file is returned.
     * @param basePath
     * @param file
     * @return
     */
    protected File getFileForWriting(final FileBasePath basePath, final File file) throws Exception {
        return getFileForWriting(basePath, file, false);
    }

    /**
     * If the given file doesn't exist in session path, then the file existing in real path is
     * returned.
     * If the given file doesn't exist in real path, then the new session file is returned.
     * @param basePath
     * @param file
     * @param append
     * @return
     */
    protected File getFileForWriting(final FileBasePath basePath, final File file, final boolean append)
            throws Exception {
        final File sessionFile = translateToSessionPath(basePath, file);
        if (append && !isMarkedToDelete(basePath, file)) {
            final File realFile = translateToRealPath(basePath, file);
            if (realFile.exists()) {
                FileUtils.copyFile(realFile, sessionFile);
            }
        }
        markToDelete(basePath, file);
        setIoAccess(IOAccess.READ_WRITE);
        return sessionFile;
    }

    /**
     * Build session path
     * @param basePath
     * @return
     */
    File getSessionPath(final FileBasePath basePath) {
        if (!isHandledPath(basePath)) {
            throw new FileHandlerException("EX_GETTING_SESSION_PATH_IS_NOT_POSSIBLE");
        }
        return FileUtils.getFile(sessionRootPath, getSession().getId(), basePath.getHandledNodeName());
    }

    /**
     * Build session temporary path
     * @return
     */
    File getSessionTemporaryPath() {
        return FileUtils.getFile(sessionRootPath, getSession().getId(), SESSION_TEMP_NODE);
    }

    /**
     * This method calculates the size of files contained in the given relative root path from the
     * session and subtracts from the previous result the size of files marked to be deleted.
     * Dummy handled files are included (according to relativeRootPath that is normally a list of
     * component instance ids).
     */
    public long sizeOfSessionWorkingPath(final String... relativeRootPath) {
        long size = 0;
        for (final FileBasePath basePath : handledBasePath) {
            size += sizeOfSessionWorkingPath(basePath, relativeRootPath);
        }

        // Finally adding/removing the size of dummy handled files
        Set<String> componentInstanceIds = new HashSet<>();
        if (relativeRootPath != null) {
            // Only a part of dummy files is aimed
            Collections.addAll(componentInstanceIds, relativeRootPath);
        }
        if (componentInstanceIds.isEmpty()) {
            // If relativeRootPath is empty, then all dummy files are aimed
            componentInstanceIds.addAll(dummyHandledFiles.keySet());
        }
        for (String componentInstanceId : componentInstanceIds) {
            Set<DummyHandledFile> dummyHandledFilesOfCurrentComponentInstanceId = dummyHandledFiles
                    .get(componentInstanceId);
            if (dummyHandledFilesOfCurrentComponentInstanceId != null) {
                for (DummyHandledFile dummyHandledFile : dummyHandledFilesOfCurrentComponentInstanceId) {
                    if (dummyHandledFile.isDeleted()) {
                        size -= dummyHandledFile.getSize();
                    } else {
                        size += dummyHandledFile.getSize();
                    }
                }
            }
        }
        return size;
    }

    /**
     * This method calculates the size of files contained in the given relative root path from the
     * session and subtracts from the previous result the size of files marked to be deleted.
     */
    protected long sizeOfSessionWorkingPath(final FileBasePath basePath, final String... relativeRootPath) {

        // Computing root path from which the size has to be calculated
        final List<String> rootPathParts = new ArrayList<>();
        rootPathParts.add(getSession().getId());
        rootPathParts.add(basePath.getHandledNodeName());
        if (relativeRootPath != null) {
            rootPathParts.addAll(Arrays.asList(relativeRootPath));
        }

        // Root path (or file ?)
        final File rootPath = FileUtils.getFile(sessionRootPath,
                rootPathParts.toArray(new String[rootPathParts.size()]));

        // Size of not deleted files
        long size = (rootPath.exists()) ? FileUtils.sizeOf(rootPath) : 0;

        // Remove the size of deleted files from previous result
        final String realRootPath = translateToRealPath(basePath, rootPath).getPath();
        for (final Map.Entry<FileBasePath, Set<File>> entry : toDelete.entrySet()) {
            for (final File fileToDelete : entry.getValue()) {
                if (fileToDelete.getPath().startsWith(realRootPath) && fileToDelete.exists()) {
                    size -= FileUtils.sizeOf(fileToDelete);
                }
            }
        }
        return size;
    }

    /**
     * Gets handled root directories from the session. (reads, writes, deletes)
     * @return
     */
    public Collection<String> getSessionHandledRootPathNames() {
        return getSessionHandledRootPathNames(false);
    }

    /**
     * Gets handled root directories from the session. (reads, writes, deletes)
     * The result contains root directories of dummy handled files.
     * @param skipDeleted
     * @return
     */
    public Collection<String> getSessionHandledRootPathNames(final boolean skipDeleted) {
        final Set<String> rootPathNames = new HashSet<>();
        for (final FileBasePath basePath : handledBasePath) {
            rootPathNames.addAll(getSessionHandledRootPathNames(basePath, skipDeleted));
        }
        rootPathNames.addAll(dummyHandledFiles.keySet());
        return rootPathNames;
    }

    /**
     * Gets handled root directories of a base path from the session. (reads, writes, deletes)
     * @param basePath
     * @param skipDeleted
     * @return
     */
    protected Collection<String> getSessionHandledRootPathNames(final FileBasePath basePath,
            final boolean skipDeleted) {
        final Set<String> rootPathNames = new HashSet<>();
        if (isHandledPath(basePath)) {

            // reads and writes
            final String[] directories = getSessionPath(basePath).list(DirectoryFileFilter.DIRECTORY);
            if (directories != null) {
                rootPathNames.addAll(Arrays.asList(directories));
            }

            // deletes
            if (!skipDeleted) {
                String[] deletedFileNameParts;
                for (final File deleted : getMarkedToDelete(basePath)) {
                    if (deleted.getPath().startsWith(basePath.getPath())) {
                        deletedFileNameParts = FilenameUtils
                                .separatorsToUnix(deleted.getPath().substring(basePath.getPath().length()))
                                .replaceAll("^/", "").split("/");
                        if (deletedFileNameParts != null && deletedFileNameParts.length > 0) {
                            rootPathNames.add(deletedFileNameParts[0]);
                        }
                    }
                }
            }

            // Potential items to remove
            rootPathNames.remove(SESSION_TEMP_NODE);
            rootPathNames.remove(null);
            rootPathNames.remove("");
        }
        return rootPathNames;
    }

    /**
     * Gets handled root directory Files of a base path from the session. (reads, writes)
     * @return
     */
    public Collection<File> listAllSessionHandledRootPathFiles() {
        final Set<File> rootPathFiles = new HashSet<>();
        for (final FileBasePath basePath : handledBasePath) {
            rootPathFiles.addAll(listAllSessionHandledRootPathFiles(basePath));
        }
        return rootPathFiles;
    }

    /**
     * Gets handled root directory Files of a base path from the session. (reads, writes)
     * @param basePath
     * @return
     */
    protected Collection<File> listAllSessionHandledRootPathFiles(final FileBasePath basePath) {
        final Set<File> rootPathNames = new HashSet<>();
        if (isHandledPath(basePath)) {

            // reads and writes
            final File[] directories = getSessionPath(basePath)
                    .listFiles((FileFilter) DirectoryFileFilter.DIRECTORY);
            if (directories != null) {
                rootPathNames.addAll(Arrays.asList(directories));
            }
        }
        return rootPathNames;
    }

    /**
     * Delete session path
     * @return
     */
    protected void deleteSessionWorkingPath() {
        FileUtils.deleteQuietly(FileUtils.getFile(sessionRootPath, getSession().getId()));
        toDelete.clear();
    }

    /**
     * Checkin session path
     * @return
     */
    protected void checkinSessionWorkingPath() throws Exception {

        // Deleting files
        for (final Map.Entry<FileBasePath, Set<File>> entry : toDelete.entrySet()) {
            for (final File fileToDelete : entry.getValue()) {
                if (fileToDelete.exists()) {
                    FileUtils.deleteQuietly(fileToDelete);
                }
            }
        }

        // Cleaning file to delete container
        toDelete.clear();

        // Copying files from the working session path
        for (final FileBasePath basePath : handledBasePath) {
            copyFiles(basePath, getSessionPath(basePath));
        }

        // Cleaning
        final File sessionPath = FileUtils.getFile(sessionRootPath, getSession().getId());
        if (sessionPath.exists()) {
            File[] files = sessionPath.listFiles();
            if (files != null) {
                for (final File file : files) {
                    FileUtils.deleteQuietly(file);
                }
            }
        }
    }

    /**
     * Recursive file copying
     * @param basePath
     * @param file
     * @throws IOException
     */
    private void copyFiles(final FileBasePath basePath, final File file) throws IOException {
        if (file.exists()) {
            final LinkedList<File> fifo = new LinkedList<>();
            fifo.add(file);
            File currentFile;
            while ((currentFile = fifo.poll()) != null) {
                if (currentFile.isDirectory()) {
                    File[] files = currentFile.listFiles();
                    if (files != null) {
                        Collections.addAll(fifo, files);
                    }
                } else {
                    FileUtils.copyFile(currentFile, translateToRealPath(basePath, currentFile));
                }
            }
        }
    }

    /**
     * Verify the integrity between handled path and file
     * @param basePath
     * @param file
     */
    protected void verify(final FileBasePath basePath, final File file) {
        verify(basePath, file, false);
    }

    /**
     * Verify the integrity between handled path and file
     * @param basePath
     * @param file
     * @param isReadOnly
     */
    protected void verify(final FileBasePath basePath, final File file, final boolean isReadOnly) {
        if (basePath == null || (!isReadOnly && isHandledPath(basePath))) {
            if (basePath != null && (file.getPath().startsWith(getSessionPath(basePath).getPath())
                    || file.getPath().startsWith(basePath.getPath()))) {
                return;
            }
            throw new FileHandlerException("EX_VERIFY_ERROR");
        }
    }

    /**
     * Verify if the given file exists
     * @param file
     * @return
     */
    protected static boolean exists(final File file) {
        return file.exists();
    }

    /**
     * Indicates if the given path is handled or not
     * @param basePath
     * @return
     */
    protected boolean isHandledPath(final FileBasePath basePath) {
        return handledBasePath.contains(basePath);
    }

    /**
     * @return the ioAccess
     */
    public IOAccess getIoAccess() {
        return ioAccess;
    }

    /**
     * @param ioAccess the ioAccess to set
     */
    private void setIoAccess(final IOAccess ioAccess) {
        this.ioAccess = ioAccess;
    }

    /**
     * Add a dummy file.
     * It can be useful for process check operations.
     * @param dummyHandledFile
     */
    public void addDummyHandledFile(DummyHandledFile dummyHandledFile) {
        setIoAccess(IOAccess.READ_WRITE);
        MapUtil.putAddSet(dummyHandledFiles, dummyHandledFile.getComponentInstanceId(), dummyHandledFile);
    }

    /**
     * Remove a dummy file.
     * It can be useful for process check operations.
     * @param dummyHandledFile
     */
    public void removeDummyHandledFile(DummyHandledFile dummyHandledFile) {
        MapUtil.removeValueSet(dummyHandledFiles, dummyHandledFile.getComponentInstanceId(), dummyHandledFile);
    }

    /**
     * Gets the dummy handled files from a given component instance id.
     * A component instance id can be see as a root handled directory.
     * @return
     */
    public Set<DummyHandledFile> getDummyHandledFiles(String componentInstanceId) {
        Set<DummyHandledFile> result = dummyHandledFiles.get(componentInstanceId);
        if (result == null) {
            //noinspection unchecked
            result = Collections.EMPTY_SET;
        }
        return result;
    }
}