/**
   * 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
 public void flush() throws CacheException {
   EasyMock.expect(client.flush()).andReturn(getFuture(true));
   EasyMock.replay(client);
   clientWrapper.flush();
   EasyMock.verify(client);
 }
 @Test
 public void delete() throws TimeoutException, CacheException {
   EasyMock.expect(client.delete("key1")).andReturn(getFuture(true));
   EasyMock.replay(client);
   assertTrue(clientWrapper.delete("key1"));
   EasyMock.verify(client);
 }
 @AfterMethod
 public void tearDown() throws Exception {
   _memcached.shutdown();
   _tomcat1.stop();
   _httpClient.getConnectionManager().shutdown();
   _daemon.stop();
 }
 @Test
 public void getString() throws TimeoutException, CacheException {
   EasyMock.expect(client.get("key1")).andReturn("test-value");
   EasyMock.replay(client);
   assertEquals("test-value", clientWrapper.get("key1"));
   EasyMock.verify(client);
 }
 @Test
 public void decrStringIntLong() throws TimeoutException, CacheException {
   EasyMock.expect(client.decr("key1", 1, 10L)).andReturn(2L);
   EasyMock.replay(client);
   assertEquals(2L, clientWrapper.decr("key1", 1, 10));
   EasyMock.verify(client);
 }
 @Test
 public void addStringIntObject() throws TimeoutException, CacheException {
   EasyMock.expect(client.add("test", 1000, "value")).andReturn(getFuture(true));
   EasyMock.replay(client);
   assertTrue(clientWrapper.add("test", 1000, "value"));
   EasyMock.verify(client);
 }
 @Test
 public void getTranscoder() {
   EasyMock.expect(client.getTranscoder()).andReturn(null);
   EasyMock.replay(client);
   clientWrapper.getTranscoder();
   EasyMock.verify(client);
 }
 @Test
 public void shutdown() {
   client.shutdown();
   EasyMock.expectLastCall();
   EasyMock.replay(client);
   clientWrapper.shutdown();
   EasyMock.verify(client);
 }
 @Test
 @SuppressWarnings("unchecked")
 public void getAvailableServers() {
   Collection<SocketAddress> servers = EasyMock.createMock(Collection.class);
   EasyMock.expect(client.getAvailableServers()).andReturn(servers);
   EasyMock.replay(client);
   assertEquals(servers, clientWrapper.getAvailableServers());
   EasyMock.verify(client);
 }
 @Test
 @SuppressWarnings("unchecked")
 public void getStringMemcacheTranscoderOfTLong() throws TimeoutException, CacheException {
   CacheTranscoder transcoder = EasyMock.createMock(CacheTranscoder.class);
   EasyMock.expect(client.asyncGet(EasyMock.eq("key1"), EasyMock.anyObject(Transcoder.class)))
       .andReturn(getFuture("test-value"));
   EasyMock.replay(client);
   assertEquals("test-value", clientWrapper.get("key1", transcoder, 100));
   EasyMock.verify(client);
 }
  @Test
  @SuppressWarnings("unchecked")
  public void getBulkCollectionOfString() throws TimeoutException, CacheException {
    Collection<String> keys = EasyMock.createMock(Collection.class);
    Map<String, Object> results = EasyMock.createMock(Map.class);

    EasyMock.expect(client.getBulk(keys)).andReturn(results);
    EasyMock.replay(client);
    assertEquals(results, clientWrapper.getBulk(keys));
    EasyMock.verify(client);
  }
  @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.");
  }
  @Test(enabled = true, dataProviderClass = TestUtils.class, dataProvider = STICKYNESS_PROVIDER)
  public void testInvalidatedSessionRemovedFromMemcached(
      @Nonnull final SessionAffinityMode sessionAffinity)
      throws IOException, InterruptedException, HttpException {

    setStickyness(sessionAffinity);

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

    final Response response = get(_httpClient, _portTomcat1, PATH_INVALIDATE, sessionId1);
    assertNull(response.getResponseSessionId());
    assertEquals(_daemon.getCache().getGetMisses(), 1); // 1 is ok

    assertNull(_memcached.get(sessionId1), "Invalidated session still existing in memcached");
    if (!sessionAffinity.isSticky()) {
      assertNull(
          _memcached.get(createValidityInfoKeyName(sessionId1)),
          "ValidityInfo for invalidated session still exists in memcached.");
    }
  }
  /**
   * 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");
  }
  /** 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();
  }
  @Test(enabled = true, dataProviderClass = TestUtils.class, dataProvider = STICKYNESS_PROVIDER)
  public void testExpiredSessionRemovedFromMemcached(
      @Nonnull final SessionAffinityMode sessionAffinity)
      throws IOException, InterruptedException, HttpException {

    setStickyness(sessionAffinity);

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

    waitForSessionExpiration(sessionAffinity.isSticky());

    assertNull(_memcached.get(sessionId1), "Expired session still existing in memcached");
  }
 @Test
 @SuppressWarnings("unchecked")
 public void addStringIntTMemcacheTranscoderOfT() throws TimeoutException, CacheException {
   CacheTranscoder transcoder = EasyMock.createMock(CacheTranscoder.class);
   EasyMock.expect(
           client.add(
               EasyMock.eq("test"),
               EasyMock.eq(1000),
               EasyMock.eq("value"),
               EasyMock.anyObject(Transcoder.class)))
       .andReturn(getFuture(true));
   EasyMock.replay(client, transcoder);
   assertTrue(clientWrapper.add("test", 1000, "value", transcoder));
   EasyMock.verify(client, transcoder);
 }
  @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 void doUpdate(final JoinPoint jp, final Object retVal) throws Throwable {
    if (isCacheDisabled()) {
      LOG.debug("Caching is disabled.");
      return;
    }

    final MemcachedClientIF cache = getMemcachedClient();
    final Method methodToCache = getMethodToCache(jp);
    List<UpdateSingleCache> lAnnotations;

    if (methodToCache.getAnnotation(UpdateSingleCache.class) != null) {
      lAnnotations = Arrays.asList(methodToCache.getAnnotation(UpdateSingleCache.class));
    } else {
      lAnnotations = Arrays.asList(methodToCache.getAnnotation(UpdateSingleCaches.class).value());
    }

    for (int i = 0; i < lAnnotations.size(); i++) {
      // This is injected caching.  If anything goes wrong in the caching, LOG the crap outta it,
      // but do not let it surface up past the AOP injection itself.
      try {
        final AnnotationInfo info =
            getAnnotationInfo(lAnnotations.get(i), methodToCache.getName(), getJitterDefault());
        final String baseKey =
            CacheBase.getBaseKey(
                info.getAsString(AType.KEY_TEMPLATE),
                info.getAsInteger(AType.KEY_INDEX, null),
                retVal,
                jp.getArgs(),
                methodToCache.toString(),
                factory,
                methodStore);
        final String cacheKey =
            buildCacheKey(
                baseKey, info.getAsString(AType.NAMESPACE), info.getAsString(AType.KEY_PREFIX));
        Object dataObject =
            getIndexObject(
                info.getAsInteger(AType.DATA_INDEX, null),
                retVal,
                jp.getArgs(),
                methodToCache.toString());
        dataObject =
            UpdateSingleCacheAdvice.getMergedData(
                dataObject,
                info.getAsString(AType.DATA_TEMPLATE, null),
                retVal,
                jp.getArgs(),
                factory);
        final Class dataTemplateType =
            (Class) info.getAsType(AType.DATA_TEMPLATE_TYPE, String.class);
        final Object submission =
            (dataObject == null)
                ? new PertinentNegativeNull()
                : applyDataTemplateType(dataObject, dataTemplateType);

        boolean cacheable = true;
        if (submission instanceof CacheConditionally) {
          cacheable = ((CacheConditionally) submission).isCacheable();
        }

        if (cacheable) {
          cache.set(
              cacheKey,
              calculateJitteredExpiration(
                  info.getAsInteger(AType.EXPIRATION), info.getAsInteger(AType.JITTER)),
              submission);
        }

        // Notify the observers that a cache interaction happened.
        final List<UpdateSingleCacheListener> listeners =
            getPertinentListeners(
                UpdateSingleCacheListener.class, info.getAsString(AType.NAMESPACE));
        if (listeners != null && !listeners.isEmpty()) {
          for (final UpdateSingleCacheListener listener : listeners) {
            try {
              listener.triggeredUpdateSingleCache(
                  info.getAsString(AType.NAMESPACE),
                  info.getAsString(AType.KEY_PREFIX, null),
                  baseKey,
                  dataObject,
                  retVal,
                  jp.getArgs());
            } catch (Exception ex) {
              LOG.warn("Problem when triggering a listener.", ex);
            }
          }
        }
      } catch (Exception ex) {
        if (LOG.isDebugEnabled()) {
          LOG.warn("Caching on " + jp.toShortString() + " aborted due to an error.", ex);
        } else {
          LOG.warn(
              "Caching on " + jp.toShortString() + " aborted due to an error: " + ex.getMessage());
        }
      }
    }
  }
 @Override
 public Object get(final String key) {
   return _memcached.get(key);
 }