public void OnError(object obj, EventArgs args)
        {
            // At this point we have information about the error
            log4net.ILog log = PtaUtil.getLog4netLogger(this.GetType().FullName + ".OnError(): ");

            log.Debug("Entered GlobalErrorHandler.OnError");
            HttpContext  ctx      = HttpContext.Current;
            HttpResponse response = ctx.Response;
            HttpRequest  request  = ctx.Request;

            Exception exception = ctx.Server.GetLastError();
            String    ex_html   = PtaUtil.GetExceptionAndEnvHtml(exception);

            String url = HttpContext.Current.Request.Url.ToString();

            log.Debug("HttpContext.Current.Request.FilePath: " + HttpContext.Current.Request.FilePath);
            log.Debug("HttpContext.Current.Request.ApplicationPath: " + HttpContext.Current.Request.ApplicationPath);
            //!! test when proxy is not root
            if (HttpContext.Current.Request.FilePath.StartsWith(
                    PtaUtil.GetProxyWebFolder() + PtaUtil.PTA_SUBFOLDER))
            {
                log.Info(ex_html);
            }
            else
            {
                response.Write(ex_html);
                ctx.Server.ClearError();
                // --------------------------------------------------
                // To let the page finish running we clear the error
                // --------------------------------------------------
            }
        }
        /// <summary>
        /// Implements redirection logic.
        /// Please see "V. Link redirection processing definition" in pta_proxy\readme.txt for description of processing.
        /// Parameters provided by Blackboard along with redirection link are described at
        /// http://www.edugarage.com/display/BBDN/Generic+Proxy+Tool+Request+Arguments
        /// Link types at
        /// http://www.edugarage.com/display/BBDN/Link+Type
        /// Response status codes and header fields:
        /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6
        /// http://grokable.com/understanding-asp-net-response-redirect/ 301 vs. 302: Response.Redirect sets an HTTP 302 header along with the URL to be redirected to. The 302 status code essentially says, "this item has moved temporarily". Search engines crawling your site are not guaranteed to follow 302 redirects, nor should they. By following a 302 the search engine could incorrectly index the location of content.
        /// Response.Redirect with POST instead of Get?
        /// http://stackoverflow.com/questions/46582/response-redirect-with-post-instead-of-get
        /// </summary>
        protected void Redirect()
        {
            log4net.ILog log              = PtaUtil.getLog4netLogger(this.GetType().FullName + ".Redirect(): ");
            String       bb_code          = PtaUtil.GetDBReaderStringField(bboardDR, "BBoardCode");
            String       proxy_web_folder = PtaUtil.GetProxyWebFolder();
            String       bb_link          = context.Request.Path.Replace(proxy_web_folder + PtaUtil.PTA_SUBFOLDER, "");

            log.Debug("bb_code: " + bb_code + "; proxy_web_folder: " + proxy_web_folder + "; bb_link: " + bb_link);

            System.Data.SqlClient.SqlConnection con = PtaUtil.GetSqlConnection();
            String sql = "SELECT BBLinkType FROM BBMenuLink WHERE BBoardCode = @BBoardCode AND BBLinkPath = @BBLinkPath";

            System.Data.SqlClient.SqlParameter[] sel_ml_params = new System.Data.SqlClient.SqlParameter[] {
                new System.Data.SqlClient.SqlParameter("@BBoardCode", bb_code),
                new System.Data.SqlClient.SqlParameter("@BBLinkPath", bb_link)
            };
            log.Info("sql: " + sql);
            drMenuLink = Microsoft.ApplicationBlocks.Data.SqlHelper.ExecuteReader(PtaUtil.GetSqlConnection(),
                                                                                  System.Data.CommandType.Text, sql, sel_ml_params);
            if (!drMenuLink.HasRows)
            {
                throw new PtaException("Unknown proxy link. " + "bb_code: " + bb_code + "; proxy_web_folder: " + proxy_web_folder + "; bb_link: " + bb_link);
            }
            drMenuLink.Read();

            sql  = "SELECT ParamTag, ParamOrderCode, ParamKey, ParamInputType, ParamOutputType, ParamInputTemplate, ";
            sql += "RegExp, RegExpOperation, RegExpReplaceString, RegExpMatchGroup, RegExpMatchCapture, SProcName ";
            sql += "FROM Param WHERE BBoardCode = @BBoardCode AND BBLinkPath = @BBLinkPath ORDER BY ParamOrderCode";
            log.Info("sql: " + sql);
            System.Data.SqlClient.SqlDataReader drParam =
                Microsoft.ApplicationBlocks.Data.SqlHelper.ExecuteReader(PtaUtil.GetSqlConnection(),
                                                                         System.Data.CommandType.Text, sql, sel_ml_params);
            if (!drParam.HasRows)
            {
                throw new PtaException("No defined link parameter transformations. " + "bb_code: " + bb_code + "; proxy_web_folder: " + proxy_web_folder + "; bb_link: " + bb_link);
            }

            System.Collections.Hashtable param_tags_hash = new System.Collections.Hashtable();
            while (drParam.Read())
            {
                String param_tag    = PtaUtil.GetDBReaderStringField(drParam, "ParamTag");
                String param_input  = PtaUtil.GetDBReaderStringField(drParam, "ParamInputType");
                String param_output = PtaUtil.GetDBReaderStringField(drParam, "ParamOutputType");
                String input_templ  = PtaUtil.GetDBReaderStringField(drParam, "ParamInputTemplate");
                String param_key    = PtaUtil.GetDBReaderStringField(drParam, "ParamKey");
                String param_value  = null;
                log.Debug("param_tag: " + param_tag + "; param_input: " + param_input + "; param_output: " + param_output + "; input_templ: " + input_templ + "; param_key: " + param_key);
                switch (param_input)
                {
                case "Param.InputTemplate":
                    MatchCollection mc = Regex.Matches(input_templ, "{[^}]*}");
                    foreach (Match match in mc)
                    {
                        GroupCollection groups = match.Groups;
                        foreach (Group grp in groups)
                        {
                            String tag       = grp.Value;
                            String tag_value = (string)param_tags_hash[tag];
                            input_templ = Regex.Replace(input_templ, tag, tag_value);
                            log.Debug("tag: " + tag + "; tag_value: " + tag_value + "; input_templ: " + input_templ);
                        }
                    }
                    param_value = input_templ;
                    break;

                case "ProxyLinkPath":
                    param_value = bb_link;
                    break;

                case "ProxyLinkType":
                    param_value = PtaUtil.GetDBReaderStringField(drMenuLink, "BBLinkType");
                    break;

                case "ProxyPath":
                    param_value = proxy_web_folder;
                    break;

                case "Request.ApplicationPath":
                    param_value = context.Request.ApplicationPath;
                    if (param_value.Equals("/"))
                    {
                        param_value = "";
                    }
                    break;

                case "Request.FilePath":
                    param_value = context.Request.FilePath;
                    break;

                case "Request.Form":
                    param_value = context.Request.Form[param_key];
                    break;

                case "Request.InputStream":
                    System.IO.StreamReader reader = new System.IO.StreamReader(context.Request.InputStream);
                    param_value = reader.ReadToEnd();
                    break;

                case "Request.QueryString":
                    throw new PtaException("ParamInputType=Request.QueryString is not implemented. Supplied URL query string parameters are ignored and cut off by Blackboard.");

                case "Request.PathInfo":
                    param_value = context.Request.PathInfo;
                    break;

                case "Request.Url.ToString":
                    log.Debug("Request.Url.AbsolutePath(): " + context.Request.Url.AbsolutePath + "; context.Request.Url.ToString(): " + context.Request.Url.ToString());
                    param_value = context.Request.Url.ToString();
                    break;

                case "SProcCall":
                    //defined this way list provides correct conversion psp_params.ToArray() accepted by SqlHelper.ExecuteNonQuery()
                    System.Collections.Generic.List <System.Data.SqlClient.SqlParameter> psp_params =
                        new System.Collections.Generic.List <System.Data.SqlClient.SqlParameter>();

                    MatchCollection mc1 = Regex.Matches(input_templ, "{[^}]*}");
                    foreach (Match match in mc1)
                    {
                        GroupCollection groups = match.Groups;
                        foreach (Group grp in groups)
                        {
                            String tag       = grp.Value;
                            String tag_value = (string)param_tags_hash[tag];
                            tag = tag.Replace("{", "").Replace("}", "");
                            log.Debug("tag: " + tag + "; tag_value: " + tag_value);
                            psp_params.Add(new System.Data.SqlClient.SqlParameter("@" + tag, tag_value));
                        }
                    }
                    String sp_out_param_name = param_key;
                    System.Data.SqlClient.SqlParameter out_param = null;
                    if (sp_out_param_name != null)
                    {
                        out_param = //had problems with getting of output value, required ParameterDirection - this is why called constructor is so complex
                                    //Initializes a new instance of the SqlParameter class that uses the parameter name, the type of the parameter, the size of the parameter, a ParameterDirection, the precision of the parameter, the scale of the parameter, the source column, a DataRowVersion to use, and the value of the parameter.
                                    new System.Data.SqlClient.SqlParameter("@" + sp_out_param_name, SqlDbType.NVarChar,
                                                                           255, ParameterDirection.InputOutput, true, 0, 0, null,
                                                                           DataRowVersion.Current, "");
                        log.Debug("sp_out_param_name: " + sp_out_param_name);
                        psp_params.Add(out_param);
                    }
                    String sproc_name = PtaUtil.GetDBReaderStringField(drParam, "SProcName");
                    log.Debug("sproc_name: " + sproc_name);
                    if (psp_params.Count > 0)
                    {
                        Microsoft.ApplicationBlocks.Data.SqlHelper.ExecuteNonQuery(con, CommandType.StoredProcedure, sproc_name, psp_params.ToArray());
                    }
                    else
                    {
                        Microsoft.ApplicationBlocks.Data.SqlHelper.ExecuteNonQuery(con, CommandType.StoredProcedure, sproc_name);
                    }
                    if (out_param != null)
                    {
                        param_value = (string)out_param.Value;
                    }
                    break;

                case "BBWSCall":
                    throw new PtaException("ParamInputType=BBWSCall is not implemented at this time. It may provide possibility to get additional information from Blackboard via WS calls in future.");

                default:
                    throw new PtaException("ParamInputType=" + param_input + " is not implemented. Unknown, possible inconsistensy of code and ParamInputCheck DB constraint.");
                }
                log.Debug("param_value after input processing: " + param_value);

                String regexp_oper = PtaUtil.GetDBReaderStringField(drParam, "RegExpOperation");
                String regexp      = PtaUtil.GetDBReaderStringField(drParam, "RegExp");
                String regexp_rs   = PtaUtil.GetDBReaderStringField(drParam, "RegExpReplaceString");
                int    regexp_mc   = PtaUtil.GetDBReaderInt16Field(drParam, "RegExpMatchCapture");
                int    regexp_mg   = PtaUtil.GetDBReaderInt16Field(drParam, "RegExpMatchGroup");
                log.Debug("regexp_oper: " + regexp_oper + "; regexp: " + regexp + "; regexp_rs: " + regexp_rs + "; regexp_mc: " + regexp_mc + "; regexp_mg: " + regexp_mg);
                switch (regexp_oper)
                {
                case "Assign":
                    break;

                case "REMatch":
                    param_value = "";
                    MatchCollection mc3 = Regex.Matches(param_value, regexp);
                    log.Debug("mc3.Count: " + mc3.Count);
                    if (mc3.Count < regexp_mc)
                    {
                        Match m = mc3[regexp_mc];
                        log.Debug("m.Groups.Count: " + m.Groups.Count);
                        if (m.Groups.Count < regexp_mg)
                        {
                            param_value = m.Groups[regexp_mg].Value;
                        }
                    }
                    break;

                case "REReplace":
                    param_value = Regex.Replace(param_value, regexp, regexp_rs);
                    break;

                default:
                    throw new PtaException("RegExpOperation=" + regexp_oper + " is not implemented. Unknown, possible inconsistensy of code and RegExpOperationCheck DB constraint.");
                }
                log.Info("final param_tag: " + param_tag + "; param_value: " + param_value);
                param_tags_hash.Add(param_tag, param_value);

                switch (param_output)
                {
                case "None":
                    break;

                case "FormsAuthentication.SetAuthCookie":
                    System.Web.Security.FormsAuthentication.SetAuthCookie(param_value, false);
                    break;

                case "Context.Items":
                    context.Items[param_key] = param_value;
                    break;

                case "Context.Session":
                    context.Session.Add(param_key, param_value);
                    break;

                case "Response.AppendHeader":
                    context.Response.AppendHeader(param_key, param_value);
                    break;

                case "Response.Output":
                    context.Response.Output.Write(param_value);
                    break;

                case "Response.Status":
                    context.Response.Status = param_value;
                    break;

                case "Server.Transfer":
                    context.Server.Transfer(param_value, true);
                    break;

                default:
                    throw new PtaException("ParamOutputType=" + param_output + " is not implemented. Unknown, possible inconsistensy of code and ParamOutputCheck DB constraint.");
                }
            } //while (drParam.Read()) {
              //http://weblogs.asp.net/bleroy/archive/2004/08/03/Don_2700_t-redirect-after-setting-a-Session-variable-_2800_or-do-it-right_2900_.aspx
              //recommends use of Response.Redirect("~/default.aspx", false);
              //In comments:
              //I was lossing session data after clicking on a new page on a web site I was building only one of the pages out of about 25 was having this issue and it was always the same page.  As soon as I copied the page on to the IIS server on my computer the problem went away.  I think that something was going wrong on the server that visual web developer 2005 was running.

            //context.Response.End(); -- does not preserve session variables
            //finally ended with no specific processing here - everything seems fine this way.
            //probably enabling of Flush() should be tried first if something will stop working as required
            //context.Response.Flush();
        }
        /// <summary>
        /// Made as static for simplicity of registration XML preview (pta_proxy\Admin\Register.aspx).
        /// </summary>
        static public String GetRegistrationXML(String bbCode)
        {
            log4net.ILog log = PtaUtil.getLog4netLogger(typeof(RegisterToolWSCall).FullName + ".getRegistrationXML: ");
            String       desc;
            //method is static because can be called for preview, i.e. WSCall.wsWrapper.GetBBoardDR() is unavailvable
            SqlDataReader bboardDR = PtaUtil.GetBBoardDataReaderByCode(bbCode);

            try {
                if ("Y".Equals(PtaUtil.GetDBReaderStringField(bboardDR, "IsRegistered")))
                {
                    //??probably some logic on preliminary handling of this can be placed here,
                    //but capability to reregister even with this flag set is necessary
                    //because it is possible proxy to be unregistered at Blackboard but not updated at proxy
                    //(if proxy is not accessibel at the moment of unregistration),
                    //i.e. IsRegistered (as well as ProxyState) can be out of sync with Blackboard
                }
                String proxy_http_url  = WebConfigurationManager.AppSettings["PtaProxyHttpUrl"];
                String proxy_https_url = WebConfigurationManager.AppSettings["PtaProxyHttpsURL"];
                String proxy_s2s_url   = WebConfigurationManager.AppSettings["PtaProxyServerToServerURL"];
                String base_urls       = "";
                if ("Y".Equals(PtaUtil.GetDBReaderStringField(bboardDR, "RegisterHttpsURL")))
                {
                    base_urls += "<base-url type=\"https\">" + proxy_https_url + "</base-url>\n";
                }
                if ("Y".Equals(PtaUtil.GetDBReaderStringField(bboardDR, "RegisterHttpURL")))
                {
                    base_urls += "<base-url type=\"http\">" + proxy_http_url + "</base-url>\n";
                }
                if ("Y".Equals(PtaUtil.GetDBReaderStringField(bboardDR, "RegisterS2SURL")))
                {
                    base_urls += "<base-url type=\"server-to-server\">" + proxy_s2s_url + "</base-url>\n";
                }
                desc = Regex.Replace(REG_XML, "{BASE_URLS}", base_urls);
                if ("Y".Equals(PtaUtil.GetDBReaderStringField(bboardDR, "RegisterActions")))
                {
                    desc = Regex.Replace(desc, "{HTTP_ACTIONS}", HTTP_ACTIONS_XML);
                }
                String proxy_code = PtaUtil.GetDBReaderStringField(bboardDR, "BBoardProxyCode");
                desc = Regex.Replace(desc, "{PROXY_CODE}", proxy_code);


                //String desc = Regex.Replace(REG_XML, "{BASE_HTTP_URL}", proxy_http_url);
                //desc = Regex.Replace(desc, "{BASE_HTTPS_URL}", proxy_https_url);
                desc = Regex.Replace(desc, "{PROXY_WEB_FOLDER}", PtaUtil.GetProxyWebFolder());


                String sql = "SELECT BBLinkPath, BBLinkType, BBLinkName, BBLinkIconPath FROM BBMenuLink WHERE BBoardCode = @BBoardCode AND Enabled = 'Y'";
                log.Info("sql: " + sql);
                SqlConnection con = PtaUtil.GetSqlConnection();
                System.Data.SqlClient.SqlParameter param
                    = new System.Data.SqlClient.SqlParameter("BBoardCode", PtaUtil.GetDBReaderStringField(bboardDR, "BBoardCode"));
                SqlDataReader drMenuLink =
                    Microsoft.ApplicationBlocks.Data.SqlHelper.ExecuteReader(con, System.Data.CommandType.Text,
                                                                             sql, param);
                String xml_links = "";
                while (drMenuLink.Read())
                {
                    String xml_part  = MENU_LINK_XML;
                    String link_path = PtaUtil.GetProxyWebFolder() + PtaUtil.PTA_SUBFOLDER + PtaUtil.GetDBReaderStringField(drMenuLink, "BBLinkPath");
                    String link_type = PtaUtil.GetDBReaderStringField(drMenuLink, "BBLinkType");
                    String link_name = PtaUtil.GetDBReaderStringField(drMenuLink, "BBLinkName");
                    String icon_path = PtaUtil.GetDBReaderStringField(drMenuLink, "BBLinkIconPath");
                    xml_part = Regex.Replace(xml_part, "{LINK_PATH}", link_path);
                    xml_part = Regex.Replace(xml_part, "{MENU_LINK_TYPE}", link_type);
                    xml_part = Regex.Replace(xml_part, "{LINK_NAME}", "<name locale:key=\"" + link_name + "\"></name>");
                    if (icon_path != null)
                    {
                        xml_part = Regex.Replace(xml_part, "{LINK_ICON}", "<icon platform=\"blackboard\" style=\"listitem\" >" + icon_path + "</icon>");
                    }
                    else
                    {
                        xml_part = Regex.Replace(xml_part, "{LINK_ICON}", "");
                    }
                    xml_links = xml_links + xml_part;
                }
                desc = Regex.Replace(desc, "{MENU_LINKS}", xml_links);
            } finally {
                PtaUtil.CloseDataReader(bboardDR);
            }
            return(desc);
        }