blob: 6c277e43549d935617d6c7f6afb2af6086a9c67a [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2013, 2017, 2019 CEA LIST, Christian W. Damus, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* CEA LIST - Initial API and implementation
* Christian W. Damus (CEA) - bug 429242
* Christian W. Damus (CEA) - bug 422257
* Christian W. Damus (CEA) - bug 437052
* Christian W. Damus - bug 436998
* Eike Stepper (CEA) - bug 466520
* Nicolas FAUVERGUE (ALL4TEC) nicolas.fauvergue@all4tec.net - Bug 496905
* Vincent LORENZO (CEA) - bug 507763
*****************************************************************************/
package org.eclipse.papyrus.cdo.core.resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.CDOState;
import org.eclipse.emf.cdo.common.protocol.CDOProtocolConstants;
import org.eclipse.emf.cdo.dawn.gmf.util.DawnDiagramUpdater;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.eresource.CDOResourceNode;
import org.eclipse.emf.cdo.explorer.CDOExplorerUtil;
import org.eclipse.emf.cdo.explorer.checkouts.CDOCheckout;
import org.eclipse.emf.cdo.internal.explorer.checkouts.CDOCheckoutViewProvider;
import org.eclipse.emf.cdo.transaction.CDOTransaction;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.util.CommitException;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.emf.cdo.view.CDOViewSet;
import org.eclipse.emf.common.notify.Adapter;
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.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.spi.cdo.FSMUtil;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.papyrus.cdo.internal.core.CDOUtils;
import org.eclipse.papyrus.cdo.internal.core.controlmode.CDOControlModeParticipant;
import org.eclipse.papyrus.cdo.internal.core.controlmode.CDOProxyManager;
import org.eclipse.papyrus.infra.core.Activator;
import org.eclipse.papyrus.infra.core.resource.ModelMultiException;
import org.eclipse.papyrus.infra.services.resourceloading.OnDemandLoadingModelSet;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
/**
* This is the CDOAwareModelSet type. Enjoy.
*/
public class CDOAwareModelSet extends OnDemandLoadingModelSet {
private static final Set<CDOState> DIRTY_STATES = EnumSet.of(CDOState.NEW, CDOState.DIRTY, CDOState.CONFLICT, CDOState.INVALID_CONFLICT);
private final ThreadLocal<Boolean> inGetResource = new ThreadLocal<Boolean>();
private final CDOProxyManager proxyManager = new CDOProxyManager(this);
private final PapyrusCDOResourceFactory resourceFactory = new PapyrusCDOResourceFactory(this);
private CDOCheckout checkout;
private CDOView view;
private IListener invalidationListener;
public CDOAwareModelSet() {
super();
this.resources = new SafeResourceList();
Map<String, Object> map = getResourceFactoryRegistry().getProtocolToFactoryMap();
map.put(CDOProtocolConstants.PROTOCOL_NAME, resourceFactory);
map.put(CDOCheckoutViewProvider.SCHEME, resourceFactory);
}
@Override
protected Adapter createModificationTrackingAdapter() {
return new CDOAwareProxyModificationTrackingAdapter();
}
@Override
public EObject getEObject(URI uri, boolean loadOnDemand) {
return CDOUtils.isCDOURI(uri) ? getCDOObject(uri, loadOnDemand) : super.getEObject(uri, loadOnDemand);
}
protected EObject getCDOObject(URI uri, boolean loadOnDemand) {
EObject result = null;
if (CDOProxyManager.isCDOProxyURI(uri)) {
// get a real proxy object, out of thin air, to give CDO the non-null
// instance that it needs (otherwise the 'non-null constraint' of
// all kinds of reference lists will be violated)
result = proxyManager.getProxy(uri);
} else {
result = super.getEObject(uri, loadOnDemand);
}
return result;
}
@Override
public Resource getResource(URI uri, boolean loadOnDemand) {
Boolean oldValue = inGetResource.get();
inGetResource.set(Boolean.TRUE);
try {
return super.getResource(uri, loadOnDemand);
} finally {
inGetResource.set(oldValue);
}
}
boolean isInGetResource() {
return inGetResource.get() == Boolean.TRUE;
}
@Override
public Resource createResource(URI uri, String contentType) {
Resource resource = super.createResource(uri, contentType);
initTransaction(resource);
return resource;
}
@Override
protected void demandLoad(Resource resource) throws IOException {
URI uri = resource.getURI();
// if (CDOCheckoutViewProvider.SCHEME.equals(uri.scheme())) {
//
// } else
if (CDOUtils.isCDOURI(uri)) {
// XML options not applicable to CDO resources
resource.load(null);
resourceLoadedHook(resource);
} else {
super.demandLoad(resource);
}
initTransaction(resource);
}
@Override
protected Resource setResourceOptions(Resource r) {
if (r instanceof CDOResource) {
return r;
}
return super.setResourceOptions(r);
}
protected void resourceLoadedHook(Resource resource) {
for (Diagram next : Iterables.filter(resource.getContents(), Diagram.class)) {
DawnDiagramUpdater.initializeElement(next);
}
}
public CDOView getCDOView() {
return view;
}
@Override
public void createModels(URI newURI) {
super.createModels(newURI);
}
@Override
public void loadModels(URI uri) throws ModelMultiException {
super.loadModels(uri);
// Did any of the models create a new resource? (the DiModel and/or HistoryModel may have done)
// If so, commit them now so that they don't dangle if we later close the editor without saving.
out: for (CDOResource next : Iterables.filter(getResources(), CDOResource.class)) {
switch (next.cdoState()) {
case NEW:
case DIRTY:
try {
((CDOTransaction) getCDOView()).commit(new NullProgressMonitor());
} catch (CommitException e) {
Activator.log.error("Commit implicitly created resources failed.", e); //$NON-NLS-1$
}
break out;
default:
// Pass
break;
}
}
}
/**
* @param resource
*/
protected void initTransaction(Resource resource) {
if (resource instanceof CDOResource) {
CDOResource cdoResource = (CDOResource) resource;
view = cdoResource.cdoView();
if (view != null) {
final IListener invalidationListener = getInvalidationListener();
if (false == Arrays.asList(view.getListeners()).contains(invalidationListener)) {
view.addListener(getInvalidationListener());
checkout = CDOExplorerUtil.getCheckout(view);
}
// URI from = URI.createURI("cdo://" + view.getSession().getRepositoryInfo().getName() + "/");
// URI to = URI.createURI("cdo.checkout://" + checkout.getID() + "/" + checkout.getRepository().getID() + "/");
// getURIConverter().getURIMap().put(from, to);
}
}
}
@Override
public void unload() {
boolean logError = true;
try {
logError = view == null || !view.isClosed();
// CDOResources don't implement unload(), but we can remove adapters from
// all of the objects that we have loaded in this view
if (view != null && !view.isClosed()) {
CDOUtils.unload(view);
}
} finally {
try {
super.unload();
} catch (Exception ex) {
if (logError) {
Activator.log.error(ex);
}
} finally {
LifecycleUtil.deactivate(view);
CDOCheckoutViewProvider.disposeResourceSet(this);
// now, we can remove the CDOViewSet adapter
eAdapters().clear();
}
}
}
protected final IListener getInvalidationListener() {
if (invalidationListener == null) {
invalidationListener = createInvalidationListener();
}
return invalidationListener;
}
protected IListener createInvalidationListener() {
return new InvalidationListener();
}
private class InvalidationListener implements IListener {
@Override
public void notifyEvent(IEvent event) {
if (event instanceof CDOViewInvalidationEvent) {
TransactionalEditingDomain domain = getTransactionalEditingDomain();
if (domain instanceof CDOAwareTransactionalEditingDomain) {
((CDOAwareTransactionalEditingDomain) domain).fireResourceSetChanged((CDOViewInvalidationEvent) event);
}
}
}
}
@Override
public boolean isUserModelResource(URI uri) {
return super.isUserModelResource(uri) || CDOUtils.isCDOURI(uri);
}
@Override
public EList<Adapter> eAdapters() {
if (eAdapters == null) {
eAdapters = new EAdapterList<Adapter>(this) {
private static final long serialVersionUID = 1L;
@Override
public Adapter remove(int index) {
Adapter toRemove = primitiveGet(index);
if ((toRemove instanceof CDOViewSet) && !canDisconnectCDOViewSet()) {
// don't allow its removal if my view is still open!
// (Papyrus attempts to clear the resource set's adapters when disposing a ModelSet)
return null;
}
return super.remove(index);
}
@Override
public void clear() {
if (!canDisconnectCDOViewSet()) {
// we can remove everything but the view-set adapter
Adapter viewSetAdapter = getViewSetAdapter();
if (viewSetAdapter != null) {
retainAll(Collections.singleton(viewSetAdapter));
} else {
super.clear();
}
} else {
super.clear();
}
}
private Adapter getViewSetAdapter() {
return Iterables.find(this, Predicates.instanceOf(CDOViewSet.class), null);
}
};
}
return eAdapters;
}
private boolean canDisconnectCDOViewSet() {
CDOView view = getCDOView();
return ((view == null) || view.isClosed()) && getResources().isEmpty();
}
boolean isDirty(Resource resource) {
return (resource instanceof CDOResource) && DIRTY_STATES.contains(((CDOResource) resource).cdoState());
}
@Override
protected void cleanModelSet() {
Set<URI> toDelete = getResourcesToDeleteOnSave();
for (Iterator<Resource> iter = getResources().iterator(); iter.hasNext();) {
Resource next = iter.next();
// Can't remove a dirty CDO resource
if (toDelete.contains(next.getURI()) && (!(next instanceof CDOResource) || FSMUtil.isClean((CDOResource) next) || FSMUtil.isTransient((CDOResource) next))) {
iter.remove();
}
}
}
@Override
protected void handleResourcesToDelete() {
final int initialCount = getResourcesToDeleteOnSave().size();
super.handleResourcesToDelete();
// any changes made by resource deletions?
CDOView view = getCDOView();
if ((view instanceof CDOTransaction) && (getResourcesToDeleteOnSave().size() < initialCount)) {
try {
((CDOTransaction) view).commit();
} catch (CommitException e) {
Activator.log.error("Failed to commit resource deletions.", e); //$NON-NLS-1$
}
}
}
@Override
protected boolean validateDeleteResource(URI uri) {
boolean result = true;
Resource resource = getResource(uri, false);
// if it was meant to be deleted, an attempt would have been made to remove it. That may
// have been prevented if it was dirty. If it isn't now, then delete it. Otherwise,
// block again
if ((resource != null) && isDirty(resource)) {
result = false;
Activator.log.warn("Attempt to delete a dirty CDO resource: " + uri); //$NON-NLS-1$
}
return result;
}
@Override
public boolean deleteResource(URI uri) {
Resource res = getResource(uri, false);
boolean result = res instanceof CDOResource;
if (!result) {
// not a CDO resource. Default behaviour
result = super.deleteResource(uri);
} else {
try {
res.delete(null);
if (res.getResourceSet() != null) {
res.unload();
res.getResourceSet().getResources().remove(res);
}
} catch (IOException e) {
Activator.log.error(e);
}
}
return result;
}
@Override
public void save(IProgressMonitor monitor) throws IOException {
CDOView view = getCDOView();
CDOTransaction transaction = null;
Collection<CDOObject> updates;
if ((view instanceof CDOTransaction) && view.isDirty()) {
// collect updated objects to post-process for cross-unit references
transaction = (CDOTransaction) view;
updates = ImmutableList.<CDOObject> builder() //
.addAll(transaction.getNewObjects().values()) //
.addAll(transaction.getDirtyObjects().values()) //
.build();
} else {
updates = Collections.emptyList();
}
SubMonitor sub = SubMonitor.convert(monitor, updates.isEmpty() ? 1 : 2);
super.save(sub.newChild(1));
if (!updates.isEmpty()) {
CDOControlModeParticipant control = new CDOControlModeParticipant();
CDOControlModeParticipant.IUpdate run = CDOControlModeParticipant.IUpdate.EMPTY;
for (CDOObject next : updates) {
// Resources don't have cross-references that we need to refactor
if (!(next instanceof CDOResourceNode)) {
EObject object = CDOUtil.getEObject(next);
if (object != null) {
for (EReference xref : object.eClass().getEAllReferences()) {
// do include containment references because we may have added a model
// element and controlled it in the same transaction
if (xref.isChangeable() && !xref.isDerived() && !xref.isTransient()) {
run = run.chain(control.getProxyCrossReferencesUpdate(object, xref));
}
}
}
}
}
if (!run.isEmpty()) {
run.apply();
try {
transaction.commit(sub.newChild(1));
} catch (CommitException e) {
Activator.log.error("Follow-up commit after save failed.", e); //$NON-NLS-1$
}
} else {
sub.done();
}
}
}
//
// Nested types
//
/**
* CDO doesn't permit resources to be removed from the resource set if they are {@linkplain CDOState#DIRTY dirty}, so this specialized list
* prevents that.
*/
private class SafeResourceList extends ResourcesEList<Resource> {
private static final long serialVersionUID = 1L;
@Override
public boolean remove(Object object) {
boolean result = !(object instanceof CDOResource) || !isDirty((CDOResource) object);
if (result) {
result = super.remove(object);
}
return result;
}
}
}