/**
   * Tests update of session expiration in memcached (like {@link
   * #testExpirationOfSessionsInMemcachedIfBackupWasSkippedSimple()}) but for the scenario where
   * many readonly requests occur: in this case, we cannot just use <em>maxInactiveInterval -
   * secondsSinceLastBackup</em> (in {@link MemcachedSessionService#updateExpirationInMemcached}) to
   * determine if an expiration update is required, but we must use the last expiration time sent to
   * memcached.
   *
   * @throws Exception if something goes wrong with the http communication with tomcat
   */
  @Test(enabled = true, dataProviderClass = TestUtils.class, dataProvider = STICKYNESS_PROVIDER)
  public void testExpirationOfSessionsInMemcachedIfBackupWasSkippedManyReadonlyRequests(
      final SessionAffinityMode stickyness) throws Exception {

    final SessionManager manager = _tomcat1.getManager();
    setStickyness(stickyness);

    // set to 1 sec above (in setup), default is 10 seconds
    final int delay = manager.getContainer().getBackgroundProcessorDelay();
    manager.setMaxInactiveInterval(delay * 4);

    final String sessionId1 = makeRequest(_httpClient, _portTomcat1, null);
    assertNotNull(sessionId1, "No session created.");
    assertWaitingWithProxy(Predicates.<MemcachedClientIF>notNull(), 200l, _memcached)
        .get(sessionId1);

    /* after 3 seconds make another request without changing the session, so that
     * it's not sent to memcached
     */
    Thread.sleep(TimeUnit.SECONDS.toMillis(delay * 3));
    assertEquals(
        makeRequest(_httpClient, _portTomcat1, sessionId1),
        sessionId1,
        "SessionId should be the same");
    assertNotNull(_memcached.get(sessionId1), "Session should still exist in memcached.");

    /* after another 3 seconds make another request without changing the session
     */
    Thread.sleep(TimeUnit.SECONDS.toMillis(delay * 3));
    assertEquals(
        makeRequest(_httpClient, _portTomcat1, sessionId1),
        sessionId1,
        "SessionId should be the same");
    assertNotNull(_memcached.get(sessionId1), "Session should still exist in memcached.");

    /* after another nearly 4 seconds (maxInactiveInterval) check that the session is still alive in memcached,
     * this would have been expired without an updated expiration
     */
    Thread.sleep(TimeUnit.SECONDS.toMillis(manager.getMaxInactiveInterval()) - 500);
    assertNotNull(_memcached.get(sessionId1), "Session should still exist in memcached.");

    /* after another second in sticky mode (more than 4 seconds since the last request), or an two times the
     * maxInactiveInterval in non-sticky mode (we must keep sessions in memcached with double expirationtime)
     * the session must be expired in memcached
     */
    Thread.sleep(TimeUnit.SECONDS.toMillis(delay) + 500);
    assertNotSame(
        makeRequest(_httpClient, _portTomcat1, sessionId1),
        sessionId1,
        "The sessionId should have changed due to expired sessin");
  }
  /**
   * Test for issue #60 (Add possibility to disable msm at runtime): start msm disabled and
   * afterwards enable
   */
  @Test(enabled = true)
  public void testStartMsmDisabled() throws Exception {

    // shutdown our server and our client
    _memcached.shutdown();
    _daemon.stop();

    // start a new tomcat with msm initially disabled
    _tomcat1.stop();
    Thread.sleep(500);
    final String memcachedNodes = _memcachedNodeId + ":localhost:" + _memcachedPort;
    _tomcat1 =
        getTestUtils()
            .tomcatBuilder()
            .port(_portTomcat1)
            .memcachedNodes(memcachedNodes)
            .sticky(true)
            .enabled(false)
            .jvmRoute("app1")
            .buildAndStart();

    LOG.info("Waiting, check logs to see if the client causes any 'Connection refused' logging...");
    Thread.sleep(1000);

    // some basic tests for session functionality
    checkSessionFunctionalityWithMsmDisabled();

    // start memcached, client and reenable msm
    _daemon.start();
    _memcached =
        createMemcachedClient(memcachedNodes, new InetSocketAddress("localhost", _memcachedPort));
    _tomcat1.getManager().setEnabled(true);
    // Wait a little bit, so that msm's memcached client can connect and is ready when test starts
    Thread.sleep(100);

    // memcached based stuff should work again
    final String sessionId1 = makeRequest(_httpClient, _portTomcat1, null);
    assertNotNull(sessionId1, "No session created.");
    assertNotNull(
        new SessionIdFormat().extractMemcachedId(sessionId1),
        "memcached node id missing with msm switched to enabled");
    Thread.sleep(50);
    assertNotNull(_memcached.get(sessionId1), "Session not available in memcached.");

    waitForSessionExpiration(true);

    assertNull(_memcached.get(sessionId1), "Expired session still existing in memcached");
  }
  /**
   * Test for issue #49: Sessions not associated with a memcached node don't get associated as soon
   * as a memcached is available
   *
   * @throws InterruptedException
   * @throws IOException
   * @throws TimeoutException
   * @throws ExecutionException
   */
  @Test(enabled = true)
  public void testNotAssociatedSessionGetsAssociatedIssue49()
      throws InterruptedException, IOException, ExecutionException, TimeoutException {
    _daemon.stop();

    final SessionManager manager = _tomcat1.getManager();
    manager.setMaxInactiveInterval(5);
    manager.setSticky(true);
    final SessionIdFormat sessionIdFormat = new SessionIdFormat();

    final Session session = manager.createSession(null);
    assertNull(sessionIdFormat.extractMemcachedId(session.getId()));

    _daemon.start();

    // Wait so that the daemon will be available and the client can reconnect (async get didn't do
    // the trick)
    Thread.sleep(4000);

    final String newSessionId =
        manager.getMemcachedSessionService().changeSessionIdOnMemcachedFailover(session.getId());
    assertNotNull(newSessionId);
    assertEquals(newSessionId, session.getId());
    assertEquals(sessionIdFormat.extractMemcachedId(newSessionId), _memcachedNodeId);
  }
  @Test(enabled = true, dataProviderClass = TestUtils.class, dataProvider = STICKYNESS_PROVIDER)
  public void testSessionAvailableInMemcached(final SessionAffinityMode sessionAffinity)
      throws IOException, InterruptedException, HttpException {

    setStickyness(sessionAffinity);

    final String sessionId1 = makeRequest(_httpClient, _portTomcat1, null);
    assertNotNull(sessionId1, "No session created.");
    Thread.sleep(50);
    assertNotNull(_memcached.get(sessionId1), "Session not available in memcached.");
  }
 private void waitForSessionExpiration(final boolean sticky) throws InterruptedException {
   final SessionManager manager = _tomcat1.getManager();
   assertEquals(manager.getMemcachedSessionService().isSticky(), sticky);
   final Container container = manager.getContainer();
   final long timeout =
       TimeUnit.SECONDS.toMillis(
               sticky
                   ? container.getBackgroundProcessorDelay() + manager.getMaxInactiveInterval()
                   : 2 * manager.getMaxInactiveInterval())
           + 1000;
   Thread.sleep(timeout);
 }
  /**
   * Tests, that for a session that was not sent to memcached (because it's attributes were not
   * modified), the expiration is updated so that they don't expire in memcached before they expire
   * in tomcat.
   *
   * @throws Exception if something goes wrong with the http communication with tomcat
   */
  @Test(enabled = true, dataProviderClass = TestUtils.class, dataProvider = STICKYNESS_PROVIDER)
  public void testExpirationOfSessionsInMemcachedIfBackupWasSkippedSimple(
      final SessionAffinityMode stickyness) throws Exception {

    final SessionManager manager = _tomcat1.getManager();
    setStickyness(stickyness);

    // set to 1 sec above (in setup), default is 10 seconds
    final int delay = manager.getContainer().getBackgroundProcessorDelay();
    manager.setMaxInactiveInterval(delay * 4);

    final String sessionId1 = makeRequest(_httpClient, _portTomcat1, null);
    assertNotNull(sessionId1, "No session created.");
    assertNotNull(_memcached.get(sessionId1), "Session not available in memcached.");

    /* after 2 seconds make another request without changing the session, so that
     * it's not sent to memcached
     */
    Thread.sleep(TimeUnit.SECONDS.toMillis(delay * 2));
    assertEquals(
        makeRequest(_httpClient, _portTomcat1, sessionId1),
        sessionId1,
        "SessionId should be the same");

    /* after another 3 seconds check that the session is still alive in memcached,
     * this would have been expired without an updated expiration
     */
    Thread.sleep(TimeUnit.SECONDS.toMillis(delay * 3));
    assertNotNull(_memcached.get(sessionId1), "Session should still exist in memcached.");

    /* after another >1 second (4 seconds since the last request)
     * the session must be expired in memcached
     */
    Thread.sleep(
        TimeUnit.SECONDS.toMillis(delay) + 500); // +1000 just to be sure that we're >4 secs
    assertNotSame(
        makeRequest(_httpClient, _portTomcat1, sessionId1),
        sessionId1,
        "The sessionId should have changed due to expired sessin");
  }
  private void setStickyness(final SessionAffinityMode sessionAffinity) {
    if (!sessionAffinity.isSticky()) {
      _tomcat1.getEngine().setJvmRoute(null);
    }
    final SessionManager manager = _tomcat1.getManager();
    manager.setSticky(sessionAffinity.isSticky());

    try {
      waitForReconnect(manager.getMemcachedSessionService().getMemcached(), 1, 500);
    } catch (final InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RuntimeException(e);
    }
  }
  @Test(enabled = true, dataProviderClass = TestUtils.class, dataProvider = STICKYNESS_PROVIDER)
  public void testSessionAvailableInMemcachedWithCookiesDisabled(
      final SessionAffinityMode sessionAffinity) throws Exception {
    _tomcat1.stop();
    _tomcat1 =
        tcBuilder()
            .sticky(sessionAffinity.isSticky())
            .cookies(false)
            .jvmRoute("app1")
            .buildAndStart();

    final Response response = get(_httpClient, _portTomcat1, null);
    final String sessionId = response.get(TestServlet.ID);
    assertNotNull(sessionId, "No session created.");
    Thread.sleep(50);
    assertNotNull(_memcached.get(sessionId), "Session not available in memcached.");
  }
  private MemcachedClient createMemcachedClient(
      final String memcachedNodes, final InetSocketAddress address)
      throws IOException, InterruptedException {
    final MemcachedNodesManager nodesManager =
        MemcachedNodesManager.createFor(memcachedNodes, null, _memcachedClientCallback);
    final ConnectionFactory cf =
        nodesManager.isEncodeNodeIdInSessionId()
            ? new SuffixLocatorConnectionFactory(
                nodesManager, nodesManager.getSessionIdFormat(), Statistics.create(), 1000, 1000)
            : new DefaultConnectionFactory();
    final MemcachedClient result = new MemcachedClient(cf, Arrays.asList(address));

    // Wait a little bit, so that the memcached client can connect and is ready when test starts
    Thread.sleep(100);

    return result;
  }
  @Test(enabled = true, dataProviderClass = TestUtils.class, dataProvider = STICKYNESS_PROVIDER)
  public void testInvalidSessionNotFound(@Nonnull final SessionAffinityMode sessionAffinity)
      throws IOException, InterruptedException, HttpException {

    setStickyness(sessionAffinity);

    final String sessionId1 = makeRequest(_httpClient, _portTomcat1, null);
    assertNotNull(sessionId1, "No session created.");

    /*
     * wait some time, as processExpires runs every second and the
     * maxInactiveTime is set to 1 sec...
     */
    Thread.sleep(2100);

    final String sessionId2 = makeRequest(_httpClient, _portTomcat1, sessionId1);
    assertNotSame(sessionId1, sessionId2, "Expired session returned.");
  }