Java tutorial
// 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); } }