@Override // !!! note that this gets called for missing pages, but not if exceptions are thrown;
           // exceptions are handled separately
 public void handle(
     String target,
     Request request,
     HttpServletRequest httpServletRequest,
     HttpServletResponse httpServletResponse)
     throws IOException {
   request.setHandled(true);
   httpServletResponse
       .getWriter()
       .println(
           String.format("<h1>Page doesn't exist: %s</h1>", request.getUri().getDecodedPath()));
 }
  /**
   * Normally sets the path and a few attributes that the JSPs are likely to need. Also verifies the
   * login information. If necessary, just redirects to the login page.
   *
   * @param target
   * @param request
   * @param httpServletResponse
   * @param secured
   * @return true if the request is already handled so the .jsp shouldn't get called
   * @throws Exception
   */
  private boolean prepareForJspGet(
      String target, Request request, HttpServletResponse httpServletResponse, boolean secured)
      throws Exception {

    LoginInfo.SessionInfo sessionInfo = UserHelpers.getSessionInfo(request);

    LOG.info(
        String.format(
            "hndl - %s ; %s; %s ; %s",
            target,
            request.getPathInfo(),
            request.getMethod(),
            secured ? "secured" : "not secured"));

    String path = request.getUri().getDecodedPath();

    boolean redirectToLogin = path.equals(PATH_LOGOUT);
    LoginInfo loginInfo = null;
    if (sessionInfo.isNull()) {
      redirectToLogin = true;
      LOG.info("Null session info. Logging in again.");
    } else {
      loginInfo =
          loginInfoDb.get(
              sessionInfo.browserId,
              sessionInfo.sessionId); // ttt2 use a cache, to avoid going to DB
      if (loginInfo == null || loginInfo.expiresOn < System.currentTimeMillis()) {
        LOG.info("Session has expired. Logging in again. Info: " + loginInfo);
        redirectToLogin = true;
      }
    }

    if (!path.equals(PATH_LOGIN) && !path.equals(PATH_SIGNUP) && !path.equals(PATH_ERROR)) {

      if (redirectToLogin) {
        // ttt2 perhaps store URI, to return to it after login
        logOut(sessionInfo.browserId);
        addLoginParams(request, loginInfo);
        httpServletResponse.sendRedirect(PATH_LOGIN);
        return true;
      }

      User user = userDb.get(loginInfo.userId);
      if (user == null) {
        WebUtils.redirectToError("Unknown user", request, httpServletResponse);
        return true;
      }
      if (!user.active) {
        WebUtils.redirectToError("Account is not active", request, httpServletResponse);
        return true;
      }
      request.setAttribute(VAR_FEED_DB, feedDb);
      request.setAttribute(VAR_USER_DB, userDb);
      request.setAttribute(VAR_ARTICLE_DB, articleDb);
      request.setAttribute(VAR_READ_ARTICLES_COLL_DB, readArticlesCollDb);

      request.setAttribute(VAR_USER, user);
      request.setAttribute(VAR_LOGIN_INFO, loginInfo);

      MultiMap<String> params = new MultiMap<>();
      params.put(PARAM_PATH, path);
      request.setParameters(params);
    }

    if (path.equals(PATH_LOGIN)) {
      addLoginParams(request, loginInfo);
    }
    return false;
  }
  @Override
  public void doHandle(
      String target,
      Request request,
      HttpServletRequest httpServletRequest,
      HttpServletResponse httpServletResponse)
      throws IOException, ServletException {

    LOG.info("handling " + target);

    // !!! doHandle() is called twice for a request when using redirectiion, first time with
    // request.getPathInfo()
    // set to the URI and target set to the path, then with request.getPathInfo() set to null and
    // target set to the .jsp
    try {
      // request.setHandled(true);
      boolean secured;
      if (request.getScheme().equals("https")) {
        secured = true;
      } else if (request.getScheme().equals("http")) {
        secured = false;
      } else {
        httpServletResponse
            .getWriter()
            .println(
                String.format(
                    "<h1>Unknown scheme %s at %s</h1>",
                    request.getScheme(), request.getUri().getDecodedPath()));
        return;
      }

      if (request.getMethod().equals("GET")) {
        if (isInJar || target.endsWith(".jsp")) {
          // !!! when not in jar there's no need to do anything about params if it's not a .jsp,
          // as this will get called again for the corresponding .jsp
          if (prepareForJspGet(target, request, httpServletResponse, secured)) {
            return;
          }
        }
        if (target.startsWith(PATH_OPEN_ARTICLE)) {
          handleOpenArticle(request, httpServletResponse, target);
          return;
        }
        super.doHandle(target, request, httpServletRequest, httpServletResponse);
        LOG.info("handling of " + target + " went to super");

        // httpServletResponse.setDateHeader("Date", System.currentTimeMillis());     //ttt2 review
        // these, probably not use
        // httpServletResponse.setDateHeader("Expires", System.currentTimeMillis() + 60000);

        return;
      }

      if (request.getMethod().equals("POST")) {
        if (request.getUri().getDecodedPath().equals(PATH_LOGIN)) {
          handleLoginPost(request, httpServletResponse, secured);
        } else if (request.getUri().getDecodedPath().equals(PATH_SIGNUP)) {
          handleSignupPost(request, httpServletResponse);
        } else if (request.getUri().getDecodedPath().equals(PATH_CHANGE_PASSWORD)) {
          handleChangePasswordPost(request, httpServletResponse);
        } else if (request.getUri().getDecodedPath().equals(PATH_UPDATE_FEED_LIST)) {
          handleUpdateFeedListPost(request, httpServletResponse);
        } else if (request.getUri().getDecodedPath().equals(PATH_ADD_FEED)) {
          handleAddFeedPost(request, httpServletResponse);
        } else if (request.getUri().getDecodedPath().equals(PATH_REMOVE_FEED)) {
          handleRemoveFeedPost(request, httpServletResponse);
        } else if (request.getUri().getDecodedPath().equals(PATH_CHANGE_SETTINGS)) {
          handleChangeSettingsPost(request, httpServletResponse);
        }
      }

      /*{ // for tests only;
          httpServletResponse.getWriter().println(String.format("<h1>Unable to process request %s</h1>",
                  request.getUri().getDecodedPath()));
          request.setHandled(true);
      }*/
    } catch (Exception e) {
      LOG.error("Error processing request", e);
      try {
        // redirectToError(e.toString(), request, httpServletResponse); //!!! redirectToError leads
        // to infinite loop, probably related to
        // the fact that we get 2 calls for a regular request when redirecting
        httpServletResponse
            .getWriter()
            .println(
                String.format(
                    "<h1>Unable to process request %s</h1>", // ttt1 generate some HTML
                    request.getUri().getDecodedPath()));
        request.setHandled(true);
      } catch (Exception e1) {
        LOG.error("Error redirecting", e1);
      }
    }
  }