    //@include lib_Class.js

    /**
     * HTTP request utilities
     *
     * @author ELO Digital Office GmbH
     *
     * @eloas
     * @eloix
     * @elojc
     *
     * @requires sol.common.WfUtils
     * @requires sol.common.AsUtils
     */
    sol.define("sol.common.HttpUtils", {
      singleton: true,

      /**
       * Sends a HTTP request.
       *
       * Example:
       *
       *     var responseObj = sol.common.HttpUtils.sendRequest({
       *       url: "{{eloWfBaseUrl}}/apps/rest/cmd/app/deploy",
       *       method: "post",
       *       connectTimeout: 10000,
       *       readTimeout: 60000,
       *       contentType: "application/json;charset=UTF-8",
       *       params: { Key2: "Value2" },
       *       addTicket: true,
       *       cookies: { Key1: "Value1" },
       *       addCookieTicket: true
       *     });
       *
       *    var responseObj = sol.common.HttpUtils.sendRequest({
       *       url: "https://example.com"
       *       trustAllHosts: true,
       *       trustAllCerts: true
       *    });
       *
       * URL placeholders:
       *
       *     {{eloWfBaseUrl}} ELOwf base URL
       *     {{eloAsBaseUrl}} ELOas base URL
       *     {{ticket}} ELO authorization ticket
       *
       * Response object:
       *
       *     {
       *       "responseCode": 200,
       *       "responseOk": true,
       *       "content": "",
       *       "errorMessage": ""
       *     }
       *
       * @param {Object} config HTTP request configuration object
       * @param {String} config.url URL
       * @param {String} [config.method=get] Request method, e.g. `get`, `post` or `put`
       * @param {String} config.connectTimeout Connect timeout
       * @param {String} config.readTimeout Read timeout
       * @param {String} config.contentType Content type
       * @param {String} config.user User name
       * @param {String} config.password Password
       * @param {String} [config.authType=Basic] Authentification type
       * @param {Object} config.params Map of key-value pairs which will be submitted as HTTP parameters
       * @param {String} config.addTicket. If true the ELO authorization ticket will be added as HTTP parameter
       * @param {Object} config.cookies Map of key-value pairs which will be added as cookies
       * @param {Object} config.data Data
       * @param {Boolean} [config.encodeData=true] Encode data
       * @param {Object} config.dataObj Data object
       * @param {String} config.addCookieTicket. If true the ELO authorization ticket will be added as cookie
       * @param {Object} config.requestProperties Request properties
       * @param {Boolean} config.resolve Enrich the URL with specific variables (`{{eloWfBaseUrl}}`, `{{ticket}}`) and given parameters
       * @param {String} config.proxyHost Proxy host
       * @param {String} config.proxyPort Proxy port
       * @return {java.net.HttpURLConnection} httpConn. HTTP connection
       * @return {Object} HTTP response object
       */
      sendRequest: function (config) {
        var me = this,
            result;

        config = config || {};
        config.url += "";
        config.resolve = (typeof config.resolve != "undefined") ? config.resolve : !!config.params || (config.url.indexOf("{{") > -1);
        config.encodeData = (typeof config.encodeData == "undefined") ? true : config.encodeData;
        config.method = (config.method || "").toLowerCase();

        if ((config.method == "post") || (config.method == "put")) {
          if (config.dataObj) {
            config.contentType = "application/json;charset=UTF-8";
            config.data = JSON.stringify(config.dataObj);
          }
          result = me.sendPost(config.url, config.data, config);
        } else {
          result = me.sendGet(config.url, config);
        }
        return result;
      },

      /**
       * Sends a HTTP-GET request
       * @private
       * @param {String} urlString URL
       * @param {Object} config HTTP request configuration object
       * @return {Object} Response object
       */
      sendGet: function (urlString, config) {
        var me = this,
            urlConn, resultObj;

        config = config || {};

        if (config.resolve) {
          urlString = me.resolveUrl(urlString, config);
        }
        resultObj = me.buildResultObj(urlString);

        try {
          urlConn = me.prepareRequest(urlString, config);
          urlConn.requestMethod = "GET";
          urlConn.connect();
          me.readResponse(urlConn, resultObj);
        } catch (ex) {
          resultObj.errorMessage = ex;
          me.logger.warn(["Exception while HttpGet: {0}", ex]);
        } finally {
          if (urlConn) {
            urlConn.disconnect();
          }
        }
        return resultObj;
      },

      /**
       * Sends a HTTP-POST request
       * @private
       * @param {String} urlString URL
       * @param {String} data Data
       * @param {Object} config HTTP request configuration object
       * @return {Object} Response object
       */
      sendPost: function (urlString, data, config) {
        var me = this,
            urlConn, outputStream, resultObj, encodeData;

        config = config || {};

        if (config.resolve) {
          urlString = me.resolveUrl(urlString, config);
        }
        resultObj = me.buildResultObj(urlString);

        try {
          urlConn = me.prepareRequest(urlString, config);
          data = data || "";
          if (config.encodeData) {
            encodeData = java.net.URLEncoder.encode(data, "UTF-8");
          } else {
            encodeData = new java.lang.String(data);
          }

          urlConn.doOutput = true;
          urlConn.requestMethod = config.method.toUpperCase();
          urlConn.connect();
          outputStream = urlConn.outputStream;
          outputStream.write(encodeData.getBytes("UTF-8"));
          outputStream.close();
          me.readResponse(urlConn, resultObj);
        } catch (ex) {
          resultObj.errorMessage = ex;
          me.logger.warn(["Exception while HttpPost: {0}", ex]);
        } finally {
          if (urlConn) {
            urlConn.disconnect();
          }
        }
        return resultObj;
      },

      /**
       * @private
       * Prepares a HTTP request
       * @param {String} urlString URL
       * @param {Object} config HTTP request configuration object
       * @return {java.net.HttpURLConnection}
       */
      prepareRequest: function (urlString, config) {
        var me = this,
            cookiesArr = [],
            url, urlObj, proxy, urlConn, userpass, basicAuth, key, trustManager, sslContext, value,
            connectTimeout, readTimeout;

        url = urlString;
        urlObj = new java.net.URL(url);

        if (config.proxyHost) {
          proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, new java.net.InetSocketAddress(config.proxyHost, config.proxyPort));
          urlConn = urlObj.openConnection(proxy);
        } else {
          urlConn = urlObj.openConnection();
        }

        connectTimeout = java.lang.Integer.valueOf(config.connectTimeout || 10000);
        readTimeout = java.lang.Integer.valueOf(config.readTimeout || 60000);

        urlConn.setConnectTimeout(connectTimeout);
        urlConn.setReadTimeout(readTimeout);

        if (config.contentType) {
          urlConn.setRequestProperty("Content-Type", config.contentType);
        }
        if (config.user) {
          if (config.authType == "NTLM") {
            java.net.Authenticator.default = new JavaAdapter(java.net.Authenticator, {
              getPasswordAuthentication: function () {
                return new java.net.PasswordAuthentication(config.user, java.lang.String(config.password).toCharArray());
              }
            });
          } else {
            userpass = java.lang.String(config.user + ":" + config.password);
            basicAuth = "Basic " + me.encodeBase64(userpass);
            urlConn.setRequestProperty("Authorization", basicAuth);
          }
        }

        if (config.requestProperties) {
          for (key in config.requestProperties) {
            value = config.requestProperties[key];
            urlConn.setRequestProperty(key, value);
          }
        }

        if (config.addCookieTicket) {
          config.cookies = config.cookies || {};
          config.cookies.ticket = ixConnect.loginResult.clientInfo.ticket;
        }

        if (config.cookies) {
          for (key in config.cookies) {
            cookiesArr.push(key + "=" + config.cookies[key]);
          }
          urlConn.setRequestProperty("Cookie", cookiesArr.join("; "));
        }

        // Avoid SSL exception:
        //   javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching <hostname> found
        if (urlConn.defaultHostnameVerifier && config.trustAllHosts) {
          urlConn.hostnameVerifier = new javax.net.ssl.HostnameVerifier({
            verify: function (hostname, session) {
              return true;
            }
          });
        }

        // Avoid SSL exception:
        //   javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed:
        //   sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        if (urlConn.defaultSSLSocketFactory && config.trustAllCerts) {
          trustManager = new javax.net.ssl.X509TrustManager({
            checkClientTrusted: function (chain, authType) {
            },

            checkServerTrusted: function (chain, authType) {
            },

            getAcceptedIssuers: function () {
              return null;
            }
          });

          sslContext = javax.net.ssl.SSLContext.getInstance("SSL");
          sslContext.init(null, [trustManager], new java.security.SecureRandom());
          urlConn.SSLSocketFactory = sslContext.socketFactory;
        }

        me.logRequestProperties(urlConn);

        return urlConn;
      },

      /**
       * @private
       * @param {java.net.HttpURLConnection} urlConn
       */
      logRequestProperties: function (urlConn) {
        var me = this,
            requestProperties, entrySetIterator, entrySet, requestPropertyName, requestPropertyValueList, requestPropertyValues,
            headerValueListIterator, requestPropertyValue;

        if (me.logger.debugEnabled) {
          me.logger.debug("Request properties:");
          requestProperties = urlConn.requestProperties;
          entrySetIterator = requestProperties.entrySet().iterator();
          while (entrySetIterator.hasNext()) {
            entrySet = entrySetIterator.next();
            requestPropertyName = entrySet.key;
            requestPropertyValueList = entrySet.value;
            requestPropertyValues = [];
            headerValueListIterator = requestPropertyValueList.iterator();
            while (headerValueListIterator.hasNext()) {
              requestPropertyValue = headerValueListIterator.next() + "";
              requestPropertyValues.push(requestPropertyValue);
            }
            me.logger.debug(requestPropertyName + ": " + requestPropertyValues.join(";"));
          }
        }
      },

      /**
       * @private
       * @return {javax.mail.PasswordAuthentication}
       */
      getPasswordAuthentication: function () {
        var me = this,
            authenticator;

        authenticator = new javax.mail.PasswordAuthentication(me.user, me.password);
        return authenticator;
      },

      /**
       * Resolves an URL
       *
       * @param {String} urlString URL
       * @param {Object} config HTTP request configuration object
       * @return {String} URL
       */
      resolveUrl: function (urlString, config) {
        var ticket, wfBaseUrl, asBaseUrl, key,
            paramArr = [];

        config = config || {};

        ticket = ixConnect.loginResult.clientInfo.ticket;
        urlString += "";

        if (urlString.indexOf("{{eloWfBaseUrl}}") > -1) {
          wfBaseUrl = sol.common.WfUtils.getWfBaseUrl();
          urlString = urlString.replace("{{eloWfBaseUrl}}", wfBaseUrl);
        }
        if (urlString.indexOf("{{eloAsBaseUrl}}") > -1) {
          asBaseUrl = sol.common.AsUtils.getAsBaseUrl();
          urlString = urlString.replace("{{eloAsBaseUrl}}", asBaseUrl);
        }

        if (config) {
          if (config.params) {
            for (key in config.params) {
              paramArr.push(encodeURI(key) + "=" + encodeURI(config.params[key]));
            }
          }
          if (config.addTicket) {
            paramArr.push("ticket=" + ticket);
          }
        }

        if (paramArr.length > 0) {
          urlString += "?" + paramArr.join("&");
        }
        urlString = urlString.replace("{{ticket}}", ticket);
        return urlString;
      },

      /**
       * @private
       * Builds a response object
       * @param {String} url URL
       * @return {Object} Response object
       */
      buildResultObj: function (url) {
        var resultObj, logUrl;

        url += "";
        logUrl = url.replace(/(ticket=\w{7})\w+/, "$1...");
        resultObj = { url: logUrl, responseOk: false };
        return resultObj;
      },

      /**
       * @private
       * Read HTTP response
       * @param {java.net.HttpURLConnection} urlConn HTTP connection
       * @param {Object} resultObj Result object
       */
      readResponse: function (urlConn, resultObj) {
        var me = this;

        resultObj.responseCode = urlConn.responseCode;
        if ((urlConn.responseCode >= java.net.HttpURLConnection.HTTP_OK) && (urlConn.responseCode <= java.net.HttpURLConnection.HTTP_PARTIAL)) {
          resultObj.responseOk = true;
          resultObj.content = me.inputStreamToString(urlConn.inputStream);
        } else {
          resultObj.responseOk = false;
          resultObj.content = me.inputStreamToString(urlConn.errorStream);
          resultObj.errorMessage = "HTTP status code " + resultObj.responseCode;
        }
      },

      /**
       * @private
       * Converts the content of an input stream into a string
       * @param {java.io.InputStream} inputStream Input Stream
       * @return {String} Content as string
       */
      inputStreamToString: function (inputStream) {
        if (!inputStream) {
          return "";
        }
        var content = String(Packages.org.apache.commons.io.IOUtils.toString(inputStream, "UTF-8"));
        inputStream.close();
        return content;
      },

      /**
       * Returns a Base64 encoded string
       * @param {String} str String
       * @return {String} Base64 encoded string
       */
      encodeBase64: function (str) {
        var me = this,
            srcBytes, base64Bytes, base64String;
        if (!str) {
          throw "String is empty";
        }
        srcBytes = me.convertStringToByteArray(str);
        base64Bytes = Packages.org.apache.commons.codec.binary.Base64.encodeBase64(srcBytes);
        base64String = me.convertByteArrayToString(base64Bytes);
        return base64String;
      },

      /**
       * @private
       * Converts a string to a byte array
       * @param {String} str String
       * @return {Byte[]}
       */
      convertStringToByteArray: function (str) {
        var bytes;
        if (!str) {
          throw "String is empty";
        }
        bytes = java.lang.String(str).getBytes("UTF-8");
        return bytes;
      },

      /**
       * @private
       * Converts a byte array to a string
       * @param {Byte[]} bytes
       * @return {String}
       */
      convertByteArrayToString: function (bytes) {
        var javaString, str;
        if (!bytes) {
          throw "Bytes are empty";
        }
        javaString = new java.lang.String(bytes, "UTF-8");
        str = String(javaString);
        return str;
      }
    });