Clover icon

Magnolia Module EhCache 5.5.9

  1. Project Clover database Mon Nov 25 2019 16:52:57 CET
  2. Package info.magnolia.module.cache.ehcache

File EhCacheFactoryTest.java

 

Code metrics

0
177
23
2
501
307
25
0.14
7.7
11.5
1.09
3.8% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
EhCacheFactoryTest 78 175 3.9% 23 30
0.846938884.7%
EhCacheFactoryTest.SeparateWebappSimulatingEhCacheFactory 489 2 0% 2 0
1.0100%
 

Contributing tests

This file is covered by 13 tests. .

Source view

1    /**
2    * This file Copyright (c) 2010-2018 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
32    *
33    */
34    package info.magnolia.module.cache.ehcache;
35   
36    import static org.hamcrest.Matchers.*;
37    import static org.junit.Assert.*;
38    import static org.mockito.Mockito.*;
39   
40    import info.magnolia.context.MgnlContext;
41    import info.magnolia.init.DefaultMagnoliaInitPaths;
42    import info.magnolia.init.MagnoliaConfigurationProperties;
43    import info.magnolia.init.MagnoliaInitPaths;
44    import info.magnolia.module.ModuleLifecycleContext;
45    import info.magnolia.module.cache.Cache;
46    import info.magnolia.module.cache.CacheFactory;
47    import info.magnolia.module.cache.exception.MgnlLockTimeoutException;
48    import info.magnolia.test.hamcrest.UtilMatchers;
49    import info.magnolia.test.mock.MockWebContext;
50   
51    import java.io.File;
52    import java.util.Collections;
53    import java.util.HashMap;
54    import java.util.Map;
55    import java.util.concurrent.Callable;
56    import java.util.concurrent.Executor;
57    import java.util.concurrent.Executors;
58    import java.util.concurrent.FutureTask;
59    import java.util.concurrent.TimeUnit;
60   
61    import javax.servlet.http.HttpServletRequest;
62   
63    import org.junit.After;
64    import org.junit.Before;
65    import org.junit.Ignore;
66    import org.junit.Test;
67    import org.slf4j.Logger;
68    import org.slf4j.LoggerFactory;
69   
70    import net.sf.ehcache.CacheManager;
71    import net.sf.ehcache.config.Configuration;
72    import net.sf.ehcache.config.PersistenceConfiguration;
73    import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
74   
75    /**
76    * Test to assert whether there are still any issues with cache failing to un-block on access from multiple threads.
77    */
 
78    public class EhCacheFactoryTest extends AbstractEhCacheTest {
79   
80    private static final Logger log = LoggerFactory.getLogger(EhCacheFactoryTest.class);
81   
82    private DefaultMagnoliaInitPaths magnoliaInitPaths = mock(DefaultMagnoliaInitPaths.class);
83    private final MockWebContext ctx = new MockWebContext();
84   
 
85  13 toggle @Before
86    public void setUp() throws Exception {
87  13 super.setUp();
88  13 EhCacheConfiguration config = newCacheConfigForTest();
89  13 factory.getCaches().put(CacheFactory.DEFAULT_CACHE_NAME, config);
90    // Not starting the factory here because some tests need to configure it further
91    // factory.start();
92  13 ctx.setRequest(mock(HttpServletRequest.class));
93    }
94   
 
95  13 toggle @After
96    public void tearDown() throws Exception {
97  13 super.tearDown();
98  13 MgnlContext.setInstance(null);
99    }
100   
 
101  16 toggle @Override
102    protected EhCacheConfiguration newCacheConfigForTest() {
103  16 EhCacheConfiguration config = new EhCacheConfiguration();
104  16 config.setDiskPersistent(false);
105  16 config.setOverflowToDisk(false);
106  16 config.setTimeToIdleSeconds(0);
107  16 config.setTimeToLiveSeconds(0);
108  16 config.setMaxEntriesLocalHeap(1);
109  16 return config;
110    }
111   
 
112  1 toggle @Test
113    public void fallbackToDefaultsIfNoConfigForGivenCacheName() throws Exception {
114  1 final Map<String, EhCacheConfiguration> cfgMap = new HashMap<String, EhCacheConfiguration>();
115  1 final EhCacheConfiguration customCC = newCacheConfigForTest();
116  1 customCC.setName("cache-2");
117  1 customCC.setMemoryStoreEvictionPolicy("FIFO");
118  1 cfgMap.put(customCC.getName(), customCC);
119   
120  1 factory.setCaches(cfgMap);
121  1 factory.start(false);
122   
123  1 assertEquals("LRU is our default", MemoryStoreEvictionPolicy.LRU, getEvictionStrategy(factory, "cache-1"));
124  1 assertEquals("7 was configured explicitly for cache2", MemoryStoreEvictionPolicy.FIFO, getEvictionStrategy(factory, "cache-2"));
125    }
126   
 
127  1 toggle @Test
128    public void canCustomizeDefaultCacheSettingsAndStillUseDifferentCacheConfigPerNamedCache() throws Exception {
129  1 final Map<String, EhCacheConfiguration> cfgMap = new HashMap<String, EhCacheConfiguration>();
130  1 final EhCacheConfiguration customCC = newCacheConfigForTest();
131  1 customCC.setName("cache-X");
132  1 customCC.setMemoryStoreEvictionPolicy("FIFO");
133  1 cfgMap.put("cache-X", customCC);
134   
135    // "explicitly" configured default
136  1 final EhCacheConfiguration def = newCacheConfigForTest();
137  1 def.setName("default"); // The default (template) cache configuration should not have a name, but node2bean sets it (based on nodename), so we simulate this here. We effectively null the name later on with info.magnolia.module.cache.ehcache.EhCacheConfiguration.resetName
138  1 def.setMemoryStoreEvictionPolicy("LFU");
139  1 cfgMap.put("default", def);
140   
141  1 factory.setCaches(cfgMap);
142  1 factory.start(false);
143   
144  1 assertEquals("LFU is the default for caches that are not configured explicitly", MemoryStoreEvictionPolicy.LFU, getEvictionStrategy(factory, "cache-foobar"));
145  1 assertEquals("FIFO was configured explicitly for cache-X", MemoryStoreEvictionPolicy.FIFO, getEvictionStrategy(factory, "cache-X"));
146    }
147   
148    /**
149    * There were some issues when a cache was effectively called "default", ending up with net.sf.ehcache.ObjectExistsException: The Default Cache has already been configured
150    * at net.sf.ehcache.config.Configuration.addCache(Configuration.java:966)
151    *
152    * It's not entirely impossible to get this to work, i *think*, but there are just too many tiny hidden places in EhCache that could break this, that I'd rather
153    * simply fail early when one tries to use a cache called "default".
154    */
 
155  1 toggle @Test
156    public void canNotUseDefaultAsACacheName() throws Exception {
157  1 try {
158  1 factory.getCache("default");
159  0 fail("should have failed");
160    } catch (Throwable t) {
161  1 assertThat(t, UtilMatchers.isExceptionWithMessage(IllegalArgumentException.class, "'default' is not a valid cache name."));
162    }
163    }
164   
 
165  4 toggle private MemoryStoreEvictionPolicy getEvictionStrategy(CacheFactory cacheFactory, String cacheName) {
166  4 return ((EhCacheWrapper) cacheFactory.getCache(cacheName)).getWrappedEhcache().getCacheConfiguration().getMemoryStoreEvictionPolicy();
167    }
168   
169    /**
170    * Simple test for serial access to the cache.
171    */
 
172  1 toggle @Test
173    public void testSerialAccess() throws Exception {
174    // WHEN
175  1 factory.start(false);
176  1 final Cache ehCache = factory.getCache("test1");
177   
178    // THEN
179  1 assertNull(ehCache.get("foo"));
180  1 ehCache.put("foo", "xxx");
181  1 assertEquals("xxx", ehCache.get("foo"));
182    }
183   
184    /**
185    * Simple test of item eviction.
186    */
 
187  1 toggle @Test
188    public void testAddMoreThanMaxSize() throws Exception {
189  1 factory.start(false);
190   
191    // make sure there's only one item allowed
192  1 assertEquals(1, factory.getCaches().get(CacheFactory.DEFAULT_CACHE_NAME).getMaxEntriesLocalHeap());
193   
194  1 final Cache ehCache = factory.getCache("test2");
195  1 assertNull(ehCache.get("foo"));
196    // add first
197  1 ehCache.put("foo", "xxx");
198    // and check first is there
199  1 assertEquals("xxx", ehCache.get("foo"));
200    // add second (and evict first
201  1 ehCache.put("boo", "xxx");
202    // first is gone
203  1 assertEquals(null, ehCache.get("foo"));
204    // second is there
205  1 assertEquals("xxx", ehCache.get("boo"));
206   
207    }
208   
209    /**
210    * MGNLCACHE-67
211    * It would appear that since ehcache 2.6, caches aren't persisted after a restart.
212    * http://ehcache.org/documentation/configuration/fast-restart
213    */
 
214  0 toggle @Test
215    @Ignore("This is indeed not passing anymore. Passes with ehcache-ee.jar in the classpath and changing the persistence strategy to 'localRestartable'")
216    public void cacheIsPersistedAcrossRestarts() throws Exception {
217  0 final String k1 = "TestKey_1";
218  0 final String k2 = "TestKey_2";
219  0 final String v1 = "This is the cached String for TestKey_1";
220  0 final String v2 = "This is the cached String for TestKey_2";
221  0 final String cacheName = "myCache";
222   
223    // Configure the factory locally to this test
224  0 factory = new EhCacheFactory(null, null, null);
225  0 EhCacheConfiguration config = new EhCacheConfiguration();
226  0 config.setName(cacheName);
227    // This is the default, which makes this test fails (explicitly does *not* persist caches on shutdown)
228  0 config.setPersistence(new EhCachePersistenceConfiguration(PersistenceConfiguration.Strategy.LOCALTEMPSWAP, false));
229    // This makes the test pass, but requires ehcache-ee (or "BigMemory Go")
230  0 config.setPersistence(new EhCachePersistenceConfiguration(PersistenceConfiguration.Strategy.LOCALRESTARTABLE, false));
231  0 config.setMaxEntriesLocalHeap(3);
232  0 factory.setCaches(Collections.singletonMap(config.getName(), config));
233   
234    // Given
235  0 factory.start(false);
236    {
237  0 final Cache cache = factory.getCache(cacheName);
238  0 assertThat(cache.get(k1), nullValue());
239  0 assertThat(cache.get(k2), nullValue());
240   
241  0 cache.put(k1, v1);
242  0 cache.put(k2, v2);
243   
244  0 assertThat((String) cache.get(k1), equalTo(v1));
245  0 assertThat((String) cache.get(k2), equalTo(v2));
246    }
247   
248    // When
249  0 moduleLifecycleContext.setPhase(ModuleLifecycleContext.PHASE_MODULE_RESTART);
250  0 factory.stop(false);
251  0 assertThat(CacheManager.ALL_CACHE_MANAGERS, empty());
252   
253    // Then
254    // We'd like the items to still be in the cache after a restart
255  0 factory.start(false);
256    {
257  0 final Cache cache = factory.getCache(cacheName);
258    // Check the entries are still in the cache
259  0 assertThat((String) cache.get(k1), equalTo(v1));
260  0 assertThat((String) cache.get(k2), equalTo(v2));
261    }
262    }
263   
264    /**
265    * Ensure that cache unblocks all threads waiting for item to be cached.
266    */
 
267  1 toggle @Test
268    public void testBlocking() throws Exception {
269  1 factory.start(false);
270   
271    // block forever, making sure taskX.get() will fail our tests if blockage occurs
272  1 factory.setBlockingTimeout(0);
273  1 final Cache ehCache = factory.getCache("test3");
274    // 1st call - place mutex
275  1 Object entry = ehCache.get("blah");
276  1 log.debug("On first get: {}", entry);
277  1 assertNull(entry);
278  1 Executor ex = Executors.newFixedThreadPool(2);
279   
280    // 2nd call - wait until mutex is gone and entry is available
281  1 FutureTask<Object> task2 = new FutureTask<Object>(new Callable<Object>() {
 
282  1 toggle @Override
283    public Object call() throws Exception {
284  1 log.debug("2nd get called");
285  1 Object res = ehCache.get("blah");
286  1 log.debug("2nd unblocked");
287  1 return res;
288    }
289    });
290  1 ex.execute(task2);
291   
292    // 3rd call - wait until mutex is gone and entry is available
293  1 FutureTask<Object> task3 = new FutureTask<Object>(new Callable<Object>() {
 
294  1 toggle @Override
295    public Object call() throws Exception {
296  1 log.debug("3rd get called");
297  1 Object res = ehCache.get("blah");
298  1 log.debug("3rd unblocked");
299  1 return res;
300    }
301    });
302  1 ex.execute(task3);
303   
304  1 log.debug("Put");
305    // add something in the main thread
306  1 ehCache.put("blah", "boo");
307    // try to read what you just put in (if the cache is misconfigured, you get the first block here, and since that would block whole test we skip it and rely on block to be exercised also in the tasks that will time out without blocking whole execution
308    //assertEquals("boo", ehCache.get("blah"));
309   
310  1 log.debug("verify");
311    // thread2
312  1 Object result = task2.get(5, TimeUnit.SECONDS);
313  1 log.debug("2nd get: {]", result);
314   
315    // thread3
316  1 Object result2 = task3.get(5, TimeUnit.SECONDS);
317  1 log.debug("3rd get: {}", result2);
318   
319  1 assertNotNull(result);
320  1 assertNotNull(result2);
321   
322    }
323   
324    /**
325    * Ensure cache unblocks and returns proper item to all the threads waiting for the item even if such is soon after evicted from the cache.
326    */
 
327  1 toggle @Test
328    public void testBlockingAfterAddingMoreThanMaxSize() throws Exception {
329  1 factory.start(false);
330   
331    // make sure there's only one item allowed
332  1 assertEquals(1, factory.getCaches().get(CacheFactory.DEFAULT_CACHE_NAME).getMaxEntriesLocalHeap());
333   
334    // set timeout shorter then timeout on taskX.get()
335  1 factory.setBlockingTimeout(1000);
336  1 final Cache ehCache = factory.getCache("test4");
337    // 1st call - place mutex
338  1 Object entry = ehCache.get("blah");
339  1 log.info("On first get: {}", entry);
340  1 assertNull(entry);
341  1 Executor ex = Executors.newFixedThreadPool(2);
342   
343  1 log.info("Put");
344    // add something in the main thread - release mutex on "blah"
345  1 ehCache.put("blah", "boo");
346    // try to read what you just put in (if the cache is misconfigured, you get the first block here, and since that would block whole test we skip it and rely on block to be exercised also in the tasks that will time out without blocking whole execution
347    // assertEquals("boo", ehCache.get("blah"));
348   
349    // instead make 2nd call on separate thread - cache entry should exist
350  1 FutureTask<Object> task2 = new FutureTask<Object>(new Callable<Object>() {
351   
 
352  1 toggle @Override
353    public Object call() throws Exception {
354  1 log.info("2nd get called");
355  1 Object res = ehCache.get("blah");
356  1 log.info("2nd not blocked");
357  1 return res;
358    }
359    });
360  1 ex.execute(task2);
361  1 Object result = task2.get(5, TimeUnit.SECONDS);
362  1 log.info("2nd get: {}", result);
363  1 assertEquals("boo", result);
364   
365   
366    // put new item to evict old
367  1 ehCache.put("foo", "xxx");
368    // try to read evicted item (place the mutex on "blah" again)
369  1 entry = ehCache.get("blah");
370  1 assertNull(entry);
371   
372    // 3rd call - after evicted and before cached again == > block forever
373  1 FutureTask<Object> task3 = new FutureTask<Object>(new Callable<Object>() {
374   
 
375  1 toggle @Override
376    public Object call() throws Exception {
377  1 MgnlContext.setInstance(ctx);
378  1 log.info("3rd get called");
379  1 Object res = "futureDummyNotModifiedByCacheGetCall";
380  1 try {
381  1 res = ehCache.get("blah");
382  0 fail("should not get here. Cache config is wrong!");
383    } catch (MgnlLockTimeoutException e) {
384    // expected
385    }
386  1 log.info("3rd unblocked");
387  1 return res;
388    }
389    });
390  1 ex.execute(task3);
391    // thread3 - since mutex on "blah" is still in place, the call should end with LTE and return null
392  1 Object result2 = task3.get(5, TimeUnit.SECONDS);
393  1 log.info("3rd get: {}", result2);
394  1 assertEquals("futureDummyNotModifiedByCacheGetCall", result2);
395    }
396   
 
397  1 toggle @Test
398    public void configuration() throws Exception {
399    //WHEN
400  1 factory.start(false);
401  1 Configuration configuration = factory.getWrappedCacheManager().getConfiguration();
402   
403    //THEN
404  1 assertThat(configuration.getDefaultCacheConfiguration().getName(), equalTo(factory.getCaches().get(CacheFactory.DEFAULT_CACHE_NAME).getName()));
405  1 assertThat(new File(configuration.getDiskStoreConfiguration().getPath()).getCanonicalFile(), equalTo(cacheFolder.getAbsoluteFile()));
406    }
407   
 
408  1 toggle @Test
409    public void twoWebappsRunningInTheSameJVM() throws Exception {
410    // GIVEN
411  1 DefaultMagnoliaInitPaths webapp1magnoliaInitPaths = mock(DefaultMagnoliaInitPaths.class);
412  1 when(webapp1magnoliaInitPaths.getRootPath()).thenReturn("/Users/username/bundles/apache-tomcat-7.0.47/webapps/magnoliaPublic");
413  1 EhCacheFactory webapp1Cachefactory = new SeparateWebappSimulatingEhCacheFactory(webapp1magnoliaInitPaths, magnoliaConfigurationProperties);
414   
415  1 DefaultMagnoliaInitPaths webapp2magnoliaInitPaths = mock(DefaultMagnoliaInitPaths.class);
416  1 when(webapp1magnoliaInitPaths.getRootPath()).thenReturn("/Users/username/bundles/apache-tomcat-7.0.47/webapps/magnoliaAuthor");
417  1 EhCacheFactory webapp2Cachefactory = new SeparateWebappSimulatingEhCacheFactory(webapp2magnoliaInitPaths, magnoliaConfigurationProperties);
418   
419    // WHEN
420  1 webapp1Cachefactory.start(false);
421  1 webapp2Cachefactory.start(false);
422   
423    // THEN net.sf.ehcache.CacheException: Another CacheManager with same name already exists in the same VM should not occur
424    }
425   
 
426  1 toggle @Test
427    public void cacheManagerIdSpecifiedViaPropertiesFile() throws Exception {
428    // GIVEN
429  1 MagnoliaConfigurationProperties magnoliaConfigurationProperties1 = mock(MagnoliaConfigurationProperties.class);
430  1 when(magnoliaConfigurationProperties1.hasProperty(EhCacheFactory.MAGNOLIA_PROPERTY_CACHE_MANAGER_ID)).thenReturn(true);
431  1 when(magnoliaConfigurationProperties1.getProperty(EhCacheFactory.MAGNOLIA_PROPERTY_CACHE_MANAGER_ID)).thenReturn("cacheManagerWebappAuthor");
432  1 EhCacheFactory webapp1Cachefactory = new SeparateWebappSimulatingEhCacheFactory(magnoliaInitPaths, magnoliaConfigurationProperties1);
433   
434  1 MagnoliaConfigurationProperties magnoliaConfigurationProperties2 = mock(MagnoliaConfigurationProperties.class);
435  1 when(magnoliaConfigurationProperties2.hasProperty(EhCacheFactory.MAGNOLIA_PROPERTY_CACHE_MANAGER_ID)).thenReturn(true);
436  1 when(magnoliaConfigurationProperties2.getProperty(EhCacheFactory.MAGNOLIA_PROPERTY_CACHE_MANAGER_ID)).thenReturn("cacheManagerWebappPublic");
437  1 EhCacheFactory webapp2Cachefactory = new SeparateWebappSimulatingEhCacheFactory(magnoliaInitPaths, magnoliaConfigurationProperties2);
438   
439    // WHEN
440  1 webapp1Cachefactory.start(false);
441  1 webapp2Cachefactory.start(false);
442   
443    // THEN net.sf.ehcache.CacheException: Another CacheManager with same name already exists in the same VM should not occur
444    }
445   
 
446  1 toggle @Test
447    public void cacheManagerIdentifierCantContainSpecialCharacters() throws Exception {
448    // GIVEN
449  1 when(magnoliaInitPaths.getRootPath()).thenReturn(EhCacheFactory.CACHE_MANAGER_ID_INVALID_CHARS);
450  1 EhCacheFactory cacheFactory = new EhCacheFactory(null, cacheModule, magnoliaInitPaths, magnoliaConfigurationProperties);
451  1 cacheFactory.start(false);
452   
453    // WHEN
454  1 cacheFactory.createCache("cacheName");
455   
456    // THEN should not occur: net.sf.ehcache.CacheException: javax.management.MalformedObjectNameException: Invalid character in value part of property
457    }
458   
 
459  1 toggle @Test
460    public void cacheManagerIsNotLostOnRestart() throws Exception {
461    // GIVEN
462  1 factory = new EhCacheFactory(null, cacheModule, magnoliaInitPaths, magnoliaConfigurationProperties); //configuration change, new instantiation by N2B
463  1 factory.start(false); //start
464  1 factory = new EhCacheFactory(null, cacheModule, magnoliaInitPaths, magnoliaConfigurationProperties); //configuration change, new instantiation by N2B
465   
466    // WHEN
467  1 factory.start(true); //restart
468   
469    // THEN
470  1 assertThat(factory.getWrappedCacheManager(), not(nullValue()));
471    }
472   
 
473    toggle @Test
474    public void getQuiet() throws Exception {
475    // GIVEN
476    factory.start(true);
477    Cache cache = factory.getCache("cacheName");
478    cache.put("key", "value");
479   
480    // WHEN
481    Object existingObject = cache.getQuiet("key");
482    Object nonExistingObject = cache.getQuiet("nonExistingKey");
483   
484    // THEN
485    assertEquals(existingObject, "value");
486    assertNull(nonExistingObject);
487    }
488   
 
489    private class SeparateWebappSimulatingEhCacheFactory extends EhCacheFactory {
490   
 
491  4 toggle public SeparateWebappSimulatingEhCacheFactory(MagnoliaInitPaths magnoliaInitPaths, MagnoliaConfigurationProperties magnoliaConfigurationProperties) {
492  4 super(cacheMonitor, cacheModule, magnoliaInitPaths, magnoliaConfigurationProperties);
493    }
494   
 
495  4 toggle @Override
496    protected boolean shouldRegisterCacheManager() {
497  4 return true; //always simulate module start (not restart as it'd be a separate, not yet started webapp)
498    }
499    }
500   
501    }