/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2001-2003
 *      Sleepycat Software.  All rights reserved.
 *
 * $Id: DbServer.java,v 1.7 2003/10/20 21:13:59 mjc Exp $
 */

package com.sleepycat.db.rpcserver;

import com.sleepycat.db.*;
import java.io.*;
import java.util.*;
import org.acplt.oncrpc.OncRpcException;
import org.acplt.oncrpc.server.OncRpcCallInformation;

/**
 * Main entry point for the Java version of the Berkeley DB RPC server
 */
public class DbServer extends DbDispatcher
{
	public static long idleto = 10 * 60 * 1000;	// 5 minutes
	public static long defto = 5 * 60 * 1000;	// 5 minutes
	public static long maxto = 60 * 60 * 1000;	// 1 hour
	public static String passwd = null;
	public static PrintWriter err;

	long now, hint; // updated each operation
	FreeList env_list = new FreeList();
	FreeList db_list = new FreeList();
	FreeList txn_list = new FreeList();
	FreeList cursor_list = new FreeList();

	public DbServer() throws IOException, OncRpcException
	{
		super();
		init_lists();
	}

	public void dispatchOncRpcCall(OncRpcCallInformation call, int program,
		int version, int procedure) throws OncRpcException, IOException
	{
		long newnow = System.currentTimeMillis();
		// DbServer.err.println("Dispatching RPC call " + procedure + " after delay of " + (newnow - now));
		now = newnow;
		try {
			super.dispatchOncRpcCall(call, program, version, procedure);
		} catch(Throwable t) {
			System.err.println("Caught " + t + " while dispatching RPC call " + procedure);
			t.printStackTrace(DbServer.err);
		} finally {
			doTimeouts();
			DbServer.err.flush();
		}
	}

	// Internal methods to track context
	private void init_lists()
	{
		// We do this so that getEnv/Db/etc(0) == null
		env_list.add(null);
		db_list.add(null);
		txn_list.add(null);
		cursor_list.add(null);
	}

	int addEnv(RpcDbEnv rdbenv)
	{
		rdbenv.timer.last_access = now;
		int id = env_list.add(rdbenv);
		return id;
	}

	int addDb(RpcDb rdb)
	{
		int id = db_list.add(rdb);
		return id;
	}

	int addTxn(RpcDbTxn rtxn)
	{
		rtxn.timer.last_access = now;
		int id = txn_list.add(rtxn);
		return id;
	}

	int addCursor(RpcDbc rdbc)
	{
		rdbc.timer.last_access = now;
		int id = cursor_list.add(rdbc);
		return id;
	}

	void delEnv(RpcDbEnv rdbenv, boolean dispose)
	{
		env_list.del(rdbenv);

		// cursors and transactions will already have been cleaned up
		for(LocalIterator i = db_list.iterator(); i.hasNext(); ) {
			RpcDb rdb = (RpcDb)i.next();
			if (rdb != null && rdb.rdbenv == rdbenv)
				delDb(rdb, true);
		}

		if (dispose)
			rdbenv.dispose();
	}

	void delDb(RpcDb rdb, boolean dispose)
	{
		db_list.del(rdb);

		for(LocalIterator i = cursor_list.iterator(); i.hasNext(); ) {
			RpcDbc rdbc = (RpcDbc)i.next();
			if (rdbc != null && rdbc.timer == rdb) {
				i.remove();
				rdbc.dispose();
			}
		}

		if (dispose)
			rdb.dispose();
	}

	void delTxn(RpcDbTxn rtxn, boolean dispose)
	{
		txn_list.del(rtxn);

		for(LocalIterator i = cursor_list.iterator(); i.hasNext(); ) {
			RpcDbc rdbc = (RpcDbc)i.next();
			if (rdbc != null && rdbc.timer == rtxn) {
				i.remove();
				rdbc.dispose();
			}
		}

		for(LocalIterator i = txn_list.iterator(); i.hasNext(); ) {
			RpcDbTxn rtxn_child = (RpcDbTxn)i.next();
			if (rtxn_child != null && rtxn_child.timer == rtxn) {
				i.remove();
				rtxn_child.dispose();
			}
		}

		if (dispose)
			rtxn.dispose();
	}

	void delCursor(RpcDbc rdbc, boolean dispose)
	{
		cursor_list.del(rdbc);
		if (dispose)
			rdbc.dispose();
	}

	RpcDbEnv getEnv(int envid)
	{
		RpcDbEnv rdbenv = (RpcDbEnv)env_list.get(envid);
		if (rdbenv != null)
			rdbenv.timer.last_access = now;
		return rdbenv;
	}

	RpcDb getDb(int dbid)
	{
		RpcDb rdb = (RpcDb)db_list.get(dbid);
		if (rdb != null)
			rdb.rdbenv.timer.last_access = now;
		return rdb;
	}

	RpcDbTxn getTxn(int txnid)
	{
		RpcDbTxn rtxn = (RpcDbTxn)txn_list.get(txnid);
		if (rtxn != null)
			rtxn.timer.last_access = rtxn.rdbenv.timer.last_access = now;
		return rtxn;
	}

	RpcDbc getCursor(int dbcid)
	{
		RpcDbc rdbc = (RpcDbc)cursor_list.get(dbcid);
		if (rdbc != null)
			rdbc.last_access = rdbc.timer.last_access = rdbc.rdbenv.timer.last_access = now;
		return rdbc;
	}

	void doTimeouts()
	{
		if (now < hint) {
			// DbServer.err.println("Skipping cleaner sweep - now = " + now + ", hint = " + hint);
			return;
		}

		// DbServer.err.println("Starting a cleaner sweep");
		hint = now + DbServer.maxto;

		for(LocalIterator i = cursor_list.iterator(); i.hasNext(); ) {
			RpcDbc rdbc = (RpcDbc)i.next();
			if (rdbc == null)
				continue;

			long end_time = rdbc.timer.last_access + rdbc.rdbenv.timeout;
			// DbServer.err.println("Examining " + rdbc + ", time left = " + (end_time - now));
			if (end_time < now) {
				DbServer.err.println("Cleaning up " + rdbc);
				delCursor(rdbc, true);
			} else if (end_time < hint)
				hint = end_time;
		}

		for(LocalIterator i = txn_list.iterator(); i.hasNext(); ) {
			RpcDbTxn rtxn = (RpcDbTxn)i.next();
			if (rtxn == null)
				continue;

			long end_time = rtxn.timer.last_access + rtxn.rdbenv.timeout;
			// DbServer.err.println("Examining " + rtxn + ", time left = " + (end_time - now));
			if (end_time < now) {
				DbServer.err.println("Cleaning up " + rtxn);
				delTxn(rtxn, true);
			} else if (end_time < hint)
				hint = end_time;
		}

		for(LocalIterator i = env_list.iterator(); i.hasNext(); ) {
			RpcDbEnv rdbenv = (RpcDbEnv)i.next();
			if (rdbenv == null)
				continue;

			long end_time = rdbenv.timer.last_access + rdbenv.idletime;
			// DbServer.err.println("Examining " + rdbenv + ", time left = " + (end_time - now));
			if (end_time < now) {
				DbServer.err.println("Cleaning up " + rdbenv);
				delEnv(rdbenv, true);
			}
		}

		 // if we didn't find anything, reset the hint
		if (hint == now + DbServer.maxto)
			hint = 0;

		// DbServer.err.println("Finishing a cleaner sweep");
	}

	// Some constants that aren't available elsewhere
	static final int EINVAL = 22;
	static final int DB_SERVER_FLAGMASK = Db.DB_LOCKDOWN |
	    Db.DB_PRIVATE | Db.DB_RECOVER | Db.DB_RECOVER_FATAL |
	    Db.DB_SYSTEM_MEM | Db.DB_USE_ENVIRON |
	    Db.DB_USE_ENVIRON_ROOT;
	static final int DB_SERVER_ENVFLAGS = Db.DB_INIT_CDB |
	    Db.DB_INIT_LOCK | Db.DB_INIT_LOG | Db.DB_INIT_MPOOL |
	    Db.DB_INIT_TXN | Db.DB_JOINENV;
	static final int DB_SERVER_DBFLAGS = Db.DB_DIRTY_READ |
	    Db.DB_NOMMAP | Db.DB_RDONLY;
	static final int DB_SERVER_DBNOSHARE = Db.DB_EXCL | Db.DB_TRUNCATE;

	static Vector homes = new Vector();

	static void add_home(String home) {
		File f = new File(home);
		try { home = f.getCanonicalPath(); } catch(IOException e) {}
		homes.addElement(home);
	}

	static boolean check_home(String home) {
		if (home == null)
			return false;
		File f = new File(home);
		try { home = f.getCanonicalPath(); } catch(IOException e) {}
		return homes.contains(home);
	}

	public static void main(String[] args)
	{
		System.out.println("Starting DbServer...");
		for (int i = 0; i < args.length; i++) {
			if (args[i].charAt(0) != '-')
				usage();

			switch (args[i].charAt(1)) {
			case 'h':
				add_home(args[++i]);
				break;
			case 'I':
				idleto = Long.parseLong(args[++i]) * 1000L;
				break;
			case 'P':
				passwd = args[++i];
				break;
			case 't':
				defto = Long.parseLong(args[++i]) * 1000L;
				break;
			case 'T':
				maxto = Long.parseLong(args[++i]) * 1000L;
				break;
			case 'V':
				// version;
				break;
			case 'v':
				// verbose
				break;
			default:
				usage();
			}
		}

		try {
			DbServer.err = new PrintWriter(new FileOutputStream("JavaRPCServer.trace", true));
			// DbServer.err = new PrintWriter(System.err);
			DbServer server = new DbServer();
			server.run();
		} catch (Throwable e) {
			System.out.println("DbServer exception:");
			e.printStackTrace(DbServer.err);
		} finally {
			if (DbServer.err != null)
				DbServer.err.close();
		}

		System.out.println("DbServer stopped.");
	}

	static void usage()
	{
		System.err.println("usage: java com.sleepycat.db.rpcserver.DbServer \\");
		System.err.println("[-Vv] [-h home] [-P passwd] [-I idletimeout] [-L logfile] [-t def_timeout] [-T maxtimeout]");
		System.exit(1);
	}
}
