/* * copyright 2005 joe walker * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ /** * declare an object to which we can add real functions. */ if (dwr == null) var dwr = {}; if (dwr.engine == null) dwr.engine = {}; if (dwrengine == null) var dwrengine = dwr.engine; /** * set an alternative error handler from the default alert box. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.seterrorhandler = function(handler) { dwr.engine._errorhandler = handler; }; /** * set an alternative warning handler from the default alert box. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.setwarninghandler = function(handler) { dwr.engine._warninghandler = handler; }; /** * setter for the text/html handler - what happens if a dwr request gets an html * reply rather than the expected javascript. often due to login timeout */ dwr.engine.settexthtmlhandler = function(handler) { dwr.engine._texthtmlhandler = handler; }; /** * set a default timeout value for all calls. 0 (the default) turns timeouts off. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.settimeout = function(timeout) { dwr.engine._timeout = timeout; }; /** * the pre-hook is called before any dwr remoting is done. * @see getahead.org/dwr/browser/engine/hooks */ dwr.engine.setprehook = function(handler) { dwr.engine._prehook = handler; }; /** * the post-hook is called after any dwr remoting is done. * @see getahead.org/dwr/browser/engine/hooks */ dwr.engine.setposthook = function(handler) { dwr.engine._posthook = handler; }; /** * custom headers for all dwr calls * @see getahead.org/dwr/???? */ dwr.engine.setheaders = function(headers) { dwr.engine._headers = headers; }; /** * custom parameters for all dwr calls * @see getahead.org/dwr/???? */ dwr.engine.setparameters = function(parameters) { dwr.engine._parameters = parameters; }; /** xhr remoting type constant. see dwr.engine.set[rpc|poll]type() */ dwr.engine.xmlhttprequest = 1; /** xhr remoting type constant. see dwr.engine.set[rpc|poll]type() */ dwr.engine.iframe = 2; /** xhr remoting type constant. see dwr.engine.setrpctype() */ dwr.engine.scripttag = 3; /** * set the preferred remoting type. * @param newtype one of dwr.engine.xmlhttprequest or dwr.engine.iframe or dwr.engine.scripttag * @see getahead.org/dwr/browser/engine/options */ dwr.engine.setrpctype = function(newtype) { if (newtype != dwr.engine.xmlhttprequest && newtype != dwr.engine.iframe && newtype != dwr.engine.scripttag) { dwr.engine._handleerror(null, { name:"dwr.engine.invalidrpctype", message:"rpctype must be one of dwr.engine.xmlhttprequest or dwr.engine.iframe or dwr.engine.scripttag" }); return; } dwr.engine._rpctype = newtype; }; /** * which http method do we use to send results? must be one of "get" or "post". * @see getahead.org/dwr/browser/engine/options */ dwr.engine.sethttpmethod = function(httpmethod) { if (httpmethod != "get" && httpmethod != "post") { dwr.engine._handleerror(null, { name:"dwr.engine.invalidhttpmethod", message:"remoting method must be one of get or post" }); return; } dwr.engine._httpmethod = httpmethod; }; /** * ensure that remote calls happen in the order in which they were sent? (default: false) * @see getahead.org/dwr/browser/engine/ordering */ dwr.engine.setordered = function(ordered) { dwr.engine._ordered = ordered; }; /** * do we ask the xhr object to be asynchronous? (default: true) * @see getahead.org/dwr/browser/engine/options */ dwr.engine.setasync = function(async) { dwr.engine._async = async; }; /** * does dwr poll the server for updates? (default: false) * @see getahead.org/dwr/browser/engine/options */ dwr.engine.setactivereverseajax = function(activereverseajax) { if (activereverseajax) { // bail if we are already started if (dwr.engine._activereverseajax) return; dwr.engine._activereverseajax = true; dwr.engine._poll(); } else { // can we cancel an existing request? if (dwr.engine._activereverseajax && dwr.engine._pollreq) dwr.engine._pollreq.abort(); dwr.engine._activereverseajax = false; } // todo: in iframe mode, if we start, stop, start then the second start may // well kick off a second iframe while the first is still about to return // we should cope with this but we don't }; /** * the default message handler. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.defaulterrorhandler = function(message, ex) { dwr.engine._debug("error: " + ex.name + ", " + ex.message, true); /*if (message == null || message == "") alert("a server error has occured."); // ignore ns_error_not_available if mozilla is being narky else if (message.indexof("0x80040111") != -1) dwr.engine._debug(message); else if (message.indexof("failed to read input") == -1 && message.indexof("not implemented") == -1 && message.indexof("service unavailable") == -1 ) alert(message); */ dwr.engine._debug(message); }; /** * the default warning handler. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.defaultwarninghandler = function(message, ex) { dwr.engine._debug(message); }; /** * for reduced latency you can group several remote calls together using a batch. * @see getahead.org/dwr/browser/engine/batch */ dwr.engine.beginbatch = function() { if (dwr.engine._batch) { dwr.engine._handleerror(null, { name:"dwr.engine.batchbegun", message:"batch already begun" }); return; } dwr.engine._batch = dwr.engine._createbatch(); }; /** * finished grouping a set of remote calls together. go and execute them all. * @see getahead.org/dwr/browser/engine/batch */ dwr.engine.endbatch = function(options) { var batch = dwr.engine._batch; if (batch == null) { dwr.engine._handleerror(null, { name:"dwr.engine.batchnotbegun", message:"no batch in progress" }); return; } dwr.engine._batch = null; if (batch.map.callcount == 0) return; // the hooks need to be merged carefully to preserve ordering if (options) dwr.engine._mergebatch(batch, options); // in ordered mode, we don't send unless the list of sent items is empty if (dwr.engine._ordered && dwr.engine._batcheslength != 0) { dwr.engine._batchqueue[dwr.engine._batchqueue.length] = batch; } else { dwr.engine._senddata(batch); } }; /** @deprecated */ dwr.engine.setpollmethod = function(type) { dwr.engine.setpolltype(type); }; dwr.engine.setmethod = function(type) { dwr.engine.setrpctype(type); }; dwr.engine.setverb = function(verb) { dwr.engine.sethttpmethod(verb); }; dwr.engine.setpolltype = function() { dwr.engine._debug("manually setting the poll type is not supported"); }; //============================================================================== // only private stuff below here //============================================================================== /** the original page id sent from the server */ dwr.engine._origscriptsessionid = "c00bfdb5cb4b5f41b8b1d7f709e7afbe"; /** the session cookie name */ dwr.engine._sessioncookiename = "jsessionid"; // jsessionid /** is get enabled for the benefit of safari? */ dwr.engine._allowgetforsafaributmakeforgeryeasier = "false"; /** the script prefix to strip in the case of scripttagprotection. */ dwr.engine._scripttagprotection = "throw 'allowscripttagremoting is false.';"; /** the default path to the dwr servlet */ dwr.engine._defaultpath = "/system/dwr"; /** do we use xhr for reverse ajax because we are not streaming? */ dwr.engine._pollwithxhr = "false"; /** the read page id that we calculate */ dwr.engine._scriptsessionid = null; /** the function that we use to fetch/calculate a session id */ dwr.engine._getscriptsessionid = function() { if (dwr.engine._scriptsessionid == null) { dwr.engine._scriptsessionid = dwr.engine._origscriptsessionid + math.floor(math.random() * 1000); } return dwr.engine._scriptsessionid; }; /** a function to call if something fails. */ dwr.engine._errorhandler = dwr.engine.defaulterrorhandler; /** for debugging when something unexplained happens. */ dwr.engine._warninghandler = dwr.engine.defaultwarninghandler; /** a function to be called before requests are marshalled. can be null. */ dwr.engine._prehook = null; /** a function to be called after replies are received. can be null. */ dwr.engine._posthook = null; /** an map of the batches that we have sent and are awaiting a reply on. */ dwr.engine._batches = {}; /** a count of the number of outstanding batches. should be == to _batches.length unless prototype has messed things up */ dwr.engine._batcheslength = 0; /** in ordered mode, the array of batches waiting to be sent */ dwr.engine._batchqueue = []; /** what is the default rpc type */ dwr.engine._rpctype = dwr.engine.xmlhttprequest; /** what is the default remoting method (ie get or post) */ dwr.engine._httpmethod = "post"; /** do we attempt to ensure that calls happen in the order in which they were sent? */ dwr.engine._ordered = false; /** do we make the calls async? */ dwr.engine._async = true; /** the current batch (if we are in batch mode) */ dwr.engine._batch = null; /** the global timeout */ dwr.engine._timeout = 0; /** activex objects to use when we want to convert an xml string into a dom object. */ dwr.engine._domdocument = ["msxml2.domdocument.6.0", "msxml2.domdocument.5.0", "msxml2.domdocument.4.0", "msxml2.domdocument.3.0", "msxml2.domdocument", "msxml.domdocument", "microsoft.xmldom"]; /** the activex objects to use when we want to do an xmlhttprequest call. */ dwr.engine._xmlhttp = ["msxml2.xmlhttp.6.0", "msxml2.xmlhttp.5.0", "msxml2.xmlhttp.4.0", "msxml2.xmlhttp.3.0", "msxml2.xmlhttp", "microsoft.xmlhttp"]; /** are we doing comet or polling? */ dwr.engine._activereverseajax = false; /** the iframe that we are using to poll */ dwr.engine._outstandingiframes = []; /** the xhr object that we are using to poll */ dwr.engine._pollreq = null; /** how many milliseconds between internal comet polls */ dwr.engine._pollcometinterval = 200; /** how many times have we re-tried to poll? */ dwr.engine._pollretries = 0; dwr.engine._maxpollretries = 0; /** do we do a document.reload if we get a text/html reply? */ dwr.engine._texthtmlhandler = null; /** if you wish to send custom headers with every request */ dwr.engine._headers = null; /** if you wish to send extra custom request parameters with each request */ dwr.engine._parameters = null; /** undocumented interceptors - do not use */ dwr.engine._postseperator = "&"; dwr.engine._defaultinterceptor = function(data) { return data; }; dwr.engine._urlrewritehandler = dwr.engine._defaultinterceptor; dwr.engine._contentrewritehandler = dwr.engine._defaultinterceptor; dwr.engine._replyrewritehandler = dwr.engine._defaultinterceptor; /** batch ids allow us to know which batch the server is answering */ dwr.engine._nextbatchid = 0; /** a list of the properties that need merging from calls to a batch */ dwr.engine._propnames = [ "rpctype", "httpmethod", "async", "timeout", "errorhandler", "warninghandler", "texthtmlhandler" ]; /** do we stream, or can be hacked to do so? */ dwr.engine._partialresponseno = 0; dwr.engine._partialresponseyes = 1; dwr.engine._partialresponseflush = 2; /** is this page in the process of unloading? */ dwr.engine._unloading = false; /** * @private send a request. called by the javascript interface stub * @param path part of url after the host and before the exec bit without leading or trailing /s * @param scriptname the class to execute * @param methodname the method on said class to execute * @param func the callback function to which any returned data should be passed * if this is null, any returned data will be ignored * @param vararg_params the parameters to pass to the above class */ dwr.engine._execute = function(path, scriptname, methodname, vararg_params) { var singleshot = false; if (dwr.engine._batch == null) { dwr.engine.beginbatch(); singleshot = true; } var batch = dwr.engine._batch; // to make them easy to manipulate we copy the arguments into an args array var args = []; for (var i = 0; i < arguments.length - 3; i++) { args[i] = arguments[i + 3]; } // all the paths must be to the same servlet if (batch.path == null) { batch.path = path; } else { if (batch.path != path) { dwr.engine._handleerror(batch, { name:"dwr.engine.multipleservlets", message:"can't batch requests to multiple dwr servlets." }); return; } } // from the other params, work out which is the function (or object with // call meta-data) and which is the call parameters var calldata; var lastarg = args[args.length - 1]; if (typeof lastarg == "function" || lastarg == null) calldata = { callback:args.pop() }; else calldata = args.pop(); // merge from the calldata into the batch dwr.engine._mergebatch(batch, calldata); batch.handlers[batch.map.callcount] = { exceptionhandler:calldata.exceptionhandler, callback:calldata.callback }; // copy to the map the things that need serializing var prefix = "c" + batch.map.callcount + "-"; batch.map[prefix + "scriptname"] = scriptname; batch.map[prefix + "methodname"] = methodname; batch.map[prefix + "id"] = batch.map.callcount; var refctx = []; for (i = 0; i < args.length; i++) { dwr.engine._serializeall(batch, refctx, args[i], prefix + "param" + i); } // now we have finished remembering the call, we incr the call count batch.map.callcount++; if (singleshot) dwr.engine.endbatch(); }; /** @private poll the server to see if there is any data waiting */ dwr.engine._poll = function() { if (!dwr.engine._activereverseajax) return; var batch = dwr.engine._createbatch(); batch.map.id = 0; // todo: do we need this?? batch.map.callcount = 1; batch.ispoll = true; if (dwr.engine._pollwithxhr == "true") { batch.rpctype = dwr.engine.xmlhttprequest; batch.map.partialresponse = dwr.engine._partialresponseno; } else { if (navigator.useragent.indexof("gecko/") != -1) { batch.rpctype = dwr.engine.xmlhttprequest; batch.map.partialresponse = dwr.engine._partialresponseyes; } else { batch.rpctype = dwr.engine.xmlhttprequest; batch.map.partialresponse = dwr.engine._partialresponseno; } } batch.httpmethod = "post"; batch.async = true; batch.timeout = 0; batch.path = dwr.engine._defaultpath; batch.prehooks = []; batch.posthooks = []; batch.errorhandler = dwr.engine._pollerrorhandler; batch.warninghandler = dwr.engine._pollerrorhandler; batch.handlers[0] = { callback:function(pause) { dwr.engine._pollretries = 0; settimeout(dwr.engine._poll, pause); } }; // send the data dwr.engine._senddata(batch); if (batch.rpctype == dwr.engine.xmlhttprequest && batch.map.partialresponse == dwr.engine._partialresponseyes) { dwr.engine._checkcometpoll(); } }; /** try to recover from polling errors */ dwr.engine._pollerrorhandler = function(msg, ex) { // if anything goes wrong then just silently try again (up to 3x) after 10s dwr.engine._pollretries++; dwr.engine._debug("reverse ajax poll failed (pollretries=" + dwr.engine._pollretries + "): " + ex.name + " : " + ex.message); if (dwr.engine._pollretries < dwr.engine._maxpollretries) { settimeout(dwr.engine._poll, 10000); } else { dwr.engine._activereverseajax = false; dwr.engine._debug("giving up."); } }; /** @private generate a new standard batch */ dwr.engine._createbatch = function() { var batch = { map:{ callcount:0, page:window.location.pathname + window.location.search, httpsessionid:dwr.engine._getjsessionid(), scriptsessionid:dwr.engine._getscriptsessionid() }, charsprocessed:0, paramcount:0, parameters:{}, headers:{}, ispoll:false, handlers:{}, prehooks:[], posthooks:[], rpctype:dwr.engine._rpctype, httpmethod:dwr.engine._httpmethod, async:dwr.engine._async, timeout:dwr.engine._timeout, errorhandler:dwr.engine._errorhandler, warninghandler:dwr.engine._warninghandler, texthtmlhandler:dwr.engine._texthtmlhandler }; if (dwr.engine._prehook) batch.prehooks.push(dwr.engine._prehook); if (dwr.engine._posthook) batch.posthooks.push(dwr.engine._posthook); var propname, data; if (dwr.engine._headers) { for (propname in dwr.engine._headers) { data = dwr.engine._headers[propname]; if (typeof data != "function") batch.headers[propname] = data; } } if (dwr.engine._parameters) { for (propname in dwr.engine._parameters) { data = dwr.engine._parameters[propname]; if (typeof data != "function") batch.parameters[propname] = data; } } return batch; }; /** @private take further options and merge them into */ dwr.engine._mergebatch = function(batch, overrides) { var propname, data; for (var i = 0; i < dwr.engine._propnames.length; i++) { propname = dwr.engine._propnames[i]; if (overrides[propname] != null) batch[propname] = overrides[propname]; } if (overrides.prehook != null) batch.prehooks.unshift(overrides.prehook); if (overrides.posthook != null) batch.posthooks.push(overrides.posthook); if (overrides.headers) { for (propname in overrides.headers) { data = overrides.headers[propname]; if (typeof data != "function") batch.headers[propname] = data; } } if (overrides.parameters) { for (propname in overrides.parameters) { data = overrides.parameters[propname]; if (typeof data != "function") batch.map["p-" + propname] = "" + data; } } }; /** @private what is our session id? */ dwr.engine._getjsessionid = function() { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i]; while (cookie.charat(0) == ' ') cookie = cookie.substring(1, cookie.length); if (cookie.indexof(dwr.engine._sessioncookiename + "=") == 0) { return cookie.substring(dwr.engine._sessioncookiename.length + 1, cookie.length); } } return ""; }; /** @private check for reverse ajax activity */ dwr.engine._checkcometpoll = function() { for (var i = 0; i < dwr.engine._outstandingiframes.length; i++) { var text = ""; var iframe = dwr.engine._outstandingiframes[i]; try { text = dwr.engine._gettextfromcometiframe(iframe); } catch (ex) { dwr.engine._handlewarning(iframe.batch, ex); } if (text != "") dwr.engine._processcometresponse(text, iframe.batch); } if (dwr.engine._pollreq) { var req = dwr.engine._pollreq; var text = req.responsetext; if (text != null) dwr.engine._processcometresponse(text, req.batch); } // if the poll resources are still there, come back again if (dwr.engine._outstandingiframes.length > 0 || dwr.engine._pollreq) { settimeout(dwr.engine._checkcometpoll, dwr.engine._pollcometinterval); } }; /** @private extract the whole (executed an all) text from the current iframe */ dwr.engine._gettextfromcometiframe = function(frameele) { var body = frameele.contentwindow.document.body; if (body == null) return ""; var text = body.innerhtml; // we need to prevent ie from stripping line feeds if (text.indexof("
") == 0 || text.indexof("
") == 0) {
    text = text.substring(5, text.length - 7);
  }
  return text;
};

/** @private some more text might have come in, test and execute the new stuff */
dwr.engine._processcometresponse = function(response, batch) {
  if (batch.charsprocessed == response.length) return;
  if (response.length == 0) {
    batch.charsprocessed = 0;
    return;
  }

  var firststarttag = response.indexof("//#dwr-start#", batch.charsprocessed);
  if (firststarttag == -1) {
    // dwr.engine._debug("no start tag (search from " + batch.charsprocessed + "). skipping '" + response.substring(batch.charsprocessed) + "'");
    batch.charsprocessed = response.length;
    return;
  }
  // if (firststarttag > 0) {
  //   dwr.engine._debug("start tag not at start (search from " + batch.charsprocessed + "). skipping '" + response.substring(batch.charsprocessed, firststarttag) + "'");
  // }

  var lastendtag = response.lastindexof("//#dwr-end#");
  if (lastendtag == -1) {
    // dwr.engine._debug("no end tag. unchanged charsprocessed=" + batch.charsprocessed);
    return;
  }

  // skip the end tag too for next time, remembering cr and lf
  if (response.charcodeat(lastendtag + 11) == 13 && response.charcodeat(lastendtag + 12) == 10) {
    batch.charsprocessed = lastendtag + 13;
  }
  else {
    batch.charsprocessed = lastendtag + 11;
  }

  var exec = response.substring(firststarttag + 13, lastendtag);

  dwr.engine._receivedbatch = batch;
  dwr.engine._eval(exec);
  dwr.engine._receivedbatch = null;
};

/** @private actually send the block of data in the batch object. */
dwr.engine._senddata = function(batch) {
  batch.map.batchid = dwr.engine._nextbatchid;
  dwr.engine._nextbatchid++;
  dwr.engine._batches[batch.map.batchid] = batch;
  dwr.engine._batcheslength++;
  batch.completed = false;

  for (var i = 0; i < batch.prehooks.length; i++) {
    batch.prehooks[i]();
  }
  batch.prehooks = null;
  // set a timeout
  if (batch.timeout && batch.timeout != 0) {
    batch.timeoutid = settimeout(function() { dwr.engine._abortrequest(batch); }, batch.timeout);
  }
  // get setup for xmlhttprequest if possible
  if (batch.rpctype == dwr.engine.xmlhttprequest) {
    if (window.xmlhttprequest) {
      batch.req = new xmlhttprequest();
    }
    // ie5 for the mac claims to support window.activexobject, but throws an error when it's used
    else if (window.activexobject && !(navigator.useragent.indexof("mac") >= 0 && navigator.useragent.indexof("msie") >= 0)) {
      batch.req = dwr.engine._newactivexobject(dwr.engine._xmlhttp);
    }
  }

  var prop, request;
  if (batch.req) {
    // proceed using xmlhttprequest
    if (batch.async) {
      batch.req.onreadystatechange = function() {
        if (typeof dwr != 'undefined') dwr.engine._statechange(batch);
      };
    }
    // if we're polling, record this for monitoring
    if (batch.ispoll) {
      dwr.engine._pollreq = batch.req;
      // in ie xhr is an activex control so you can't augment it like this
      if (!(document.all && !window.opera)) batch.req.batch = batch;
    }
    // workaround for safari 1.x post bug
    var indexsafari = navigator.useragent.indexof("safari/");
    if (indexsafari >= 0) {
      var version = navigator.useragent.substring(indexsafari + 7);
      if (parseint(version, 10) < 400) {
        if (dwr.engine._allowgetforsafaributmakeforgeryeasier == "true") batch.httpmethod = "get";
        else dwr.engine._handlewarning(batch, { name:"dwr.engine.oldsafari", message:"safari get support disabled. see getahead.org/dwr/server/servlet and allowgetforsafaributmakeforgeryeasier." });
      }
    }
    batch.mode = batch.ispoll ? dwr.engine._modeplainpoll : dwr.engine._modeplaincall;
    request = dwr.engine._constructrequest(batch);
    try {
      batch.req.open(batch.httpmethod, request.url, batch.async);
      try {
        for (prop in batch.headers) {
          var value = batch.headers[prop];
          if (typeof value == "string") batch.req.setrequestheader(prop, value);
        }
        //if (!batch.headers["content-type"]) batch.req.setrequestheader("content-type", "text/plain");
        batch.req.setrequestheader("content-type", "application/x-www-form-urlencoded;charset=utf-8");
      }
      catch (ex) {
        dwr.engine._handlewarning(batch, ex);
      }
      batch.req.send(request.body);
      if (!batch.async) dwr.engine._statechange(batch);
    }
    catch (ex) {
      dwr.engine._handleerror(batch, ex);
    }
  }
  else if (batch.rpctype != dwr.engine.scripttag) {
    var idname = batch.ispoll ? "dwr-if-poll-" + batch.map.batchid : "dwr-if-" + batch.map.batchid;
    // removed htmlfile implementation. don't expect it to return before v3
    batch.div = document.createelement("div");
    // add the div to the document first, otherwise ie 6 will ignore onload handler.
    document.body.appendchild(batch.div);
    batch.div.innerhtml = "";
    batch.document = document;
    batch.iframe = batch.document.getelementbyid(idname);
    batch.iframe.batch = batch;
    batch.mode = batch.ispoll ? dwr.engine._modehtmlpoll : dwr.engine._modehtmlcall;
    if (batch.ispoll) dwr.engine._outstandingiframes.push(batch.iframe);
    request = dwr.engine._constructrequest(batch);
    if (batch.httpmethod == "get") {
      batch.iframe.setattribute("src", request.url);
    }
    else {
      batch.form = batch.document.createelement("form");
      batch.form.setattribute("id", "dwr-form");
      batch.form.setattribute("action", request.url);
      batch.form.setattribute("style", "display:none;");
      batch.form.setattribute("target", idname);
      batch.form.target = idname;
      batch.form.setattribute("method", batch.httpmethod);
      for (prop in batch.map) {
        var value = batch.map[prop];
        if (typeof value != "function") {
          var forminput = batch.document.createelement("input");
          forminput.setattribute("type", "hidden");
          forminput.setattribute("name", prop);
          forminput.setattribute("value", value);
          batch.form.appendchild(forminput);
        }
      }
      batch.document.body.appendchild(batch.form);
      batch.form.submit();
    }
  }
  else {
    batch.httpmethod = "get"; // there's no such thing as scripttag using post
    batch.mode = batch.ispoll ? dwr.engine._modeplainpoll : dwr.engine._modeplaincall;
    request = dwr.engine._constructrequest(batch);
    batch.script = document.createelement("script");
    batch.script.id = "dwr-st-" + batch.map["c0-id"];
    batch.script.src = request.url;
    document.body.appendchild(batch.script);
  }
};

dwr.engine._modeplaincall = "/call/plaincall/";
dwr.engine._modehtmlcall = "/call/htmlcall/";
dwr.engine._modeplainpoll = "/call/plainpoll/";
dwr.engine._modehtmlpoll = "/call/htmlpoll/";

/** @private work out what the url should look like */
dwr.engine._constructrequest = function(batch) {
  // a quick string to help people that use web log analysers
  var request = { url:batch.path + batch.mode, body:null };
  if (batch.ispoll == true) {
    request.url += "reverseajax.dwr";
  }
  else if (batch.map.callcount == 1) {
    request.url += batch.map["c0-scriptname"] + "." + batch.map["c0-methodname"] + ".dwr";
  }
  else {
    request.url += "multiple." + batch.map.callcount + ".dwr";
  }
  // play nice with url re-writing
  var sessionmatch = location.href.match(/jsessionid=([^?]+)/);
  if (sessionmatch != null) {
    request.url += ";jsessionid=" + sessionmatch[1];
  }

  var prop;
  if (batch.httpmethod == "get") {
    // some browsers (opera/safari2) seem to fail to convert the callcount value
    // to a string in the loop below so we do it manually here.
    batch.map.callcount = "" + batch.map.callcount;
    request.url += "?";
    for (prop in batch.map) {
      if (typeof batch.map[prop] != "function") {
        request.url += encodeuricomponent(prop) + "=" + encodeuricomponent(batch.map[prop]) + "&";
      }
    }
    request.url = request.url.substring(0, request.url.length - 1);
  }
  else {
    // performance: for iframe mode this is thrown away.
    request.body = "";
    if (document.all && !window.opera) {
      // use array joining on ie (fastest)
      var buf = [];
      for (prop in batch.map) {
        if (typeof batch.map[prop] != "function") {
          buf.push(prop + "=" + encodeuricomponent(batch.map[prop]) + dwr.engine._postseperator);
        }
      }
      request.body = buf.join("");
    }
    else {
      // use string concat on other browsers (fastest)
      for (prop in batch.map) {
        if (typeof batch.map[prop] != "function") {
          request.body += prop + "=" + encodeuricomponent(batch.map[prop]) + dwr.engine._postseperator;
        }
      }
    }
    request.body = dwr.engine._contentrewritehandler(request.body);
  }
  request.url = dwr.engine._urlrewritehandler(request.url);
  return request;
};

/** @private called by xmlhttprequest to indicate that something has happened */
dwr.engine._statechange = function(batch) {
  var toeval;

  if (batch.completed) {
    dwr.engine._debug("error: _statechange() with batch.completed");
    return;
  }

  var req = batch.req;
  try {
    if (req.readystate != 4) return;
  }
  catch (ex) {
    dwr.engine._handlewarning(batch, ex);
    // it's broken - clear up and forget this call
    dwr.engine._clearup(batch);
    return;
  }

  if (dwr.engine._unloading) {
    dwr.engine._debug("ignoring reply from server as page is unloading.");
    return;
  }
  
  try {
    var reply = req.responsetext;
    reply = dwr.engine._replyrewritehandler(reply);
    var status = req.status; // causes mozilla to except on page moves

    if (reply == null || reply == "") {
      dwr.engine._handlewarning(batch, { name:"dwr.engine.missingdata", message:"no data received from server" });
    }
    else if (status != 200) {
      dwr.engine._handleerror(batch, { name:"dwr.engine.http." + status, message:req.statustext });
    }
    else {
      var contenttype = req.getresponseheader("content-type");
      if (!contenttype.match(/^text\/plain/) && !contenttype.match(/^text\/javascript/)) {
        if (contenttype.match(/^text\/html/) && typeof batch.texthtmlhandler == "function") {
          batch.texthtmlhandler({ status:status, responsetext:reply, contenttype:contenttype });
        }
        else {
          dwr.engine._handlewarning(batch, { name:"dwr.engine.invalidmimetype", message:"invalid content type: '" + contenttype + "'" });
        }
      }
      else {
        // comet replies might have already partially executed
        if (batch.ispoll && batch.map.partialresponse == dwr.engine._partialresponseyes) {
          dwr.engine._processcometresponse(reply, batch);
        }
        else {
          if (reply.search("//#dwr") == -1) {
            dwr.engine._handlewarning(batch, { name:"dwr.engine.invalidreply", message:"invalid reply from server" });
          }
          else {
            toeval = reply;
          }
        }
      }
    }
  }
  catch (ex) {
    dwr.engine._handlewarning(batch, ex);
  }

  dwr.engine._callposthooks(batch);

  // outside of the try/catch so errors propogate normally:
  dwr.engine._receivedbatch = batch;
  if (toeval != null) toeval = toeval.replace(dwr.engine._scripttagprotection, "");
  dwr.engine._eval(toeval);
  dwr.engine._receivedbatch = null;
  dwr.engine._validatebatch(batch);
  if (!batch.completed) dwr.engine._clearup(batch);
};

/**
 * @private this function is invoked when a batch reply is received.
 * it checks that there is a response for every call in the batch. otherwise,
 * an error will be signaled (a call without a response indicates that the 
 * server failed to send complete batch response). 
 */
dwr.engine._validatebatch = function(batch) {
  // if some call left unreplied, report an error.
  if (!batch.completed) {
    for (var i = 0; i < batch.map.callcount; i++) {
      if (batch.handlers[i] != null) {
        dwr.engine._handlewarning(batch, { name:"dwr.engine.incompletereply", message:"incomplete reply from server" });
        break;
      }
    }
  }
}

/** @private called from iframe onload, check batch using batch-id */
dwr.engine._iframeloadingcomplete = function(batchid) {
  // dwr.engine._checkcometpoll();
  var batch = dwr.engine._batches[batchid];
  if (batch) dwr.engine._validatebatch(batch);
}

/** @private called by the server: execute a callback */
dwr.engine._remotehandlecallback = function(batchid, callid, reply) {
  var batch = dwr.engine._batches[batchid];
  if (batch == null) {
    dwr.engine._debug("warning: batch == null in remotehandlecallback for batchid=" + batchid, true);
    return;
  }
  // error handlers inside here indicate an error that is nothing to do
  // with dwr so we handle them differently.
  try {
    var handlers = batch.handlers[callid];
    batch.handlers[callid] = null;
    if (!handlers) {
      dwr.engine._debug("warning: missing handlers. callid=" + callid, true);
    }
    else if (typeof handlers.callback == "function") handlers.callback(reply);
  }
  catch (ex) {
    dwr.engine._handleerror(batch, ex);
  }
};

/** @private called by the server: handle an exception for a call */
dwr.engine._remotehandleexception = function(batchid, callid, ex) {
  var batch = dwr.engine._batches[batchid];
  if (batch == null) { dwr.engine._debug("warning: null batch in remotehandleexception", true); return; }
  var handlers = batch.handlers[callid];
  batch.handlers[callid] = null;
  if (handlers == null) { dwr.engine._debug("warning: null handlers in remotehandleexception", true); return; }
  if (ex.message == undefined) ex.message = "";
  if (typeof handlers.exceptionhandler == "function") handlers.exceptionhandler(ex.message, ex);
  else if (typeof batch.errorhandler == "function") batch.errorhandler(ex.message, ex);
};

/** @private called by the server: the whole batch is broken */
dwr.engine._remotehandlebatchexception = function(ex, batchid) {
  var searchbatch = (dwr.engine._receivedbatch == null && batchid != null);
  if (searchbatch) {
    dwr.engine._receivedbatch = dwr.engine._batches[batchid];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleerror(dwr.engine._receivedbatch, ex);
  if (searchbatch) {
    dwr.engine._receivedbatch = null;
    dwr.engine._clearup(dwr.engine._batches[batchid]);
  }
};

/** @private called by the server: reverse ajax should not be used */
dwr.engine._remotepollcometdisabled = function(ex, batchid) {
  dwr.engine.setactivereverseajax(false);
  var searchbatch = (dwr.engine._receivedbatch == null && batchid != null);
  if (searchbatch) {
    dwr.engine._receivedbatch = dwr.engine._batches[batchid];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleerror(dwr.engine._receivedbatch, ex);
  if (searchbatch) {
    dwr.engine._receivedbatch = null;
    dwr.engine._clearup(dwr.engine._batches[batchid]);
  }
};

/** @private called by the server: an iframe reply is about to start */
dwr.engine._remotebeginiframeresponse = function(iframe, batchid) {
  if (iframe != null) dwr.engine._receivedbatch = iframe.batch;
  dwr.engine._callposthooks(dwr.engine._receivedbatch);
};

/** @private called by the server: an iframe reply is just completing */
dwr.engine._remoteendiframeresponse = function(batchid) {
  dwr.engine._clearup(dwr.engine._receivedbatch);
  dwr.engine._receivedbatch = null;
};

/** @private this is a hack to make the context be this window */
dwr.engine._eval = function(script) {
  if (script == null) return null;
  if (script == "") { dwr.engine._debug("warning: blank script", true); return null; }
  // dwr.engine._debug("exec: [" + script + "]", true);
  return eval(script);
};

/** @private called as a result of a request timeout */
dwr.engine._abortrequest = function(batch) {
  if (batch && !batch.completed) {
    dwr.engine._clearup(batch);
    if (batch.req) batch.req.abort();
    dwr.engine._handleerror(batch, { name:"dwr.engine.timeout", message:"timeout" });
  }
};

/** @private call all the post hooks for a batch */
dwr.engine._callposthooks = function(batch) {
  if (batch.posthooks) {
    for (var i = 0; i < batch.posthooks.length; i++) {
      batch.posthooks[i]();
    }
    batch.posthooks = null;
  }
};

/** @private a call has finished by whatever means and we need to shut it all down. */
dwr.engine._clearup = function(batch) {
  if (!batch) { dwr.engine._debug("warning: null batch in dwr.engine._clearup()", true); return; }
  if (batch.completed) { dwr.engine._debug("warning: double complete", true); return; }

  // iframe tidyup
  if (batch.div) batch.div.parentnode.removechild(batch.div);
  if (batch.iframe) {
    // if this is a poll frame then stop comet polling
    for (var i = 0; i < dwr.engine._outstandingiframes.length; i++) {
      if (dwr.engine._outstandingiframes[i] == batch.iframe) {
        dwr.engine._outstandingiframes.splice(i, 1);
      }
    }
    batch.iframe.parentnode.removechild(batch.iframe);
  }
  if (batch.form) batch.form.parentnode.removechild(batch.form);

  // xhr tidyup: avoid ie handles increase
  if (batch.req) {
    // if this is a poll frame then stop comet polling
    if (batch.req == dwr.engine._pollreq) dwr.engine._pollreq = null;
    delete batch.req;
  }

  // timeout tidyup
  if (batch.timeoutid != null) {
    cleartimeout(batch.timeoutid);
    delete batch.timeoutid;
  }

  if (batch.map && (batch.map.batchid || batch.map.batchid == 0)) {
    delete dwr.engine._batches[batch.map.batchid];
    dwr.engine._batcheslength--;
  }

  batch.completed = true;

  // if there is anything on the queue waiting to go out, then send it.
  // we don't need to check for ordered mode, here because when ordered mode
  // gets turned off, we still process *waiting* batches in an ordered way.
  if (dwr.engine._batchqueue.length != 0) {
    var sendbatch = dwr.engine._batchqueue.shift();
    dwr.engine._senddata(sendbatch);
  }
};

/** @private abort any xhrs in progress at page unload (solves zombie socket problems in ie). */
dwr.engine._unloader = function() {
  dwr.engine._unloading = true;

  // empty queue of waiting ordered requests
  dwr.engine._batchqueue.length = 0;

  // abort any ongoing xhrs and clear their batches
  for (var batchid in dwr.engine._batches) {
    var batch = dwr.engine._batches[batchid];
    // only process objects that look like batches (avoid prototype additions!)
    if (batch && batch.map) {
      if (batch.req) {
        batch.req.abort();
      }
      dwr.engine._clearup(batch);
    }
  }
};
// now register the unload handler
if (window.addeventlistener) window.addeventlistener('unload', dwr.engine._unloader, false);
else if (window.attachevent) window.attachevent('onunload', dwr.engine._unloader);

/** @private generic error handling routing to save having null checks everywhere */
dwr.engine._handleerror = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.errorhandler == "function") batch.errorhandler(ex.message, ex);
  else if (dwr.engine._errorhandler) dwr.engine._errorhandler(ex.message, ex);
  if (batch) dwr.engine._clearup(batch);
};

/** @private generic error handling routing to save having null checks everywhere */
dwr.engine._handlewarning = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.warninghandler == "function") batch.warninghandler(ex.message, ex);
  else if (dwr.engine._warninghandler) dwr.engine._warninghandler(ex.message, ex);
  if (batch) dwr.engine._clearup(batch);
};

/**
 * @private marshall a data item
 * @param batch a map of variables to how they have been marshalled
 * @param referto an array of already marshalled variables to prevent recurrsion
 * @param data the data to be marshalled
 * @param name the name of the data being marshalled
 */
dwr.engine._serializeall = function(batch, referto, data, name) {
  if (data == null) {
    batch.map[name] = "null:null";
    return;
  }

  switch (typeof data) {
  case "boolean":
    batch.map[name] = "boolean:" + data;
    break;
  case "number":
    batch.map[name] = "number:" + data;
    break;
  case "string":
    batch.map[name] = "string:" + encodeuricomponent(data);
    break;
  case "object":
    var objstr = object.prototype.tostring.call(data);
    if (objstr == "[object string]") batch.map[name] = "string:" + encodeuricomponent(data);
    else if (objstr == "[object boolean]") batch.map[name] = "boolean:" + data;
    else if (objstr == "[object number]") batch.map[name] = "number:" + data;
    else if (objstr == "[object date]") batch.map[name] = "date:" + data.gettime();
    else if (objstr == "[object array]") batch.map[name] = dwr.engine._serializearray(batch, referto, data, name);
    else batch.map[name] = dwr.engine._serializeobject(batch, referto, data, name);
    break;
  case "function":
    // we just ignore functions.
    break;
  default:
    dwr.engine._handlewarning(null, { name:"dwr.engine.unexpectedtype", message:"unexpected type: " + typeof data + ", attempting default converter." });
    batch.map[name] = "default:" + data;
    break;
  }
};

/** @private have we already converted this object? */
dwr.engine._lookup = function(referto, data, name) {
  var lookup;
  // can't use a map: getahead.org/ajax/javascript-gotchas
  for (var i = 0; i < referto.length; i++) {
    if (referto[i].data == data) {
      lookup = referto[i];
      break;
    }
  }
  if (lookup) return "reference:" + lookup.name;
  referto.push({ data:data, name:name });
  return null;
};

/** @private marshall an object */
dwr.engine._serializeobject = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  // this check for an html is not complete, but is there a better way?
  // maybe we should add: data.haschildnodes typeof "function" == true
  if (data.nodename && data.nodetype) {
    return dwr.engine._serializexml(batch, referto, data, name);
  }

  // treat objects as an associative arrays
  var reply = "object_" + dwr.engine._getobjectclassname(data) + ":{";
  var element;
  for (element in data) {
    if (typeof data[element] != "function") {
      batch.paramcount++;
      var childname = "c" + dwr.engine._batch.map.callcount + "-e" + batch.paramcount;
      dwr.engine._serializeall(batch, referto, data[element], childname);

      reply += encodeuricomponent(element) + ":reference:" + childname + ", ";
    }
  }

  if (reply.substring(reply.length - 2) == ", ") {
    reply = reply.substring(0, reply.length - 2);
  }
  reply += "}";

  return reply;
};

/** @private returns the classname of supplied argument obj */
dwr.engine._errorclasses = { "error":error, "evalerror":evalerror, "rangeerror":rangeerror, "referenceerror":referenceerror, "syntaxerror":syntaxerror, "typeerror":typeerror, "urierror":urierror };
dwr.engine._getobjectclassname = function(obj) {
  // try to find the classname by stringifying the object's constructor
  // and extract  from "function ".
  if (obj && obj.constructor && obj.constructor.tostring)
  {
    var str = obj.constructor.tostring();
    var regexpmatch = str.match(/function\s+(\w+)/);
    if (regexpmatch && regexpmatch.length == 2) {
      return regexpmatch[1];
    }
  }

  // now manually test against the core error classes, as these in some 
  // browsers successfully match to the wrong class in the 
  // object.tostring() test we will do later
  if (obj && obj.constructor) {
    for (var errorname in dwr.engine._errorclasses) {
      if (obj.constructor == dwr.engine._errorclasses[errorname]) return errorname;
    }
  }

  // try to find the classname by calling object.tostring() on the object
  // and extracting  from "[object ]"
  if (obj) {
    var str = object.prototype.tostring.call(obj);
    var regexpmatch = str.match(/\[object\s+(\w+)/);
    if (regexpmatch && regexpmatch.length==2) {
      return regexpmatch[1];
    }
  }

  // supplied argument was probably not an object, but what is better?
  return "object";
};

/** @private marshall an object */
dwr.engine._serializexml = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  var output;
  if (window.xmlserializer) output = new xmlserializer().serializetostring(data);
  else if (data.toxml) output = data.toxml;
  else output = data.innerhtml;

  return "xml:" + encodeuricomponent(output);
};

/** @private marshall an array */
dwr.engine._serializearray = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  if (document.all && !window.opera) {
    // use array joining on ie (fastest)
    var buf = ["array:["];
    for (var i = 0; i < data.length; i++) {
      if (i != 0) buf.push(",");
      batch.paramcount++;
      var childname = "c" + dwr.engine._batch.map.callcount + "-e" + batch.paramcount;
      dwr.engine._serializeall(batch, referto, data[i], childname);
      buf.push("reference:");
      buf.push(childname);
    }
    buf.push("]");
    reply = buf.join("");
  }
  else {
    // use string concat on other browsers (fastest)
    var reply = "array:[";
    for (var i = 0; i < data.length; i++) {
      if (i != 0) reply += ",";
      batch.paramcount++;
      var childname = "c" + dwr.engine._batch.map.callcount + "-e" + batch.paramcount;
      dwr.engine._serializeall(batch, referto, data[i], childname);
      reply += "reference:";
      reply += childname;
    }
    reply += "]";
  }

  return reply;
};

/** @private convert an xml string into a dom object. */
dwr.engine._unserializedocument = function(xml) {
  var dom;
  if (window.domparser) {
    var parser = new domparser();
    dom = parser.parsefromstring(xml, "text/xml");
    if (!dom.documentelement || dom.documentelement.tagname == "parsererror") {
      var message = dom.documentelement.firstchild.data;
      message += "\n" + dom.documentelement.firstchild.nextsibling.firstchild.data;
      throw message;
    }
    return dom;
  }
  else if (window.activexobject) {
    dom = dwr.engine._newactivexobject(dwr.engine._domdocument);
    dom.loadxml(xml); // what happens on parse fail with ie?
    return dom;
  }
  else {
    var div = document.createelement("div");
    div.innerhtml = xml;
    return div;
  }
};

/** @param axarray an array of strings to attempt to create activex objects from */
dwr.engine._newactivexobject = function(axarray) {
  var returnvalue;  
  for (var i = 0; i < axarray.length; i++) {
    try {
      returnvalue = new activexobject(axarray[i]);
      break;
    }
    catch (ex) { /* ignore */ }
  }
  return returnvalue;
};

/** @private used internally when some message needs to get to the programmer */
dwr.engine._debug = function(message, stacktrace) {
  var written = false;
  try {
    if (window.console) {
      if (stacktrace && window.console.trace) window.console.trace();
      window.console.log(message);
      written = true;
    }
    else if (window.opera && window.opera.posterror) {
      window.opera.posterror(message);
      written = true;
    }
  }
  catch (ex) { /* ignore */ }

  if (!written) {
    var debug = document.getelementbyid("dwr-debug");
    if (debug) {
      var contents = message + "
" + debug.innerhtml; if (contents.length > 2048) contents = contents.substring(0, 2048); debug.innerhtml = contents; } } };