// The WWWJexec class provides the communication pathway to allow
// multi-threaded scriptservers to service script requests by running
// Java code inside a client thread.  The scriptserver makes Java Native
// Interface (JNI) calls to load the WWWJexec class into the Java virt.
// machine (JVM) and define implementations for the native methods necessary
// for carrying the scriptserver protocol data messages.  To service a request,
// the scriptserver attaches the client thread to the JVM, loads the specified
// class and invokes the static WWWrun(WWWJexec link) method defined for that 
// class.
//
// If the target classname contains a period, the scriptserver checks for
// the existence of a 'shell' class and if found invokes that class's
// WWWrun(WWWJexec link, java.lang.Class target) rather than the standard
// WWWrun.  The additional argument for the 'shell' activation is class object
// for the class orginally referred to.  Any further semantics for the target
// class are under the control of the shell class.
//
// Native methods (low level):
//   String read();		    Read message from server, returns message.
//   String read(byte[], int);	    Read message from server. returns length.
//   int write(String);		    Write message to server.
//   int write(byte[], int);	    Write message to server.
//   int close();		    Close connection (net_link, MST cnx).
//   int putLog(int, String);       Add line to scriptserver log file.
//
// Instance variables:
//   String func;       	    Scriptserver function (HTBIN,CONVERT,etc)
//   String method;		    HTTP method specified in request (e.g. GET)
//   String url_path;		    Translated URL path.
//   String protocol;		    HTTP protocol specified in request.
//   String script_path;	    Prefix string that caused script invocation.
// 
// Additional utility routines.
//   String query(String);	    Combined write followed by read.
//   String query(String,String);   Combined 2 writes followed by read.
//   boolean shell_invoke(	    Chained invocation of named methods.
//	java.lang.Object,	    Instance object of target class
//	String, String, String,     Methods to invoke: setup, main, teardown.
//	java.lang.Object[] );	    Argument list for setup and teardown methods.
//   Hashtable CGI_table ( 	    Creates hash table containing standard CGI
//		WWWJexec link );    variables (performs dialog over link).
//
import java.lang.*;

class WWWJexec {
    //
    // read() returns data read as String, read(byte[]) returns number of
    // bytes read.  Read error result in IOException being thrown.
    //
    public final native String read ( ) throws java.io.IOException;
    public final native int read ( byte[] buffer, int count ) throws java.io.IOException;
    //
    // Write, close, and formatError methods return VMS-style condition
    // value (low bit set for success).   write(String) converts argument
    // to 7-bit ASCII (UTF).  Write(byte[],count) writes minimum of count
    // or size of byte array argument.
    //
    public final native int write ( String buffer );
    public final native int write ( byte[] buffer, int count );
    public final native int close ( );
    public final native int putLog ( int level, String message );
    //
    // Following fields are loaded from scriptserver prologue sent by web server.
    //
    public String func;				// Scriptserver function code
    public String method;			// command line arguments.
    public String url_path;
    public String protocol;
    public String script_path;			// response to <DNETPATH> query
    //
    // Composite read/write functions
    //
    public final String query ( String tag ) throws java.io.IOException {
	int status = this.write ( tag );
	if ( (status&1) == 1 ) return this.read ();
	else return "";
    }
    public final String query ( String tag, String arg ) throws java.io.IOException {
	int status = this.write ( tag );
	if ( (status&1) == 1 ) status = this.write ( arg );
	if ( (status&1) == 1 ) return this.read ();
	else return "";
    }

    /////////////////////////////////////////////////////////////////////////
    // Shell support routine.  make abort messages.
    //
    public final boolean shell_invoke (
	java.lang.Object instance,		// Object.
	String setup_name,			// init function.
	String method_name,			// Name of method to invoke
	String rundown_name,			//
	java.lang.Object[] setup_arg  ) {		// Arguments for setup/rundow

	java.lang.Class target = instance.getClass();	// target class.
	java.lang.Class targ_parent = target.getSuperclass();

	java.lang.reflect.Method setup_routine, main_routine, rundown_routine;
        //
	// Generate signature class array from setup_arg and lookup methods
        // for setup and rundown routines.
        //
	int arg_count = java.lang.reflect.Array.getLength(setup_arg);
        java.lang.Class[] setup_signature = new java.lang.Class[arg_count];
	for (int i=0; i < arg_count; i++) {
	    setup_signature[i] = setup_arg[i].getClass();
	}
        try {
	    setup_routine = target.getMethod ( setup_name, setup_signature );
        } catch ( java.lang.NoSuchMethodException e5 ) {
	    //
	    // Method not found, abort.
	    //
	    System.out.println ( e5 );
	    this.write ( "<DNETTEXT>" );
	    this.write ( "404 no such method" );
	    this.write ( "Error executing setup method " + setup_name + 
			" in "+ target );
	    this.write ( e5.toString() );
	    this.write ( "</DNETTEXT>" );
	    return false;
	}
        try {
	    rundown_routine = target.getMethod(rundown_name, setup_signature);
        } catch ( java.lang.NoSuchMethodException e5 ) {
	    //
	    // Method not found, abort.
	    //
	    System.out.println ( e5 );
	    this.write ( "<DNETTEXT>" );
	    this.write ( "404 no such method" );
	    this.write ( "Error executing rundown method " + rundown_name + 
			" in "+ target );
	    this.write ( e5.toString() );
	    this.write ( "</DNETTEXT>" );
	    return false;
	}
	//
	// Invoke the setup method, returning an argument list.
	//
	java.lang.Object[] arg_list;
        try {
	    arg_list = (java.lang.Object[]) setup_routine.invoke 
			( instance, setup_arg );
	} catch ( java.lang.IllegalAccessException e3 ) {
	    System.out.println ( e3 );
	    return false;
	} catch ( java.lang.reflect.InvocationTargetException e4 ) {
	    System.out.println ( e4 );
	    return false;
	}
        //
	// Generate signature class array from arg_list returned by setup.
        //
	arg_count = java.lang.reflect.Array.getLength(arg_list);
        java.lang.Class[] signature = new java.lang.Class[arg_count];
        try {
	    main_routine = target.getMethod ( method_name, signature );
        } catch ( java.lang.NoSuchMethodException e5 ) {
	    //
	    // Method not found, abort, can't generate HTTP response because
	    // we don't know what setup routine did.
	    //
	    System.out.println ( e5 );
	    return false;
	}
	//
	// Invoke the main method.
	//
        try {
	    main_routine.invoke ( instance, arg_list );
	} catch ( java.lang.IllegalAccessException e3 ) {
	    System.out.println ( e3 );
	    return false;
	} catch ( java.lang.reflect.InvocationTargetException e4 ) {
	    System.out.println ( e4 );
	    return false;
	}
	//
	// Invoke the rundown method.
	//
        try {
	    rundown_routine.invoke ( instance, setup_arg );
	} catch ( java.lang.IllegalAccessException e3 ) {
	    System.out.println ( e3 );
	    return false;
	} catch ( java.lang.reflect.InvocationTargetException e4 ) {
	    System.out.println ( e4 );
	    return false;
	}
	return true;
    }
    //
    // Standard routine for creating CGI environment array from dialog
    // with server.
    //
    public static final java.util.Hashtable CGI_table ( WWWJexec link ) 
		throws java.io.IOException {
        //
        // Make standard variables that derive from info already in link
        // object.
        //
	java.util.Hashtable var = new java.util.Hashtable(20);
	var.put ( "REQUEST_METHOD", link.method );
	var.put ( "SERVER_PROTOCOL", link.protocol );
	var.put ( "SCRIPT_PATH", link.script_path );
	var.put ( "GATEWAY_INTERFACE", "CGI/1.1" );
	int path_len = link.script_path.length();
        if ( link.url_path.regionMatches(0,link.script_path,0,path_len) ) {
	    //
	    // construct Script_name from url_path and script_path
	    //
	    int next_slash = link.url_path.indexOf('/',path_len);
	    try {
		if ( next_slash >= 0 ) {
		    var.put ( "SCRIPT_NAME",link.url_path.substring(0,next_slash) );
	            var.put ( "PATH_INFO", link.url_path.substring(next_slash) );
		    String translation = link.query("<DNETXLATE>",
			(String) var.get("PATH_INFO") );
		    var.put ( "PATH_TRANSLATED", translation );
		} else {
		    var.put ( "SCRIPT_NAME", link.url_path );
	    	    var.put ( "PATH_INFO", "" );
		    var.put ( "PATH_TRANSLATED", "" );
		}
	    } catch ( StringIndexOutOfBoundsException e ) {
	    } catch ( java.io.IOException e ) {
	    }
        } else {
	    var.put ( "SCRIPT_NAME", link.url_path );	// needs work
        }
        //
        // Make standard variables that derive from info returned by <DNETID2>
        //
	String id2 = link.query ( "<DNETID2>" );
	java.util.StringTokenizer tok = new java.util.StringTokenizer(id2);
	var.put ( "SERVER_SOFTWARE", tok.nextToken() );
	if ( tok.hasMoreTokens() ) var.put ("SERVER_NAME", tok.nextToken());
	if ( tok.hasMoreTokens() ) var.put ("SERVER_PORT", tok.nextToken());
	if ( tok.hasMoreTokens() ) var.put ("REMOTE_PORT", tok.nextToken());
	if ( tok.hasMoreTokens() ) try {
	    //
	    // convert remote address to 'dot' notation
	    //
	    long rem_addr = java.lang.Long.parseLong(tok.nextToken());
	    String octet1 = String.valueOf(rem_addr & 255);
	    String octet2 = String.valueOf((rem_addr>>8) & 255);
	    String octet3 = String.valueOf((rem_addr>>16) & 255);
	    String octet4 = String.valueOf((rem_addr>>>24) & 255);
            //
            // Set remote_host if present or falback to same as remote_addr
            //
	    var.put ("REMOTE_ADDR", octet1+"."+octet2+"."+octet3+"."+octet4);
	    if ( tok.hasMoreTokens() ) {
		var.put ("REMOTE_HOST", tok.nextToken());
	    }  else {
		var.put ("REMOTE_HOST", var.get("REMOTE_ADDR") );
	    }
	} catch ( NumberFormatException e ) {
	    // Error converting digits in remote address number
	}
        //
        // See if query string if present, stripping '?' at beginning of
	// string response returned by server. (server returns null string if 
        // no query argument present).
	//
	try {
	    String query_string = link.query ( "<DNETARG>" );
	    if ( query_string.length() > 0 ) var.put ( "QUERY_STRING", 
		query_string.substring(1) );
	} catch ( java.io.IOException e ) {
	}
        //
        // Make HTTP_ lines from headers, renaming content-type and
	// content-length headers.
        //
        link.write ( "<DNETHDR>" );
	String hdr;
	for ( hdr=link.read(); hdr.length() > 0; hdr=link.read() ) {
		int colon_pos = hdr.indexOf(':');
		if ( colon_pos > 1 ) {
		    String label = (hdr.substring(0,colon_pos)).toUpperCase();
		    label = "HTTP_" + label.replace('-','_');
		    if ( label.equals("HTTP_CONTENT_TYPE") )
			label="CONTENT_TYPE";
		    else if ( label.equals("HTTP_CONTENT_LENGTH") )
			label = "CONTENT_LENGTH";

		    String value = hdr.substring(colon_pos+1,hdr.length());
		    var.put(label,value.trim());
		}
	}
	return var;
    }
    //
    // Following field used by JNI routines.
    //
    private long cnx;				// MST context for I/O

    public static void WWWrun ( WWWJexec link ) {
	// Dummy method, redirect to HTML document.
	int status;
	status = link.write ( "<DNETRECMODE>" );
        status = link.write ( "<DNETCGI>" );
	status = link.write ( "Location: /demo/java_script.html" );
        status = link.write ( "" );
	status = link.write ( "</DNETCGI>" );
        link.close();
    }
}
