package com.celements.store; import static com.celements.common.test.CelementsTestUtils.*; import static org.easymock.EasyMock.*; import static org.junit.Assert.*; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang.exception.ExceptionUtils; import org.easymock.IAnswer; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.Query; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.classic.Session; import org.hibernate.impl.AbstractQueryImpl; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xwiki.cache.CacheFactory; import org.xwiki.context.Execution; import org.xwiki.context.ExecutionContext; import org.xwiki.context.ExecutionContextException; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.WikiReference; import com.celements.common.test.AbstractComponentTest; import com.celements.navigation.INavigationClassConfig; import com.celements.pagetype.IPageTypeClassConfig; import com.celements.store.DocumentCacheStore.InvalidateState; import com.celements.web.service.IWebUtilsService; import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiConfig; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiAttachment; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.BaseProperty; import com.xpn.xwiki.objects.PropertyInterface; import com.xpn.xwiki.objects.classes.BaseClass; import com.xpn.xwiki.store.XWikiCacheStore; import com.xpn.xwiki.store.XWikiCacheStoreInterface; import com.xpn.xwiki.store.XWikiStoreInterface; import com.xpn.xwiki.store.hibernate.HibernateSessionFactory; import com.xpn.xwiki.util.AbstractXWikiRunnable; import com.xpn.xwiki.web.Utils; public class ConcurrentCacheTest extends AbstractComponentTest { private static final Logger LOGGER = LoggerFactory.getLogger(ConcurrentCacheTest.class); private volatile XWikiCacheStore theCacheStore; private volatile DocumentCacheStore theCacheStoreFixed; private volatile ConcurrentMap<DocumentReference, List<BaseObject>> baseObjMap = new ConcurrentHashMap<>(); private volatile DocumentReference testDocRef; private static volatile Collection<Object> defaultMocks; private static volatile XWikiContext defaultContext; private final static AtomicBoolean fastFail = new AtomicBoolean(); private final String wikiName = "testWiki"; private final WikiReference wikiRef = new WikiReference(wikiName); private final String testFullName = "TestSpace.TestDoc"; private XWikiConfig configMock; private SessionFactory sessionFactoryMock; private IPageTypeClassConfig pageTypeClassConfig; private INavigationClassConfig navClassConfig; private IWebUtilsService webUtilsService; /** * CAUTION: the doc load counting with AtomicIntegers leads to better memory visibility * and thus reduces likeliness of a race condition. Hence it may camouflage the race condition! * Nevertheless is it important to check that the cache is working at all. * Hence test it with and without counting. */ private final boolean verifyDocLoads = false; private final AtomicInteger countDocLoads = new AtomicInteger(); private final AtomicInteger expectedCountDocLoads = new AtomicInteger(); private final AtomicInteger expectedCountDocLoadsFixed = new AtomicInteger(); private final AtomicInteger failedToRemoveFromCacheCount = new AtomicInteger(0); private final AtomicInteger invalidatedLoadingCount = new AtomicInteger(0); private final AtomicInteger failedToInvalidatedLoadingCount = new AtomicInteger(0); private final AtomicInteger invalidatedMultipleCount = new AtomicInteger(0); @SuppressWarnings("deprecation") @Before public void setUp_ConcurrentCacheTest() throws Exception { pageTypeClassConfig = Utils.getComponent(IPageTypeClassConfig.class); navClassConfig = Utils.getComponent(INavigationClassConfig.class); webUtilsService = Utils.getComponent(IWebUtilsService.class); getContext().setDatabase(wikiName); sessionFactoryMock = createMockAndAddToDefault(SessionFactory.class); Utils.getComponent(HibernateSessionFactory.class).setSessionFactory(sessionFactoryMock); testDocRef = new DocumentReference(wikiName, "TestSpace", "TestDoc"); configMock = createMockAndAddToDefault(XWikiConfig.class); expect(getWikiMock().getConfig()).andReturn(configMock).anyTimes(); expect(configMock.getProperty(eq("xwiki.store.hibernate.path"), eq( "/WEB-INF/hibernate.cfg.xml"))).andReturn("testhibernate.cfg.xml"); expect(getWikiMock().Param(eq("xwiki.store.cache.capacity"))).andReturn(null).anyTimes(); expect(getWikiMock().Param(eq("xwiki.store.cache.pageexistcapacity"))).andReturn( null).anyTimes(); CacheFactory cacheFactory = Utils.getComponent(CacheFactory.class, "jbosscache"); expect(getWikiMock().getCacheFactory()).andReturn(cacheFactory).anyTimes(); expect(getWikiMock().getPlugin(eq("monitor"), isA(XWikiContext.class))).andReturn( null).anyTimes(); expect(getWikiMock().hasDynamicCustomMappings()).andReturn(false).anyTimes(); expect(getWikiMock().isVirtualMode()).andReturn(false).anyTimes(); expect(getWikiMock().Param(eq("xwiki.store.hibernate.useclasstables.read"), eq("1"))).andReturn( "0").anyTimes(); expect(getWikiMock().getXClass(isA(DocumentReference.class), isA( XWikiContext.class))).andStubDelegateTo(new TestXWiki()); createBaseObjects(); } @Test public void test_singleThreaded_sync_fixed() throws Exception { setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); preloadCache(theCacheStoreFixed); if (verifyDocLoads) { assertEquals(expectedCountDocLoadsFixed.get(), countDocLoads.get()); } assertNotNull("Expecting document in cache.", theCacheStoreFixed.getDocFromCache( theCacheStoreFixed.getKeyWithLang(testDocRef, ""))); verifyDefault(); } @Test public void test_singleThreaded_sync() throws Exception { setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); preloadCache(theCacheStore); if (verifyDocLoads) { assertEquals(expectedCountDocLoads.get(), countDocLoads.get()); } assertNotNull(theCacheStore.getCache().get(theCacheStore.getKey(wikiName, testFullName, ""))); verifyDefault(); } @Test public void test_singleThreaded_async_fixed() throws Exception { setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); preloadCache(theCacheStoreFixed); ScheduledExecutorService theExecutor = Executors.newScheduledThreadPool(1); Future<LoadDocCheckResult> testFuture = theExecutor.submit( (Callable<LoadDocCheckResult>) new LoadXWikiDocCommand(theCacheStoreFixed)); theExecutor.shutdown(); while (!theExecutor.isTerminated()) { theExecutor.awaitTermination(1, TimeUnit.SECONDS); } LoadDocCheckResult result = testFuture.get(); assertTrue(Arrays.deepToString(result.getMessages().toArray()), result.isSuccessfull()); if (verifyDocLoads) { assertEquals(expectedCountDocLoadsFixed.get(), countDocLoads.get()); } verifyDefault(); } @Test public void test_singleThreaded_async() throws Exception { setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); preloadCache(theCacheStore); ScheduledExecutorService theExecutor = Executors.newScheduledThreadPool(1); Future<LoadDocCheckResult> testFuture = theExecutor.submit( (Callable<LoadDocCheckResult>) new LoadXWikiDocCommand(theCacheStore)); theExecutor.shutdown(); while (!theExecutor.isTerminated()) { theExecutor.awaitTermination(1, TimeUnit.SECONDS); } LoadDocCheckResult result = testFuture.get(); assertTrue(Arrays.deepToString(result.getMessages().toArray()), result.isSuccessfull()); if (verifyDocLoads) { assertEquals(expectedCountDocLoads.get(), countDocLoads.get()); } verifyDefault(); } @Test public void test_multiRuns_singleThreaded_scenario1_fixed() throws Exception { int cores = 1; int executeRuns = 5000; setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); assertSuccessFullRuns(testScenario1(theCacheStoreFixed, cores, executeRuns)); if (verifyDocLoads) { assertEquals(expectedCountDocLoadsFixed.get(), countDocLoads.get()); } verifyDefault(); } @Test public void test_multiRuns_singleThreaded_scenario1() throws Exception { int cores = 1; int executeRuns = 5000; setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); assertSuccessFullRuns(testScenario1(theCacheStore, cores, executeRuns)); if (verifyDocLoads) { assertEquals(expectedCountDocLoads.get(), countDocLoads.get()); } verifyDefault(); } @Test public void test_multiThreaded_scenario1_fixed_DocumentCacheStore() throws Exception { int cores = Runtime.getRuntime().availableProcessors(); assertTrue("This tests needs real multi core processors, but found " + cores, cores > 1); // tested on an intel quadcore 4770 // without triggering any race condition! Tested with up to 10'000'000 executeRuns! // int executeRuns = 10000000; int executeRuns = 30000; setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); assertSuccessFullRuns(testScenario1(theCacheStoreFixed, cores, executeRuns)); if (verifyDocLoads) { int countLoads = countDocLoads.get(); int expectedLoads = expectedCountDocLoadsFixed.get(); final String failingDetails = " expected loads '" + expectedLoads + "' must be lower equal to count loads '" + countLoads + "'\n diff '" + (countLoads - expectedLoads) + "'\n invalidatedLoadingCount '" + invalidatedLoadingCount.get() + "'\n invalidatedMultipleCount '" + invalidatedMultipleCount.get() + "'\n failedToInvalidatedLoadingCount '" + failedToInvalidatedLoadingCount.get() + "'\n failedToRemoveFromCacheCount '" + failedToRemoveFromCacheCount.get() + "'"; assertTrue("invalidating during load leads to multiple loads for one invalidation, thus\n" + failingDetails, expectedLoads <= countLoads); } verifyDefault(); } /** * IMPORTANT: This test shows, that XWikiCacheStore is broken!!! */ @Test public void test_failing_multiThreaded_scenario1_XWikiCacheStore() throws Exception { int cores = Runtime.getRuntime().availableProcessors(); assertTrue("This tests needs real multi core processors, but found " + cores, cores > 1); // on an intel quadcore 4770 first race condition generally happens already after a few thousand int executeRuns = 30000; setupTestMocks(); replayDefault(); initStorePrepareMultiThreadMocks(); List<Future<LoadDocCheckResult>> testResults = testScenario1(theCacheStore, cores, executeRuns); try { assertSuccessFullRuns(testResults); fail(); } catch (AssertionError exp) { // expected } if (verifyDocLoads) { assertTrue("no synchronisation on loading must lead to concurrent loads." + " Thus more than the expected loads must have happend.", expectedCountDocLoads.get() <= countDocLoads.get()); } verifyDefault(); } private void preloadCache(XWikiCacheStoreInterface store) throws Exception { if (verifyDocLoads) { countDocLoads.set(0); expectedCountDocLoads.set(1); expectedCountDocLoadsFixed.set(1); } LoadXWikiDocCommand testLoadCommand = new LoadXWikiDocCommand(store); LoadDocCheckResult result = testLoadCommand.call(); assertTrue(Arrays.deepToString(result.getMessages().toArray()), result.isSuccessfull()); } private void setupTestMocks() { Session sessionMock = createMockAndAddToDefault(Session.class); expect(sessionFactoryMock.openSession()).andReturn(sessionMock).anyTimes(); sessionMock.setFlushMode(eq(FlushMode.COMMIT)); expectLastCall().atLeastOnce(); sessionMock.setFlushMode(eq(FlushMode.MANUAL)); expectLastCall().atLeastOnce(); Transaction transactionMock = createMockAndAddToDefault(Transaction.class); expect(sessionMock.beginTransaction()).andReturn(transactionMock).anyTimes(); transactionMock.rollback(); expectLastCall().anyTimes(); expect(sessionMock.close()).andReturn(null).anyTimes(); XWikiDocument myDoc = new XWikiDocument(testDocRef); expectXWikiDocLoad(sessionMock, myDoc); expectLoadEmptyAttachmentList(sessionMock); expectBaseObjectLoad(sessionMock); } /** * Scenario 1 * prepare executeRuns as follows * 1.1 first and every 3*cores run add a reset cache entry task * 1.2 load document 3*cores in parallels for core threads * 2. invoke all tasks once to the executor * !!CAUTION!!! * be careful NOT to add accidentally any memory visibility synchronization * e.g. by using CountDownLatch or similar * for more details see: * http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html# * MemoryVisibility */ private List<Future<LoadDocCheckResult>> testScenario1(XWikiCacheStoreInterface store, int cores, int maxLoadTasks) throws Exception { fastFail.set(false); preloadCache(store); final int numTimesFromCache = cores * 3; final int oneRunRepeats = 200; int count = (maxLoadTasks / (oneRunRepeats * numTimesFromCache)) + 1; ScheduledExecutorService theExecutor = Executors.newScheduledThreadPool(cores); List<Future<LoadDocCheckResult>> futureList = new ArrayList<>(count * (numTimesFromCache + 1) * oneRunRepeats); try { do { count--; List<Callable<LoadDocCheckResult>> loadTasks = new ArrayList<>(oneRunRepeats * numTimesFromCache); for (int i = 0; i < oneRunRepeats; i++) { if (i > 0) { loadTasks.add(new RefreshCacheEntryCommand(store)); loadTasks.add(new LoadXWikiDocCommand(store)); } for (int j = 1; j <= numTimesFromCache; j++) { loadTasks.add(new LoadXWikiDocCommand(store)); } } futureList.addAll(theExecutor.invokeAll(loadTasks)); final Future<LoadDocCheckResult> lastFuture = futureList.get(futureList.size() - oneRunRepeats); while (!lastFuture.isDone() && !fastFail.get()) { Thread.sleep(50); } } while (!fastFail.get() && (count > 0)); } finally { theExecutor.shutdown(); while (!theExecutor.isTerminated()) { if (fastFail.get()) { theExecutor.shutdownNow(); } theExecutor.awaitTermination(500, TimeUnit.MILLISECONDS); } } return futureList; } private void assertSuccessFullRuns(List<Future<LoadDocCheckResult>> futureList) throws InterruptedException, ExecutionException { int successfulRuns = 0; int failedRuns = 0; List<String> failMessgs = new ArrayList<>(); for (Future<LoadDocCheckResult> testFuture : futureList) { LoadDocCheckResult result = testFuture.get(); if (result.isSuccessfull()) { successfulRuns += 1; } else { failedRuns += 1; List<String> messages = result.getMessages(); failMessgs.add("Run num: " + (successfulRuns + failedRuns) + "\n"); failMessgs.addAll(messages); } } assertEquals("Found " + failedRuns + " failing runs: " + Arrays.deepToString( failMessgs.toArray()), futureList.size(), successfulRuns); } private void expectBaseObjectLoad(Session sessionMock) { String loadBaseObjectHql = "from BaseObject as bobject where bobject.name = :name order by " + "bobject.number"; Query queryObj = new TestQuery<>(loadBaseObjectHql, new QueryList<BaseObject>() { @Override public List<BaseObject> list(String string, Map<String, Object> params) throws HibernateException { DocumentReference theDocRef = webUtilsService.resolveDocumentReference((String) params.get( "name")); List<BaseObject> attList = new ArrayList<>(); for (BaseObject templBaseObject : baseObjMap.get(theDocRef)) { BaseObject bObj = createBaseObject(templBaseObject.getNumber(), templBaseObject.getXClassReference()); bObj.setDocumentReference(theDocRef); attList.add(bObj); } return attList; } }); expect(sessionMock.createQuery(eq(loadBaseObjectHql))).andReturn(queryObj).anyTimes(); expectPropertiesLoad(sessionMock); } private void expectPropertiesLoad(Session sessionMock) { String loadPropHql = "select prop.name, prop.classType from BaseProperty as prop where " + "prop.id.id = :id"; Query queryProp = new TestQuery<>(loadPropHql, new QueryList<String[]>() { @Override public List<String[]> list(String string, Map<String, Object> params) throws HibernateException { Integer objId = (Integer) params.get("id"); List<String[]> propList = new ArrayList<>(); for (BaseObject templBaseObject : baseObjMap.get(testDocRef)) { if (objId.equals(templBaseObject.getId())) { for (Object theObj : templBaseObject.getFieldList()) { PropertyInterface theField = (PropertyInterface) theObj; String[] row = new String[2]; row[0] = theField.getName(); row[1] = theField.getClass().getCanonicalName(); propList.add(row); } } } return propList; } }); expect(sessionMock.createQuery(eq(loadPropHql))).andReturn(queryProp).atLeastOnce(); sessionMock.load(isA(PropertyInterface.class), isA(Serializable.class)); expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { BaseProperty property = (BaseProperty) getCurrentArguments()[0]; Integer objId = property.getObject().getId(); for (BaseObject templBaseObject : baseObjMap.get(testDocRef)) { if (objId.equals(templBaseObject.getId())) { for (Object theObj : templBaseObject.getFieldList()) { BaseProperty theField = (BaseProperty) theObj; if (theField.getName().equals(property.getName()) && theField.getClass().equals( property.getClass())) { property.setValue(theField.getValue()); } } } } return this; } }).atLeastOnce(); } private void expectLoadEmptyAttachmentList(Session sessionMock) { String loadAttachmentHql = "from XWikiAttachment as attach where attach.docId=:docid"; Query query = new TestQuery<>(loadAttachmentHql, new QueryList<XWikiAttachment>() { @Override public List<XWikiAttachment> list(String string, Map<String, Object> params) throws HibernateException { List<XWikiAttachment> attList = new ArrayList<>(); return attList; } }); expect(sessionMock.createQuery(eq(loadAttachmentHql))).andReturn(query).anyTimes(); } private void expectXWikiDocLoad(Session sessionMock, XWikiDocument myDoc) { sessionMock.load(isA(XWikiDocument.class), eq(new Long(myDoc.getId()))); expectLastCall().andAnswer(new IAnswer<Object>() { @Override public Object answer() throws Throwable { XWikiDocument theDoc = (XWikiDocument) getCurrentArguments()[0]; if (testDocRef.equals(theDoc.getDocumentReference())) { if (verifyDocLoads) { countDocLoads.incrementAndGet(); } theDoc.setContent("test Content"); theDoc.setTitle("the test Title"); theDoc.setAuthor("XWiki.testAuthor"); theDoc.setCreationDate(new java.sql.Date(new Date().getTime() - 5000L)); theDoc.setContentUpdateDate(new java.sql.Date(new Date().getTime() - 2000L)); theDoc.setLanguage(""); theDoc.setDefaultLanguage("en"); } return this; } }).anyTimes(); } private void createBaseObjects() { DocumentReference testDocRefClone = new DocumentReference(testDocRef.clone()); BaseObject bObj1 = createBaseObject(0, navClassConfig.getMenuNameClassRef(wikiName)); bObj1.setDocumentReference(testDocRefClone); addStringField(bObj1, INavigationClassConfig.MENU_NAME_LANG_FIELD, "de"); addStringField(bObj1, INavigationClassConfig.MENU_NAME_FIELD, "Hause"); BaseObject bObj2 = createBaseObject(1, navClassConfig.getMenuNameClassRef(wikiName)); bObj2.setDocumentReference(testDocRefClone); addStringField(bObj2, INavigationClassConfig.MENU_NAME_LANG_FIELD, "en"); addStringField(bObj2, INavigationClassConfig.MENU_NAME_FIELD, "Home"); BaseObject bObj3 = createBaseObject(0, navClassConfig.getMenuItemClassRef(wikiRef)); bObj3.setDocumentReference(testDocRefClone); addIntField(bObj3, INavigationClassConfig.MENU_POSITION_FIELD, 1); BaseObject bObj4 = createBaseObject(0, pageTypeClassConfig.getPageTypeClassRef(wikiRef)); bObj4.setDocumentReference(testDocRefClone); addStringField(bObj4, IPageTypeClassConfig.PAGE_TYPE_FIELD, "Performance"); List<BaseObject> attList = new Vector<>(Arrays.asList(bObj1, bObj2, bObj3, bObj4)); baseObjMap.put(testDocRefClone, attList); } private void initStorePrepareMultiThreadMocks() throws XWikiException { defaultContext = (XWikiContext) getContext().clone(); XWikiStoreInterface store = Utils.getComponent(XWikiStoreInterface.class); theCacheStore = new XWikiCacheStore(store, defaultContext); theCacheStoreFixed = (DocumentCacheStore) Utils.getComponent(XWikiStoreInterface.class, DocumentCacheStore.COMPONENT_NAME); theCacheStoreFixed.initalize(); // ensure cache is initialized theCacheStoreFixed.getStore(); // ensure store is initialized defaultMocks = Collections.unmodifiableCollection(getDefaultMocks()); } private class LoadDocCheckResult { private final List<String> messages = new Vector<>(); public void addMessage(String message) { messages.add(message); fastFail.set(true); } public boolean isSuccessfull() { return (messages.size() == 0); } public List<String> getMessages() { return messages; } } private abstract class AbstractXWikiTestFuture extends AbstractXWikiRunnable implements Callable<LoadDocCheckResult> { private boolean hasNewContext; private final LoadDocCheckResult result = new LoadDocCheckResult(); private final XWikiCacheStoreInterface store; protected AbstractXWikiTestFuture(XWikiCacheStoreInterface store) { this.store = store; } protected XWikiCacheStoreInterface getStore() { return this.store; } private ExecutionContext getExecutionContext() { return Utils.getComponent(Execution.class).getContext(); } protected LoadDocCheckResult getResult() { return result; } @Override public LoadDocCheckResult call() throws Exception { try { try { hasNewContext = (getExecutionContext() == null); if (hasNewContext) { initExecutionContext(); getExecutionContext().setProperty(EXECUTIONCONTEXT_KEY_MOCKS, defaultMocks); getExecutionContext().setProperty(XWikiContext.EXECUTIONCONTEXT_KEY, defaultContext.clone()); } try { runInternal(); } finally { if (hasNewContext) { // cleanup execution context cleanupExecutionContext(); } } } catch (ExecutionContextException e) { LOGGER.error("Failed to initialize execution context", e); } } catch (Throwable exp) { // anything could happen in the test and we want to catch all failures getResult().addMessage("Exception: " + exp.getMessage() + "\n" + ExceptionUtils.getStackTrace(exp)); } return getResult(); } } private class RefreshCacheEntryCommand extends AbstractXWikiTestFuture { public RefreshCacheEntryCommand(XWikiCacheStoreInterface store) { super(store); } @Override public void runInternal() { if (!successfullRemoveFromCache()) { if (verifyDocLoads) { failedToRemoveFromCacheCount.incrementAndGet(); } } } boolean successfullRemoveFromCache() { if (getStore() instanceof DocumentCacheStore) { return newStore(); } else { return oldStore(); } } private boolean newStore() { final InvalidateState invalidState = theCacheStoreFixed.removeDocFromCache(new XWikiDocument( testDocRef), true); if (verifyDocLoads) { switch (invalidState) { case LOADING_CANCELED: invalidatedLoadingCount.incrementAndGet(); break; case REMOVED: expectedCountDocLoadsFixed.incrementAndGet(); break; case LOADING_MULTI_CANCELED: invalidatedMultipleCount.incrementAndGet(); break; default: failedToInvalidatedLoadingCount.incrementAndGet(); break; } } return (invalidState.equals(InvalidateState.LOADING_CANCELED) || invalidState.equals( InvalidateState.REMOVED)); } private boolean oldStore() { String key = theCacheStore.getKey(wikiName, testFullName, ""); XWikiDocument oldCachedDoc = null; if (theCacheStore.getCache() != null) { oldCachedDoc = theCacheStore.getCache().get(key); theCacheStore.getCache().remove(key); if (verifyDocLoads) { expectedCountDocLoads.incrementAndGet(); } } if (theCacheStore.getPageExistCache() != null) { theCacheStore.getPageExistCache().remove(key); } return oldCachedDoc != null; } } private class LoadXWikiDocCommand extends AbstractXWikiTestFuture { private XWikiDocument loadedXWikiDoc; public LoadXWikiDocCommand(XWikiCacheStoreInterface store) { super(store); } @Override protected void runInternal() { loadTestDocument(); testLoadedDocument(); } private void testLoadedDocument() { if (loadedXWikiDoc != null) { if (loadedXWikiDoc.isNew()) { getResult().addMessage("unexpected: isNew is true"); } if (!loadedXWikiDoc.isMostRecent()) { getResult().addMessage("unexpected: isMostRecent is false"); } for (BaseObject theTestObj : baseObjMap.get(testDocRef)) { Map<DocumentReference, List<BaseObject>> loadedObjs = loadedXWikiDoc.getXObjects(); final List<BaseObject> xclassObjs = loadedObjs.get(theTestObj.getXClassReference()); if (!xclassObjs.contains(theTestObj)) { getResult().addMessage("Object missing " + theTestObj); } else { BaseObject theLoadedObj = xclassObjs.get(xclassObjs.indexOf(theTestObj)); if (theLoadedObj == theTestObj) { getResult().addMessage("Object is same " + theTestObj); } else { for (String theFieldName : theTestObj.getPropertyNames()) { BaseProperty theField = (BaseProperty) theLoadedObj.getField(theFieldName); BaseProperty theTestField = (BaseProperty) theTestObj.getField(theFieldName); if (theField == theTestField) { getResult().addMessage("Field is same " + theField); } else if (!theTestField.getValue().equals(theField.getValue())) { getResult().addMessage("Field value missmatch expected: " + theField + "\n but found: " + theField.getValue()); } } } } } } else { getResult().addMessage("Loaded document reference is null."); } } private void loadTestDocument() { try { XWikiDocument myDoc = new XWikiDocument(testDocRef); try { loadedXWikiDoc = getStore().loadXWikiDoc(myDoc, getContext()); } catch (XWikiException exp) { throw new IllegalStateException(exp); } } catch (Exception exp) { throw new RuntimeException(exp); } } } private final void addIntField(BaseObject bObj, String fieldName, int value) { bObj.setIntValue(fieldName, value); } private final void addStringField(BaseObject bObj, String fieldName, String value) { bObj.setStringValue(fieldName, value); } private final BaseObject createBaseObject(int num, DocumentReference classRef) { BaseObject bObj = new BaseObject(); bObj.setXClassReference(new DocumentReference(classRef.clone())); bObj.setNumber(num); return bObj; } private interface QueryList<T> { public List<T> list(String string, Map<String, Object> params) throws HibernateException; } private class TestQuery<T> extends AbstractQueryImpl { private Query theQueryMock; private QueryList<T> listStub; private Map<String, Object> params; public TestQuery(String queryStr, QueryList<T> listStub) { super(queryStr, FlushMode.AUTO, null, null); this.listStub = listStub; this.params = new HashMap<>(); theQueryMock = createMock(Query.class); replay(theQueryMock); } @SuppressWarnings("rawtypes") @Override public Iterator iterate() throws HibernateException { return theQueryMock.iterate(); } @Override public ScrollableResults scroll() throws HibernateException { return theQueryMock.scroll(); } @Override public ScrollableResults scroll(ScrollMode scrollMode) throws HibernateException { return theQueryMock.scroll(scrollMode); } @SuppressWarnings("unchecked") @Override public List<T> list() throws HibernateException { if (listStub != null) { return listStub.list(getQueryString(), params); } return theQueryMock.list(); } @Override public Query setText(String named, String val) { this.params.put(named, val); return this; } @Override public Query setInteger(String named, int val) { this.params.put(named, new Integer(val)); return this; } @Override public Query setLong(String named, long val) { this.params.put(named, new Long(val)); return this; } @Override public int executeUpdate() throws HibernateException { return theQueryMock.executeUpdate(); } @Override public Query setLockMode(String alias, LockMode lockMode) { return theQueryMock.setLockMode(alias, lockMode); } @SuppressWarnings("rawtypes") @Override protected Map getLockModes() { throw new UnsupportedOperationException("getLockModes not supported"); } } private class TestXWiki extends XWiki { @Override public BaseClass getXClass(DocumentReference documentReference, XWikiContext context) throws XWikiException { // Used to avoid recursive loading of documents if there are recursive usage of classes BaseClass bclass = context.getBaseClass(documentReference); if (bclass == null) { bclass = new BaseClass(); bclass.setDocumentReference(documentReference); context.addBaseClass(bclass); } return bclass; } } }