/** 
 * @(#)JavaCGIBridgeExtension.java 2.00 07/18/98
 * 
 * Copyright Info: This class was written by Gunther Birznieks
 * gunther@clark.net having been inspired by countless other
 * software developers.
 * 
 * Version 1.00: 08/10/97
 * Version 2.00: 07/19/98
 * 
 * The second version of this software was written for
 * independent study under the guidance of Professor
 * Marty Hall as part of the Johns Hopkins University
 * Computer Science Master's Degree program.
 * 
 * Feel free to copy, cite, reference, sample, borrow, resell
 * or plagiarize the contents.  However, if you don't mind,
 * please let me know where it goes so that I can at least
 * watch and take part in the development of the memes. Information
 * wants to be free; support public domain freeware.
 * 
 * Donations are appreciated.  If you use this software please
 * consider making a donation to the Electronic Frontier Foundation
 * http://www.eff.org/ or another organization of your choice
 * dedicated to defending liberty on the net.
 * 
 */

package com.extropia.net;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Observable;

import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.net.MalformedURLException;

import java.io.ObjectOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.io.OptionalDataException;

import java.applet.AppletContext;

/**
 * This class extends the core JavaCGIBridge class. It contains
 * helper methods which may be useful for programmers communicating with
 * a web server in a browser environment, but are not as likely
 * to be used by every applet.
 * <P>
 * The main helper method subsets are as follows:
 * <P>
 * [1] Serialization Support. Applets can send serialized Objects to
 * and from a Web Server. Typically this will be used with CGI
 * programs written in Java or with Java Servlets. However, serialization
 * can also be a useful way of storing an applet's state on the server.
 * In this case, another language such as Perl can easily serve as
 * a conduit through which the serialized objects are stored as files
 * on the web server.
 * <P>
 * [2] fetch records support. Typically, you will get all the records
 * at once with getParsedData() or use the observer interface to 
 * keep track of records as they are parsed on the fly. However,
 * if the observer interface seems like it is too much overhead 
 * to set up, the fetchNextRecord() method provides an intermediate
 * alternative to getting all the records at the end of the entire
 * response and the overhead of setting up an observer interface in
 * your applet.
 * <P>
 * [3] show document support. Mac Netscape 3.x and below have a
 * bug in the showDocument method.  The show document method here
 * provides a cross-browser way of displaying other HTML pages.
 * For more information on Cross-Browser Bugs, view the web site
 * devoted to these topics at http://gunther.web66.com/crossjava/.
 * <P>
 * In addition, show document also supports passing the JavaCGIBridge
 * form variables Hashtable data structure.  JavaCGIBridgeExtension's
 * showDocument() method will automatically URL encoded and add the 
 * form variables to the URL so that a CGI program can be called with
 * the GET method from showDocument().
 * 
 * @version     2.00, 18 Jul 1998
 * @author      Gunther Birznieks
 * @author      Marty Hall
 * <P>
 */
public class JavaCGIBridgeExtension extends JavaCGIBridge {

    /**
     * long which contains a runnning counter of how many
     * documents have been shown with the showDocument()
     * method.  This is done to resolve a bug in Mac Netscape
     * 3.0 showDocument() method.
     * <P>
     * @see #showDocument
     */
    private static long _showDocCount = 0;

    /**
     * Constructs the object with new separator values
     */
    public JavaCGIBridgeExtension(String field, String record, String startData, String endData) {
        super(field,record,startData,endData);
    }

    /**
     * Constructs the object with no initialization. This is the default
     * (empty) constructor.
     */
    public JavaCGIBridgeExtension() {
    } // Empty constructor

    /**
     * Obtains a ByteArrayOutputStream. This is used to store
     * raw data to send to a CGI Script.  A more advanced version of
     * this method is getObjectOutputStream which is used to 
     * send serialized objects as binary data over a byte array output
     * stream. You can use this method to bypass posting data with
     * normal URLEncoded data.  This can be useful to post binary data
     * to a CGI script which will decode it in whatever way you choose
     * to program the parser.
     *
     * @return ByteArrayOutputStream
     * @see #getObjectOutputStream
     */ 
    public ByteArrayOutputStream getByteArrayOutputStream() throws IOException {
        _serialStream = new ByteArrayOutputStream();
        return _serialStream;
    } // end of getByteArrayOutputStream

    /**
     * Obtains an ObjectOutputStream. This is used to store the objects
     * which will be serialized and streamed to the web server.
     * <P>
     * Example Usage:
     * <P>
     * <PRE>
     * JavaCGIBridgeExtension jcbe = new JavaCGIBridgeExtension();
     * ObjectOutputStream oos = jcbe.getObjectOutputStream();
     * oos.writeObject(myFirstObject);
     * oos.writeObject(mySecondObject);
     * oos.close();
     * ObjectInputStream ois = jcbe.getObjectData(myURL);
     * </PRE>
     * <P>
     * @return ObjectOutputStream
     * @see #getObjectData
     */
    public ObjectOutputStream getObjectOutputStream() throws IOException {
        _serialStream = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(_serialStream);
        return oos;
    } // end of getObjectOutputStream

    /**
     * Obtains an ObjectInputStream that encapsulates the stream
     * of data being returned from the web server.
     * <P>
     * It follows the same basic syntax of getRawData() and
     * getParsedData() methods from the core JavaCGIBridge class.
     * <P>
     * @param u URL to get object data from
     * @return ObjectInputStream data stream to deserialize objects from
     * @exception JavaCGIBridgeTimeOutException If the retrieval times out
     * @exception StreamCorruptedException If something is wrong with the stream 
     *                                     of serialized objects.
     * @exception IOException If there is something wrong with the stream
     *                        other than being in the wrong format for retrieving
     *                        serialized objects.
     */
    public ObjectInputStream getObjectData(URL u) throws JavaCGIBridgeTimeOutException,
                                                         StreamCorruptedException,
                                                         IOException {
        return getObjectData(u, null);
    } // end of getObjectData

    /**
     * Obtains an ObjectInputStream that encapsulates the stream
     * of data being returned from the web server.
     * <P>
     * It follows the same basic syntax of getRawData() and 
     * getParsedData() methods from the core JavaCGIBridge class.
     * <P>
     * @param u URL to get object data from
     * @param ht Hashtable containing form variables to POST
     * @return ObjectInputStream data stream to deserialize objects from
     * @exception JavaCGIBridgeTimeOutException If the retrieval times out
     * @exception StreamCorruptedException If something is wrong with the stream
     *                                     of serialized objects.
     * @exception IOException If there is something wrong with the stream
     *                        other than being in the wrong format for retrieving
     *                        serialized objects.
     */
    public ObjectInputStream getObjectData(URL u, Hashtable ht) throws
                          JavaCGIBridgeTimeOutException, StreamCorruptedException,
                          IOException {
        ByteArrayInputStream bais = new ByteArrayInputStream(getRawData(u,ht));
        ObjectInputStream ois= new ObjectInputStream(bais);
        return ois;
    } // end of getObjectData

    /**
     * Obtains a Vector containing the fields of the individual record
     * returned from parsing the records currently being processed by
     * the JavaCGIBridge object.
     * <P>
     * This method is useful for users who wish to retrieve results before
     * the entire response has been sent from the Web Server yet without
     * the programmatic overhead of using the observer interface.  For example,
     * you may want to start displaying the first 100 or so records of data
     * to the user right away even if there may be a lot more records to
     * retrieve eventually such as 10,000.
     * <P>
     * Please note that you should be aware that there are multi-threading
     * concerns when using this method.  If you are using JavaCGIBridgePool
     * to manage your objects, then be aware that the JavaCGIBridge object
     * delibrately does not set processing to IDLE at the end of processing
     * when fetchNextRecord is being called.  This behavior is activated
     * by passing "true" to the callOneWay method.  
     * <P>
     * Because it is not set to IDLE, JavaCGIBridgePool has no way of
     * knowing that you are done with the objects.  There are two ways to
     * set the JavaCGIBridge object idle.  The first and easiest way
     * demonstrated below), is to simply iterate over fetchNextRecord()
     * until it returns null (end of records).  When it does this, the
     * JavaCGIBridge will immediately be set to the IDLE state.  The second
     * way is to manually called setStatus(JavaCGIBridge.IDLE) yourself.
     * <P>
     * If you are not using JavaCGIBridgePool, then this need to set the object
     * IDLE is not necesssary if you do not plan on reusing the object itself.
     * <P>
     * Example Usage:
     * <P>
     * <PRE>
     * Vector v;
     * JavaCGIBridgeExtension jcbe = new JavaCGIBridgeExtension();
     * // true is passed because we are fetching records after the
     * // one-way operation
     * jcbe.callOneWay(myURL, true);
     * while (null != (v = jcbe.fetchNextRecord())) {
     *     // Do something with Vector v
     * }
     * </PRE>
     * <P>
     * @return Vector a vector containing the fields of parsed data
     * @exception JavaCGIBridgeTimeOutException If the retrieval times out
     */
    public Vector fetchNextRecord() throws JavaCGIBridgeTimeOutException {
        while(!_threadCompleted &&
              (_vectorParsedData == null ||
              // Note that we will always be behind one record
              _fetchCount + 2 > _vectorParsedData.size() )) {
            Thread.yield();
            if (_threadTimeOut < (System.currentTimeMillis() - _threadBaseTime)) {
                this._thread.stop();
                this.setStatus(IDLE);
                throw new JavaCGIBridgeTimeOutException();
            }
        }
        if (_threadCompleted &&
            (_vectorParsedData == null ||
             _fetchCount == _vectorParsedData.size())) {
            setStatus(IDLE);
            return null;
        }
        
        _fetchCount++;
        _threadBaseTime = System.currentTimeMillis();
        return ((Vector)_vectorParsedData.elementAt(_fetchCount - 1));
    } // end of fetchNextRecord

    /**
     * Tells the browser to show a document.  Basically the difference between
     * this showDocument() and AppletContext's showDocument() method is that
     * this one fixes some cross platform problems with the Mac Netscape
     * 3.x implementation of showDocument().
     * <P>
     * @param ac AppletContext
     * @param u URL to load
     * @param target Target to load document in.  Same as target in
     *               AppletContext.showDocument()
     */
    public void showDocument(AppletContext ac, URL u, String target) {
        showDocument(ac,u,target,null,true);
    } // end of showDocument

    /**
     * Tells the browser to show a document. Basically the difference between
     * this showDocument() and AppletContext's showDocument() method is
     * that this one fixes some cross platform problems with the Mac Netscape
     * 3.x implementation of showDocument().
     * <P>
     * In addition, you can pass a Hashtable with form variables which will be 
     * used to construct a URL encoded query string to append to the URL itself.
     * <P>
     * @param ac AppletContext
     * @param u URL to load
     * @param target Target to load document in. Same as target in 
     *               AppletContext.showDocument().
     * @param formVars Hashtable contains form variables to add to GET method
     */
    public void showDocument(AppletContext ac, URL u, String target, Hashtable formVars) {
        showDocument(ac,u,target,formVars,true);
    } // end of showDocument()

    /**
     * Tells the browser to show a document. Basically the difference between
     * this showDocument() and AppletContext's showDocument() method is
     * that this one fixes some cross platform problems with the Mac Netscape
     * 3.x implementation of showDocument().
     * <P>
     * In addition, you can pass a Hashtable with form variables which will be 
     * used to construct a URL encoded query string to append to the URL itself.
     * <P>
     * @param ac AppletContext
     * @param u URL to load
     * @param target Target to load document in. Same as target in 
     *               AppletContext.showDocument().
     * @param formVars Hashtable contains form variables to add to GET method
     * @param fixMacBug boolean indicating whether or not the code for fixing the
     *                  Mac Bug should be activated.  There may be intranet
     *                  applications where you may choose not to activate the 
     *                  Mac Bug fix feature.
     */
    public void showDocument(AppletContext ac, URL u, String target, Hashtable formVars, boolean fixMacBug) {
        URL newURL = u;

        if (fixMacBug && target == "_blank") {
            target = "_doc" + _showDocCount;
            _showDocCount++; 
        } 
   
        if (formVars != null) {
            try {
                newURL = new URL(u.toString() + "?" + getURLEncodedHashtable(formVars)); 
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
        ac.showDocument(newURL, target);
        if (fixMacBug) {
            String osName = System.getProperty("os.name").toLowerCase();
            String browser = System.getProperty("java.vendor").toLowerCase();
            if ((osName.indexOf("mac") != -1 &&
              browser.indexOf("netscape") != -1)) {
                ac.showDocument(newURL, target);
            }
        }
    } // end of showDocument()

} // End of JavaCGIBridgeExtension class
