/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.cache.test.util; import com.liferay.portal.cache.LowLevelCache; import com.liferay.portal.cache.internal.mvcc.MVCCPortalCache; import com.liferay.portal.kernel.cache.PortalCache; import com.liferay.portal.kernel.cache.PortalCacheHelperUtil; import com.liferay.portal.kernel.model.MVCCModel; import com.liferay.portal.kernel.test.rule.AggregateTestRule; import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor; import com.liferay.portal.kernel.test.rule.NewEnv; import com.liferay.portal.test.rule.AdviseWith; import com.liferay.portal.test.rule.AspectJNewEnvTestRule; import java.io.Serializable; import java.util.List; import java.util.concurrent.Semaphore; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; /** * @author Tina Tian */ public class MVCCPortalCacheTest { @ClassRule @Rule public static final AggregateTestRule aggregateTestRule = new AggregateTestRule( new CodeCoverageAssertor() { @Override public void appendAssertClasses(List<Class<?>> assertClasses) { assertClasses.add(MVCCPortalCache.class); } }, AspectJNewEnvTestRule.INSTANCE); @Before public void setUp() { _portalCache = new TestPortalCache<>(_PORTAL_CACHE_NAME); _mvccPortalCache = new MVCCPortalCache<>( (LowLevelCache<String, MockMVCCModel>)_portalCache); _testPortalCacheListener = new TestPortalCacheListener<>(); _portalCache.registerPortalCacheListener(_testPortalCacheListener); _testPortalCacheReplicator = new TestPortalCacheReplicator<>(); _portalCache.registerPortalCacheListener(_testPortalCacheReplicator); } @SuppressWarnings("unchecked") @Test public void testForHiddenBridge() { @SuppressWarnings("rawtypes") MVCCPortalCache mvccPortalCache = new MVCCPortalCache( new TestPortalCache(_PORTAL_CACHE_NAME)); Serializable key = _KEY_1; MockMVCCModel value = new MockMVCCModel(_VERSION_1); mvccPortalCache.put(key, value); mvccPortalCache.put(key, value, 10); } @AdviseWith(adviceClasses = {TestPortalCacheAdvice.class}) @NewEnv(type = NewEnv.Type.CLASSLOADER) @Test public void testMVCCCacheWithAdvice() throws Exception { Assert.assertNull(_mvccPortalCache.get(_KEY_1)); Assert.assertNull(_mvccPortalCache.get(_KEY_2)); // Concurrent put 1 TestPortalCacheAdvice.block(); Thread thread1 = new Thread() { @Override public void run() { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_1)); } }; thread1.start(); TestPortalCacheAdvice.waitUntilBlock(1); Thread thread2 = new Thread() { @Override public void run() { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_1)); } }; thread2.start(); TestPortalCacheAdvice.waitUntilBlock(2); TestPortalCacheAdvice.unblock(2); thread1.join(); thread2.join(); _assertVersion(_VERSION_1, _mvccPortalCache.get(_KEY_1)); Assert.assertNull(_mvccPortalCache.get(_KEY_2)); _testPortalCacheListener.assertActionsCount(1); _testPortalCacheListener.assertPut( _KEY_1, new MockMVCCModel(_VERSION_1)); _testPortalCacheListener.reset(); _testPortalCacheReplicator.assertActionsCount(1); _testPortalCacheReplicator.assertPut( _KEY_1, new MockMVCCModel(_VERSION_1)); _testPortalCacheReplicator.reset(); // Concurrent put 2 TestPortalCacheAdvice.block(); thread1 = new Thread() { @Override public void run() { PortalCacheHelperUtil.putWithoutReplicator( _mvccPortalCache, _KEY_1, new MockMVCCModel(_VERSION_2)); } }; thread1.start(); TestPortalCacheAdvice.waitUntilBlock(1); thread2 = new Thread() { @Override public void run() { PortalCacheHelperUtil.putWithoutReplicator( _mvccPortalCache, _KEY_1, new MockMVCCModel(_VERSION_2)); } }; thread2.start(); TestPortalCacheAdvice.waitUntilBlock(2); TestPortalCacheAdvice.unblock(2); thread1.join(); thread2.join(); _assertVersion(_VERSION_2, _mvccPortalCache.get(_KEY_1)); Assert.assertNull(_mvccPortalCache.get(_KEY_2)); _testPortalCacheListener.assertActionsCount(1); _testPortalCacheListener.assertUpdated( _KEY_1, new MockMVCCModel(_VERSION_2)); _testPortalCacheListener.reset(); _testPortalCacheReplicator.assertActionsCount(0); } @Test public void testMVCCCacheWithoutTTL() { doTestMVCCCache(false); } @Test public void testMVCCCacheWithTTL() { doTestMVCCCache(true); } @Aspect public static class TestPortalCacheAdvice { public static void block() { _semaphore = new Semaphore(0); } public static void unblock(int permits) { Semaphore semaphore = _semaphore; _semaphore = null; semaphore.release(permits); } public static void waitUntilBlock(int threadCount) { Semaphore semaphore = _semaphore; if (semaphore != null) { while (semaphore.getQueueLength() < threadCount); } } @Around( "execution(protected * com.liferay.portal.cache.test.util." + "TestPortalCache.doPutIfAbsent(..))" ) public Object doPutIfAbsent(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Semaphore semaphore = _semaphore; if (semaphore != null) { semaphore.acquire(); } return proceedingJoinPoint.proceed(); } @Around( "execution(protected * com.liferay.portal.cache.test.util." + "TestPortalCache.doReplace(..))" ) public Object doReplace(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Semaphore semaphore = _semaphore; if (semaphore != null) { semaphore.acquire(); } return proceedingJoinPoint.proceed(); } private static volatile Semaphore _semaphore; } protected void doTestMVCCCache(final boolean timeToLive) { Assert.assertNull(_mvccPortalCache.get(_KEY_1)); Assert.assertNull(_mvccPortalCache.get(_KEY_2)); // Put 1 if (timeToLive) { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_1), 10); } else { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_1)); } _assertVersion(_VERSION_1, _mvccPortalCache.get(_KEY_1)); Assert.assertNull(_mvccPortalCache.get(_KEY_2)); _testPortalCacheListener.assertActionsCount(1); if (timeToLive) { _testPortalCacheListener.assertPut( _KEY_1, new MockMVCCModel(_VERSION_1), 10); } else { _testPortalCacheListener.assertPut( _KEY_1, new MockMVCCModel(_VERSION_1)); } _testPortalCacheListener.reset(); _testPortalCacheReplicator.assertActionsCount(1); if (timeToLive) { _testPortalCacheReplicator.assertPut( _KEY_1, new MockMVCCModel(_VERSION_1), 10); } else { _testPortalCacheReplicator.assertPut( _KEY_1, new MockMVCCModel(_VERSION_1)); } _testPortalCacheReplicator.reset(); // Put 2 if (timeToLive) { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_0), 10); } else { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_0)); } _assertVersion(_VERSION_1, _mvccPortalCache.get(_KEY_1)); Assert.assertNull(_mvccPortalCache.get(_KEY_2)); _testPortalCacheListener.assertActionsCount(0); _testPortalCacheReplicator.assertActionsCount(0); // Put 3 if (timeToLive) { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_2), 10); } else { _mvccPortalCache.put(_KEY_1, new MockMVCCModel(_VERSION_2)); } _assertVersion(_VERSION_2, _mvccPortalCache.get(_KEY_1)); Assert.assertNull(_mvccPortalCache.get(_KEY_2)); _testPortalCacheListener.assertActionsCount(1); if (timeToLive) { _testPortalCacheListener.assertUpdated( _KEY_1, new MockMVCCModel(_VERSION_2), 10); } else { _testPortalCacheListener.assertUpdated( _KEY_1, new MockMVCCModel(_VERSION_2)); } _testPortalCacheListener.reset(); _testPortalCacheReplicator.assertActionsCount(1); if (timeToLive) { _testPortalCacheReplicator.assertUpdated( _KEY_1, new MockMVCCModel(_VERSION_2), 10); } else { _testPortalCacheReplicator.assertUpdated( _KEY_1, new MockMVCCModel(_VERSION_2)); } _testPortalCacheReplicator.reset(); } private void _assertVersion(long version, MockMVCCModel mockMVCCModel) { Assert.assertEquals(version, mockMVCCModel.getMvccVersion()); } private static final String _KEY_1 = "KEY_1"; private static final String _KEY_2 = "KEY_2"; private static final String _PORTAL_CACHE_NAME = "PORTAL_CACHE_NAME"; private static final long _VERSION_0 = 0; private static final long _VERSION_1 = 1; private static final long _VERSION_2 = 2; private MVCCPortalCache<String, MockMVCCModel> _mvccPortalCache; private PortalCache<String, MockMVCCModel> _portalCache; private TestPortalCacheListener<String, MockMVCCModel> _testPortalCacheListener; private TestPortalCacheReplicator<String, MockMVCCModel> _testPortalCacheReplicator; private static class MockMVCCModel implements MVCCModel, Serializable { public MockMVCCModel(long version) { _version = version; } @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof MockMVCCModel)) { return false; } MockMVCCModel mockMVCCModel = (MockMVCCModel)object; if (_version == mockMVCCModel._version) { return true; } return false; } @Override public long getMvccVersion() { return _version; } @Override public int hashCode() { return (int)_version; } @Override public void setMvccVersion(long mvccVersion) { _version = mvccVersion; } private long _version; } }