v7db.files.mongodb.Vermongo.java Source code

Java tutorial

Introduction

Here is the source code for v7db.files.mongodb.Vermongo.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.util.ArrayList;
import java.util.List;

import org.bson.BSONObject;

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

class Vermongo {

    static final String _VERSION = "_version";

    /**
     * inserts a new object into the collection. The _version property must not
     * be present in the object, and will be set to 1 (integer).
     * 
     * @param object
     */
    static void insert(DBCollection collection, DBObject object) {
        if (object.containsField(_VERSION))
            throw new IllegalArgumentException();

        object.put(_VERSION, 1);
        collection.insert(object, WriteConcern.SAFE);
    }

    /**
     * updates an existing object. The object must have been the _version
     * property set to the version number of the base revision (i.e. the version
     * number to be replaced). If the current version in the DB does not have a
     * matching version number, the operation aborts with an
     * UpdateConflictException.
     * 
     * After the update is successful, _version in the object is updated to the
     * new version number.
     * 
     * The version that was replaced is moved into the collection's shadow
     * collection.
     * 
     * @param collection
     * @param object
     * @throws UpdateConflictException
     */
    static void update(DBCollection collection, DBObject object) throws UpdateConflictException {
        if (!object.containsField(_VERSION))
            throw new IllegalArgumentException("the base version number needs to be included as _version");

        int baseVersion = (Integer) object.get(_VERSION);

        // load the base version
        {
            DBObject base = collection.findOne(new BasicDBObject("_id", getId(object)));
            if (base == null) {
                throw new IllegalArgumentException("document to update not found in collection");
            }
            Object bV = base.get(_VERSION);
            if (bV instanceof Integer) {
                if (baseVersion != (Integer) bV) {
                    throw new UpdateConflictException(object, base);
                }
            } else {
                throw new UpdateConflictException(object, base);
            }
            // copy to shadow
            DBCollection shadow = getShadowCollection(collection);
            base.put("_id", new BasicDBObject("_id", getId(base)).append(_VERSION, baseVersion));
            WriteResult r = shadow.insert(base, WriteConcern.SAFE);

            // TODO: if already there, no error
            r.getLastError().throwOnError();
        }

        try {
            object.put(_VERSION, baseVersion + 1);
            DBObject found = collection
                    .findAndModify(new BasicDBObject("_id", getId(object)).append(_VERSION, baseVersion), object);

            if (found == null) {
                // document has changed in the mean-time. get the latest version
                // again
                DBObject base = collection.findOne(new BasicDBObject("_id", getId(object)));
                if (base == null) {
                    throw new IllegalArgumentException("document to update not found in collection");
                }
                throw new UpdateConflictException(object, base);
            }

        } catch (RuntimeException e) {
            object.put(_VERSION, baseVersion);
            throw e;
        }

    }

    /**
     * @return the _id property of the object
     */
    static Object getId(BSONObject o) {
        return o.get("_id");
    }

    /**
     * @return the version number of the object (from the _version property)
     */

    static Integer getVersion(BSONObject o) {
        return (Integer) o.get(_VERSION);
    }

    /**
     * @return true, if the object represents a dummy version inserted to mark a
     *         deleted version
     */
    static boolean isDeletedDummyVersion(BSONObject o) {
        return o.get(_VERSION).toString().startsWith("deleted:");
    }

    /**
     * deletes the object without checking for conflicts. An existing version is
     * moved to the shadow collection, along with a dummy version to mark the
     * deletion. This dummy version can contain optional meta-data (such as who
     * deleted the object, and when).
     */
    static DBObject remove(DBCollection collection, Object id, BSONObject metaData) {
        DBObject base = collection.findOne(new BasicDBObject("_id", id));
        if (base == null)
            return null;

        // copy to shadow
        DBCollection shadow = getShadowCollection(collection);
        int version = getVersion(base);
        BasicDBObject revId = new BasicDBObject("_id", getId(base)).append(_VERSION, version);
        base.put("_id", revId);
        WriteResult r = shadow.insert(base, WriteConcern.SAFE);

        // TODO: if already there, no error
        r.getLastError().throwOnError();

        // add the dummy version
        BasicDBObject dummy = new BasicDBObject("_id", revId.append(_VERSION, version + 1)).append(_VERSION,
                "deleted:" + (version + 1));
        if (metaData != null)
            dummy.putAll(metaData);
        r = shadow.insert(dummy, WriteConcern.SAFE);
        // TODO: if already there, no error
        r.getLastError().throwOnError();

        collection.remove(new BasicDBObject("_id", id));
        return base;

    }

    /**
     * @return the shadow collection wherein the old versions of documents are
     *         stored
     */
    static DBCollection getShadowCollection(DBCollection c) {
        return c.getCollection("vermongo");
    }

    /**
     * @return an old version of the document with the given id, at the given
     *         version number
     */

    static DBObject getOldVersion(DBCollection c, Object id, int versionNumber) {

        BasicDBObject query = new BasicDBObject("_id",
                new BasicDBObject("_id", id).append("_version", versionNumber));

        DBObject result = getShadowCollection(c).findOne(query);
        if (result == null)
            return null;
        result.put("_id", ((BasicDBObject) getId(result)).get("_id"));
        return result;
    }

    /**
     * The list of old versions does not include the current version of the
     * document, but it does include dummy entries to mark the deletion (if the
     * document was deleted). The list is ordered by version number.
     * 
     * @return the list of old version of the document with the given id
     */
    static List<DBObject> getOldVersions(DBCollection c, Object id) {
        DBObject query = QueryUtils.between("_id", new BasicDBObject("_id", id).append("_version", 0),
                new BasicDBObject("_id", id).append("_version", Integer.MAX_VALUE));

        List<DBObject> result = new ArrayList<DBObject>();
        for (DBObject o : getShadowCollection(c).find(query).sort(new BasicDBObject("_id", 1))) {
            o.put("_id", ((BasicDBObject) getId(o)).get("_id"));
            result.add(o);
        }

        return result;
    }

}