blob: 4499d586afb76171e452cf53d60c192b8b19f21d [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2013, 2017 CEA LIST 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
* Eike Stepper (CEA) - bug 466520
*****************************************************************************/
package org.eclipse.papyrus.cdo.internal.ui.editors;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.lock.CDOLockState;
import org.eclipse.emf.cdo.common.revision.CDOIDAndBranch;
import org.eclipse.emf.cdo.dawn.gmf.util.DawnDiagramUpdater;
import org.eclipse.emf.cdo.dawn.spi.DawnState;
import org.eclipse.emf.cdo.transaction.CDOTransactionConflictEvent;
import org.eclipse.emf.cdo.transaction.CDOTransactionFinishedEvent;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.emf.cdo.view.CDOViewLocksChangedEvent;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.notify.impl.BasicNotifierImpl.EObservableAdapterList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.workspace.IWorkspaceCommandStack;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.papyrus.cdo.core.resource.CDOUndoContext;
import org.eclipse.papyrus.cdo.internal.core.CDOUtils;
import org.eclipse.papyrus.cdo.internal.ui.Activator;
import org.eclipse.papyrus.cdo.internal.ui.decorators.CDOStateAdapter;
import org.eclipse.papyrus.cdo.internal.ui.decorators.CDOStateLabelDecorator;
import org.eclipse.papyrus.cdo.internal.ui.l10n.Messages;
import org.eclipse.papyrus.cdo.internal.ui.util.UIUtil;
import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.core.services.ServicesRegistry;
import org.eclipse.papyrus.infra.widgets.toolbox.notification.Type;
import org.eclipse.papyrus.infra.widgets.toolbox.notification.builders.NotificationBuilder;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* This is the PapyrusTransactionListener type. Enjoy.
*/
public class PapyrusTransactionListener implements IListener {
private final ServicesRegistry services;
private final Predicate<Adapter> shouldPropagate = new Predicate<Adapter>() {
@Override
public boolean apply(Adapter input) {
return (input instanceof ECrossReferenceAdapter) //
|| (input instanceof EContentAdapter) //
|| (input instanceof ChangeRecorder);
}
};
private final Set<Adapter> contentAdapters;
public PapyrusTransactionListener(ServicesRegistry services, ResourceSet resourceSet) {
super();
this.services = services;
contentAdapters = Sets.newHashSet(Iterables.filter(resourceSet.eAdapters(), shouldPropagate));
((EObservableAdapterList) resourceSet.eAdapters()).addListener(new EObservableAdapterList.Listener() {
@Override
public void added(Notifier notifier, Adapter adapter) {
if (adapter instanceof EContentAdapter) {
contentAdapters.add(adapter);
}
}
@Override
public void removed(Notifier notifier, Adapter adapter) {
contentAdapters.remove(adapter);
}
});
}
@Override
public void notifyEvent(IEvent event) {
if (event instanceof CDOViewInvalidationEvent) {
handleViewInvalidationEvent((CDOViewInvalidationEvent) event);
} else if (event instanceof CDOTransactionConflictEvent) {
handleTransactionConflictEvent((CDOTransactionConflictEvent) event);
} else if (event instanceof CDOViewLocksChangedEvent) {
handleLocksChangedEvent((CDOViewLocksChangedEvent) event);
} else if (event instanceof CDOTransactionFinishedEvent) {
handleTransactionFinishedEvent((CDOTransactionFinishedEvent) event);
} else {
handleEvent(event);
}
}
protected void handleTransactionConflictEvent(CDOTransactionConflictEvent event) {
if (UIUtil.ensureUIThread(this, event)) {
CDOObject cdoObject = event.getConflictingObject();
EObject element = CDOUtil.getEObject(cdoObject);
View view = DawnDiagramUpdater.findViewByContainer(element);
if (view == null) {
// it's not actually a view that is conflicted, but a model
// element
if (cdoObject.cdoConflict()) {
CDOStateAdapter.setState(element, DawnState.CONFLICT);
CDOStateLabelDecorator.fireLabelUpdates();
}
}
}
}
protected void handleLocksChangedEvent(CDOViewLocksChangedEvent event) {
if (UIUtil.ensureUIThread(this, event)) {
Map<EObject, DawnState> changedObjects = new HashMap<EObject, DawnState>();
CDOView cdoView = event.getSource();
for (CDOLockState state : event.getLockStates()) {
Object lockedObject = state.getLockedObject();
CDOID id;
if (lockedObject instanceof CDOID) {
id = (CDOID) lockedObject;
} else if (lockedObject instanceof CDOIDAndBranch) {
id = ((CDOIDAndBranch) lockedObject).getID();
} else {
throw new RuntimeException("Unexpected object type: " //$NON-NLS-1$
+ lockedObject);
}
if (id != null) {
CDOObject object = cdoView.getObject(id);
EObject element = CDOUtil.getEObject(object);
View view = DawnDiagramUpdater.findViewByContainer(element);
if (view == null) {
// it's not actually a view that is locked, but a model
// element
if (CDOUtils.isLocked(object, true)) {
changedObjects.put(element, DawnState.LOCKED_REMOTELY);
} else {
changedObjects.put(element, DawnState.CLEAN);
}
}
}
}
handleLocks(changedObjects);
}
}
/**
* @precondition The current thread is the UI thread
*/
void handleLocks(Map<EObject, DawnState> changedObjects) {
if (!changedObjects.isEmpty()) {
for (Map.Entry<EObject, DawnState> next : changedObjects.entrySet()) {
EObject element = next.getKey();
DawnState state = next.getValue();
CDOStateAdapter.setState(element, state);
}
CDOStateLabelDecorator.fireLabelUpdates();
}
}
protected void handleViewInvalidationEvent(CDOViewInvalidationEvent event) {
if (UIUtil.ensureUIThread(this, event)) {
// process changed objects to ensure propagation of adapters to new objects.
// CDO doesn't tell us about new objects, but it does tell us that their
// containers changed, so we have to go fishing
for (EObject next : CDOUtils.getEObjects(event.getDirtyObjects())) {
for (EObject possiblyNew : next.eContents()) {
synchronizeAdapters(possiblyNew);
}
}
// also, purge any operations from the local history that changed the objects that
// were changed by the remote transaction
handleOperationHistory(event);
}
}
protected void synchronizeAdapters(EObject object) {
object.eAdapters().addAll(Sets.difference(contentAdapters, ImmutableSet.copyOf(object.eAdapters())));
}
protected void handleOperationHistory(CDOViewInvalidationEvent event) {
IOperationHistory history = getOperationHistory();
if (history != null) {
Iterable<EObject> affectedObjects = CDOUtils.getEObjects(Iterables.concat(event.getDirtyObjects(), event.getDetachedObjects()));
if (!Iterables.isEmpty(affectedObjects)) {
IUndoContext context = new CDOUndoContext(affectedObjects);
if (context != null) {
IUndoableOperation undoOperation = history.getUndoOperation(context);
IUndoableOperation redoOperation = history.getRedoOperation(context);
if ((undoOperation != null) || (redoOperation != null)) {
// notify listeners of the impending changes. We do this before flushing
// the undo context because we depend on the fact that the UndoActionHandler
// and RedoActionHandler only react when the operation is their current
// operation (which means it mustn't have the context removed yet) *and* the
// update is posted asynchronously
if (undoOperation != null) {
history.operationChanged(undoOperation);
}
if (redoOperation != null) {
history.operationChanged(redoOperation);
}
// flush the history for this editor
history.dispose(getEditorUndoContext(), true, true, false);
NotificationBuilder.createAsyncPopup(Messages.PapyrusTransactionListener_0).setType(Type.INFO).run();
}
}
}
}
}
protected IOperationHistory getOperationHistory() {
IOperationHistory result = null;
try {
EditingDomain domain = services.getService(TransactionalEditingDomain.class);
if (domain != null) {
CommandStack stack = domain.getCommandStack();
if (stack instanceof IWorkspaceCommandStack) {
result = ((IWorkspaceCommandStack) stack).getOperationHistory();
}
}
} catch (ServiceException e) {
Activator.log.error("Failed to get editor operation history from service registry.", e); //$NON-NLS-1$
}
return result;
}
protected IUndoContext getEditorUndoContext() {
IUndoContext result = null;
try {
result = services.getService(IUndoContext.class);
} catch (ServiceException e) {
Activator.log.error("Failed to get editor undo context from service registry.", e); //$NON-NLS-1$
}
return result;
}
protected void handleTransactionFinishedEvent(CDOTransactionFinishedEvent event) {
// review all conflicts and locks
Map<EObject, DawnState> stateUpdates = Maps.newHashMap();
for (CDOStateAdapter next : CDOStateAdapter.getAll(event.getSource().getResourceSet())) {
Object target = next.getTarget();
if (target instanceof EObject) {
EObject object = (EObject) target;
stateUpdates.put(object, CDOUtils.computeState(object));
}
}
for (Map.Entry<EObject, DawnState> next : stateUpdates.entrySet()) {
CDOStateAdapter.setState(next.getKey(), next.getValue());
}
CDOStateLabelDecorator.fireLabelUpdates();
}
protected void handleEvent(IEvent event) {
// pass
}
}