/* * Copyright (c) 2007-2012, 2015, 2016, 2018, 2019, 2022 Eike Stepper (Loehne, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation * Simon McDuff - maintenance */ package org.eclipse.emf.cdo.tests; import org.eclipse.emf.cdo.CDOObject; import org.eclipse.emf.cdo.CDOState; import org.eclipse.emf.cdo.common.CDOCommonSession.Options.PassiveUpdateMode; import org.eclipse.emf.cdo.common.branch.CDOBranch; import org.eclipse.emf.cdo.common.id.CDOID; import org.eclipse.emf.cdo.eresource.CDOResource; import org.eclipse.emf.cdo.session.CDOSession; import org.eclipse.emf.cdo.session.CDOSessionInvalidationEvent; import org.eclipse.emf.cdo.tests.config.IRepositoryConfig; import org.eclipse.emf.cdo.tests.model1.Category; import org.eclipse.emf.cdo.tests.model1.Company; import org.eclipse.emf.cdo.tests.model1.Customer; import org.eclipse.emf.cdo.tests.util.TestAdapter; import org.eclipse.emf.cdo.transaction.CDOTransaction; import org.eclipse.emf.cdo.util.CDOUtil; import org.eclipse.emf.cdo.util.InvalidObjectException; import org.eclipse.emf.cdo.view.CDOInvalidationPolicy; import org.eclipse.emf.cdo.view.CDOView; import org.eclipse.emf.internal.cdo.session.CDOSessionImpl; import org.eclipse.emf.internal.cdo.view.CDOViewImpl; import org.eclipse.net4j.util.ObjectUtil; import org.eclipse.net4j.util.ReflectUtil; import org.eclipse.net4j.util.event.IEvent; import org.eclipse.net4j.util.event.IListener; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.spi.cdo.FSMUtil; import java.io.IOException; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; /** * @author Eike Stepper */ public class InvalidationTest extends AbstractCDOTest { public void _testIvalidationMemoryLeak() throws Exception { final CDOSession session = openSession(); /** * @author Eike Stepper */ class MyTransTest implements Runnable { private int nr; public MyTransTest(int nr) { this.nr = nr; } @Override public void run() { for (int i = 0; i < 10; i++) { Category category = getModel1Factory().createCategory(); category.setName("Category " + System.currentTimeMillis()); CDOTransaction transaction = session.openTransaction(); try { CDOResource resource = transaction.getOrCreateResource(getResourcePath("/test1_" + nr)); resource.getContents().add(category); transaction.commit(); } catch (Exception ex) { ex.printStackTrace(); } finally { transaction.close(); } } } } List threads = new ArrayList<>(); for (int i = 0; i < 20; i++) { threads.add(new Thread(new MyTransTest(i + 1))); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { thread.join(); } // session.refresh(); // sleep(2000); new MyTransTest(0).run(); sleep(60000); // session.refresh(); Field field = ReflectUtil.getField(CDOSessionImpl.class, "outOfSequenceInvalidations"); Map outOfSequenceInvalidations = (Map)ReflectUtil.getValue(field, session); int size = outOfSequenceInvalidations.size(); assertEquals(0, size); } public void testSeparateView() throws Exception { final CDOSession session = openSession(); // ************************************************************* // final Category category1A = getModel1Factory().createCategory(); category1A.setName("category1"); final Category category2A = getModel1Factory().createCategory(); category2A.setName("category2"); final Category category3A = getModel1Factory().createCategory(); category3A.setName("category3"); final Company companyA = getModel1Factory().createCompany(); companyA.getCategories().add(category1A); category1A.getCategories().add(category2A); category2A.getCategories().add(category3A); final CDOTransaction transaction = session.openTransaction(); final CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); resourceA.getContents().add(companyA); transaction.commit(); // ************************************************************* // final CDOView view = session.openTransaction(); final CDOResource resourceB = view.getResource(getResourcePath("/test1")); assertProxy(resourceB); EList contents = resourceB.getContents(); final Company companyB = (Company)contents.get(0); assertClean(companyB, view); assertClean(resourceB, view); assertContent(resourceB, companyB); final Category category1B = companyB.getCategories().get(0); assertClean(category1B, view); assertClean(companyB, view); assertContent(companyB, category1B); final Category category2B = category1B.getCategories().get(0); assertClean(category2B, view); assertClean(category1B, view); assertContent(category1B, category2B); final Category category3B = category2B.getCategories().get(0); assertClean(category3B, view); assertClean(category2B, view); assertContent(category2B, category3B); assertClean(category3B, view); // ************************************************************* // category1A.setName("CHANGED NAME"); assertEquals("category1", category1B.getName()); transaction.commit(); assertNoTimeout(() -> "CHANGED NAME".equals(category1B.getName())); } public void testSeparateViewNotification() throws Exception { final CDOSession session = openSession(); // ************************************************************* // final Category category1A = getModel1Factory().createCategory(); category1A.setName("category1"); final Category category2A = getModel1Factory().createCategory(); category2A.setName("category2"); final Category category3A = getModel1Factory().createCategory(); category3A.setName("category3"); final Company companyA = getModel1Factory().createCompany(); companyA.getCategories().add(category1A); category1A.getCategories().add(category2A); category2A.getCategories().add(category3A); final CDOTransaction transaction = session.openTransaction(); final CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); resourceA.getContents().add(companyA); transaction.commit(); // ************************************************************* // final CDOView viewB = session.openTransaction(); final CDOResource resourceB = viewB.getResource(getResourcePath("/test1")); assertProxy(resourceB); EList contents = resourceB.getContents(); final Company companyB = (Company)contents.get(0); assertClean(companyB, viewB); assertClean(resourceB, viewB); assertContent(resourceB, companyB); final Category category1B = companyB.getCategories().get(0); assertClean(category1B, viewB); assertClean(companyB, viewB); assertContent(companyB, category1B); final Category category2B = category1B.getCategories().get(0); assertClean(category2B, viewB); assertClean(category1B, viewB); assertContent(category1B, category2B); final Category category3B = category2B.getCategories().get(0); assertClean(category3B, viewB); assertClean(category2B, viewB); assertContent(category2B, category3B); assertClean(category3B, viewB); // ************************************************************* // final CountDownLatch latch = new CountDownLatch(1); viewB.getSession().addListener(new IListener() { @Override public void notifyEvent(IEvent event) { if (event instanceof CDOSessionInvalidationEvent) { CDOSessionInvalidationEvent e = (CDOSessionInvalidationEvent)event; if (e.getLocalTransaction() == transaction) { msg("CDOSessionInvalidationEvent: " + e); latch.countDown(); } } } }); category1A.setName("CHANGED NAME"); transaction.commit(); await(latch); } public void testConflictSameSession() throws Exception, IOException { CDOSession session = openSession(); CDOTransaction trans1 = session.openTransaction(); CDOTransaction trans2 = session.openTransaction(); testConflict(trans1, trans2); } public void testConflictDifferentSession() throws Exception, IOException { CDOSession session1 = openSession(); CDOTransaction trans1 = session1.openTransaction(); CDOSession session2 = openSession(); CDOTransaction trans2 = session2.openTransaction(); testConflict(trans1, trans2); } private void testConflict(CDOTransaction trans1, CDOTransaction trans2) throws Exception { final CDOResource res1 = trans1.getOrCreateResource(getResourcePath("/test")); trans1.commit(); final CDOResource res2 = trans2.getOrCreateResource(getResourcePath("/test")); final Customer customerA1 = getModel1Factory().createCustomer(); res1.getContents().add(customerA1); final Customer customerB2 = getModel1Factory().createCustomer(); res2.getContents().add(customerB2); trans1.commit(); assertNoTimeout(() -> CDOUtil.getCDOObject(res2).cdoState() == CDOState.CONFLICT); final Customer customerA2 = getModel1Factory().createCustomer(); res1.getContents().add(customerA2); trans1.commit(); assertNoTimeout(() -> CDOUtil.getCDOObject(res2).cdoState() == CDOState.CONFLICT); trans2.rollback(); assertEquals(2, res1.getContents().size()); } public void testDetachedConflictSameSession() throws Exception, IOException { CDOSession session = openSession(); CDOTransaction trans1 = session.openTransaction(); trans1.options().setInvalidationPolicy(CDOInvalidationPolicy.STRICT); CDOTransaction trans2 = session.openTransaction(); trans2.options().setInvalidationPolicy(CDOInvalidationPolicy.STRICT); testDetachedConflict(trans1, trans2); } public void testDetachedConflictDifferentSession() throws Exception, IOException { CDOSession session1 = openSession(); CDOTransaction trans1 = session1.openTransaction(); trans1.options().setInvalidationPolicy(CDOInvalidationPolicy.STRICT); CDOSession session2 = openSession(); CDOTransaction trans2 = session2.openTransaction(); trans2.options().setInvalidationPolicy(CDOInvalidationPolicy.STRICT); testDetachedConflict(trans1, trans2); } private void testDetachedConflict(CDOTransaction trans1, CDOTransaction trans2) throws Exception { final CDOResource res1 = trans1.getOrCreateResource(getResourcePath("/test")); trans1.commit(); final CDOResource res2 = trans2.getResource(getResourcePath("/test")); res1.delete(null); res2.getContents().add(getModel1Factory().createCustomer()); assertEquals(true, res2.isExisting()); commitAndSync(trans1, trans2); assertEquals(CDOState.INVALID_CONFLICT, CDOUtil.getCDOObject(res2).cdoState()); trans2.rollback(); assertEquals(CDOState.INVALID, CDOUtil.getCDOObject(res2).cdoState()); assertEquals(false, res2.isExisting()); try { res2.getContents().get(0); fail("InvalidObjectException expected"); } catch (InvalidObjectException expected) { // SUCCESS } } public void testSeparateSession() throws Exception { final Category category1A = getModel1Factory().createCategory(); category1A.setName("category1"); final Category category2A = getModel1Factory().createCategory(); category2A.setName("category2"); final Category category3A = getModel1Factory().createCategory(); category3A.setName("category3"); final Company companyA = getModel1Factory().createCompany(); companyA.getCategories().add(category1A); category1A.getCategories().add(category2A); category2A.getCategories().add(category3A); final CDOSession sessionA = openSession(); final CDOTransaction transaction = sessionA.openTransaction(); final CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); resourceA.getContents().add(companyA); transaction.commit(); // ************************************************************* // final CDOSession sessionB = openSession(); final CDOView viewB = sessionB.openTransaction(); final CDOResource resourceB = viewB.getResource(getResourcePath("/test1")); assertProxy(resourceB); EList contents = resourceB.getContents(); final Company companyB = (Company)contents.get(0); assertClean(companyB, viewB); assertClean(resourceB, viewB); assertContent(resourceB, companyB); final Category category1B = companyB.getCategories().get(0); assertClean(category1B, viewB); assertClean(companyB, viewB); assertContent(companyB, category1B); final Category category2B = category1B.getCategories().get(0); assertClean(category2B, viewB); assertClean(category1B, viewB); assertContent(category1B, category2B); final Category category3B = category2B.getCategories().get(0); assertClean(category3B, viewB); assertClean(category2B, viewB); assertContent(category2B, category3B); assertClean(category3B, viewB); // ************************************************************* // category1A.setName("CHANGED NAME"); transaction.commit(); assertNoTimeout(() -> "CHANGED NAME".equals(category1B.getName())); } /** * See bug 236784 */ public void testInvalidateAndCache() throws Exception { msg("Opening sessionA"); CDOSession sessionA = openSession(); msg("Opening transactionA"); final CDOTransaction transactionA = sessionA.openTransaction(); final CDOID cdoidA; // ************************************************************* { msg("Creating categoryA"); Category categoryA = getModel1Factory().createCategory(); categoryA.setName("categoryA"); msg("Creating companyA"); Company companyA = getModel1Factory().createCompany(); msg("Adding categories"); companyA.getCategories().add(categoryA); msg("Creating resource"); CDOResource resourceA = transactionA.createResource(getResourcePath("/test1")); msg("Adding companyA"); resourceA.getContents().add(companyA); msg("Committing"); transactionA.commit(); cdoidA = CDOUtil.getCDOObject(categoryA).cdoID(); ((CDOViewImpl)transactionA).removeObject(cdoidA); } // ************************************************************* msg("Opening sessionB"); CDOSession sessionB = openSession(); msg("Opening transactionB"); CDOTransaction transactionB = sessionB.openTransaction(); Category categoryB; categoryB = (Category)CDOUtil.getEObject(transactionB.getObject(cdoidA, true)); msg("Changing name"); categoryB.setName("CHANGED NAME"); msg("\n\n\n\n\n\n\n\n\n\n\nCommitting"); transactionB.commit(); msg("Checking after commit"); assertNoTimeout(() -> { Category categoryA = (Category)CDOUtil.getEObject(transactionA.getObject(cdoidA, true)); String name = categoryA.getName(); return "CHANGED NAME".equals(name); }); } public void testRefreshEmptyRepository() throws Exception { CDOSession session = openSession(); assertEquals(0, session.refresh()); session.close(); } public void testSeparateSession_PassiveUpdateDisable() throws Exception { Category category1A = getModel1Factory().createCategory(); category1A.setName("category1"); Category category2A = getModel1Factory().createCategory(); category2A.setName("category2"); Category category3A = getModel1Factory().createCategory(); category3A.setName("category3"); Company companyA = getModel1Factory().createCompany(); companyA.getCategories().add(category1A); category1A.getCategories().add(category2A); category2A.getCategories().add(category3A); CDOSession sessionA = openSession(); CDOTransaction transaction = sessionA.openTransaction(); CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); resourceA.getContents().add(companyA); transaction.commit(); URI uriCategory1 = EcoreUtil.getURI(category1A); // ************************************************************* // CDOSession sessionB = openSession(); sessionB.options().setPassiveUpdateEnabled(false); CDOView viewB = sessionB.openTransaction(); final Category category1B = (Category)viewB.getResourceSet().getEObject(uriCategory1, true); // ************************************************************* // category1A.setName("CHANGED NAME"); transaction.commit(); sessionB.refresh(); // TODO Why poll? refresh is synchronous... assertNoTimeout(() -> "CHANGED NAME".equals(category1B.getName())); } public void testPassiveUpdateOnAndOff() throws Exception { msg("Creating category1"); final Category category1A = getModel1Factory().createCategory(); category1A.setName("category1"); msg("Creating category2"); final Category category2A = getModel1Factory().createCategory(); category2A.setName("category2"); msg("Creating category3"); final Category category3A = getModel1Factory().createCategory(); category3A.setName("category3"); msg("Creating company"); final Company companyA = getModel1Factory().createCompany(); msg("Adding categories"); companyA.getCategories().add(category1A); category1A.getCategories().add(category2A); category2A.getCategories().add(category3A); msg("Opening sessionA"); final CDOSession sessionA = openSession(); msg("Attaching transaction"); final CDOTransaction transaction = sessionA.openTransaction(); msg("Creating resource"); final CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); msg("Adding company"); resourceA.getContents().add(companyA); msg("Committing"); transaction.commit(); URI uriCategory1 = EcoreUtil.getURI(category1A); // ************************************************************* // msg("Opening sessionB"); final CDOSession sessionB = openSession(); sessionB.options().setPassiveUpdateEnabled(false); msg("Attaching viewB"); final CDOView viewB = sessionB.openTransaction(); final Category category1B = (Category)viewB.getResourceSet().getEObject(uriCategory1, true); // ************************************************************* // msg("Opening sessionB"); final CDOSession sessionC = openSession(); assertEquals(true, sessionC.options().isPassiveUpdateEnabled()); msg("Attaching viewB"); final CDOView viewC = sessionC.openTransaction(); final Category category1C = (Category)viewC.getResourceSet().getEObject(uriCategory1, true); msg("Changing name"); category1A.setName("CHANGED NAME"); class TimeOuterB extends PollingTimeOuter { @Override protected boolean successful() { return "CHANGED NAME".equals(category1B.getName()); } } class TimeOuterC extends PollingTimeOuter { @Override protected boolean successful() { return "CHANGED NAME".equals(category1C.getName()); } } transaction.commit(); new TimeOuterC().assertNoTimeOut(); // It should refresh the session sessionB.options().setPassiveUpdateEnabled(true); msg("Checking after sync"); new TimeOuterB().assertNoTimeOut(); new TimeOuterC().assertNoTimeOut(); category1A.setName("CHANGED NAME-VERSION2"); class TimeOuterB_2 extends PollingTimeOuter { @Override protected boolean successful() { return "CHANGED NAME-VERSION2".equals(category1B.getName()); } } class TimeOuterC_2 extends PollingTimeOuter { @Override protected boolean successful() { return "CHANGED NAME-VERSION2".equals(category1C.getName()); } } transaction.commit(); new TimeOuterB_2().assertNoTimeOut(); new TimeOuterC_2().assertNoTimeOut(); } public void testDetach() throws Exception { final Category categoryA = getModel1Factory().createCategory(); categoryA.setName("category1"); final CDOSession sessionA = openSession(); final CDOTransaction transaction = sessionA.openTransaction(); final CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); resourceA.getContents().add(categoryA); transaction.commit(); // ************************************************************* // final CDOSession sessionB = openSession(); final CDOView viewB = sessionB.openTransaction(); viewB.options().setInvalidationNotificationEnabled(true); final CDOResource resourceB = viewB.getResource(getResourcePath("/test1")); assertProxy(resourceB); EList contents = resourceB.getContents(); final Category categoryB = (Category)contents.get(0); final TestAdapter testAdapter = new TestAdapter(categoryB); // ************************************************************* // resourceA.getContents().remove(categoryA); testAdapter.assertNotifications(0); transaction.commit(); assertNoTimeout(() -> FSMUtil.isInvalid(CDOUtil.getCDOObject(categoryB))); testAdapter.assertNoTimeout(1); } public void testDetachAndPassiveUpdate() throws Exception { detachAndPassiveUpdate(false); } public void testDetachAndPassiveUpdateWithoutRevisionTimestamp() throws Exception { detachAndPassiveUpdate(true); } private void detachAndPassiveUpdate(boolean isRemoveRevision) throws Exception { msg("Creating category1"); final Category categoryA = getModel1Factory().createCategory(); categoryA.setName("category1"); msg("Opening sessionA"); final CDOSession sessionA = openSession(); msg("Attaching transaction"); final CDOTransaction transaction = sessionA.openTransaction(); msg("Creating resource"); final CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); msg("Adding company"); resourceA.getContents().add(categoryA); msg("Committing"); transaction.commit(); // ************************************************************* // msg("Opening sessionB"); final CDOSession sessionB = openSession(); sessionB.options().setPassiveUpdateEnabled(false); msg("Attaching viewB"); final CDOView viewB = sessionB.openTransaction(); viewB.options().setInvalidationNotificationEnabled(true); msg("Loading resource"); final CDOResource resourceB = viewB.getResource(getResourcePath("/test1")); assertProxy(resourceB); EList contents = resourceB.getContents(); final Category categoryB = (Category)contents.get(0); final TestAdapter testAdapter = new TestAdapter(categoryB); // ************************************************************* // resourceA.getContents().remove(categoryA); testAdapter.assertNotifications(0); transaction.commit(); final Category categoryA2 = getModel1Factory().createCategory(); categoryA2.setName("categoryA2"); resourceA.getContents().add(categoryA2); transaction.commit(); if (isRemoveRevision) { clearCache(getRepository().getRevisionManager()); getRepository().getRevisionManager().getCache().removeRevision(resourceA.cdoID(), resourceA.cdoRevision().getBranch().getVersion(1)); getRepository().getRevisionManager().getCache().removeRevision(resourceA.cdoID(), resourceA.cdoRevision().getBranch().getVersion(2)); } testAdapter.assertNotifications(0); sessionB.refresh(); assertNoTimeout(() -> FSMUtil.isInvalid(CDOUtil.getCDOObject(categoryB))); testAdapter.assertNoTimeout(1); } public void testPassiveUpdateMode_CHANGES() throws Exception { Category categoryA = getModel1Factory().createCategory(); categoryA.setName("category1"); CDOSession sessionA = openSession(); CDOTransaction transaction = sessionA.openTransaction(); CDOResource resourceA = transaction.createResource(getResourcePath("/test1")); resourceA.getContents().add(categoryA); transaction.commit(); // ************************************************************* // CDOSession sessionB = openSession(); sessionB.options().setPassiveUpdateMode(PassiveUpdateMode.CHANGES); CDOView viewB = sessionB.openTransaction(); CDOResource resourceB = viewB.getResource(getResourcePath("/test1")); Category categoryB = (Category)resourceB.getContents().get(0); final TestAdapter testAdapter = new TestAdapter(categoryB); // ************************************************************* // categoryA.setName("CHANGED"); testAdapter.assertNotifications(0); transaction.commit(); assertNoTimeout(() -> { Notification[] notifications = testAdapter.getNotifications(); if (notifications.length != 0) { if (!ObjectUtil.equals(notifications[0].getOldStringValue(), "category1")) { fail("No old value"); } if (!ObjectUtil.equals(notifications[0].getNewStringValue(), "CHANGED")) { fail("No new value"); } return true; } return false; }); } @Requires(IRepositoryConfig.CAPABILITY_BRANCHING) public void testDeleteFromOtherBranch() throws Exception { Company companyA = getModel1Factory().createCompany(); Category categoryA = getModel1Factory().createCategory(); companyA.getCategories().add(categoryA); CDOSession session = openSession(); session.options().setPassiveUpdateEnabled(true); session.options().setPassiveUpdateMode(PassiveUpdateMode.CHANGES); CDOTransaction transactionA = session.openTransaction(); CDOResource resourceA = transactionA.createResource(getResourcePath("/test1")); resourceA.getContents().add(companyA); CDOBranch mainBranch = transactionA.getBranch(); transactionA.commit(); // company v1 assertEquals(mainBranch, CDOUtil.getCDOObject(companyA).cdoRevision().getBranch()); assertEquals(1, CDOUtil.getCDOObject(companyA).cdoRevision().getVersion()); companyA.setName("ESC"); transactionA.commit(); // company v2 assertEquals(mainBranch, CDOUtil.getCDOObject(companyA).cdoRevision().getBranch()); assertEquals(2, CDOUtil.getCDOObject(companyA).cdoRevision().getVersion()); CDOTransaction transactionB = session.openTransaction(); CDOResource resourceB = transactionB.getResource(getResourcePath("/test1")); Company companyB = (Company)resourceB.getContents().get(0); companyB.getCategories().get(0); CDOObject cdoCompanyB = CDOUtil.getCDOObject(companyB); CDOBranch branch1 = mainBranch.createBranch(getBranchName("branch1")); transactionB.setBranch(branch1); transactionA.setBranch(branch1); assertEquals(mainBranch, cdoCompanyB.cdoRevision().getBranch()); assertEquals(2, cdoCompanyB.cdoRevision().getVersion()); companyA.getCategories().remove(0); commitAndSync(transactionA, transactionB); assertEquals(CDOState.CLEAN, cdoCompanyB.cdoState()); assertEquals(branch1, cdoCompanyB.cdoRevision().getBranch()); assertEquals(1, cdoCompanyB.cdoRevision().getVersion()); } }