URingPaxos-master/src/java/ch/usi/da/paxos/storage/RocksDbStorage.java
package ch.usi.da.paxos.storage;
/* 
 * Copyright (c) 2015 Università della Svizzera italiana (USI)
 * 
 * This file is part of URingPaxos.
 *
 * URingPaxos is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * URingPaxos 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with URingPaxos.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.rocksdb.ColumnFamilyDescriptor;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.DBOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.WriteOptions;

import ch.usi.da.paxos.Util;
import ch.usi.da.paxos.api.PaxosRole;
import ch.usi.da.paxos.api.StableStorage;
import ch.usi.da.paxos.message.Message;
import ch.usi.da.paxos.message.MessageType;

import com.sleepycat.je.DatabaseException;

/**
 * Name: RocksDbStorage<br>
 * Description: <br>
 * 
 * Creation date: Aug 4, 2015<br>
 * $Id$
 * 
 * @author Samuel Benz benz@geoid.ch
 */
public class RocksDbStorage implements StableStorage {

	private final static Logger logger = Logger.getLogger(RocksDbStorage.class);

	private RocksDB db = null;

	private final DBOptions dboptions = new DBOptions();

	private final WriteOptions woptions = new WriteOptions();
	
	private ColumnFamilyHandle ballotdb;

	static {
		RocksDB.loadLibrary();
	}
	
	public RocksDbStorage(){
		this(null,true);
	}
	
	public RocksDbStorage(File file, boolean async){
		if(file == null){
	        int pid = 0;
	        try {
				pid = Integer.parseInt((new File("/proc/self")).getCanonicalFile().getName());
			} catch (NumberFormatException | IOException e) {
			}
	        String path = "/tmp";
			String db_path = System.getenv("DB");
			if(db_path != null){
				path = db_path;
			}
	        file = new File(path + "/ringpaxos-db/" + pid);
	        file.mkdirs();
		}
		
		try {
			dboptions.setCreateIfMissing(true);
			dboptions.setCreateMissingColumnFamilies(true);
			List<ColumnFamilyDescriptor> cfdesc = new ArrayList<ColumnFamilyDescriptor>();
			cfdesc.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY));
			cfdesc.add(new ColumnFamilyDescriptor("ballot".getBytes()));		
		    List<ColumnFamilyHandle> cfhandle = new ArrayList<ColumnFamilyHandle>();
			db = RocksDB.open(dboptions,file.toString(),cfdesc,cfhandle);			
			for(ColumnFamilyHandle handle : cfhandle){ // better way to identify ColumnFamilyHandle ?
				if(handle != db.getDefaultColumnFamily()){
					ballotdb = handle;
				}
			}
		} catch (RocksDBException e) {
			logger.error("RocksDbStorage DB create failed!", e);
		}
		
    	if(async){
    		woptions.setSync(false);
    	}else{
    		woptions.setSync(true);
    	}
        
        logger.info("RocksDbStorage sync: " + woptions.sync());
	}

	@Override
	public synchronized void putBallot(Long instance, int ballot) {
		try {
			db.put(ballotdb,woptions,Util.longToByte(instance),Util.intToByte(ballot));
		} catch (RocksDBException e) {
			logger.error("RocksDbStorage ballot put failed!", e);
		}
	}

	@Override
	public synchronized int getBallot(Long instance) {
		try {
			byte[] ret = db.get(ballotdb,Util.longToByte(instance));
			if(ret != null){
				return Util.byteToInt(ret);
			}
		} catch (RocksDBException e) {
			logger.error("RocksDbStorage ballot get failed!", e);
		}
		return -1;
	}

	@Override
	public synchronized boolean containsBallot(Long instance) {
		boolean found = false;
		if(getBallot(instance) > 0){
			found = true;
		}
		return found;
	}

	@Override
	public synchronized void putDecision(Long instance, Decision decision) {
		Message m = new Message(decision.getInstance(), decision.getRing(), PaxosRole.Proposer, MessageType.Value, decision.getBallot(), decision.getBallot(), decision.getValue());
		try {
			db.put(woptions,Util.longToByte(instance),Message.toWire(m));
		} catch (RocksDBException e) {
			logger.error("RocksDbStorage decision put failed!", e);
		}
	}

	@Override
	public synchronized Decision getDecision(Long instance) {
	    Decision decision = null;
		try {
			byte[] ret = db.get(Util.longToByte(instance));
			if(ret != null){
				Message m = Message.fromWire(ret);
				Decision d = new Decision(m.getSender(),m.getInstance(),m.getBallot(),m.getValue());
				return d;
			}
		} catch (RocksDBException e) {
			logger.error("RocksDbStorage decision get failed!", e);
		}
		return decision;
	}

	@Override
	public synchronized boolean containsDecision(Long instance) {
		boolean found = false;
		if(getDecision(instance) != null){
			found = true;
		}
		return found;
	}
	
	@Override
	public synchronized boolean trim(Long instance) {
		if(instance == 0) { return true; } // fast track
		long init = getLastTrimInstance();
		putDecision(-1L,new Decision(0,instance,0,null));
		for(long i = init; i < instance; i++){
			try {
				db.remove(Util.longToByte(i));
			} catch (RocksDBException e) {
				logger.error("RocksDbStorage decision remove failed!", e);
			}
		}
		return true;
	}

	@Override
	public synchronized Long getLastTrimInstance() {
		Decision d = getDecision(-1L);
		if(d != null){
			return getDecision(-1L).getInstance();
		}else{
			return 0L;
		}
	}
	
	@Override
	public synchronized void close() {
		try {
			if(db != null){
				db.close();
			}
			dboptions.dispose();
		} catch(DatabaseException dbe) {
			logger.error("Error closing db environment!",dbe);
		}
    }
	
}
