/// <summary>
        /// The main routine that handles each request, calls the database, and outputs the response from the OWA toolkit back to the client through IIS.
        /// </summary>
        /// <param name="o"></param>
        /// <param name="a"></param>

        private void HandleRequest(object o, EventArgs a)
        {
            // grab the request context from ASP.NET
            HttpApplication app = (HttpApplication)o;
            HttpContext     ctx = (HttpContext)app.Context;

            if (DadConfiguration.HideServerBanner)
            {
                // http://blogs.technet.com/b/stefan_gossner/archive/2008/03/12/iis-7-how-to-send-a-custom-server-http-header.aspx

                try
                {
                    app.Response.Headers.Remove("Server");
                    app.Response.Headers.Remove("X-AspNet-Version");
                    app.Response.Headers.Remove("X-Powered-By");
                }
                catch (PlatformNotSupportedException e)
                {
                    logger.Warn("Attempted to hide server banners (HideServerBanner = True), but failed with error: " + e.Message);
                }
            }

            // check if gateway should be bypassed (return normal execution to IIS)
            if (DadConfiguration.ServeStaticContent)
            {
                string physicalPath = app.Request.PhysicalPath;

                if (File.Exists(physicalPath))
                {
                    logger.Debug("Requested file " + physicalPath + " exists on disk. Gateway will not handle this request.");
                    return;
                }
                else
                {
                    logger.Debug("Requested file " + physicalPath + " was NOT found on disk, continuing with normal gateway request.");
                }
            }

            // parse the request (URL); we are expecting calls in a format similar to the following:
            // http://servername/PLSQLGatewayModule/dadname/[schema.][package.]procedure?parameter1=xxx&parameter2=yyy

            string serverName         = app.Request.ServerVariables["HTTP_HOST"];
            string requestContentType = app.Request.ContentType;
            string requestBody        = "";
            string requestPath        = app.Request.FilePath.Substring(1);
            string soapAction         = app.Request.Headers["SOAPAction"];

            if (requestContentType.ToLower() != "application/x-www-form-urlencoded")
            {
                requestBody = new StreamReader(app.Request.InputStream, System.Text.Encoding.Default).ReadToEnd();
                requestBody = HttpUtility.UrlDecode(requestBody);
            }

            GatewayRequest gReq = new GatewayRequest(serverName, app.Request.HttpMethod, app.Request.FilePath, app.Request.RawUrl, soapAction);

            if (!gReq.DadSpecifiedInRequest)
            {
                if (DadConfiguration.DefaultDadEnabled)
                {
                    string defaultURL = "/" + gReq.ModuleName + "/" + gReq.DadName + "/" + gReq.ProcName;

                    logger.Debug("DAD not specified in URL, or specified DAD not defined in configuration file. Redirecting to default DAD and default procedure: " + defaultURL);
                    ctx.Response.Redirect(defaultURL);
                    //ctx.Response.StatusCode = 302; // moved temporarily
                    app.CompleteRequest();
                    return;
                }
                else
                {
                    logger.Warn("DAD not specified in request, or DAD not defined in configuration file, and default DAD disabled. Request terminated with 404.");
                    throw new HttpException(404, "Not Found");
                }
            }

            if (gReq.ValidRequest)
            {
                // the requested procedure name is valid
                // now process querystring and form variables, set up the OWA packages with the CGI environment information,
                // and check if there are files to be uploaded

                if (gReq.IsWsdlRequest)
                {
                    logger.Debug("Request is for WSDL document");
                }
                else if (gReq.IsSoapRequest)
                {
                    logger.Debug("Invocation protocol is SOAP");
                    gReq.AddRequestParametersForSOAP(gReq.ProcName, requestBody);
                }
                else
                {
                    gReq.AddRequestParameters(app.Request.QueryString, app.Request.Form, app.Request.Files, requestBody);
                }

                gReq.AddCGIEnvironment(ctx.Request.ServerVariables);

                OracleParameterCache opc = new OracleParameterCache(ctx);

                // connect to the database
                OracleInterface ora = new OracleInterface(gReq, opc);

                if (ora.Connected())
                {
                    ora.SetupOwaCGI(gReq.CGIParameters, ctx.Request.UserHostName, ctx.Request.UserHostAddress, gReq.BasicAuthUsername, gReq.BasicAuthPassword);

                    if (ctx.Request.Files.Count > 0)
                    {
                        bool uploadSuccess = ora.UploadFiles(gReq.UploadedFiles);
                    }
                }

                bool success = false;

                // the GatewayResponse object will hold the result of the database call (headers, cookies, status codes, response body, etc.)
                GatewayResponse gResp = new GatewayResponse();

                if (!ora.Connected())
                {
                    logger.Debug("Failed to connect to database, skipping procedure execution.");
                    success = false;
                }
                else if (gReq.IsSoapRequest)
                {
                    if (gReq.IsWsdlRequest)
                    {
                        success = ora.GenerateWsdl(gReq.ServerName, gReq.ModuleName, gReq.DadName, gReq.ProcName);
                    }
                    else
                    {
                        success = ora.ExecuteMainProc(gReq.OwaProc, gReq.RequestParameters, false, gReq.ProcName);
                        if (success)
                        {
                            ora.GenerateSoapResponse(gReq.ProcName);
                        }
                        else
                        {
                            int    errorCode = ora.GetLastErrorCode();
                            string errorText = ora.GetLastErrorText();
                            logger.Debug("SOAP request failed with error code " + errorCode + ", generating SOAP Fault response.");
                            ora.GenerateSoapFault(errorCode, errorText);
                        }
                    }
                }
                else if (gReq.IsFlexibleParams)
                {
                    logger.Debug("Using flexible parameter mode (converting parameters to name/value arrays)");
                    success = ora.ExecuteMainProc(gReq.OwaProc, gReq.GetFlexibleParams(), false, gReq.ProcName);
                }
                else if (gReq.IsPathAlias)
                {
                    logger.Debug("Forwarding request to PathAliasProcedure");
                    success = ora.ExecuteMainProc(gReq.OwaProc, gReq.GetPathAliasParams(), false, gReq.ProcName);
                }
                else if (gReq.IsXdbAlias)
                {
                    logger.Debug("Forwarding request to XDB Repository");
                    success = ora.GetXdbResource(gReq.XdbAliasValue);
                }
                else if (gReq.IsDocumentPath)
                {
                    logger.Debug("Forwarding request to DocumentProcedure");
                    success = ora.ExecuteMainProc(gReq.OwaProc, new List <NameValuePair>(), false, gReq.ProcName);
                }
                else
                {
                    success = ora.ExecuteMainProc(gReq.OwaProc, gReq.RequestParameters, false, gReq.ProcName);

                    if (!success && ora.GetLastErrorText().IndexOf("PLS-00306:") > -1)
                    {
                        logger.Error("Call failed: " + ora.GetLastErrorText());
                        logger.Debug("Wrong number or types of arguments in call. Will retry call after looking up parameter metadata in data dictionary.");
                        success = ora.ExecuteMainProc(gReq.OwaProc, gReq.RequestParameters, true, gReq.ProcName);
                    }
                    else if (!success && ora.GetLastErrorText().IndexOf("ORA-01460:") > -1)
                    {
                        logger.Error("Call failed: " + ora.GetLastErrorText());
                        logger.Debug("Unimplemented or unreasonable conversion requested. Will retry call after looking up parameter metadata in data dictionary.");
                        success = ora.ExecuteMainProc(gReq.OwaProc, gReq.RequestParameters, true, gReq.ProcName);
                    }
                    else if (!success && ora.GetLastErrorText().IndexOf("ORA-00201:") > -1)
                    {
                        logger.Error("Call failed: " + ora.GetLastErrorText());
                        logger.Debug("Identifier must be declared. Will retry call after looking up parameter metadata in data dictionary.");
                        success = ora.ExecuteMainProc(gReq.OwaProc, gReq.RequestParameters, true, gReq.ProcName);
                    }
                }

                if (success)
                {
                    logger.Info("Gateway procedure executed successfully.");
                    ora.DoCommit();

                    if (gReq.IsWsdlRequest)
                    {
                        logger.Info("Responding with WSDL document");
                        gResp.FetchWsdlResponse(ora);
                    }
                    else if (gReq.IsSoapRequest)
                    {
                        logger.Info("Responding with SOAP response");
                        gResp.FetchSoapResponse(ora);
                    }
                    else if (gReq.IsXdbAlias)
                    {
                        logger.Info("Responding with XDB Resource");
                        gResp.FetchXdbResponse(ora);
                    }
                    else
                    {
                        // fetch the response buffers from OWA
                        logger.Debug("Fetch buffer size = " + gReq.DadConfig.FetchBufferSize);
                        gResp.FetchOwaResponse(ora);
                    }

                    ora.CloseConnection();

                    // process response
                    ProcessResponse(gReq, gResp, ctx, app);
                }
                else
                {
                    logger.Error("Call failed: " + ora.GetLastErrorText());

                    ora.DoRollback();

                    if (gReq.IsSoapRequest)
                    {
                        logger.Debug("SOAP request failed, returning SOAP fault as part of normal response.");
                        gResp.FetchSoapResponse(ora);
                        ProcessResponse(gReq, gResp, ctx, app);
                    }
                    else if (gReq.DadConfig.ErrorStyle == "DebugStyle")
                    {
                        logger.Error("Request failed, showing debug error page");
                        ctx.Response.Write(GatewayError.GetErrorDebugPage(gReq, ora.GetLastErrorText()));
                    }
                    else
                    {
                        logger.Error("Request failed, user gets status code 404");
                        throw new HttpException(404, "Not Found");
                        //ctx.Response.Clear();
                        //ctx.Response.StatusCode = 404;
                    }

                    // TODO: does this get called if HttpException is thrown above... don't think so!
                    ora.CloseConnection();
                }
            }
            else
            {
                logger.Warn("Request (" + requestPath + ") not valid, returning 404...");

                if (gReq.DadConfig.ErrorStyle == "DebugStyle")
                {
                    ctx.Response.Write(GatewayError.GetInvalidRequestDebugPage(requestPath, gReq.DadConfig));
                }
                else
                {
                    throw new HttpException(404, "Not Found");
                    //ctx.Response.Clear();
                    //ctx.Response.StatusCode = 404;
                }
            }

            app.CompleteRequest();
            logger.Info("Request completed.");
        }
        private void ProcessResponse(GatewayRequest req, GatewayResponse resp, HttpContext ctx, HttpApplication app)
        {
            string acceptEncoding = app.Request.Headers["Accept-Encoding"];

            acceptEncoding = (acceptEncoding != null ? acceptEncoding.ToLower() : String.Empty);

            // check if dynamic content should be compressed
            bool doCompress = (DadConfiguration.CompressDynamicContent && !resp.IsDownload && acceptEncoding.Length > 0);

            if (doCompress)
            {
                if (logger.IsDebugEnabled)
                {
                    logger.Debug("Compression of dynamic content is enabled, client accepts " + acceptEncoding);
                }

                if (acceptEncoding.Contains("gzip") || acceptEncoding == "*")
                {
                    ctx.Response.Filter = new GZipStream(ctx.Response.Filter, CompressionMode.Compress);
                    ctx.Response.AppendHeader("Content-encoding", "gzip");
                    ctx.Response.Cache.VaryByHeaders["Accept-encoding"] = true;
                }
                else if (acceptEncoding.Contains("deflate"))
                {
                    ctx.Response.Filter = new DeflateStream(ctx.Response.Filter, CompressionMode.Compress);
                    app.Response.AppendHeader("Content-Encoding", "deflate");
                    ctx.Response.Cache.VaryByHeaders["Accept-encoding"] = true;
                }

                // note: since compression changes the content-length, the content-length header (if set in database) cannot be used
            }


            // see http://stackoverflow.com/questions/472906/net-string-to-byte-array-c
            int responseBodyLength = System.Text.Encoding.UTF8.GetBytes(resp.ResponseBody.ToString()).Length;

            ctx.Response.ContentType = resp.ContentType;

            if ((resp.ContentLength != 0) && (!doCompress)) // don't write content-length if response is compressed
            {
                if ((!resp.IsDownload) && (resp.ContentLength != responseBodyLength))
                {
                    if (logger.IsDebugEnabled)
                    {
                        logger.Warn(string.Format("Actual response content length ({0}) is different from Content-Length header ({1}). Content-Length was set via X-DB-Content-Length header (which means the client/gateway character set is different from the database character set).", responseBodyLength, resp.ContentLength));
                        logger.Debug("To ensure proper page rendering in browser, Content-Length header will be set equal to actual content length");
                    }

                    ctx.Response.AddHeader("Content-Length", responseBodyLength.ToString());
                }
                else
                {
                    ctx.Response.AddHeader("Content-Length", resp.ContentLength.ToString());
                }
            }

            for (int i = 0; i < resp.Cookies.Count; i++)
            {
                HttpCookie c = resp.Cookies.Get(i);
                ctx.Response.Cookies.Add(c);
            }

            if (resp.RedirectLocation.Length > 0)
            {
                string newLocation = resp.RedirectLocation;

                if (!newLocation.StartsWith("/") && !newLocation.StartsWith("http://") && !newLocation.StartsWith("https://"))
                {
                    logger.Debug("Converting relative path (" + resp.RedirectLocation + ") to absolute path");
                    newLocation = "/" + req.ModuleName + "/" + req.DadName + "/" + newLocation;
                }

                logger.Debug("Redirecting to " + newLocation);
                //ctx.Response.StatusCode = 302; // moved temporarily
                ctx.Response.Redirect(newLocation);
                app.CompleteRequest();
                return;
            }

            if (resp.StatusCode != 0)
            {
                logger.Debug("Setting HTTP status code to " + resp.StatusCode.ToString());
                ctx.Response.StatusCode = resp.StatusCode;
                //ctx.Response.StatusDescription = gResp.StatusDescription;

                // see http://www.evanclosson.com/devlog/bettercustomerrorsinaspnet for more about status codes
            }

            foreach (NameValuePair nvp in resp.Headers)
            {
                if (nvp.Name == "Content-length")
                {
                    if ((!resp.IsDownload) && (int.Parse(nvp.Value) != responseBodyLength))
                    {
                        logger.Warn(string.Format("Actual response content length ({0}) is different from Content-Length header ({1}).", responseBodyLength, nvp.Value));
                    }

                    if (!doCompress) // don't write content-length if response is compressed)
                    {
                        ctx.Response.AddHeader(nvp.Name, nvp.Value);
                    }
                }
                else
                {
                    ctx.Response.AddHeader(nvp.Name, nvp.Value);
                }
            }

            if (resp.IsDownload)
            {
                logger.Debug(string.Format("Writing binary response ({0} bytes) to client...", resp.FileData.Length));

                if (resp.FileData.Length > 0)
                {
                    ctx.Response.BinaryWrite(resp.FileData);
                }
                else
                {
                    logger.Warn("Binary response is empty");
                }
            }
            else
            {
                if (logger.IsDebugEnabled)
                {
                    logger.Debug(string.Format("Writing response ({0} bytes) to client...", responseBodyLength));
                    //logger.Debug(string.Format("Last 100 characters of response body: {0}", resp.ResponseBody.ToString().Substring(resp.ResponseBody.Length - 100)));
                }

                ctx.Response.Write(resp.ResponseBody.ToString());

                if (resp.ResponseBody.Length == 0)
                {
                    logger.Info("Response body is empty");
                }
            }
        }