All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.flowable.content.engine.impl.fs.SimpleFileSystemContentStorage Maven / Gradle / Ivy

/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flowable.content.engine.impl.fs;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.content.api.ContentMetaDataKeys;
import org.flowable.content.api.ContentObject;
import org.flowable.content.api.ContentStorage;
import org.flowable.content.api.ContentStorageException;
import org.flowable.engine.common.api.FlowableObjectNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.uuid.EthernetAddress;
import com.fasterxml.uuid.Generators;
import com.fasterxml.uuid.impl.TimeBasedGenerator;

/**
 * (Very) simple implementation of the {@link ContentStorage} that relies on the passed metadata to store content.
 * 
 * Under a root folder, a division between 'task' and 'process-instance' content is made. New content gets a new UUID assigned and is placed in one of these folders.
 * 
 * The id of the returned {@link ContentObject} indicates in which folder it is stored.
 * 
 * @author Joram Barrez
 */
public class SimpleFileSystemContentStorage implements ContentStorage {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleFileSystemContentStorage.class);

    private static TimeBasedGenerator UUID_GENERATOR = Generators.timeBasedGenerator(EthernetAddress.fromInterface());

    private static final String TEMP_SUFFIX = "_TEMP";
    private static final String OLD_SUFFIX = "_OLD";

    public static final String TYPE_TASK = "task-content";
    public static final String TYPE_PROCESS_INSTANCE = "process-instance-content";
    public static final String TYPE_UNCATEGORIZED = "uncategorized";

    public static final String TASK_PREFIX = "task";
    public static final String PROCESS_INSTANCE_PREFIX = "proc";
    public static final String UNCATEGORIZED_PREFIX = "uncategorized";

    protected File contentFolderRoot;
    protected File taskFolder;
    protected File processInstanceFolder;
    protected File uncategorizedFolder;

    public SimpleFileSystemContentStorage(File contentFolderRoot) {
        this.contentFolderRoot = contentFolderRoot;
        validateOrCreateSubfolders();
    }

    protected void validateOrCreateSubfolders() {
        taskFolder = validateOrCreateFolder(TYPE_TASK);
        processInstanceFolder = validateOrCreateFolder(TYPE_PROCESS_INSTANCE);
        uncategorizedFolder = validateOrCreateFolder(TYPE_UNCATEGORIZED);
    }

    protected File validateOrCreateFolder(String folderName) {
        File subFolder = new File(contentFolderRoot, folderName);
        if (!subFolder.exists()) {
            boolean created = subFolder.mkdir();
            if (created) {
                LOGGER.info("Created content folder in {}", subFolder.getAbsolutePath());
            } else {
                LOGGER.warn("Could not create content folder. This might impact the storage of related content");
            }
        }
        return subFolder;
    }

    @Override
    public ContentObject createContentObject(InputStream contentStream, Map metaData) {
        String uuid = UUID_GENERATOR.generate().toString();
        File file = getContentFile(metaData, uuid);
        try {
            long length = IOUtils.copy(contentStream, new FileOutputStream(file, false));
            String contentId = generateContentId(uuid, metaData);
            return new FileSystemContentObject(file, contentId, length);
        } catch (IOException e) {
            throw new ContentStorageException("Could not write content to " + file.getAbsolutePath(), e);
        }
    }

    protected String generateContentId(String uuid, Map metaData) {
        String contentId = "";
        switch (determineType(metaData)) {
        case TYPE_PROCESS_INSTANCE:
            String processInstanceId = (String) metaData.get(ContentMetaDataKeys.PROCESS_INSTANCE_ID);
            contentId = PROCESS_INSTANCE_PREFIX + "." + processInstanceId;
            break;

        case TYPE_TASK:
            String taskId = (String) metaData.get(ContentMetaDataKeys.TASK_ID);
            contentId = TASK_PREFIX + "." + taskId;
            break;

        default:
            contentId = UNCATEGORIZED_PREFIX;
            break;
        }
        contentId += "." + uuid;
        return contentId;
    }

    @Override
    public ContentObject updateContentObject(String id, InputStream contentStream, Map metaData) {
        File contentFile = getContentFile(id);

        // Write stream to a temporary file and rename when content is read
        // successfully to prevent overriding existing file and failing or keeping existing file
        File tempContentFile = new File(contentFile.getParentFile(), id + TEMP_SUFFIX);
        File oldContentFile = new File(contentFile.getParentFile(), id + OLD_SUFFIX);
        boolean tempFileCreated = false;
        long length = -1;

        try {
            if (!tempContentFile.createNewFile()) {
                // File already exists, being updated by another thread
                throw new ContentStorageException("Cannot update content with id: " + id + ", being updated by another user");
            }

            tempFileCreated = true;

            // Write the actual content to the file
            FileOutputStream tempOutputStream = new FileOutputStream(tempContentFile);
            length = IOUtils.copy(contentStream, tempOutputStream);
            IOUtils.closeQuietly(tempOutputStream);

            // Rename the content file first
            if (contentFile.renameTo(oldContentFile)) {
                if (tempContentFile.renameTo(contentFile)) {
                    // Rename was successful
                    oldContentFile.delete();
                } else {
                    // Rename failed, restore previous content
                    oldContentFile.renameTo(contentFile);
                    throw new ContentStorageException("Error while renaming new content file, content not updated");
                }
            } else {
                throw new ContentStorageException("Error while renaming existing content file, content not updated");
            }
        } catch (IOException ioe) {
            throw new ContentStorageException("Error while updating content with id: " + id, ioe);

        } finally {
            if (tempFileCreated) {
                try {
                    tempContentFile.delete();
                } catch (Throwable t) {
                    // No need to throw, shouldn't cause an error if the temp file cannot
                    // be deleted
                }
            }
        }

        return new FileSystemContentObject(contentFile, id, length);
    }

    @Override
    public ContentObject getContentObject(String id) {
        return new FileSystemContentObject(getContentFile(id), id);
    }

    protected File getContentFile(String id) {
        String[] ids = id.split("\\.");
        String type = ids[0];
        if (PROCESS_INSTANCE_PREFIX.equals(type) || TASK_PREFIX.equals(type)) {
            File subFolder = PROCESS_INSTANCE_PREFIX.equals(type) ? processInstanceFolder : taskFolder;
            File idFolder = new File(subFolder, ids[1]);
            File contentFile = new File(idFolder, ids[2]);
            return contentFile;

        } else if (UNCATEGORIZED_PREFIX.equals(type)) {
            File contentFile = new File(uncategorizedFolder, ids[1]);
            return contentFile;
        }

        throw new FlowableObjectNotFoundException("No content found for id " + id);
    }

    @Override
    public Map getMetaData() {
        // Currently not yet supported
        return null;
    }

    @Override
    public void deleteContentObject(String id) {
        try {
            File contentFile = getContentFile(id);
            File parentFile = contentFile.getParentFile();
            contentFile.delete();

            if (parentFile.listFiles().length == 0) {
                parentFile.delete();
            }
        } catch (Exception e) {
            throw new ContentStorageException("Error while deleting content", e);
        }
    }

    @Override
    public String getContentStoreName() {
        return "file";
    }

    protected File getContentFile(Map metaData, String contentId) {
        return new File(createOrGetFolderBasedOnMetaData(metaData), contentId);
    }

    protected String determineType(Map metaData) {
        String processInstanceId = (String) metaData.get(ContentMetaDataKeys.PROCESS_INSTANCE_ID);
        if (StringUtils.isNotEmpty(processInstanceId)) {
            return TYPE_PROCESS_INSTANCE;
        }

        String taskId = (String) metaData.get(ContentMetaDataKeys.TASK_ID);
        if (StringUtils.isNotEmpty(taskId)) {
            return TYPE_TASK;
        }

        return TYPE_UNCATEGORIZED;
    }

    protected File createOrGetFolderBasedOnMetaData(Map metaData) {
        switch (determineType(metaData)) {
        case TYPE_PROCESS_INSTANCE:
            String processInstanceId = (String) metaData.get(ContentMetaDataKeys.PROCESS_INSTANCE_ID);
            return internalCreateOrGetFolder(processInstanceFolder, processInstanceId);

        case TYPE_TASK:
            String taskId = (String) metaData.get(ContentMetaDataKeys.TASK_ID);
            return internalCreateOrGetFolder(taskFolder, taskId);

        default:
            return uncategorizedFolder;
        }
    }

    protected File internalCreateOrGetFolder(File parentFolder, String id) {
        File folder = new File(parentFolder, id);
        if (!folder.exists()) {
            folder.mkdir();
        }
        return folder;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy