/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program 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 2
    of the License, or (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


*/


package com.sap.dbtech.powertoys;

import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Properties;

import com.sap.dbtech.jdbc.DriverSapDB;
import com.sap.dbtech.jdbc.exceptions.SQLExceptionSapDB;
import com.sap.dbtech.rte.comm.JdbcCommFactory;
import com.sap.dbtech.rte.comm.JdbcCommunication;
import com.sap.dbtech.rte.comm.NativeComm;
import com.sap.dbtech.rte.comm.RTEException;
import com.sap.dbtech.rte.comm.SocketComm;
import com.sap.dbtech.util.StructuredMem;
import com.sap.dbtech.util.Tracer;
import com.sap.dbtech.util.security.Authentication;
import com.sap.dbtech.util.security.SCRAMMD5;
/**
 * Executes administration commands for SAP DB
 */
public class DBM
{
    public static final String hostKeyC = "host";
    private static final String hostDefaultC = "";
    public static final String dbnameKeyC = "dbname";
    private static final String dbnameDefaultC = "";
    public static final String dbrootKeyC = "dbroot";
    private static final String dbrootDefaultC = "";
    public static final String userKeyC = "user";
    private static final String pgmNameC = "dbmsrv";
    private static final String transportC = "transport";

    private static final int alignSizeC = 8;
    static final int indicatorLengthC = 4;

    private JdbcCommunication connection;
    private boolean inCommunication;
    
    /**
       creates a new DBM.

       The following properties are used by this class:
        <UL>
        <LI>"host": start dbmserver on this host
        <LI>"dbname": start dbmserver for this database
        <LI>"dbroot": start dbmserver for this release/diretory
        <LI>"user": user,pwd implicit dbm connect (not yet implemented)
        <UL>
     */
    public DBM (Properties properties)
        throws RTEException
    {
        String host = properties.getProperty (hostKeyC, hostDefaultC);
        String dbname = properties.getProperty (dbnameKeyC, dbnameDefaultC);
        String dbroot = properties.getProperty (dbrootKeyC , dbrootDefaultC);
        JdbcCommFactory factory=null;

        try {
	        try {
	            if (DriverSapDB.loadNativeCommunication() == DriverSapDB.nativeCommunication_enabled) {
	                factory = NativeComm.factory;
	            } else {
	                factory = SocketComm.factory;
	            }
	            DriverSapDB.openTrace("",properties);
	            Tracer.print ("new DBM Connection ", properties);
	            this.connection = factory.xopen(host, dbname, dbroot, pgmNameC,
	                    properties);
	
	        } catch (Error linkErr) {
	            factory = SocketComm.factory;
	            this.connection = factory.xopen(host, dbname,
	                                            dbroot, pgmNameC, properties);
	        }
        }catch (RTEException rteExc) {
            Tracer.println ("using "+factory);
            Tracer.println ("=> FAILED");
            throw rteExc;
        }
        Tracer.println ("using "+this.connection);
        Tracer.println ("=> " + this);
 }

    /**
     * closes the connection
     */
    public void release ()
        throws RTEException
    {
        Tracer.println (this+"->release ()");
        if (this.connection != null) {
            try {
              try {
                this.cmd("release");
              }
              catch (Exception ignore) {}
              this.connection.release ();
            }
            finally {
                this.connection = null;
            }
        }
    }
    /**
     *
     */
    public void finalize ()
        throws RTEException
    {
        this.release ();
    }
    
    
    private String userLogon(String logonData) throws RTEException,
            DBMException {
        Authentication auth;
        try {
            auth = new Authentication();
        } catch (NoSuchAlgorithmException ex) {
            return this.cmd("USER_LOGON " + logonData, false);
        }
        int sepPos = logonData.indexOf(',');
        if (-1 == sepPos) {
            throw new DBMException(-24950, "ERR_USRFAIL:",
                    "User authorization failed [password not set]");
        }
        String user = logonData.substring(0, sepPos);
        String pass = logonData.substring(sepPos + 1).toUpperCase();
        String erg = null;
        try {
            erg = this.cmd("USER_GETCHALLENGE "
                    + user
                    + " METHODS "
                    + SCRAMMD5.algorithmname
                    + " "
                    + Tracer.Hex2String(auth.getClientchallenge())
                            .toUpperCase(), false);
        } catch (DBMException e) {
            if (e.getErrorID().equalsIgnoreCase("ERR_COMMAND")
                    && e.getErrorCode() == -24977) {
                return this.cmd("USER_LOGON " + logonData, false);
            } else {
                throw e;
            }
        }
        if (!erg.startsWith(SCRAMMD5.algorithmname)) {
            throw new DBMException(-24950, "ERR_USRFAIL:",
                    "User authorization failed [unknown authentication algorithm received]");
        }
        sepPos = erg.indexOf('\n');
        if (-1 == sepPos) {
            throw new DBMException(-24950, "ERR_USRFAIL:",
                    "User authorization failed [wrong format of server challenge]");
        }
        erg = erg.substring(sepPos + 1, erg.length() - 1);
        try {
            auth.parseServerChallenge(Tracer.String2Hex(erg));

            erg = this.cmd("USER_RESPONSE "
                    + Tracer.Hex2String(auth.getClientProof(pass.getBytes()))
                            .toUpperCase(), false);
            return erg;
        } catch (SQLExceptionSapDB ex1) {
            throw new DBMException(-24950, "ERR_USRFAIL:",
                    "User authorization failed [" + ex1.toString() + "]");
        }
    }
    /**
     * executes <i>cmdString </i> in the current dbm session.
     * 
     * @exception DBMException
     * @exception RTEException
     */
    public String cmd(String cmdString) throws RTEException, DBMException {
        try {
            Tracer.println(this + "->cmd (" + cmdString + ")");
            String erg = this.cmd(cmdString, true);
            Tracer.println("=> " + erg);
            return erg;
        } catch (DBMException e) {
            Tracer.println(" <-!");
            Tracer.traceException(e);
            throw e;
        } catch (RTEException err) {
            Tracer.println(" <-!");
            Tracer.traceException(err);
            throw err;
        }

    }

    public String cmd(String cmdString, boolean checklogon)
            throws RTEException, DBMException {
        Tracer.println(this + "->cmd (" + cmdString + ")");
        StructuredMem request;
        int alignedLen;
        StructuredMem reply;
        String result;

        if (checklogon) {
            cmdString = cmdString.trim();
            if (cmdString.toUpperCase().startsWith("USER_LOGON")) {
                cmdString = cmdString.substring("USER_LOGON".length()).trim();
                return userLogon(cmdString);
            }
        }
        /*
         * send request
         */
        request = this.connection.getRequestPacket();
        alignedLen = (cmdString.length() + alignSizeC - 1) / alignSizeC
                * alignSizeC;
        request.putString(cmdString, 0);
        this.connection.request(request, alignedLen);
        try {
        	this.inCommunication = true;
        	reply = this.connection.receive();
        	this.inCommunication = false;
        	String errorIndicator = reply.getString(0, Math.min(indicatorLengthC,
        			reply.size()));
	        if (errorIndicator.startsWith("OK")) {
	            int dataStart = errorIndicator.indexOf('\n') + 1;
	            result = reply.getString(dataStart, reply.size() - dataStart);
	        } else {
	            throw DBMException.create(reply);
	        }
	        return result;
        }
        finally {
        	this.inCommunication = false;
        }
    }
    
    public void cancel() throws SQLException
    {
    	if(this.inCommunication) {
    		this.connection.cancel();
    	}
    }
    
    /**
     * creates a new DBM by specifying the host and the dbname
     */
    public static DBM dbDBM (String host, String dbname)
        throws RTEException
    {
        Properties props = new Properties ();

        if (host != null) {
            props.put (hostKeyC, host);
        }
        if (dbname != null) {
            props.put (dbnameKeyC, dbname);
        }
        return new DBM (props);
    }
    /**
     * create a new DBM by specifying the host and the dbroot
     */
    public static DBM dbrootDBM (String host, String dbroot)
        throws RTEException
    {
        Properties props = new Properties ();

        if (host != null) {
            props.put (hostKeyC, host);
        }
        props.put (dbrootKeyC, dbroot);
        return new DBM (props);
    }

}
