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; import java.util.Collections; import java.util.HashMap; import java.util.Map; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.gridfs.GridFS; import de.fhg.igd.mongomvcc.VBranch; import de.fhg.igd.mongomvcc.VCollection; import de.fhg.igd.mongomvcc.VException; import de.fhg.igd.mongomvcc.VLargeCollection; import de.fhg.igd.mongomvcc.helper.IdMap; import de.fhg.igd.mongomvcc.helper.IdMapIterator; import de.fhg.igd.mongomvcc.helper.IdSet; import de.fhg.igd.mongomvcc.helper.IdSetIterator; import de.fhg.igd.mongomvcc.impl.internal.Commit; import de.fhg.igd.mongomvcc.impl.internal.CompatibilityHelper; import de.fhg.igd.mongomvcc.impl.internal.Index; import de.fhg.igd.mongomvcc.impl.internal.MongoDBConstants; import de.fhg.igd.mongomvcc.impl.internal.Tree; /** * <p>Implementation of {@link VBranch} for MongoDB. Each * thread owns an index and a head of the branch/commit it has checked out. * This ensures isolation. If two threads try to create a commit with the * same head, the {@link #commit()} method will fail. The rule is: come first, * serve first.</p> * <p><strong>Thread-safety:</strong> this class is thread-safe. Instances * can be shared between threads, but each thread has its own index and head.</p> * @author Michel Kraemer */ public class MongoDBVBranch implements VBranch { /** * The head of this branch. A thread-local variable since objects of * this class can be shared between threads. */ private final ThreadLocal<Commit> _head = new ThreadLocal<Commit>(); /** * The index. Provides access to all objects from the currently * checked out branch/commit and stores information on dirty objects. */ private final ThreadLocal<Index> _index = new ThreadLocal<Index>(); /** * Holds the query object for the current head. A query object is used to * limit the number of objects transferred from the database. */ private final ThreadLocal<Map<String, Object>> _currentQueryObject = new ThreadLocal<Map<String, Object>>(); /** * The branch's name (may be null) */ private final String _name; /** * The CID of the branch's root */ private final long _rootCid; /** * The tree of commits */ private final Tree _tree; /** * The database object */ private final MongoDBVDatabase _db; /** * Constructs a new branch object (not the branch itself) * @param name the branch's name (may be null for unnamed branches) * @param rootCid the CID of the branch's root * @param tree the tree of commits * @param db the database object */ public MongoDBVBranch(String name, long rootCid, Tree tree, MongoDBVDatabase db) { _name = name; _rootCid = rootCid; _tree = tree; _db = db; } /** * @return the thread-local head of this branch */ private Commit getHeadCommit() { Commit r = _head.get(); if (r == null) { if (_name != null) { r = _tree.resolveBranch(_name); } else { r = _tree.resolveCommit(_rootCid); } _head.set(r); } return r; } private Map<String, Object> makeQueryObject() { BasicDBList l = new BasicDBList(); String lba = MongoDBConstants.LIFETIME + "." + getRootCid(); String iba = MongoDBConstants.LIFETIME + ".i" + getRootCid(); //(1) check if there is information about the document's insertion // available. if not just include this object. // TODO this condition must be removed once we know the whole branching history l.add(new BasicDBObject(iba, new BasicDBObject("$exists", false))); if (!CompatibilityHelper.supportsAnd(getDB())) { //(2) check if the object has been deleted after this commit. // we use a $not here, so the query will also return 'true' if // the attribute is not set. l.add(new BasicDBObject(lba, new BasicDBObject("$not", new BasicDBObject("$lte", getHead())))); } else { BasicDBList l2 = new BasicDBList(); //(2) check if the object has been inserted in this commit or later l2.add(new BasicDBObject(iba, new BasicDBObject("$lte", getHead()))); //(3) check if the object has been deleted after this commit. // we use a $not here, so the query will also return 'true' if // the attribute is not set. l2.add(new BasicDBObject(lba, new BasicDBObject("$not", new BasicDBObject("$lte", getHead())))); l.add(new BasicDBObject("$and", l2)); } return Collections.unmodifiableMap(new BasicDBObject("$or", l)); } /** * @return a query object which limits the number of objects returned * by accessing their lifetime information */ public Map<String, Object> getQueryObject() { Map<String, Object> r = _currentQueryObject.get(); if (r == null) { r = makeQueryObject(); _currentQueryObject.set(r); } return r; } /** * Updates the thread-local head of the currently checked out branch/commit * @param newHead the new head */ private void updateHead(Commit newHead) { _currentQueryObject.remove(); _head.set(newHead); } @Override public long getHead() { return getHeadCommit().getCID(); } @Override public VCollection getCollection(String name) { return new MongoDBVCollection(_db.getDB().getCollection(name), this, _db.getCounter()); } @Override public VLargeCollection getLargeCollection(String name) { DB db = _db.getDB(); return new MongoDBVLargeCollection(db.getCollection(name), new GridFS(db, name), this, _db.getCounter()); } /** * Gets or creates a database collection that can handle large * objects (BLOBs) and uses a given access strategy. * @param name the collection's name * @param accessStrategy the strategy used to access large objects * @return the collection (never null) */ public VLargeCollection getLargeCollection(String name, AccessStrategy accessStrategy) { DB db = _db.getDB(); return new MongoDBVLargeCollection(db.getCollection(name), new GridFS(db, name), this, _db.getCounter(), accessStrategy); } /** * @return the index. The index provides access to all objects from * the currently checked out branch/commit and stores information on * dirty objects. */ public Index getIndex() { Index r = _index.get(); if (r == null) { r = new Index(getHeadCommit(), _tree); _index.set(r); } return r; } /** * @return the database from which this branch has been checked out */ public MongoDBVDatabase getDB() { return _db; } /** * @return the CID of this branch's root */ public long getRootCid() { return _rootCid; } @Override public long commit() { Index idx = getIndex(); //clone dirty objects because we clear them below Map<String, IdMap> dos = new HashMap<String, IdMap>(idx.getDirtyObjects()); Commit head = getHeadCommit(); Commit c = new Commit(_db.getCounter().getNextId(), head.getCID(), _rootCid, dos); _tree.addCommit(c); updateHead(c); //mark deleted objects as deleted in the database DB db = _db.getDB(); String lifetimeAttr = MongoDBConstants.LIFETIME + "." + getRootCid(); for (Map.Entry<String, IdSet> e : idx.getDeletedOids().entrySet()) { DBCollection dbc = db.getCollection(e.getKey()); IdSetIterator li = e.getValue().iterator(); while (li.hasNext()) { long oid = li.next(); //save the CID of the commit where the object has been deleted dbc.update(new BasicDBObject(MongoDBConstants.ID, oid), new BasicDBObject("$set", new BasicDBObject(lifetimeAttr, head.getCID()))); } } //mark dirty objects as inserted String instimeAttr = MongoDBConstants.LIFETIME + ".i" + getRootCid(); for (Map.Entry<String, IdMap> e : dos.entrySet()) { DBCollection dbc = db.getCollection(e.getKey()); IdMap m = e.getValue(); IdMapIterator li = m.iterator(); while (li.hasNext()) { li.advance(); long oid = li.value(); if (oid == -1) { //the document has been inserted and then deleted again //do not save time of insertion continue; } //save the CID of the commit where the object has been inserted dbc.update(new BasicDBObject(MongoDBConstants.ID, oid), new BasicDBObject("$set", new BasicDBObject(instimeAttr, head.getCID()))); } } //reset index idx.clearDirtyObjects(); //if we fail below, the commit has already been performed and the //index is clear. failing below simply means the named branch's //head could not be updated. If the caller wants to keep the commit //he/she just has to create a new named branch based on this //branch's head. //update named branch's head if (_name != null) { //synchronize the following part, because we first resolve the branch //and then update it synchronized (this) { //check for conflicts (i.e. if another thread has already updated the branch's head) if (_tree.resolveBranch(_name).getCID() != c.getParentCID()) { throw new VException("Branch " + _name + " has already been " + "updated by another commit"); } _tree.updateBranchHead(_name, c.getCID()); } } return c.getCID(); } @Override public void rollback() { //simply reset the whole index _index.remove(); } }