org.opencastproject.archive.storage.FileSystemElementStore.java Source code

Java tutorial

Introduction

Here is the source code for org.opencastproject.archive.storage.FileSystemElementStore.java

Source

/**
 * Licensed to The Apereo Foundation under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 *
 * The Apereo Foundation licenses this file to you under the Educational
 * Community 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://opensource.org/licenses/ecl2.txt
 *
 * 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.opencastproject.archive.storage;

import static org.apache.commons.io.FilenameUtils.EXTENSION_SEPARATOR;
import static org.apache.commons.io.FilenameUtils.getExtension;
import static org.apache.commons.lang.exception.ExceptionUtils.getMessage;
import static org.opencastproject.util.FileSupport.link;
import static org.opencastproject.util.IoSupport.file;
import static org.opencastproject.util.PathSupport.path;
import static org.opencastproject.util.data.Option.none;
import static org.opencastproject.util.data.Option.option;
import static org.opencastproject.util.data.Option.some;
import static org.opencastproject.util.data.functions.Strings.trimToNone;

import org.opencastproject.archive.api.Version;
import org.opencastproject.archive.base.StoragePath;
import org.opencastproject.archive.base.storage.DeletionSelector;
import org.opencastproject.archive.base.storage.ElementStore;
import org.opencastproject.archive.base.storage.ElementStoreException;
import org.opencastproject.archive.base.storage.Source;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.PathSupport;
import org.opencastproject.util.data.Function;
import org.opencastproject.util.data.Option;
import org.opencastproject.workspace.api.Workspace;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;

public class FileSystemElementStore implements ElementStore {

    /** Log facility */
    private static final Logger logger = LoggerFactory.getLogger(FileSystemElementStore.class);

    /** Configuration key for the storage directory */
    public static final String CONFIG_STORAGE_DIR = "org.opencastproject.storage.dir";

    /** Configuration key for the archive root directory */
    public static final String CONFIG_ARCHIVE_ROOT_DIR = "org.opencastproject.episode.rootdir";

    /** The default archive directory name */
    private static final String DEFAULT_ARCHIVE_DIRECTORY = "archive";

    /** The root directory for storing files */
    private String rootDirectory = null;

    private Workspace workspace;

    /** OSGi Di */
    void setWorkspace(Workspace workspace) {
        this.workspace = workspace;
    }

    /**
     * Service activator, called via declarative services configuration.
     *
     * @param cc
     *          the component context
     */
    public void activate(final ComponentContext cc) throws IllegalStateException, IOException {
        rootDirectory = StringUtils.trimToNull(cc.getBundleContext().getProperty(CONFIG_ARCHIVE_ROOT_DIR));

        if (rootDirectory == null) {
            final String storageDir = StringUtils.trimToNull(cc.getBundleContext().getProperty(CONFIG_STORAGE_DIR));
            if (storageDir == null)
                throw new IllegalArgumentException("Storage directory must be set");
            rootDirectory = PathSupport.concat(storageDir, DEFAULT_ARCHIVE_DIRECTORY);
        }

        mkDirs(file(rootDirectory));
    }

    @Override
    public void put(StoragePath storagePath, Source source) throws ElementStoreException {
        // Retrieving the file from the workspace has the advantage that in most cases the file already exists in the local
        // working file repository. In the very few cases where the file is not in the working file repository,
        // this strategy leads to a minor overhead because the file not only gets downloaded and stored in the file system
        // but also a hard link needs to be created (or if that's not possible, a copy of the file.
        final File origin = getFileFromWorkspace(source);
        final File destination = createFile(storagePath, source);
        try {
            mkParent(destination);
            link(origin, destination);
        } catch (IOException e) {
            logger.error("Error while linking/copying file {} to {}: {}",
                    new Object[] { origin, destination, getMessage(e) });
            throw new ElementStoreException(e);
        }
    }

    private File getFileFromWorkspace(Source source) {
        try {
            return workspace.get(source.getUri());
        } catch (NotFoundException e) {
            logger.error("Source file '{}' does not exist", source.getUri());
            throw new ElementStoreException(e);
        } catch (IOException e) {
            logger.error("Error while getting file '{}' from workspace: {}", source.getUri(), getMessage(e));
            throw new ElementStoreException(e);
        }
    }

    @Override
    public boolean copy(final StoragePath from, final StoragePath to) throws ElementStoreException {
        return findStoragePathFile(from).map(new Function<File, Boolean>() {
            @Override
            public Boolean apply(File f) {
                final File t = createFile(to, f);
                mkParent(t);
                logger.debug("Copying {} to {}", f.getAbsolutePath(), t.getAbsolutePath());
                try {
                    link(f, t, true);
                } catch (IOException e) {
                    logger.error("Error copying archive file {} to {}", f, t);
                    throw new ElementStoreException(e);
                }
                return true;
            }
        }).getOrElse(false);
    }

    @Override
    public Option<InputStream> get(final StoragePath path) throws ElementStoreException {
        return findStoragePathFile(path).map(new Function<File, InputStream>() {
            @Override
            public InputStream apply(File file) {
                try {
                    return new FileInputStream(file);
                } catch (FileNotFoundException e) {
                    logger.error("Error getting archiv file {}", file);
                    throw new ElementStoreException(e);
                }
            }
        });
    }

    @Override
    public boolean contains(StoragePath path) throws ElementStoreException {
        return findStoragePathFile(path).isSome();
    }

    @Override
    public boolean delete(DeletionSelector sel) throws ElementStoreException {
        File dir = getDeletionSelectorDir(sel);
        try {
            FileUtils.deleteDirectory(dir);
            return true;
        } catch (IOException e) {
            logger.error("Error deleting directory from archive {}", dir);
            throw new ElementStoreException(e);
        }
    }

    /**
     * Returns the directory file from a deletion selector
     *
     * @param sel
     *          the deletion selector
     * @return the directory file
     */
    private File getDeletionSelectorDir(DeletionSelector sel) {
        final String basePath = path(rootDirectory, sel.getOrganizationId(), sel.getMediaPackageId());
        for (Version v : sel.getVersion())
            return file(basePath, v.toString());
        return file(basePath);
    }

    /** Create all parent directories of a file. */
    private void mkParent(File f) {
        mkDirs(f.getParentFile());
    }

    /** Create this directory and all of its parents. */
    private void mkDirs(File d) {
        if (d != null && !d.exists() && !d.mkdirs()) {
            final String msg = "Cannot create directory " + d;
            logger.error(msg);
            throw new ElementStoreException(msg);
        }
    }

    /** Return the extension of a file. */
    private Option<String> extension(File f) {
        return trimToNone(getExtension(f.getAbsolutePath()));
    }

    /** Return the extension of a URI, i.e. the extentension of its path. */
    private Option<String> extension(URI uri) {
        try {
            return trimToNone(getExtension(uri.toURL().getPath()));
        } catch (MalformedURLException e) {
            throw new Error(e);
        }
    }

    /** Create a file from a storage path and the extension of file <code>f</code>. */
    private File createFile(StoragePath p, File f) {
        return createFile(p, extension(f));
    }

    /** Create a file from a storage path and the extension of the URI of <code>s</code>. */
    private File createFile(StoragePath p, Source s) {
        return createFile(p, extension(s.getUri()));
    }

    /** Create a file from a storage path and an optional extension. */
    private File createFile(StoragePath p, Option<String> extension) {
        return file(rootDirectory, p.getOrganizationId(), p.getMediaPackageId(), p.getVersion().toString(),
                extension.isSome() ? p.getAssetId() + EXTENSION_SEPARATOR + extension.get() : p.getAssetId());
    }

    /**
     * Returns a file {@link Option} from a storage path if one is found or an empty {@link Option}
     *
     * @param storagePath
     *          the storage path
     * @return the file {@link Option}
     */
    private Option<File> findStoragePathFile(final StoragePath storagePath) {
        final FilenameFilter filter = new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return FilenameUtils.getBaseName(name).equals(storagePath.getAssetId());
            }
        };
        final File containerDir = createFile(storagePath, none("")).getParentFile();
        return option(containerDir.listFiles(filter)).bind(new Function<File[], Option<File>>() {
            @Override
            public Option<File> apply(File[] files) {
                switch (files.length) {
                case 0:
                    return none();
                case 1:
                    return some(files[0]);
                default:
                    throw new ElementStoreException("Storage path " + files[0].getParent()
                            + "contains multiple files with the same element id!: " + storagePath.getAssetId());
                }
            }
        });
    }

    @Override
    public Option<Long> getUsedSpace() {
        return Option.some(FileUtils.sizeOfDirectory(new File(rootDirectory)));
    }

    @Override
    public Option<Long> getUsableSpace() {
        return Option.some(new File(rootDirectory).getUsableSpace());
    }

    @Override
    public Option<Long> getTotalSpace() {
        return Option.some(new File(rootDirectory).getTotalSpace());
    }

}