de.fhg.igd.mongomvcc.impl.internal.Tree.java Source code

Java tutorial

Introduction

Here is the source code for de.fhg.igd.mongomvcc.impl.internal.Tree.java

Source

// This file is part of MongoMVCC.
//
// Copyright (c) 2012 Fraunhofer IGD
//
// MongoMVCC is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// MongoMVCC 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with MongoMVCC. If not, see <http://www.gnu.org/licenses/>.

package de.fhg.igd.mongomvcc.impl.internal;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

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

import de.fhg.igd.mongomvcc.VException;
import de.fhg.igd.mongomvcc.VHistory;
import de.fhg.igd.mongomvcc.helper.IdHashMap;
import de.fhg.igd.mongomvcc.helper.IdMap;
import de.fhg.igd.mongomvcc.helper.IdMapIterator;

/**
 * <p>Represents the tree of commits.</p>
 * <p><strong>Thread-safety:</strong> This class is thread-safe.</p> 
 * @author Michel Kraemer
 */
public class Tree implements VHistory {
    /**
     * Attribute names
     */
    private final static String ROOT_CID = "rootcid";
    private final static String PARENT_CID = "parent";
    private final static String OBJECTS = "objects";

    /**
     * A collection storing all branches and their current heads
     */
    private final DBCollection _branches;

    /**
     * A collection storing all commits (unsorted)
     */
    private final DBCollection _commits;

    /**
     * Creates a new tree object
     * @param db the MongoDB database
     */
    public Tree(DB db) {
        _branches = db.getCollection(MongoDBConstants.COLLECTION_BRANCHES);
        _commits = db.getCollection(MongoDBConstants.COLLECTION_COMMITS);
    }

    /**
     * @return true if the tree is empty
     */
    public boolean isEmpty() {
        return _commits.count() == 0;
    }

    /**
     * Adds a commit to the tree
     * @param commit the commit to add
     */
    public void addCommit(Commit commit) {
        DBObject o = new BasicDBObject();
        o.put(MongoDBConstants.ID, commit.getCID());
        o.put(MongoDBConstants.TIMESTAMP, commit.getTimestamp());
        o.put(PARENT_CID, commit.getParentCID());
        o.put(ROOT_CID, commit.getRootCID());
        DBObject objs = new BasicDBObject();
        for (Map.Entry<String, IdMap> e : commit.getObjects().entrySet()) {
            DBObject co = new BasicDBObject();
            IdMapIterator it = e.getValue().iterator();
            while (it.hasNext()) {
                it.advance();
                co.put(String.valueOf(it.key()), it.value());
            }
            objs.put(e.getKey(), co);
        }
        o.put(OBJECTS, objs);
        _commits.insert(o);
    }

    /**
     * Adds a named branch. Always waits for the database to fsync before
     * returning. This guarantees all threads will see the change.
     * @param name the branch's name
     * @param headCID the CID of the head commit the branch points to
     * @throws VException if there already is a branch with the given name or
     * if the given head CID could not be resolved to an existing commit
     */
    public void addBranch(String name, long headCID) {
        //synchronize here, because we first check for branch existence
        //and then we write
        synchronized (this) {
            //check prerequisites
            if (_branches.findOne(name) != null) {
                throw new VException("A branch with the name " + name + " already exists");
            }
            resolveCommit(headCID);

            //create branch
            DBObject o = new BasicDBObject();
            o.put(MongoDBConstants.ID, name);
            o.put(MongoDBConstants.CID, headCID);
            o.put(ROOT_CID, headCID);
            _branches.insert(o, WriteConcern.FSYNC_SAFE);
        }
    }

    /**
     * Updates the head of a branch. Always waits for the database to
     * fsync before returning. This guarantees all threads will see the change.
     * This operation will usually be the last one when a commit is made, so
     * fsync'ing here is crucial for the database's integrity. Fsync'ing will
     * also make the database write all other documents created during the
     * commit to the hard disk.
     * @param name the branch's name
     * @param headCID the CID of the new head
     */
    public void updateBranchHead(String name, long headCID) {
        _branches.update(new BasicDBObject(MongoDBConstants.ID, name),
                new BasicDBObject("$set", new BasicDBObject(MongoDBConstants.CID, headCID)), false, false,
                WriteConcern.FSYNC_SAFE);
    }

    /**
     * Checks if a branch with the given name exists
     * @param name the branch's name
     * @return true if the branch exists, false otherwise
     */
    public boolean existsBranch(String name) {
        return _branches.count(new BasicDBObject(MongoDBConstants.ID, name)) > 0;
    }

    /**
     * Checks if a branch with the given root CID exists
     * @param rootCID the root CID
     * @return true if the branch exists, false otherwise
     */
    public boolean existsBranch(long rootCID) {
        return _branches.count(new BasicDBObject(ROOT_CID, rootCID)) > 0;
    }

    /**
     * Loads a branch from the database or fails if it does not exist
     * @param name the branch's name
     * @return the document representing the branch
     * @throws VException if the branch does not exist
     */
    private DBObject findBranch(String name) {
        DBObject branch = _branches.findOne(name);
        if (branch == null) {
            throw new VException("Unknown branch: " + name);
        }
        return branch;
    }

    /**
     * Resolves the head commit of a named branch
     * @param name the name of the branch to resolve
     * @return the resolved commit
     * @throws VException if the commit could not be resolved
     */
    public Commit resolveBranch(String name) {
        DBObject branch = findBranch(name);
        return resolveCommit((Long) branch.get(MongoDBConstants.CID));
    }

    /**
     * Resolves the CID of a named branch's root
     * @param name the branch's name
     * @return the resolved CID
     * @throws VException if the branch does not exist
     */
    public long resolveBranchRootCid(String name) {
        DBObject branch = findBranch(name);
        return (Long) branch.get(ROOT_CID);
    }

    /**
     * Checks if a commit with a given CID exists
     * @param cid the commit's ID (CID)
     * @return true if the commit exists, false otherwise
     */
    public boolean existsCommit(long cid) {
        return _commits.count(new BasicDBObject(MongoDBConstants.ID, cid)) > 0;
    }

    /**
     * Resolves a CID to its corresponding commit
     * @param cid the CID
     * @return the commit
     * @throws VException if the commit is unknown
     */
    public Commit resolveCommit(long cid) {
        DBObject o = _commits.findOne(cid);
        if (o == null) {
            throw new VException("Unknown commit: " + cid);
        }
        return deserializeCommit(o);
    }

    /**
     * Deserializes a database object to a commit
     * @param o the object
     * @return the commit
     */
    public static Commit deserializeCommit(DBObject o) {
        long cid = (Long) o.get(MongoDBConstants.ID);
        Long timestampL = (Long) o.get(MongoDBConstants.TIMESTAMP);
        long timestamp = timestampL != null ? timestampL : 0;
        long parentCID = (Long) o.get(PARENT_CID);
        long rootCID = (Long) o.get(ROOT_CID);
        DBObject objs = (DBObject) o.get(OBJECTS);
        Map<String, IdMap> objects = new HashMap<String, IdMap>();
        for (String k : objs.keySet()) {
            if (!k.equals(MongoDBConstants.ID)) {
                objects.put(k, resolveCollectionObjects((DBObject) objs.get(k)));
            }
        }
        return new Commit(cid, timestamp, parentCID, rootCID, objects);
    }

    private static IdMap resolveCollectionObjects(DBObject o) {
        Set<String> keys = o.keySet();
        IdMap r = new IdHashMap(keys.size());
        for (String k : keys) {
            r.put(Long.parseLong(k), (Long) o.get(k));
        }
        return r;
    }

    @Override
    public long getParent(long cid) {
        DBObject o = _commits.findOne(cid, new BasicDBObject(PARENT_CID, 1));
        if (o == null) {
            throw new VException("Unknown commit: " + cid);
        }
        return (Long) o.get(PARENT_CID);
    }

    @Override
    public long[] getChildren(long cid) {
        if (cid != 0 && !existsCommit(cid)) {
            throw new VException("Unknown commit: " + cid);
        }
        DBCursor c = _commits.find(new BasicDBObject(PARENT_CID, cid), new BasicDBObject(MongoDBConstants.ID, 1));
        long[] r = new long[c.count()];
        int i = 0;
        for (DBObject o : c) {
            r[i++] = (Long) o.get(MongoDBConstants.ID);
        }
        return r;
    }

    /**
     * Checks if a given commit has got children
     * @param cid the commit's CID
     * @return true if the commit has children, false otherwise
     */
    public boolean hasChildren(long cid) {
        if (cid != 0 && !existsCommit(cid)) {
            throw new VException("Unknown commit: " + cid);
        }
        return (_commits.count(new BasicDBObject(PARENT_CID, cid)) > 0);
    }
}