de.fhg.igd.mongomvcc.impl.MongoDBVDatabase.java Source code

Java tutorial

Introduction

Here is the source code for de.fhg.igd.mongomvcc.impl.MongoDBVDatabase.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;

import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.mongodb.CommandResult;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.ServerAddress;

import de.fhg.igd.mongomvcc.VBranch;
import de.fhg.igd.mongomvcc.VConstants;
import de.fhg.igd.mongomvcc.VCounter;
import de.fhg.igd.mongomvcc.VDatabase;
import de.fhg.igd.mongomvcc.VException;
import de.fhg.igd.mongomvcc.VHistory;
import de.fhg.igd.mongomvcc.VMaintenance;
import de.fhg.igd.mongomvcc.helper.IdMap;
import de.fhg.igd.mongomvcc.impl.internal.BuildInfo;
import de.fhg.igd.mongomvcc.impl.internal.Commit;
import de.fhg.igd.mongomvcc.impl.internal.Tree;

/**
 * MongoDB implementation of a Multiversion Concurrency Control database.
 * @author Michel Kraemer
 */
public class MongoDBVDatabase implements VDatabase {
    /**
     * The MongoDB database object
     */
    private DB _db;

    /**
     * Provides a thread-safe way to generate new unique IDs
     */
    private VCounter _counter;

    /**
     * The tree of commits
     */
    private Tree _tree;

    /**
     * Build information about the database instance (may be null if the
     * information is not available)
     */
    private BuildInfo _buildInfo;

    @Override
    public void connect(String name) throws VException {
        Mongo mongo;
        try {
            mongo = new Mongo();
        } catch (UnknownHostException e) {
            throw new VException("Unknown host", e);
        }
        connectInternal(name, mongo);
    }

    @Override
    public void connect(String name, int port) throws VException {
        connect(name, ServerAddress.defaultHost(), port);
    }

    @Override
    public void connect(String name, String host, int port) throws VException {
        Mongo mongo;
        try {
            mongo = new Mongo(new ServerAddress(host, port));
        } catch (UnknownHostException e) {
            throw new VException("Unknown host", e);
        }
        connectInternal(name, mongo);
    }

    @Override
    public void connectToReplicaSet(String name, Map<String, Integer> hostsWithPort, boolean slaveOk)
            throws VException {
        Mongo mongo = null;
        try {
            List<ServerAddress> addrs = new ArrayList<ServerAddress>();
            for (Map.Entry<String, Integer> e : hostsWithPort.entrySet()) {
                addrs.add(new ServerAddress(e.getKey(), e.getValue()));
            }
            mongo = new Mongo(addrs);

            if (slaveOk && mongo != null) {
                mongo.slaveOk();
                //            mongo.setReadPreference(ReadPreference.SECONDARY); needed with version 2.2
            }
        } catch (UnknownHostException e) {
            throw new VException("Unknown host", e);
        }
        connectInternal(name, mongo);
    }

    private void connectInternal(String name, Mongo mongo) {
        _buildInfo = initBuildInfo(mongo);

        _db = mongo.getDB(name);
        _counter = new MongoDBVCounter(_db);
        _tree = new Tree(_db);

        //create root commit and master branch if needed
        if (_tree.isEmpty()) {
            Commit root = new Commit(_counter.getNextId(), 0, 0, Collections.<String, IdMap>emptyMap());
            _tree.addCommit(root);
            _tree.addBranch(VConstants.MASTER, root.getCID());
        }
    }

    /**
     * Obtains build information from the database instance. If any value is
     * not available, this method will return <code>null</code>.
     * @param mongo the database
     * @return the build information or <code>null</code>
     */
    private static BuildInfo initBuildInfo(Mongo mongo) {
        DB db = mongo.getDB("admin");
        if (db == null) {
            return null;
        }
        CommandResult cr = db.command("buildInfo");
        String version = (String) cr.get("version");
        if (version == null) {
            return null;
        }
        String[] vss = version.split("\\.");
        if (vss.length <= 2) {
            return null;
        }
        Integer maxBsonObjectSize = (Integer) cr.get("maxBsonObjectSize");
        if (maxBsonObjectSize == null) {
            maxBsonObjectSize = Integer.valueOf(0);
        }
        try {
            return new BuildInfo(Integer.parseInt(vss[0]), Integer.parseInt(vss[1]), Integer.parseInt(vss[2]),
                    maxBsonObjectSize);
        } catch (NumberFormatException e) {
            return null;
        }
    }

    /**
     * @return the underlying MongoDB database
     */
    public DB getDB() {
        return _db;
    }

    /**
     * @return build information about the database instance (may be null if the
     * information is not available)
     */
    public BuildInfo getBuildInfo() {
        return _buildInfo;
    }

    @Override
    public void drop() {
        _db.dropDatabase();
    }

    @Override
    public VBranch checkout(String name) {
        long rootCid = _tree.resolveBranchRootCid(name);
        return new MongoDBVBranch(name, rootCid, _tree, this);
    }

    /**
     * Determines the root CID for a new branch. Checks if the commit with
     * the given CID already belongs to a named branch or if it already has
     * children. If so, the root for the new branch will be the given CID.
     * Otherwise it is assumed that the commit is the head of an unnamed
     * branch and so the commit's root ID will be returned.
     * @param cid the CID of the commit to branch from
     * @return the root CID for the new branch
     */
    private long determineRootForBranch(long cid) {
        Commit head = _tree.resolveCommit(cid);
        long root = head.getRootCID();
        if (_tree.existsBranch(root) || _tree.hasChildren(cid)) {
            //we're trying to create a branch from a commit that belongs
            //to an existing branch. create a new branch from here on.
            root = cid;
        }
        return root;
    }

    @Override
    public VBranch checkout(long cid) {
        long root = determineRootForBranch(cid);
        return new MongoDBVBranch(null, root, _tree, this);
    }

    @Override
    public VBranch createBranch(String name, long headCID) {
        long root = determineRootForBranch(headCID);
        _tree.addBranch(name, root);
        return new MongoDBVBranch(name, root, _tree, this);
    }

    @Override
    public VCounter getCounter() {
        return _counter;
    }

    @Override
    public VHistory getHistory() {
        return _tree;
    }

    @Override
    public VMaintenance getMaintenance() {
        return new MongoDBVMaintenance(this);
    }
}