URingPaxos-master/clients/SMR/src/java/ch/usi/da/smr/recovery/HttpRecovery.java
package ch.usi.da.smr.recovery;
/* 
 * Copyright (c) 2014 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.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;

import ch.usi.da.smr.PartitionManager;
import ch.usi.da.smr.Replica;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

/**
 * Name: HttpRecovery<br>
 * Description: <br>
 * 
 * Creation date: Nov 6, 2014<br>
 * $Id$
 * 
 * @author Samuel Benz benz@geoid.ch
 */
public class HttpRecovery implements RecoveryInterface {

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

	private final PartitionManager partitions;
	
	private final int max_ring = 20;

	private HttpServer httpd;

	public static final String snapshot_file = "/tmp/snapshot.ser";

	public static final String state_file = "/tmp/snapshot.state"; // only used to quickly decide if remote snapshot is newer

	private boolean download_active = false;
	
	public HttpRecovery(PartitionManager partitions){
		
		this.partitions = partitions;
		
		// remote snapshot transfer server
		try {
			httpd = HttpServer.create(new InetSocketAddress(8080), 0);
			httpd.createContext("/state", new SendFile(state_file));        
			httpd.createContext("/snapshot", new SendFile(snapshot_file));
			httpd.setExecutor(null); // creates a default executor
			httpd.start();
		}catch(Exception e){
			logger.error("Replica could not start http server: " + e);
		}
	}
	
	public boolean storeState(Map<Integer,Long> instances, Map<String,byte[]> db){
		try {
			synchronized(db){
				logger.info("Replica start storing state ... ");
				for(Entry<Integer,Long> e : instances.entrySet()){
					db.put("r:" + e.getKey(),e.getValue().toString().getBytes());
				}
				FileOutputStream fs = new FileOutputStream(snapshot_file);
		        ObjectOutputStream os = new ObjectOutputStream(fs);
		        os.writeObject(db);
		        os.flush();
		        fs.getChannel().force(false); // fsync
		        os.close();
				fs = new FileOutputStream(state_file);
				for(Entry<Integer, Long> e : instances.entrySet()){
					fs.write((e.getKey() + "=" + e.getValue() + "\n").getBytes());
				}
				fs.getChannel().force(false);
		        os.close();
		        logger.info("... state stored up to instance: " + instances);
			}
	        return true;
		} catch (IOException e) {
			logger.error(e);
		}
		return false;
	}
	
	public Map<Integer,Long> installState(String token,Map<String,byte[]> db) throws Exception {
		Map<Integer,Long> instances = new HashMap<Integer,Long>();
		if(download_active){
			logger.info("Install state surpressed since download active.");
			return instances;
		}
		String host = null;
		InputStreamReader isr;
		String line;
		Map<Integer, Long> state = new HashMap<Integer, Long>();	
		// local
		try{
			isr = new InputStreamReader(new FileInputStream(state_file));
			BufferedReader bin = new BufferedReader(isr);	
			while ((line = bin.readLine()) != null){
				String[] s = line.split("=");
				state.put(Integer.parseInt(s[0]),Long.parseLong(s[1]));
			}
			logger.info("Replica found local snapshot: " + state);
			bin.close();
		}catch (FileNotFoundException e){
			logger.info("No local snapshot present.");
		}
		// remote TODO: must ask min. GSQ replicas
		for(String h : partitions.getReplicas(token)){
			if(h.contains(":")){
				h = "[" + h + "]";
			}
			try{
				URL url = new URL("http://" + h + ":8080/state");
				HttpURLConnection con = (HttpURLConnection)url.openConnection();
				isr = new InputStreamReader(con.getInputStream());
				BufferedReader in = new BufferedReader(isr);
				Map<Integer, Long> nstate = new HashMap<Integer, Long>();
				while ((line = in.readLine()) != null){
					String[] s = line.split("=");
					nstate.put(Integer.parseInt(s[0]),Long.parseLong(s[1]));
				}
				if(state.isEmpty()){
					state = nstate;
					host = h;
				}else if(Replica.newerState(nstate,state)){
					state = nstate;
					host = h;
				}
				logger.info("Replica found remote snapshot: " + nstate + " (" + h + ")");
				in.close();
			}catch(Exception e){
				logger.error("Error getting state from " + h,e);
			}
		}
		InputStream in;
		if(host != null){
			logger.info("Use remote snapshot from host " + host);
			download_active = true;
			URL url = new URL("http://" + host + ":8080/snapshot");
			HttpURLConnection con = (HttpURLConnection)url.openConnection();
			logger.info("Open remote host " + host + " size:" + con.getContentLengthLong() + " timeout:" + con.getReadTimeout());
			in = con.getInputStream();
		}else{
			in = new FileInputStream(new File(snapshot_file));
		}
		ObjectInputStream ois = new ObjectInputStream(in);
		logger.info("Start reading remote snapshot ... ");
		@SuppressWarnings("unchecked")
		Map<String,byte[]> m = (Map<String,byte[]>) ois.readObject();
		logger.info(" ... read " + m.size() + " entries.");
		ois.close();
		in.close();
		synchronized(db){
			db.clear();
			db.putAll(m);
			byte[] b = null;
			for(int i = 1;i<max_ring+1;i++){
				if((b = db.get("r:" + i)) != null){
					instances.put(i,Long.valueOf(new String(b)));
				}
			}
		}
		logger.info("Replica installed snapshot instance: " + instances);
		download_active = false;
		return instances;
	}

	public void close(){
		if(httpd != null){
			httpd.stop(1);
		}
	}

    public static class SendFile implements HttpHandler {
    	
    	private final File file;
    	
    	public SendFile(String file){
    		this.file = new File(file);
    	}
    	
        public void handle(HttpExchange t) throws IOException {
            FileInputStream fis = new FileInputStream(file);
            BufferedInputStream bis = new BufferedInputStream(fis);

        	byte[] b = new byte[(int)file.length()];
            bis.read(b, 0, b.length); //TODO: FIXME: keeps file in mem!
            bis.close();
            fis.close();

            t.sendResponseHeaders(200, b.length);
            OutputStream os = t.getResponseBody();
            os.write(b);
            os.close();
        }
    }

}
