blob: 8adf3d8035cc6b765a921a43ac314ed78560fdc1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 Ericsson 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:
* Miles Parker (Tasktop Technologies) - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.reviews.core.spi.remote.emf;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationChain;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.common.notify.impl.NotificationChainImpl;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.mylyn.reviews.core.spi.remote.AbstractRemoteConsumer;
/**
* Manages the interaction between a remote API and a local EMF object. There can be only one instance of a consumer for
* a given model object or remote object per factory.
* <p>
* After obtaining a consumer using one of the {@link RemoteEmfConsumer} <i>AbstractRemoteEmfFactory#getConsumer()</i>
* methods, call {@link RemoteEmfConsumer#retrieve(boolean)} to request an update. Any registered
* {@link IRemoteEmfObserver}s will then receive an {@link IRemoteEmfObserver#updated(EObject, Object, boolean)} event
* regardless of whether or not the actual state changed.
*
* @author Miles Parker
*/
public class RemoteEmfConsumer<EParentObjectType extends EObject, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>
extends AbstractRemoteConsumer {
private final AbstractRemoteEmfFactory<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> factory;
private RemoteKeyType remoteKey;
private RemoteType remoteObject;
private final EParentObjectType parentObject;
private EObjectType modelObject;
private LocalKeyType localKey;
private final Collection<IRemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType>> remoteEmfObservers;
private boolean pulling;
private boolean retrieving;
private boolean sending;
boolean userJob;
boolean systemJob;
//Can set to false for running with in another job
boolean asynchronous = true;
private class ConsumerAdapter extends AdapterImpl {
@Override
public void notifyChanged(Notification msg) {
if (msg instanceof RemoteNotification) {
RemoteNotification remoteMessage = (RemoteNotification) msg;
boolean notifyParent = remoteMessage.isMember()
&& msg.getNotifier() == parentObject
&& ((msg.getNewValue() == modelObject && (msg.getEventType() == RemoteNotification.REMOTE_MEMBER_CREATE || msg.getEventType() == RemoteNotification.REMOTE_MEMBER_FAILURE)) || modelObject instanceof Collection);
boolean notifyChild = !remoteMessage.isMember() && msg.getNotifier() == modelObject;
if (notifyParent || notifyChild) {
synchronized (remoteEmfObservers) {
for (IRemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> listener : remoteEmfObservers) {
switch (msg.getEventType()) {
case RemoteNotification.REMOTE_MEMBER_CREATE:
listener.created(parentObject, modelObject);
break;
case RemoteNotification.REMOTE_MEMBER_UPDATING:
case RemoteNotification.REMOTE_UPDATING:
listener.updating(parentObject, modelObject);
break;
case RemoteNotification.REMOTE_MEMBER_UPDATE:
case RemoteNotification.REMOTE_UPDATE:
listener.updated(parentObject, modelObject, remoteMessage.isModification());
break;
case RemoteNotification.REMOTE_SENDING:
listener.sending(parentObject, modelObject);
break;
case RemoteNotification.REMOTE_SEND:
listener.sent(parentObject, modelObject);
break;
case RemoteNotification.REMOTE_MEMBER_FAILURE:
case RemoteNotification.REMOTE_FAILURE:
listener.failed(parentObject, modelObject, remoteMessage.getStatus());
}
}
}
}
}
}
}
ConsumerAdapter adapter = new ConsumerAdapter();
RemoteEmfConsumer(
AbstractRemoteEmfFactory<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> factory,
EParentObjectType parent, EObjectType modelObject, LocalKeyType localKey, RemoteType remoteObject,
RemoteKeyType remoteKey) {
this.parentObject = parent;
this.modelObject = modelObject;
this.remoteObject = remoteObject;
this.remoteKey = remoteKey;
this.localKey = localKey;
this.factory = factory;
if (remoteKey == null && remoteObject != null) {
this.remoteKey = factory.getRemoteKey(remoteObject);
}
if (localKey == null && modelObject != null) {
this.localKey = factory.getLocalKey(null, modelObject);
}
if (modelObject instanceof EObject) {
((EObject) modelObject).eAdapters().add(adapter);
} else if (parent != null) {
parent.eAdapters().add(adapter);
}
remoteEmfObservers = new ArrayList<IRemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType>>();
}
/**
* Pulls the results from the factory, populating the remote object with the latest state from the remote API.
* Blocks until the remote API call completes. Does nothing if a retrieval is already occurring.
* <em>This method must not be called from the UI thread.</em>
*
* @param force
* pull from remote even when API doesn't require
* @param monitor
* @throws CoreException
*/
@Override
public void pull(boolean force, IProgressMonitor monitor) throws CoreException {
pulling = true;
if (remoteObject != null && remoteKey == null) {
remoteKey = factory.getRemoteKey(remoteObject);
}
if (remoteKey == null && localKey != null) {
remoteKey = factory.getRemoteKeyForLocalKey(parentObject, localKey);
}
//Pull when "needed" or forced, but not when we don't have a remote key as that would be pointless.
if ((factory.isPullNeeded(parentObject, modelObject, remoteObject) || force == true) && remoteKey != null) {
getFactory().getService().modelExec(new Runnable() {
public void run() {
parentObject.eNotify(new RemoteENotificationImpl((InternalEObject) parentObject,
RemoteNotification.REMOTE_MEMBER_UPDATING, factory.getParentReference(), modelObject));
if (modelObject instanceof EObject) {
((EObject) modelObject).eNotify(new RemoteENotificationImpl((InternalEObject) modelObject,
RemoteNotification.REMOTE_MEMBER_UPDATING, null, null));
}
}
}, false);
try {
remoteObject = factory.pull(parentObject, remoteKey, monitor);
if (localKey == null) {
localKey = factory.getLocalKeyForRemoteObject(remoteObject);
}
pulling = false;
} catch (final CoreException e) {
getFactory().getService().modelExec(new Runnable() {
public void run() {
parentObject.eNotify(new RemoteENotificationImpl((InternalEObject) parentObject,
RemoteNotification.REMOTE_MEMBER_FAILURE, factory.getParentReference(), null,
e.getStatus()));
if (modelObject instanceof EObject) {
((EObject) modelObject).eNotify(new RemoteENotificationImpl((InternalEObject) modelObject,
RemoteNotification.REMOTE_FAILURE, null, null, e.getStatus()));
}
}
}, false);
throw e;
}
}
pulling = false;
}
/**
* Returns true whenever the consumer is pulling from Remote API to update the remote state, that is after
* {@link #retrieve(boolean)} has been called but before the {@link RemoteEmfConsumer#applyModel(boolean)} call has
* occurred.
*/
public boolean isPulling() {
return pulling;
}
@Override
public void push(boolean force, IProgressMonitor monitor) throws CoreException {
if (remoteObject == null) {
throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.mylyn.reviews.core",
"Tried to push without a remote object!"));
}
if (modelObject == null) {
throw new CoreException(new Status(IStatus.ERROR, "org.eclipse.mylyn.reviews.core",
"Tried to push without a model object!"));
}
if (modelObject instanceof EObject) {
getFactory().getService().modelExec(new Runnable() {
@Override
public void run() {
((EObject) modelObject).eNotify(new RemoteENotificationImpl((InternalEObject) modelObject,
RemoteNotification.REMOTE_SENDING, null, null));
}
});
}
if (factory.isPushNeeded(parentObject, modelObject, remoteObject)) {
factory.push(remoteObject, monitor);
}
if (modelObject instanceof EObject) {
getFactory().getService().modelExec(new Runnable() {
@Override
public void run() {
((EObject) modelObject).eNotify(new RemoteENotificationImpl((InternalEObject) modelObject,
RemoteNotification.REMOTE_SEND, null, null));
}
});
}
}
@Override
public void applyRemote(boolean force) {
factory.updateRemote(parentObject, modelObject, remoteObject);
}
/**
* Apply the remote object to the local model object.
* <em>This method must be called from the EMF managed (e.g.) UI thread.</em>
*
* @param force
* apply the changes even when API doesn't require
* @throws CoreException
*/
@Override
public void applyModel(boolean force) {
NotificationChain msgs = new NotificationChainImpl();
EReference reference = factory.getParentReference();
boolean modified = false;
if (remoteObject != null) {
if (modelObject == null || factory.isCreateModelNeeded(parentObject, modelObject)
|| (reference.isMany() && (((Collection<?>) parentObject.eGet(reference)).size() == 0))) {
modified = true;
modelObject = factory.createModel(parentObject, remoteObject);
if (reference.isMany()) {
if (modelObject instanceof Collection) {
((EList<EObjectType>) parentObject.eGet(reference)).addAll((Collection<EObjectType>) modelObject);
} else {
((EList<EObjectType>) parentObject.eGet(reference)).add(modelObject);
}
} else {
parentObject.eSet(reference, modelObject);
}
if (modelObject instanceof EObject) {
((EObject) modelObject).eSet(factory.getLocalKeyAttribute(),
factory.getLocalKeyForRemoteObject(remoteObject));
if (!((EObject) modelObject).eAdapters().contains(adapter)) {
((EObject) modelObject).eAdapters().add(adapter);
}
}
msgs.add(new RemoteENotificationImpl((InternalEObject) parentObject,
RemoteNotification.REMOTE_MEMBER_CREATE, reference, modelObject));
}
if (factory.isUpdateModelNeeded(parentObject, modelObject, remoteObject) || force) {
modified |= factory.updateModel(parentObject, modelObject, remoteObject);
}
}
msgs.add(new RemoteENotificationImpl((InternalEObject) parentObject, RemoteNotification.REMOTE_MEMBER_UPDATE,
reference, modelObject, modified));
if (modelObject instanceof EObject) {
msgs.add(new RemoteENotificationImpl((InternalEObject) modelObject, RemoteNotification.REMOTE_UPDATE, null,
null, modified));
}
retrieving = false;
msgs.dispatch();
}
/**
* Returns true whenever the consumer is updating model state, that is after a {@link #retrieve(boolean)} has been
* called and until immediately after the {@link IRemoteEmfObserver#updated(EObject, Object, boolean)} has been
* called.
*/
public boolean isRetrieving() {
return retrieving;
}
/**
* Performs a complete remote request, result application and listener notification against the factory. This is the
* method primary factory consumers will be interested in. The method will asynchronously (as defined by remote
* service implementation):<li>
* <ol>
* Notify any registered {@link IRemoteEmfObserver}s that the object is
* {@link IRemoteEmfObserver#updating(EObject, Object)}.
* </ol>
* <ol>
* Call the remote API, retrieving the results into a local object representing the contents of the remote object.
* </ol>
* <ol>
* If the object does not yet exist, one will be created and added to the appropriate parent object.
* </ol>
* <ol>
* Notify objects of any changes via the standard EMF notification mechanisms. (As a by-product of the above step.)
* </ol>
* <ol>
* Notify any registered {@link IRemoteEmfObserver}s of object creation or update. (An update is notified even if
* object state does not change.)
* </ol>
* </li>
*
* @param force
* Forces pull and update, even if factory methods
* {@link AbstractRemoteEmfFactory#isPullNeeded(EObject, Object, Object)} and/or
* {@link AbstractRemoteEmfFactory#isUpdateModelNeeded(EObject, Object, Object)} return false.
*/
public void retrieve(boolean force) {
if (retrieving) {
return;
}
retrieving = true;
getFactory().getService().retrieve(this, force);
}
/**
* Performs a complete remote send, updating the remote value and then pushing it to the Remote API.
* <ol>
*
* @param force
* Forces update and push, even if factory methods
* {@link AbstractRemoteEmfFactory#isPushNeeded(EObject, Object, Object)} returns false.
*/
public void send(boolean force) {
if (sending) {
return;
}
sending = true;
getFactory().getService().send(this, force);
}
/**
* Notifies the consumer that a failure has occurred while performing a retrieval. (Consumers should generally
* handle update and failure notifications through the {@link IRemoteEmfObserver#failed(IStatus)} method instead.)
*/
@Override
public void notifyDone(IStatus status) {
retrieving = false;
sending = false;
}
/**
* Unregisters all listeners and adapters.
*/
@Override
public void dispose() {
retrieving = false;
parentObject.eAdapters().remove(adapter);
if (modelObject instanceof EObject) {
((EObject) modelObject).eAdapters().remove(adapter);
}
synchronized (remoteEmfObservers) {
remoteEmfObservers.clear();
}
getFactory().removeConsumer(this);
if (getModelObject() instanceof EObject) {
getFactory().getFactoryProvider().close((EObject) getModelObject());
}
modelObject = null;
remoteObject = null;
}
/**
* Adds an observer to this consumer. Updates the consumer field for {@link RemoteEmfObserver}s.
*
* @param observer
* The observer to add
*/
public void addObserver(IRemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> observer) {
if (observer instanceof RemoteEmfObserver) {
RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> remoteEmfObserver = (RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType>) observer;
if (remoteEmfObserver.getConsumer() != null && remoteEmfObserver.getConsumer() != this) {
remoteEmfObserver.getConsumer().removeObserver(remoteEmfObserver);
}
remoteEmfObserver.internalSetConsumer(this);
}
synchronized (remoteEmfObservers) {
remoteEmfObservers.add(observer);
}
if (modelObject instanceof EObject) {
if (!((EObject) modelObject).eAdapters().contains(adapter)) {
((EObject) modelObject).eAdapters().add(adapter);
}
} else if (parentObject != null) {
if (!((EObject) parentObject).eAdapters().contains(adapter)) {
((EObject) parentObject).eAdapters().add(adapter);
}
}
}
/**
* Adds an observer to this consumer. Updates the consumer field for {@link RemoteEmfObserver}s.
*
* @param observer
* The observer to remove
*/
public void removeObserver(
IRemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> observer) {
if (observer instanceof RemoteEmfObserver) {
RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> remoteEmfObserver = (RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType>) observer;
if (remoteEmfObserver.getConsumer() == this) {
remoteEmfObserver.internalSetConsumer(null);
}
}
synchronized (remoteEmfObservers) {
remoteEmfObservers.remove(observer);
}
release();
}
public void release() {
if (remoteEmfObservers.size() == 0) {
dispose();
}
}
public void updateObservers() {
NotificationChain msgs = new NotificationChainImpl();
msgs.add(new RemoteENotificationImpl((InternalEObject) parentObject, RemoteNotification.REMOTE_MEMBER_UPDATE,
factory.getParentReference(), modelObject, false));
if (modelObject instanceof EObject) {
msgs.add(new RemoteENotificationImpl((InternalEObject) modelObject, RemoteNotification.REMOTE_UPDATE, null,
null, false));
}
msgs.dispatch();
}
/**
* Returns the factory that providing services and objects for this consumer.
*/
public AbstractRemoteEmfFactory<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> getFactory() {
return factory;
}
public void open() {
Object object = getFactory().open(parentObject, localKey);
if (object instanceof EObject) {
modelObject = (EObjectType) object;
getFactory().getService().modelExec(new Runnable() {
@Override
public void run() {
if (!((EObject) modelObject).eAdapters().contains(adapter)) {
((EObject) modelObject).eAdapters().add(adapter);
}
}
}, true);
}
}
public void save() {
if (getModelObject() instanceof EObject) {
getFactory().getFactoryProvider().save();
getFactory().getFactoryProvider().save((EObject) getModelObject());
}
}
/**
* Returns the parent object for this consumer.
*/
public EParentObjectType getParentObject() {
return parentObject;
}
/**
* Returns the model object for this consumer, if one has been obtained through the {@link #retrieve(boolean)}
* method or supplied when any object obtained this consumer.
*/
public EObjectType getModelObject() {
return modelObject;
}
/**
* Returns the local key supplied by the consumer, or the local remote key if it can be inferred from the remote key
* or remote object.
*/
public LocalKeyType getLocalKey() {
if (localKey != null) {
return localKey;
} else if (remoteKey != null) {
return getFactory().getLocalKeyForRemoteKey(remoteKey);
} else if (remoteObject != null) {
return getFactory().getLocalKeyForRemoteObject(remoteObject);
}
return null;
}
/**
* Returns the remote key for this consumer.
*/
public RemoteKeyType getRemoteKey() {
return remoteKey;
}
/**
* Returns the remote object that maps to this consumer's model object, local key or remote key, if one has been
* supplied or obtained using the remote key.
*
* @return
*/
public RemoteType getRemoteObject() {
return remoteObject;
}
/**
* Should only be called by RemoteEmfFactory.
*
* @param remoteObject
*/
void setRemoteObject(RemoteType remoteObject) {
if (!factory.getLocalKeyForRemoteObject(remoteObject).equals(getLocalKey())) {
throw new RuntimeException(
"Internal Error. Tried to set a remote object that doesn't match existing local key or object.");
}
this.remoteObject = remoteObject;
}
/**
* Should only be called by RemoteEmfFactory.
*
* @param remoteObject
*/
void setRemoteKey(RemoteKeyType remoteKey) {
if (!factory.getLocalKeyForRemoteKey(remoteKey).equals(getLocalKey())) {
throw new RuntimeException(
"Internal Error. Tried to set a remote object that doesn't match existing local key or object.");
}
this.remoteKey = remoteKey;
}
@Override
public String getDescription() {
return "Retrieving " + factory.getModelDescription(getParentObject(), getModelObject(), getLocalKey());
}
@Override
public boolean isUserJob() {
return userJob;
}
public void setUiJob(boolean userJob) {
this.userJob = userJob;
}
@Override
public boolean isSystemJob() {
return systemJob;
}
public void setSystemJob(boolean systemJob) {
this.systemJob = systemJob;
}
@Override
public boolean isAsynchronous() {
return getFactory().isAsynchronous() && asynchronous;
}
public void setAsynchronous(boolean asynchronous) {
this.asynchronous = asynchronous;
}
}