/**
   * Creates new HTTP requests handler.
   *
   * @param hnd Handler.
   * @param authChecker Authentication checking closure.
   * @param log Logger.
   */
  GridJettyRestHandler(
      GridRestProtocolHandler hnd, GridClosure<String, Boolean> authChecker, GridLogger log) {
    assert hnd != null;
    assert log != null;

    this.hnd = hnd;
    this.log = log;
    this.authChecker = authChecker;

    // Init default page and favicon.
    try {
      initDefaultPage();

      if (log.isDebugEnabled()) log.debug("Initialized default page.");
    } catch (IOException e) {
      U.warn(log, "Failed to initialize default page: " + e.getMessage());
    }

    try {
      initFavicon();

      if (log.isDebugEnabled())
        log.debug(
            favicon != null ? "Initialized favicon, size: " + favicon.length : "Favicon is null.");
    } catch (IOException e) {
      U.warn(log, "Failed to initialize favicon: " + e.getMessage());
    }
  }
  /** @throws IOException If failed. */
  private void initFavicon() throws IOException {
    assert favicon == null;

    InputStream in = getClass().getResourceAsStream("favicon.ico");

    if (in != null) {
      BufferedInputStream bis = new BufferedInputStream(in);

      ByteArrayOutputStream bos = new ByteArrayOutputStream();

      try {
        byte[] buf = new byte[2048];

        while (true) {
          int n = bis.read(buf);

          if (n == -1) break;

          bos.write(buf, 0, n);
        }

        favicon = bos.toByteArray();
      } finally {
        U.closeQuiet(bis);
      }
    }
  }
  /**
   * Parses HTTP parameters in an appropriate format and return back map of values to predefined
   * list of names.
   *
   * @param req Request.
   * @return Map of parsed parameters.
   */
  @SuppressWarnings({"unchecked"})
  private Map<String, Object> parameters(ServletRequest req) {
    Map<String, String[]> params = req.getParameterMap();

    if (F.isEmpty(params)) return Collections.emptyMap();

    Map<String, Object> map = U.newHashMap(params.size());

    for (Map.Entry<String, String[]> entry : params.entrySet())
      map.put(entry.getKey(), parameter(entry.getValue()));

    return map;
  }
  /** @throws IOException If failed. */
  private void initDefaultPage() throws IOException {
    assert dfltPage == null;

    InputStream in = getClass().getResourceAsStream("rest.html");

    if (in != null) {
      LineNumberReader rdr = new LineNumberReader(new InputStreamReader(in));

      try {
        StringBuilder buf = new StringBuilder(2048);

        for (String line = rdr.readLine(); line != null; line = rdr.readLine()) {
          buf.append(line);

          if (!line.endsWith(" ")) buf.append(" ");
        }

        dfltPage = buf.toString();
      } finally {
        U.closeQuiet(rdr);
      }
    }
  }
  /**
   * Creates REST request.
   *
   * @param cmd Command.
   * @param params Parameters.
   * @return REST request.
   * @throws GridException If creation failed.
   */
  @Nullable
  private GridRestRequest createRequest(
      GridRestCommand cmd, Map<String, Object> params, ServletRequest req) throws GridException {
    GridRestRequest restReq;

    switch (cmd) {
      case CACHE_GET:
      case CACHE_GET_ALL:
      case CACHE_PUT:
      case CACHE_PUT_ALL:
      case CACHE_REMOVE:
      case CACHE_REMOVE_ALL:
      case CACHE_ADD:
      case CACHE_CAS:
      case CACHE_METRICS:
      case CACHE_REPLACE:
      case CACHE_DECREMENT:
      case CACHE_INCREMENT:
      case CACHE_APPEND:
      case CACHE_PREPEND:
        {
          GridRestCacheRequest restReq0 = new GridRestCacheRequest();

          restReq0.cacheName((String) params.get("cacheName"));
          restReq0.key(params.get("key"));
          restReq0.value(params.get("val"));
          restReq0.value2(params.get("val2"));

          Object val1 = params.get("val1");

          if (val1 != null) restReq0.value(val1);

          restReq0.cacheFlags(intValue("cacheFlags", params, 0));
          restReq0.ttl(longValue("exp", params, null));
          restReq0.initial(longValue("init", params, null));
          restReq0.delta(longValue("delta", params, null));

          if (cmd == CACHE_GET_ALL || cmd == CACHE_PUT_ALL || cmd == CACHE_REMOVE_ALL) {
            List<Object> keys = values("k", params);
            List<Object> vals = values("v", params);

            if (keys.size() < vals.size())
              throw new GridException(
                  "Number of keys must be greater or equals to number of values.");

            Map<Object, Object> map = U.newHashMap(keys.size());

            Iterator<Object> keyIt = keys.iterator();
            Iterator<Object> valIt = vals.iterator();

            while (keyIt.hasNext()) map.put(keyIt.next(), valIt.hasNext() ? valIt.next() : null);

            restReq0.values(map);
          }

          restReq = restReq0;

          break;
        }

      case TOPOLOGY:
      case NODE:
        {
          GridRestTopologyRequest restReq0 = new GridRestTopologyRequest();

          restReq0.includeMetrics(Boolean.parseBoolean((String) params.get("mtr")));
          restReq0.includeAttributes(Boolean.parseBoolean((String) params.get("attr")));

          restReq0.nodeIp((String) params.get("ip"));

          restReq0.nodeId(uuidValue("id", params));

          restReq = restReq0;

          break;
        }

      case EXE:
      case RESULT:
      case NOOP:
        {
          GridRestTaskRequest restReq0 = new GridRestTaskRequest();

          restReq0.taskId((String) params.get("id"));
          restReq0.taskName((String) params.get("name"));

          restReq0.params(values("p", params));

          restReq0.async(Boolean.parseBoolean((String) params.get("async")));

          restReq0.timeout(longValue("timeout", params, 0L));

          restReq = restReq0;

          break;
        }

      case LOG:
        {
          GridRestLogRequest restReq0 = new GridRestLogRequest();

          restReq0.path((String) params.get("path"));

          restReq0.from(intValue("from", params, -1));
          restReq0.to(intValue("to", params, -1));

          restReq = restReq0;

          break;
        }

      case VERSION:
        {
          restReq = new GridRestRequest();

          break;
        }

      default:
        throw new GridException("Invalid command: " + cmd);
    }

    restReq.address(new InetSocketAddress(req.getRemoteAddr(), req.getRemotePort()));

    restReq.command(cmd);

    if (params.containsKey("gridgain.login") || params.containsKey("gridgain.password")) {
      GridSecurityCredentials cred =
          new GridSecurityCredentials(
              (String) params.get("gridgain.login"), (String) params.get("gridgain.password"));

      restReq.credentials(cred);
    }

    String clientId = (String) params.get("clientId");

    try {
      if (clientId != null) restReq.clientId(UUID.fromString(clientId));
    } catch (Exception ignored) {
      // Ignore invalid client id. Rest handler will process this logic.
    }

    String destId = (String) params.get("destId");

    try {
      if (destId != null) restReq.destinationId(UUID.fromString(destId));
    } catch (IllegalArgumentException ignored) {
      // Don't fail - try to execute locally.
    }

    String sesTokStr = (String) params.get("sessionToken");

    try {
      if (sesTokStr != null) restReq.sessionToken(U.hexString2ByteArray(sesTokStr));
    } catch (IllegalArgumentException ignored) {
      // Ignore invalid session token.
    }

    return restReq;
  }
  /**
   * Process HTTP request.
   *
   * @param act Action.
   * @param req Http request.
   * @param res Http response.
   */
  private void processRequest(String act, HttpServletRequest req, HttpServletResponse res) {
    res.setContentType("application/json");
    res.setCharacterEncoding("UTF-8");

    GridRestCommand cmd = command(req);

    if (cmd == null) {
      res.setStatus(HttpServletResponse.SC_BAD_REQUEST);

      return;
    }

    if (!authChecker.apply(req.getHeader("X-Signature"))) {
      res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

      return;
    }

    GridRestResponse cmdRes;

    Map<String, Object> params = parameters(req);

    try {
      GridRestRequest cmdReq = createRequest(cmd, params, req);

      if (log.isDebugEnabled()) log.debug("Initialized command request: " + cmdReq);

      cmdRes = hnd.handle(cmdReq);

      if (cmdRes == null)
        throw new IllegalStateException("Received null result from handler: " + hnd);

      byte[] sesTok = cmdRes.sessionTokenBytes();

      if (sesTok != null) cmdRes.setSessionToken(U.byteArray2HexString(sesTok));

      res.setStatus(HttpServletResponse.SC_OK);
    } catch (Exception e) {
      res.setStatus(HttpServletResponse.SC_OK);

      U.error(log, "Failed to process HTTP request [action=" + act + ", req=" + req + ']', e);

      cmdRes = new GridRestResponse(STATUS_FAILED, e.getMessage());
    } catch (Throwable e) {
      U.error(log, "Failed to process HTTP request [action=" + act + ", req=" + req + ']', e);

      throw e;
    }

    JsonConfig cfg = new GridJettyJsonConfig();

    // Workaround for not needed transformation of string into JSON object.
    if (cmdRes.getResponse() instanceof String)
      cfg.registerJsonValueProcessor(cmdRes.getClass(), "response", SKIP_STR_VAL_PROC);

    if (cmdRes.getResponse() instanceof GridClientTaskResultBean
        && ((GridClientTaskResultBean) cmdRes.getResponse()).getResult() instanceof String)
      cfg.registerJsonValueProcessor(cmdRes.getResponse().getClass(), "result", SKIP_STR_VAL_PROC);

    JSON json;

    try {
      json = JSONSerializer.toJSON(cmdRes, cfg);
    } catch (JSONException e) {
      U.error(log, "Failed to convert response to JSON: " + cmdRes, e);

      json = JSONSerializer.toJSON(new GridRestResponse(STATUS_FAILED, e.getMessage()), cfg);
    }

    try {
      if (log.isDebugEnabled())
        log.debug("Parsed command response into JSON object: " + json.toString(2));

      res.getWriter().write(json.toString());

      if (log.isDebugEnabled())
        log.debug(
            "Processed HTTP request [action=" + act + ", jsonRes=" + cmdRes + ", req=" + req + ']');
    } catch (IOException e) {
      U.error(log, "Failed to send HTTP response: " + json.toString(2), e);
    }
  }