org.activiti.content.engine.impl.fs.FileSystemContentStorage.java Source code

Java tutorial

Introduction

Here is the source code for org.activiti.content.engine.impl.fs.FileSystemContentStorage.java

Source

/* 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.activiti.content.engine.impl.fs;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.Map;

import org.activiti.content.api.ContentNotFoundException;
import org.activiti.content.api.ContentObject;
import org.activiti.content.api.ContentStorage;
import org.activiti.content.api.ContentStorageException;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Note that this implementation of the {@link ContentStorage} interface doesn't
 * do anything with the passed metadata, nor returns it when requested!
 * 
 * @author Frederik Heremans
 * @author Joram Barrez
 */
public class FileSystemContentStorage implements ContentStorage {

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

    private File rootFolder;
    private File currentLeafFolder;
    private PathConverter converter;

    private Object idLock = new Object();
    private int currentIndex = 0;

    private static final Logger logger = LoggerFactory.getLogger(FileSystemContentStorage.class);

    /**
     * Maximum number of times the next folder should be attempted to be acquired
     * in a row before failing.
     */
    private static final int MAX_FOLDER_RETRIES = 50;

    /**
     * @param contentFolderRoot
     *          root folder where all content will be stored in
     * @param blockSize
     *          max size of a single folder's children
     * @param depth
     *          depth of the tree created to store content in
     * @see PathConverter
     */
    public FileSystemContentStorage(File contentFolderRoot, int blockSize, int depth) {
        this.rootFolder = contentFolderRoot;

        // Create and initialize
        converter = new PathConverter();
        converter.setBlockSize(blockSize);
        converter.setIterationDepth(depth);
    }

    @Override
    public ContentObject createContentObject(InputStream contentStream, Map<String, Object> metaData) {
        // Get hold of the next free ID to use
        BigInteger id = fetchNewId();
        File contentFile = new File(rootFolder, converter.getPathForId(id).getPath());
        long length = -1;
        try {
            length = IOUtils.copy(contentStream, new FileOutputStream(contentFile, false));
        } catch (FileNotFoundException e) {
            throw new ContentStorageException(
                    "Content file was deleted or no longer accessible prior to writing: " + contentFile, e);
        } catch (IOException e) {
            throw new ContentStorageException("Error while writing content to file: " + contentFile, e);
        }

        if (length < 0) {
            // The file was larger than 2GB (max integer value), we need to get the
            // length from the file instead
            length = contentFile.length();
        }

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

    public ContentObject getContentObject(String id) {
        File contentFile = getFileForId(id, true);
        return new FileSystemContentObject(contentFile, id, null);
    }

    @Override
    public Map<String, Object> getMetaData() {
        // This implementation doesn't support metadata
        return null;
    }

    @Override
    public ContentObject updateContentObject(String id, InputStream contentStream, Map<String, Object> metaData) {

        File contentFile = getFileForId(id, true);

        // 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.toString(), length);
    }

    public void deleteContentObject(String id) {
        try {
            File contentFile = getFileForId(id, true);
            contentFile.delete();
        } catch (Exception e) {
            throw new ContentStorageException("Error while deleting content", e);
        }
    }

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

    /**
     * @return a file reference for the given id, checking for existence based on
     *         the given flag.
     */
    protected File getFileForId(String id, boolean shouldExist) {
        BigInteger idValue = null;
        try {
            idValue = new BigInteger(id);
        } catch (NumberFormatException nfe) {
            throw new ContentStorageException("Illegal ID value, only positive numbers are supported: " + id, nfe);
        }

        File path = converter.getPathForId(idValue);

        File file = new File(rootFolder, path.getPath());

        if (shouldExist != file.exists()) {
            if (shouldExist) {
                throw new ContentNotFoundException(
                        "Content with id: " + id + " was not found (path: " + file.toString() + ")");
            } else {
                throw new ContentNotFoundException("Content with id: " + id + " already exists.");
            }
        }
        return file;
    }

    protected BigInteger fetchNewId() {
        if (currentLeafFolder == null) {
            // Take the next available folder
            currentLeafFolder = getFirstAvailableFolder(MAX_FOLDER_RETRIES);
        }

        int indexToUse;
        File leafToUse = null;
        synchronized (idLock) {
            currentIndex++;
            if (currentIndex >= converter.getBlockSize()) {
                // Fetch next folder
                currentLeafFolder = getFirstAvailableFolder(MAX_FOLDER_RETRIES);
                indexToUse = 0;
                currentIndex = 0;
            } else {
                indexToUse = currentIndex;
            }

            leafToUse = currentLeafFolder;
        }

        File contentFile = new File(leafToUse, indexToUse + "");
        if (contentFile.exists()) {
            throw new ContentStorageException(
                    "Content already stored at that location, shouldn't have happended: " + contentFile);
        }

        try {
            if (!contentFile.createNewFile()) {
                throw new ContentStorageException("Unable to create content file: " + contentFile);
            }
        } catch (IOException ioe) {
            throw new ContentStorageException("Error while creating content file: " + contentFile, ioe);
        }

        return converter.getIdForPath(contentFile);
    }

    protected File getFirstAvailableFolder(int maxRetries) {

        if (maxRetries == 0) {
            logger.error("Giving up looking for next available folder, no more retries left");
            throw new ContentStorageException("Took too many attempts to get an available free folder");
        }

        File currentMaxFolder = rootFolder;
        File child = null;
        int[] indexes = new int[converter.getIterationDepth() - 1];
        boolean created = false;
        for (int i = 0; i < converter.getIterationDepth() - 1; i++) {
            created = false;
            child = getMaxChild(currentMaxFolder);
            if (child == null) {
                // No folder yet, use 0
                child = new File(currentMaxFolder, "0");
                child.mkdir();
                created = true;
            }

            indexes[i] = Integer.parseInt(child.getName());
            currentMaxFolder = child;
        }

        if (created) {
            // No need to go to the next folder, we created the first folder in the
            // tree ever
            return currentMaxFolder;
        } else {
            int lastIndex = indexes[converter.getIterationDepth() - 2];
            if (lastIndex >= converter.getBlockSize() - 1) {

                logger.debug("Block size reached, moving up one level: " + currentMaxFolder.getAbsolutePath());

                // Need to flip to a higher folder
                boolean needsMove = false;
                for (int i = converter.getIterationDepth() - 2; i >= 0; i--) {
                    int value = indexes[i] + 1;
                    if (value <= converter.getBlockSize() - 1) {
                        // Stop looping, right level found
                        indexes[i] = value;
                        needsMove = false;
                        break;
                    } else {
                        value = 0;
                        indexes[i] = value;
                        needsMove = true;
                    }
                }

                if (needsMove) {
                    logger.error("Maximum number of content items reached, cannot store any more content: "
                            + currentMaxFolder.getAbsolutePath());
                    throw new ContentStorageException(
                            "Maximum number of content items reached, cannot store any more content");
                }

                StringBuffer buffer = new StringBuffer();
                for (int i = 0; i < indexes.length; i++) {
                    buffer.append((indexes[i]) + "").append(File.separatorChar);
                }

                File newFolder = new File(rootFolder, buffer.toString());
                if (newFolder.mkdirs()) {
                    return newFolder;
                }

                // File already existed, repeat the process again to find the next
                // available folder
                logger.debug("Next folder already created, retrying...");
                return getFirstAvailableFolder(--maxRetries);
            } else {
                File newFolder = new File(currentMaxFolder.getParentFile(), (lastIndex + 1) + "");
                if (newFolder.mkdir()) {
                    return newFolder;
                }

                // File already existed, repeat the process again to find the next
                // available folder
                logger.debug("Next folder already created, retrying...");
                return getFirstAvailableFolder(--maxRetries);
            }
        }
    }

    protected File getMaxChild(File file) {
        String[] list = file.list();
        int max = -1;
        String maxChild = null;
        int current = 0;
        for (String s : list) {
            try {
                current = Integer.parseInt(s);

                if (current > max) {
                    maxChild = s;
                    max = current;
                }
            } catch (NumberFormatException nfe) {
                // Ignore bad named folders
                logger.warn("Content store contains bad folder: " + s);
            }
        }

        if (maxChild != null) {
            return new File(file, maxChild);
        }
        return null;
    }
}