URingPaxos-master/src/java/ch/usi/da/paxos/storage/BerkeleyStorage.java
package ch.usi.da.paxos.storage;
/* 
 * Copyright (c) 2013 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 org.apache.log4j.Logger;

import ch.usi.da.paxos.api.StableStorage;

import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.je.CacheMode;
import com.sleepycat.je.Cursor;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Durability;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

/**
 * Name: BerkeleyStorage<br>
 * Description: <br>
 * 
 * Creation date: Feb 7, 2013<br>
 * $Id$
 * 
 * @author Samuel Benz benz@geoid.ch
 */
public class BerkeleyStorage implements StableStorage {

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

	private final Environment env;
	
	private final Database valuedb;

	private final Database ballotdb;

	private final Database classCatalogDb;

    private final StoredClassCatalog classCatalog;

	private final DatabaseEntry key = new DatabaseEntry();

    private final EntryBinding<Long> keyBinding;

	private final DatabaseEntry data = new DatabaseEntry();
    
	private final EntryBinding<Decision> dataBinding;

	private final DatabaseEntry ballot_data = new DatabaseEntry();
    
	private final EntryBinding<Integer> ballotBinding;

	public BerkeleyStorage(){
		this(null,false,true);
	}
	
	public BerkeleyStorage(File file, boolean readonly, 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();
		}
        EnvironmentConfig envConfig = new EnvironmentConfig();
        DatabaseConfig dbConfig = new DatabaseConfig();
        envConfig.setReadOnly(readonly);
        dbConfig.setReadOnly(readonly);
        envConfig.setAllowCreate(!readonly);
        dbConfig.setAllowCreate(!readonly);

        // performance settings
    	envConfig.setTransactional(true);
    	envConfig.setCacheMode(CacheMode.DEFAULT);
    	//envConfig.setCacheSize(1000000*800); // 800M
    	if(async){
    		dbConfig.setTransactional(false);
        	envConfig.setDurability(Durability.COMMIT_NO_SYNC);
        	dbConfig.setDeferredWrite(true);
        }else{
    		dbConfig.setTransactional(true);
        	envConfig.setDurability(Durability.COMMIT_SYNC);
        	dbConfig.setDeferredWrite(false);        	
        }
        
        env = new Environment(file, envConfig);
        valuedb = env.openDatabase(null,"valueDB",dbConfig);
        ballotdb = env.openDatabase(null,"ballotDB",dbConfig);        
        classCatalogDb = env.openDatabase(null,"ClassCatalogDB", dbConfig);
        classCatalog = new StoredClassCatalog(classCatalogDb);
        keyBinding = TupleBinding.getPrimitiveBinding(Long.class);
        dataBinding = new SerialBinding<Decision>(classCatalog,Decision.class);
        ballotBinding = TupleBinding.getPrimitiveBinding(Integer.class);

        logger.info("BerkeleyStorage cache size: " + env.getMutableConfig().getCacheSize());
        logger.info("BerkeleyStorage durability: " + env.getMutableConfig().getDurability().getLocalSync());
        logger.info("BerkeleyStorage deferred write: " + valuedb.getConfig().getDeferredWrite());
	}

	@Override
	public synchronized void putBallot(Long instance, int ballot) {
        keyBinding.objectToEntry(instance,key);
        ballotBinding.objectToEntry(ballot,ballot_data);
        OperationStatus status = ballotdb.put(null,key,ballot_data);
        if(logger.isDebugEnabled()){
        	logger.debug("DB put ballot " + ballot + " for instance " + instance + " " + status.name());
        }
	}

	@Override
	public synchronized int getBallot(Long instance) {
	    keyBinding.objectToEntry(instance,key);
	    Integer ballot = null;
	    OperationStatus status = ballotdb.get(null,key,ballot_data,LockMode.DEFAULT);
	    if (status == OperationStatus.SUCCESS) {
	        ballot = ballotBinding.entryToObject(ballot_data);
	    }
	    if(logger.isDebugEnabled()){
	    	logger.debug("DB get ballot " + ballot + " for instance " + instance + " " + status.name());
	    }
		return ballot.intValue();
	}

	@Override
	public synchronized boolean containsBallot(Long instance) {
		boolean found = false;
		keyBinding.objectToEntry(instance,key);
	    OperationStatus status = ballotdb.get(null,key,ballot_data,LockMode.DEFAULT);
	    if(status == OperationStatus.SUCCESS){
	    	found = true;
	    }
		if(logger.isDebugEnabled()){
			logger.debug("DB contains ballot " + instance + " " + found + " (" + status.name() + ")");
		}
		return found;
	}

	@Override
	public synchronized void putDecision(Long instance, Decision decision) {
        keyBinding.objectToEntry(instance,key);
        dataBinding.objectToEntry(decision,data);
        OperationStatus status = valuedb.put(null,key,data);
        if(logger.isDebugEnabled()){
        	logger.debug("DB put decision " + decision + " " + status.name());
        }
	}

	@Override
	public synchronized Decision getDecision(Long instance) {
	    keyBinding.objectToEntry(instance,key);
	    Decision decision = null;
	    OperationStatus status = valuedb.get(null,key,data,LockMode.DEFAULT);
	    if (status == OperationStatus.SUCCESS) {
	        decision = dataBinding.entryToObject(data);
	    }
	    if(logger.isDebugEnabled()){
	    	logger.debug("DB get decision " + decision + " " + status.name());
	    }
		return decision;
	}

	@Override
	public synchronized boolean containsDecision(Long instance) {
		boolean found = false;
		keyBinding.objectToEntry(instance,key);
	    OperationStatus status = valuedb.get(null,key,data,LockMode.DEFAULT);
	    if(status == OperationStatus.SUCCESS){
	    	found = true;
	    }
		if(logger.isDebugEnabled()){
			logger.debug("DB contains decision " + instance + " " + found + " (" + status.name() + ")");
		}
		return found;
	}
	
	@Override
	public synchronized boolean trim(Long instance) {
		if(instance == 0) { return true; } // fast track
		putDecision(-1L,new Decision(0,instance,0,null)); // if the transaction aborts
		Transaction t = null;
		if(valuedb.getConfig().getTransactional()){
			t = env.beginTransaction(null,null);
		}
		Cursor cursor = valuedb.openCursor(t, null);
		boolean dirty = false; 
		try{
			while (cursor.getNext(key,data,LockMode.READ_UNCOMMITTED) == OperationStatus.SUCCESS) {
				Long i = keyBinding.entryToObject(key);
				if(i > 0 && i < instance && cursor.delete() != OperationStatus.SUCCESS){
					logger.error("Error deleting instance " + i + " from DB!");
					dirty = true;
				}
			}
		}finally{
			cursor.close();
			if(!dirty){
				if(t != null){ t.commit(); }
			}else{
				if(t != null){ t.abort(); }
				return false;
			}
		}
		logger.debug("DB deltete up to instance " + instance);
		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 {
			valuedb.close();
			ballotdb.close();
			classCatalogDb.close();
			env.close();
		} catch(DatabaseException dbe) {
			logger.error("Error closing db environment!",dbe);
		}
    }
	
	/**
	 * Debug method
	 */
	public synchronized void listAllValues() {
		Cursor cursor = valuedb.openCursor(null, null);
	    while (cursor.getNext(key,data,LockMode.DEFAULT) == OperationStatus.SUCCESS) {
            Long instance = keyBinding.entryToObject(key);
            Decision decision = dataBinding.entryToObject(data);
            System.out.println("instance " +  instance + " -> " + decision + "");
	    }
	    cursor.close();
	}

	/**
	 * Debug method
	 */
	public synchronized void listAllBallots() {
		Cursor cursor = ballotdb.openCursor(null, null);
	    while (cursor.getNext(key,data,LockMode.DEFAULT) == OperationStatus.SUCCESS) {
            Long instance = keyBinding.entryToObject(key);
            Integer ballot = ballotBinding.entryToObject(data);
            System.out.println("instance " +  instance + " -> " + ballot + "");
	    }
	    cursor.close();
	}

}
