/* * #%L * Wisdom-Framework * %% * Copyright (C) 2013 - 2014 Wisdom Framework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ package org.wisdom.cache.ehcache; import org.joda.time.Duration; import org.junit.Ignore; import org.junit.Test; import org.wisdom.api.cache.Cache; import org.wisdom.api.cache.Cached; import org.wisdom.api.configuration.ApplicationConfiguration; import org.wisdom.api.http.*; import org.wisdom.api.interception.RequestContext; import java.util.TreeMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; /** * Checks the cached interceptor. */ public class CachedActionInterceptorTest { @Test public void testCaching() throws Exception { CachedActionInterceptor interceptor = new CachedActionInterceptor(); interceptor.cache = mock(Cache.class); Cached cached = mock(Cached.class); when(cached.duration()).thenReturn(10); when(cached.key()).thenReturn("key"); RequestContext context = mock(RequestContext.class); when(context.request()).thenReturn(mock(Request.class)); Context ctx = mock(Context.class); when(context.context()).thenReturn(ctx); when(context.context().header(anyString())).thenReturn(null); final Result r = Results.ok("Result"); when(context.proceed()).thenReturn(r); Result result = interceptor.call(cached, context); assertThat(result.getRenderable().<String>content()).isEqualTo("Result"); assertThat(result).isEqualTo(r); // Check that the result was put in cache. verify(interceptor.cache, times(1)).get("key"); verify(interceptor.cache, times(1)).set("key", r, Duration.standardSeconds(10)); when(interceptor.cache.get("key")).thenReturn(r); result = interceptor.call(cached, context); assertThat(result).isEqualTo(r); verify(interceptor.cache, times(2)).get("key"); } @Test public void testCachingWithoutKey() throws Exception { CachedActionInterceptor interceptor = new CachedActionInterceptor(); interceptor.cache = mock(Cache.class); Cached cached = mock(Cached.class); when(cached.duration()).thenReturn(10); when(cached.key()).thenReturn(""); RequestContext context = mock(RequestContext.class); final Request request = mock(Request.class); when(request.uri()).thenReturn("/my/url?withquery"); when(context.request()).thenReturn(request); Context ctx = mock(Context.class); when(context.context()).thenReturn(ctx); when(context.context().header(anyString())).thenReturn(null); final Result r = Results.ok("Result"); when(context.proceed()).thenReturn(r); Result result = interceptor.call(cached, context); assertThat(result.getRenderable().<String>content()).isEqualTo("Result"); assertThat(result).isEqualTo(r); // Check that the result was put in cache. verify(interceptor.cache, times(1)).get("/my/url?withquery"); verify(interceptor.cache, times(1)).set("/my/url?withquery", r, Duration.standardSeconds(10)); when(interceptor.cache.get("key")).thenReturn(r); result = interceptor.call(cached, context); assertThat(result).isEqualTo(r); verify(interceptor.cache, times(2)).get("/my/url?withquery"); } @Test public void testCachingNoCache() throws Exception { CachedActionInterceptor interceptor = new CachedActionInterceptor(); interceptor.cache = new DummyCache(); Cached cached = mock(Cached.class); when(cached.duration()).thenReturn(10); when(cached.key()).thenReturn("key"); RequestContext context = mock(RequestContext.class); when(context.request()).thenReturn(mock(Request.class)); Context ctx = mock(Context.class); when(context.context()).thenReturn(ctx); when(context.context().header(anyString())).thenReturn(null); final Result r = Results.ok("Result"); when(context.proceed()).thenReturn(r); Result result = interceptor.call(cached, context); assertThat(result.getRenderable().<String>content()).isEqualTo("Result"); assertThat(result).isEqualTo(r); final Result r2 = Results.ok("Result2"); when(context.proceed()).thenReturn(r2); result = interceptor.call(cached, context); // r is cached return r even is r2 is the new result. assertThat(result).isEqualTo(r); // The object is cached, let's use NO CACHE when(context.context().header(HeaderNames.CACHE_CONTROL)).thenReturn(HeaderNames.NOCACHE_VALUE); result = interceptor.call(cached, context); assertThat(result).isNotEqualTo(r).isEqualTo(r2); final Result r3 = Results.ok("Result3"); when(context.proceed()).thenReturn(r3); // Remove the cache-control when(context.context().header(HeaderNames.CACHE_CONTROL)).thenReturn(null); result = interceptor.call(cached, context); assertThat(result).isEqualTo(r2).isNotEqualTo(r3); } @Test @Ignore("Does not reproduce the race condition") public void testPeak() throws InterruptedException { ApplicationConfiguration configuration = mock(ApplicationConfiguration.class); final EhCacheService svc = new EhCacheService(); svc.configuration = configuration; svc.start(); final CachedActionInterceptor interceptor = new CachedActionInterceptor(); interceptor.cache = svc; final Cached cached = mock(Cached.class); when(cached.duration()).thenReturn(10); when(cached.key()).thenReturn("key"); CountDownLatch startSignal = new CountDownLatch(1); final int client = 100; final CountDownLatch doneSignal = new CountDownLatch(client); ExecutorService executor = Executors.newFixedThreadPool(client); final AtomicInteger counter = new AtomicInteger(); for (int i = 1; i < client + 1; ++i) { executor.submit(new Runnable() { @Override public void run() { try { RequestContext context = mock(RequestContext.class); when(context.request()).thenReturn(mock(Request.class)); Context ctx = mock(Context.class); when(context.context()).thenReturn(ctx); when(context.context().header(anyString())).thenReturn(null); final Result r = Results.ok("Result"); when(context.proceed()).thenReturn(r); Result result = interceptor.call(cached, context); if (! result.getRenderable().content().equals("Result")) { counter.getAndIncrement(); } } catch (Exception e) { counter.getAndIncrement(); } doneSignal.countDown(); } }); } startSignal.countDown(); doneSignal.await(60, TimeUnit.SECONDS); assertThat(counter.get()).isEqualTo(0); svc.remove("key"); svc.stop(); } private class DummyCache extends TreeMap<String, Object> implements Cache { @Override public void set(String key, Object value, int expiration) { put(key, value); } @Override public void set(String key, Object value, Duration expiration) { put(key, value); } @Override public Object get(String key) { return super.get(key); } @Override public boolean remove(String key) { return super.remove(key) != null; } } }