/**
   * 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);
  }
 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);
 }
  /** Test for issue #60 (Add possibility to disable msm at runtime): disable msm */
  @Test(enabled = true)
  public void testDisableMsmAtRuntime()
      throws InterruptedException, IOException, ExecutionException, TimeoutException,
          LifecycleException, HttpException {
    final SessionManager manager = _tomcat1.getManager();
    manager.setSticky(true);
    // disable msm, shutdown our server and our client
    manager.setEnabled(false);
    _memcached.shutdown();
    _daemon.stop();

    checkSessionFunctionalityWithMsmDisabled();
  }
  /**
   * 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");
  }
  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);
    }
  }
  /**
   * 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");
  }
  @BeforeMethod
  public void setUp() throws Exception {
    _service = mock(MemcachedSessionService.class);
    _request = mock(Request.class);
    _response = mock(Response.class);

    final Context _contextContainer = mock(Context.class);
    final Host _hostContainer = mock(Host.class);
    final SessionManager _manager = mock(SessionManager.class);

    when(_service.getManager()).thenReturn(_manager);
    when(_manager.getContainer()).thenReturn(_contextContainer);
    when(_contextContainer.getParent()).thenReturn(_hostContainer);
    when(_contextContainer.getPath()).thenReturn("/");

    _sessionTrackerValve = createSessionTrackerValve();
    _nextValve = mock(Valve.class);
    _sessionTrackerValve.setNext(_nextValve);
    _sessionTrackerValve.setContainer(_hostContainer);
    _memcachedNodesManager = mock(MemcachedNodesManager.class);
    _sessionIdFormat = mock(SessionIdFormat.class);

    when(_request.getRequestURI()).thenReturn("/someRequest");
    when(_request.getMethod()).thenReturn("GET");
    when(_request.getQueryString()).thenReturn(null);
    when(_request.getContext()).thenReturn(_contextContainer);

    when(_request.getNote(eq(RequestTrackingHostValve.REQUEST_PROCESSED))).thenReturn(Boolean.TRUE);
    when(_request.getNote(eq(RequestTrackingHostValve.SESSION_ID_CHANGED)))
        .thenReturn(Boolean.FALSE);

    when(_sessionIdFormat.extractMemcachedId(anyString())).thenReturn(PRIMARY_NODE_IDENTIFIER);
    when(_service.getMemcachedNodesManager()).thenReturn(_memcachedNodesManager);
    when(_memcachedNodesManager.isNodeAvailable(PRIMARY_NODE_IDENTIFIER))
        .thenReturn(IS_PRIMARY_MEMCACHED_NODE_OPERATIONAL);
    when(_memcachedNodesManager.getSessionIdFormat()).thenReturn(_sessionIdFormat);
  }
 private static Principal deserializePrincipal(final byte[] data, final SessionManager manager) {
   ByteArrayInputStream bis = null;
   ObjectInputStream ois = null;
   try {
     bis = new ByteArrayInputStream(data);
     ois = new ObjectInputStream(bis);
     return manager.readPrincipal(ois);
   } catch (final IOException e) {
     throw new IllegalArgumentException("Could not deserialize principal", e);
   } catch (final ClassNotFoundException e) {
     throw new IllegalArgumentException("Could not deserialize principal", e);
   } finally {
     closeSilently(bis);
     closeSilently(ois);
   }
 }
  static DeserializationResult deserializeSessionFields(
      final byte[] data, final SessionManager manager) throws InvalidVersionException {
    final MemcachedBackupSession result = manager.newMemcachedBackupSession();

    final short version = (short) decodeNum(data, 0, 2);

    if (version != CURRENT_VERSION) {
      throw new InvalidVersionException(
          "The version " + version + " does not match the current version " + CURRENT_VERSION,
          version);
    }

    final short sessionFieldsDataLength = (short) decodeNum(data, 2, 2);

    result.setCreationTimeInternal(decodeNum(data, 4, 8));
    result.setLastAccessedTimeInternal(decodeNum(data, 12, 8));
    result.setMaxInactiveInterval((int) decodeNum(data, 20, 4));
    result.setIsNewInternal(decodeBoolean(data, 24));
    result.setIsValidInternal(decodeBoolean(data, 25));
    result.setThisAccessedTimeInternal(decodeNum(data, 26, 8));
    result.setLastBackupTime(decodeNum(data, 34, 8));

    final short idLength = (short) decodeNum(data, 42, 2);
    result.setIdInternal(decodeString(data, 44, idLength));

    final short authTypeId = (short) decodeNum(data, 44 + idLength, 2);
    result.setAuthTypeInternal(AuthType.valueOfId(authTypeId).getValue());

    final int currentIdx = 44 + idLength + 2;
    final short principalDataLength = (short) decodeNum(data, currentIdx, 2);
    if (principalDataLength > 0) {
      final byte[] principalData = new byte[principalDataLength];
      System.arraycopy(data, currentIdx + 2, principalData, 0, principalDataLength);
      result.setPrincipalInternal(deserializePrincipal(principalData, manager));
    }

    final byte[] attributesData = new byte[data.length - sessionFieldsDataLength];
    System.arraycopy(
        data, sessionFieldsDataLength, attributesData, 0, data.length - sessionFieldsDataLength);

    return new DeserializationResult(result, attributesData);
  }