[351600] Cannot load resource on a previously cleared ResourceSet 
https://bugs.eclipse.org/bugs/show_bug.cgi?id=351600
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java
index 180f90c..0057bd5 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AllConfigs.java
@@ -218,6 +218,7 @@
     testClasses.add(Bugzilla_337587_Test.class);
     testClasses.add(Bugzilla_338779_Test.class);
     testClasses.add(Bugzilla_338884_Test.class);
+    testClasses.add(Bugzilla_338921_Test.class);
     testClasses.add(Bugzilla_339313_Test.class);
     testClasses.add(Bugzilla_339461_Test.class);
     testClasses.add(Bugzilla_339908_Test.class);
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/ResourceTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/ResourceTest.java
index 5026798..76529af 100644
--- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/ResourceTest.java
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/ResourceTest.java
@@ -412,55 +412,13 @@
     }
   }
 
-  public void testAttachManyResources() throws Exception
+  public void testAttachResource() throws Exception
   {
     CDOSession session = openSession();
     CDOTransaction transaction = session.openTransaction();
 
-    CDOResource resource1 = transaction.createResource(getResourcePath("/my/resource1"));
-    CDOResource resource2 = transaction.createResource(getResourcePath("/my/resource2"));
-    CDOResource resource3 = transaction.createResource(getResourcePath("/my/resource3"));
-
-    List<Resource> tobeRemoved = new ArrayList<Resource>();
-    tobeRemoved.add(resource1);
-    tobeRemoved.add(resource3);
-    assertEquals(3, transaction.getResourceSet().getResources().size());// Bug 346636
-
-    transaction.getResourceSet().getResources().removeAll(tobeRemoved);
+    transaction.createResource(getResourcePath("/my/resource1"));
     assertEquals(1, transaction.getResourceSet().getResources().size());// Bug 346636
-    assertEquals(null, transaction.getResourceSet().getResource(resource1.getURI(), false));
-    assertEquals(resource2, transaction.getResourceSet().getResource(resource2.getURI(), false));
-    assertEquals(null, transaction.getResourceSet().getResource(resource3.getURI(), false));
-
-    transaction.getResourceSet().getResources().addAll(tobeRemoved);
-    assertEquals(3, transaction.getResourceSet().getResources().size());// Bug 346636
-    assertEquals(resource1, transaction.getResourceSet().getResource(resource1.getURI(), false));
-    assertEquals(resource2, transaction.getResourceSet().getResource(resource2.getURI(), false));
-    assertEquals(resource3, transaction.getResourceSet().getResource(resource3.getURI(), false));
-
-    transaction.commit();
-    session.close();
-  }
-
-  public void testDetachManyResources() throws Exception
-  {
-    CDOSession session = openSession();
-    CDOTransaction transaction = session.openTransaction();
-
-    CDOResource resource1 = transaction.createResource(getResourcePath("/my/resource1"));
-    CDOResource resource2 = transaction.createResource(getResourcePath("/my/resource2"));
-    CDOResource resource3 = transaction.createResource(getResourcePath("/my/resource3"));
-
-    List<Resource> tobeRemoved = new ArrayList<Resource>();
-    tobeRemoved.add(resource1);
-    tobeRemoved.add(resource3);
-    assertEquals(3, transaction.getResourceSet().getResources().size());// Bug 346636
-
-    transaction.getResourceSet().getResources().removeAll(tobeRemoved);
-    assertEquals(1, transaction.getResourceSet().getResources().size());// Bug 346636
-    assertEquals(null, transaction.getResourceSet().getResource(resource1.getURI(), false));
-    assertEquals(resource2, transaction.getResourceSet().getResource(resource2.getURI(), false));
-    assertEquals(null, transaction.getResourceSet().getResource(resource3.getURI(), false));
 
     transaction.commit();
     session.close();
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_338921_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_338921_Test.java
new file mode 100644
index 0000000..5887f91
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_338921_Test.java
@@ -0,0 +1,175 @@
+/**

+ * Copyright (c) 2004 - 2011 Eike Stepper (Berlin, 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:

+ *    Victor Roldan Betancort - initial API and implementation   

+ */

+package org.eclipse.emf.cdo.tests.bugzilla;

+

+import org.eclipse.emf.cdo.eresource.CDOResource;

+import org.eclipse.emf.cdo.session.CDOSession;

+import org.eclipse.emf.cdo.tests.AbstractCDOTest;

+import org.eclipse.emf.cdo.tests.model1.Company;

+import org.eclipse.emf.cdo.transaction.CDOTransaction;

+

+import org.eclipse.emf.common.util.URI;

+import org.eclipse.emf.ecore.EObject;

+import org.eclipse.emf.ecore.resource.Resource;

+import org.eclipse.emf.ecore.resource.ResourceSet;

+

+/**

+ * Cannot load resource on a previously cleared ResourceSet

+ * <p>

+ * See Bug 338921

+ * 

+ * @author Victor Roldan Betancort

+ */

+public class Bugzilla_338921_Test extends AbstractCDOTest

+{

+  public void testLoadResourceAfterSingleRemoval() throws Exception

+  {

+    Company company = getModel1Factory().createCompany();

+

+    CDOSession session = openSession();

+    CDOTransaction transaction = session.openTransaction();

+

+    CDOResource resource = transaction.createResource(getResourcePath("/Bug338921"));

+    resource.getContents().add(company);

+

+    transaction.commit();

+

+    URI uri = resource.getURI();

+

+    ResourceSet resourceSet = resource.getResourceSet();

+    resourceSet.getResources().remove(resource);

+

+    assertInvalid(resource);

+

+    Resource resource2 = resourceSet.getResource(uri, true);

+    assertNotNull(resource2);

+    // forcing transition from PROXY to CLEAN. Revision is loaded

+    resource2.getURI();

+    assertClean((EObject)resource2, transaction);

+

+  }

+

+  public void testRemoveDirtyResourceOnResourceSetWithSingleResource() throws Exception

+  {

+    Company company = getModel1Factory().createCompany();

+

+    CDOSession session = openSession();

+    CDOTransaction transaction = session.openTransaction();

+

+    CDOResource resource = transaction.createResource(getResourcePath("/Bug338921"));

+    resource.getContents().add(company);

+

+    transaction.commit();

+

+    company.setName("foobar");

+    URI uri = resource.getURI();

+

+    ResourceSet resourceSet = resource.getResourceSet();

+    resourceSet.getResources().remove(resource);

+

+    Resource resource2 = resourceSet.getResource(uri, true);

+    assertNotNull(resource2);

+    // forcing transition from PROXY to CLEAN. Revision is loaded

+    resource2.getURI();

+    assertClean((EObject)resource2, transaction);

+

+  }

+

+  public void testRemoveDirtyResourceOnResourceSetWithMultipleResource() throws Exception

+  {

+    CDOSession session = openSession();

+    CDOTransaction transaction = session.openTransaction();

+

+    CDOResource resource1 = transaction.createResource(getResourcePath("/Bug338921/res1"));

+    CDOResource resource2 = transaction.createResource(getResourcePath("/Bug338921/res2"));

+    Company company1 = getModel1Factory().createCompany();

+    Company company2 = getModel1Factory().createCompany();

+

+    resource1.getContents().add(company1);

+    resource2.getContents().add(company2);

+

+    transaction.commit();

+

+    company1.setName("foo");

+    company2.setName("bar");

+

+    ResourceSet resourceSet = resource1.getResourceSet();

+

+    resourceSet.getResources().remove(resource1);

+    assertEquals(true, transaction.isDirty());

+  }

+

+  public void testLoadResourceAfterClearOnCleanResourceSet() throws Exception

+  {

+    Company company = getModel1Factory().createCompany();

+

+    CDOSession session = openSession();

+    CDOTransaction transaction = session.openTransaction();

+

+    CDOResource resource1 = transaction.createResource(getResourcePath("/Bug338921/res1"));

+    transaction.createResource(getResourcePath("/Bug338921/res2"));

+    resource1.getContents().add(company);

+

+    transaction.commit();

+

+    URI uri = resource1.getURI();

+    company.setName("foobar");

+

+    ResourceSet resourceSet = resource1.getResourceSet();

+    resourceSet.getResources().clear();

+

+    assertEquals(true, transaction.isDirty());

+    assertInvalid(resource1);

+

+    Resource resource3 = resourceSet.getResource(uri, true);

+    assertNotNull(resource3);

+    // forcing transition from PROXY to CLEAN. Revision is loaded

+    resource3.getURI();

+    assertClean((EObject)resource3, transaction);

+

+  }

+

+  public void testLoadResourceAfterClearOnDirtyResourceSet() throws Exception

+  {

+    Company company = getModel1Factory().createCompany();

+

+    CDOSession session = openSession();

+    CDOTransaction transaction = session.openTransaction();

+

+    CDOResource resource1 = transaction.createResource(getResourcePath("/Bug338921/res1"));

+    CDOResource resource2 = transaction.createResource(getResourcePath("/Bug338921/res2"));

+    resource1.getContents().add(company);

+

+    transaction.commit();

+

+    URI uri1 = resource1.getURI();

+    URI uri2 = resource2.getURI();

+    company.setName("foobar");

+

+    ResourceSet resourceSet = resource1.getResourceSet();

+    resourceSet.getResources().clear();

+

+    assertEquals(true, transaction.isDirty());

+    assertInvalid(resource1);

+    assertInvalid(resource2);

+

+    CDOResource resource3 = (CDOResource)resourceSet.getResource(uri1, true);

+    CDOResource resource4 = (CDOResource)resourceSet.getResource(uri2, true);

+    assertNotNull(resource3);

+    assertNotNull(resource4);

+    // forcing transition from PROXY to CLEAN. Revision is loaded

+    resource3.getURI();

+    assertClean(resource3, transaction);

+    resource4.getURI();

+    assertClean(resource4, transaction);

+  }

+

+}

diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/eresource/impl/CDOResourceImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/eresource/impl/CDOResourceImpl.java
index 395a2ad..3f75512 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/eresource/impl/CDOResourceImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/eresource/impl/CDOResourceImpl.java
@@ -26,9 +26,11 @@
 import org.eclipse.emf.cdo.view.CDOView;
 import org.eclipse.emf.cdo.view.CDOViewProvider;
 import org.eclipse.emf.cdo.view.CDOViewProviderRegistry;
+import org.eclipse.emf.cdo.view.CDOViewSet;
 
 import org.eclipse.emf.internal.cdo.bundle.OM;
 import org.eclipse.emf.internal.cdo.view.CDOStateMachine;
+import org.eclipse.emf.internal.cdo.view.CDOViewSetImpl;
 
 import org.eclipse.net4j.util.WrappedException;
 import org.eclipse.net4j.util.collection.Pair;
@@ -72,6 +74,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 /**
  * <!-- begin-user-doc --> An implementation of the model object '<em><b>CDO Resource</b></em>'.
@@ -347,12 +350,9 @@
     if (remote)
     {
       existing = false;
-      cdoView().getResourceSet().getResources().remove(this);
     }
-    else
-    {
-      removeFromResourceSet();
-    }
+
+    removeFromResourceSet();
   }
 
   /**
@@ -1190,10 +1190,21 @@
 
   private void removeFromResourceSet()
   {
-    ResourceSet resourceSet = getResourceSet();
+    final ResourceSet resourceSet = getResourceSet();
     if (resourceSet != null)
     {
-      resourceSet.getResources().remove(this);
+      CDOViewSet viewSet = CDOUtil.getViewSet(resourceSet);
+      if (viewSet instanceof CDOViewSetImpl)
+      {
+        ((CDOViewSetImpl)viewSet).executeWithoutNotificationHandling(new Callable<Boolean>()
+        {
+          public Boolean call() throws Exception
+          {
+            resourceSet.getResources().remove(CDOResourceImpl.this);
+            return true;
+          }
+        });
+      }
     }
   }
 
@@ -1235,10 +1246,22 @@
    */
   public NotificationChain basicSetResourceSet(ResourceSet resourceSet, NotificationChain notifications)
   {
-    ResourceSet oldResourceSet = getResourceSet();
+    final ResourceSet oldResourceSet = getResourceSet();
     if (oldResourceSet != null)
     {
-      notifications = ((InternalEList<Resource>)oldResourceSet.getResources()).basicRemove(this, notifications);
+      final NotificationChain finalNotifications = notifications;
+
+      CDOViewSet viewSet = CDOUtil.getViewSet(oldResourceSet);
+      if (viewSet instanceof CDOViewSetImpl)
+      {
+        notifications = ((CDOViewSetImpl)viewSet).executeWithoutNotificationHandling(new Callable<NotificationChain>()
+        {
+          public NotificationChain call() throws Exception
+          {
+            return ((InternalEList<Resource>)oldResourceSet.getResources()).basicRemove(this, finalNotifications);
+          }
+        });
+      }
     }
 
     setResourceSet(resourceSet);
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
index 3ef76dd..11aea04 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java
@@ -100,6 +100,7 @@
 import org.eclipse.emf.internal.cdo.util.IPackageClosure;
 import org.eclipse.emf.internal.cdo.view.CDOStateMachine;
 import org.eclipse.emf.internal.cdo.view.CDOViewImpl;
+import org.eclipse.emf.internal.cdo.view.CDOViewSetImpl;
 
 import org.eclipse.net4j.util.ObjectUtil;
 import org.eclipse.net4j.util.WrappedException;
@@ -132,6 +133,7 @@
 import org.eclipse.emf.spi.cdo.InternalCDOSavepoint;
 import org.eclipse.emf.spi.cdo.InternalCDOSession;
 import org.eclipse.emf.spi.cdo.InternalCDOTransaction;
+import org.eclipse.emf.spi.cdo.InternalCDOViewSet;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.NullProgressMonitor;
@@ -151,6 +153,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -1096,14 +1099,25 @@
     cleanUp(null);
   }
 
-  private void removeObject(CDOID id, CDOObject object)
+  private void removeObject(CDOID id, final CDOObject object)
   {
     ((InternalCDOObject)object).cdoInternalSetState(CDOState.TRANSIENT);
     removeObject(id);
 
     if (object instanceof CDOResource)
     {
-      getResourceSet().getResources().remove(object);
+      InternalCDOViewSet viewSet = getViewSet();
+      if (viewSet instanceof CDOViewSetImpl)
+      {
+        ((CDOViewSetImpl)viewSet).executeWithoutNotificationHandling(new Callable<Boolean>()
+        {
+          public Boolean call() throws Exception
+          {
+            getResourceSet().getResources().remove(object);
+            return true;
+          }
+        });
+      }
     }
 
     ((InternalCDOObject)object).cdoInternalSetID(null);
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewSetImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewSetImpl.java
index f0cfd02..f1ad2b8 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewSetImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/view/CDOViewSetImpl.java
@@ -8,16 +8,21 @@
  * Contributors:
  *    Simon McDuff - initial API and implementation
  *    Eike Stepper - maintenance
+ *    Victor Roldan Betancort - bug 338921
  */
 package org.eclipse.emf.internal.cdo.view;
 
+import org.eclipse.emf.cdo.CDOState;
 import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
+import org.eclipse.emf.cdo.common.util.CDOException;
 import org.eclipse.emf.cdo.eresource.CDOResource;
 import org.eclipse.emf.cdo.eresource.CDOResourceFactory;
 import org.eclipse.emf.cdo.view.CDOView;
 
 import org.eclipse.emf.internal.cdo.messages.Messages;
 
+import org.eclipse.net4j.util.WrappedException;
+
 import org.eclipse.emf.common.notify.Notification;
 import org.eclipse.emf.common.notify.Notifier;
 import org.eclipse.emf.common.notify.impl.NotificationImpl;
@@ -26,16 +31,21 @@
 import org.eclipse.emf.ecore.resource.Resource;
 import org.eclipse.emf.ecore.resource.Resource.Factory.Registry;
 import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.spi.cdo.InternalCDOObject;
 import org.eclipse.emf.spi.cdo.InternalCDOView;
 import org.eclipse.emf.spi.cdo.InternalCDOViewSet;
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.Callable;
 
 /**
  * @author Simon McDuff
@@ -53,6 +63,8 @@
 
   private ResourceSet resourceSet;
 
+  private ThreadLocal<Boolean> ignoreNotifications = new InheritableThreadLocal<Boolean>();
+
   public CDOViewSetImpl()
   {
   }
@@ -208,9 +220,137 @@
     return type instanceof ResourceSet;
   }
 
+  public synchronized <V> V executeWithoutNotificationHandling(Callable<V> callable)
+  {
+    Boolean wasIgnore = ignoreNotifications.get();
+
+    try
+    {
+      ignoreNotifications.set(true);
+      return callable.call();
+    }
+    catch (Exception ex)
+    {
+      throw WrappedException.wrap(ex);
+    }
+    finally
+    {
+      if (wasIgnore == null)
+      {
+        ignoreNotifications.remove();
+      }
+    }
+  }
+
   public void notifyChanged(Notification notification)
   {
-    // Do nothing
     // The resource <-> view association is done in CDOResourceImpl.basicSetResourceSet()
+
+    if (ignoreNotifications.get() == null)
+    {
+      // We need to deregister CDOResources from CDOView if removed from the ResourceSet, see bug 338921
+      switch (notification.getEventType())
+      {
+      case Notification.REMOVE_MANY:
+        deregisterResources((List<?>)notification.getOldValue());
+        break;
+
+      case Notification.REMOVE:
+        deregisterResources(Collections.singleton(notification.getOldValue()));
+        break;
+      }
+    }
+  }
+
+  private void deregisterResources(Collection<?> potentialResources)
+  {
+    List<CDOResource> allDirtyResources = new ArrayList<CDOResource>();
+
+    try
+    {
+      Map<CDOView, List<CDOResource>> resourcesPerView = getResourcesPerView(potentialResources);
+
+      for (Entry<CDOView, List<CDOResource>> entry : resourcesPerView.entrySet())
+      {
+        InternalCDOView view = (InternalCDOView)entry.getKey();
+        List<CDOResource> resources = entry.getValue();
+
+        if (view.isDirty())
+        {
+          List<CDOResource> dirtyResources = getDirtyResources(resources);
+          if (!dirtyResources.isEmpty())
+          {
+            allDirtyResources.addAll(dirtyResources);
+            resourceSet.getResources().addAll(resources);
+            continue;
+          }
+        }
+
+        for (CDOResource resource : resources)
+        {
+          InternalCDOObject internalResource = (InternalCDOObject)resource;
+          view.deregisterObject(internalResource);
+          internalResource.cdoInternalSetState(CDOState.INVALID);
+        }
+      }
+    }
+    finally
+    {
+      int size = allDirtyResources.size();
+      if (size == 1)
+      {
+        throw new CDOException("Attempt to remove a dirty resource from a resource set: " + allDirtyResources.get(0));
+      }
+      else if (size > 1)
+      {
+        throw new CDOException("Attempt to remove dirty resources from a resource set: " + allDirtyResources);
+      }
+    }
+  }
+
+  private List<CDOResource> getDirtyResources(List<CDOResource> resources)
+  {
+    List<CDOResource> dirtyResources = new ArrayList<CDOResource>();
+    for (CDOResource resource : resources)
+    {
+      switch (resource.cdoState())
+      {
+      case NEW:
+      case DIRTY:
+      case CONFLICT:
+      case INVALID_CONFLICT:
+        dirtyResources.addAll(resources);
+      }
+    }
+
+    return dirtyResources;
+  }
+
+  private Map<CDOView, List<CDOResource>> getResourcesPerView(Collection<?> potentialResources)
+  {
+    Map<CDOView, List<CDOResource>> resourcesPerView = new HashMap<CDOView, List<CDOResource>>();
+
+    for (Object potentialResource : potentialResources)
+    {
+      if (potentialResource instanceof CDOResource)
+      {
+        CDOResource resource = (CDOResource)potentialResource;
+        CDOView view = resource.cdoView();
+
+        if (views.contains(view))
+        {
+          List<CDOResource> resources = resourcesPerView.get(view);
+          if (resources == null)
+          {
+            resources = new ArrayList<CDOResource>();
+            resourcesPerView.put(view, resources);
+          }
+
+          resources.add(resource);
+        }
+      }
+    }
+
+    return resourcesPerView;
   }
 }