v7db.files.mongodb.V7GridFS.java Source code

Java tutorial

Introduction

Here is the source code for v7db.files.mongodb.V7GridFS.java

Source

/**
 * Copyright (c) 2011-2012, Thilo Planz. All rights reserved.
 *
 * 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.
 *
 * 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 v7db.files.mongodb;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.bson.BSONObject;
import org.bson.types.ObjectId;

import v7db.files.ContentStorageFacade;
import v7db.files.spi.Content;
import v7db.files.spi.ContentPointer;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;

public class V7GridFS {

    private final DBCollection files;

    private final ContentStorageFacade storage;

    public static final String COLLECTION_NAME_FILES = "v7files.files";

    public V7GridFS(DB db) {
        files = db.getCollection(COLLECTION_NAME_FILES);
        storage = new ContentStorageFacade(new MongoContentStorage(db), new MongoReferenceTracking(db));
    }

    public V7File getFile(String... path) {

        // the filesystem root
        V7File parentFile = V7File.lazy(this, path[0], null);

        if (path.length == 1) {
            return parentFile;
        }

        DBObject metaData;
        // directly under the root
        if (path.length == 2) {
            metaData = files.findOne(new BasicDBObject("parent", path[0]).append("filename", path[1]));
        }

        else {
            List<String> filenames = Arrays.asList(path).subList(1, path.length);
            List<DBObject> candidates = files
                    .find(new BasicDBObject("filename", new BasicDBObject("$in", filenames))).toArray();
            // we need to have at least one candidate for every path component
            if (candidates.size() < filenames.size())
                return null;

            Object parent = path[0];

            metaData = null;
            path: for (String fileName : filenames) {
                for (DBObject c : candidates) {
                    if (parent.equals(c.get("parent")) && fileName.equals(c.get("filename"))) {
                        parent = c.get("_id");
                        parentFile = new V7File(this, metaData, parentFile);
                        metaData = c;

                        continue path;
                    }
                }
                return null;
            }
        }

        if (metaData == null)
            return null;
        return new V7File(this, metaData, parentFile);
    }

    /**
     * @param data
     *            can be null, for a file without content (e.g. a folder)
     * @param parentFileId
     * @param filename
     * @return
     * @throws IOException
     */
    public ObjectId addFolder(Object parentFileId, String filename) throws IOException {
        return addFile(null, 0, 0, parentFileId, filename, null);
    }

    /**
     * @param data
     *            can be null, for a file without content (e.g. a folder)
     * @param parentFileId
     * @param filename
     * @return
     * @throws IOException
     */
    public ObjectId addFile(byte[] data, Object parentFileId, String filename, String contentType)
            throws IOException {
        if (data == null)
            return addFile(null, 0, 0, parentFileId, filename, contentType);
        return addFile(data, 0, data.length, parentFileId, filename, contentType);
    }

    public ObjectId addFile(ContentPointer data, Object parentFileId, String filename, String contentType)
            throws IOException {
        if (data == null)
            return addFile(null, 0, 0, parentFileId, filename, contentType);

        ObjectId fileId = new ObjectId();
        BasicDBObject metaData = new BasicDBObject("parent", parentFileId).append("_id", fileId);

        metaData.putAll(storage.updateBackRefs(data, fileId, filename, contentType));

        insertMetaData(metaData);
        return fileId;
    }

    public ObjectId addFile(byte[] data, int offset, int len, Object parentFileId, String filename,
            String contentType) throws IOException {

        ObjectId fileId = new ObjectId();
        BasicDBObject metaData = new BasicDBObject("parent", parentFileId).append("_id", fileId);

        metaData.putAll(
                storage.inlineOrInsertContentsAndBackRefs(100, data, offset, len, fileId, filename, contentType));

        insertMetaData(metaData);
        return fileId;
    }

    /**
     * will close the InputStream before returning
     */
    public ObjectId addFile(InputStream data, Object parentFileId, String filename, String contentType)
            throws IOException {
        ObjectId fileId = new ObjectId();
        BasicDBObject metaData = new BasicDBObject("parent", parentFileId).append("_id", fileId);

        metaData.putAll(storage.insertContentsAndBackRefs(data, fileId, filename, contentType));

        insertMetaData(metaData);
        return fileId;
    }

    public List<V7File> getChildren(V7File parent) {
        List<V7File> children = new ArrayList<V7File>();
        for (DBObject child : files.find(new BasicDBObject("parent", parent.getId()))) {
            children.add(new V7File(this, child, parent));
        }
        return children;
    }

    private void insertMetaData(DBObject metaData) throws IOException {
        metaData.put("_version", 1);
        metaData.put("created_at", new Date());
        WriteResult result = files.insert(WriteConcern.SAFE, metaData);
        String error = result.getError();
        if (error != null)
            throw new IOException(error);
    }

    void updateMetaData(DBObject metaData) throws IOException {
        metaData.put("updated_at", new Date());
        try {
            Vermongo.update(files, metaData);
        } catch (UpdateConflictException e) {
            throw new IOException(e);
        }

    }

    void updateContents(DBObject metaData, byte[] contents) throws IOException {
        updateContents(metaData, contents, 0, contents == null ? 0 : contents.length);
    }

    void updateContents(DBObject metaData, ContentPointer newContents) throws IOException {
        ContentPointer oldContents = getContentPointer(metaData);

        if (newContents.contentEquals(oldContents))
            return;

        String filename = (String) metaData.get("filename");
        String contentType = (String) metaData.get("contentType");
        Object fileId = metaData.get("_id");

        BSONObject newContent = storage.updateBackRefs(newContents, fileId, filename, contentType);

        metaData.removeField("sha");
        metaData.removeField("length");
        metaData.removeField("in");

        metaData.putAll(newContent);

        updateMetaData(metaData);
    }

    void insertContents(DBObject metaData, ContentPointer newContents) throws IOException {

        String filename = (String) metaData.get("filename");
        String contentType = (String) metaData.get("contentType");
        Object fileId = metaData.get("_id");

        if (newContents != null) {
            BSONObject newContent = storage.updateBackRefs(newContents, fileId, filename, contentType);

            metaData.removeField("sha");
            metaData.removeField("length");
            metaData.removeField("in");

            metaData.putAll(newContent);
        }

        insertMetaData(metaData);
    }

    /**
     * read into the buffer, continuing until the stream is finished or the
     * buffer is full.
     * 
     * @return the number of bytes read, which could be 0 (not -1)
     * @throws IOException
     */
    static int readFully(InputStream data, byte[] buffer) throws IOException {
        int read = data.read(buffer);
        if (read == -1) {
            return 0;
        }
        while (read < buffer.length) {
            int added = data.read(buffer, read, buffer.length - read);
            if (added == -1)
                return read;
            read += added;
        }
        return read;
    }

    void updateContents(DBObject metaData, InputStream contents, Long size) throws IOException {
        if (contents == null) {
            updateContents(metaData, (byte[]) null);
            return;
        }
        if (size != null) {
            if (size <= 1024 * 1024) {
                updateContents(metaData, IOUtils.toByteArray(contents, size));
                return;
            }
        }

        updateContents(metaData, contents);
    }

    private void updateContents(DBObject metaData, InputStream contents) throws IOException {

        Object fileId = metaData.get("_id");
        ContentPointer oldContents = getContentPointer(metaData);
        String filename = (String) metaData.get("filename");
        String contentType = (String) metaData.get("contentType");

        BSONObject newContent = storage.insertContentsAndBackRefs(contents, fileId, filename, contentType);

        // check if it has changed
        ContentPointer newContents = getContentPointer(newContent);
        if (newContents.contentEquals(oldContents))
            return;

        metaData.removeField("sha");
        metaData.removeField("length");
        metaData.removeField("in");

        metaData.putAll(newContent);

        updateMetaData(metaData);
    }

    private void updateContents(DBObject metaData, byte[] contents, int offset, int len) throws IOException {

        Object fileId = metaData.get("_id");
        ContentPointer oldContents = getContentPointer(metaData);
        String filename = (String) metaData.get("filename");
        String contentType = (String) metaData.get("contentType");

        // for up to 55 bytes, storing the complete file inline
        // takes less space than just storing the SHA-1 and length
        // 20 (SHA-1) + 1 (sha - in) + 6 (length) + 4 (int32) + 2*12
        // (ObjectId back-references)
        BSONObject newContent = storage.inlineOrInsertContentsAndBackRefs(55, contents, offset, len, fileId,
                filename, contentType);

        // check if it has changed
        ContentPointer newContents = getContentPointer(newContent);
        if (newContents.contentEquals(oldContents))
            return;

        metaData.removeField("sha");
        metaData.removeField("length");
        metaData.removeField("in");

        metaData.putAll(newContent);

        updateMetaData(metaData);
    }

    public V7File getChild(V7File parentFile, String childName) {
        DBObject child = files
                .findOne(new BasicDBObject("parent", parentFile.getId()).append("filename", childName));
        if (child == null)
            return null;
        return new V7File(this, child, parentFile);
    }

    void delete(V7File file) throws IOException {
        // TODO: should check the version present in the db
        Vermongo.remove(files, file.getId(), new BasicDBObject("deleted_at", new Date()));
        storage.insertContentsAndBackRefs(null, file.getId(), null, null);
    }

    ContentPointer getContentPointer(BSONObject metaData) {
        return storage.getContentPointer(metaData);
    }

    Content getContent(BSONObject metaData) throws IOException {
        ContentPointer pointer = storage.getContentPointer(metaData);
        return storage.getContent(pointer);
    }

}