Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote')
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteConsumer.java19
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteFactoryProvider.java14
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteService.java33
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/JobRemoteService.java72
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/AbstractRemoteEmfFactory.java416
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/IRemoteEmfObserver.java72
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfConsumer.java422
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfObserver.java87
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/ReviewsRemoteFactoryProvider.java47
-rw-r--r--org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/review/IReviewRemoteFactoryProvider.java36
10 files changed, 851 insertions, 367 deletions
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteConsumer.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteConsumer.java
index 78841461c..31323b043 100644
--- a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteConsumer.java
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteConsumer.java
@@ -28,21 +28,26 @@ public abstract class AbstractRemoteConsumer {
* Override to perform the request against remote API, storing the results of that request as state (e.g. as
* member(s) field of an implementing class). May be long-running and should be able to safely fail.
*
+ * @param force
+ * pull from remote even when API doesn't require
* @param monitor
* @throws CoreException
*/
- protected abstract void retrieve(IProgressMonitor monitor) throws CoreException;
+ protected abstract void pull(boolean force, IProgressMonitor monitor) throws CoreException;
/**
* Override to apply the remotely obtained state to a local model object. This method is expected to execute
* <em>very</em> quickly, as the typical implementation will occur on the UI thread.
*
+ * @param force
+ * apply the changes even when API doesn't require
* @throws CoreException
*/
- protected abstract void apply();
+ protected abstract void applyModel(boolean force);
/**
- * Provides notification of failure. See {@link AbstractRemoteService#execute(AbstractRemoteProcess)} for details.
+ * Provides notification of failure. See {@link AbstractRemoteService#retrieve(AbstractRemoteProcess, boolean)} for
+ * details.
*/
public abstract void notifyDone(IStatus status);
@@ -64,4 +69,12 @@ public abstract class AbstractRemoteConsumer {
* @return
*/
public abstract String getDescription();
+
+ /**
+ * Returns {@link #getDescription()}.
+ */
+ @Override
+ public String toString() {
+ return getDescription();
+ }
}
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteFactoryProvider.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteFactoryProvider.java
index 10f8ef0ab..ab6be542c 100644
--- a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteFactoryProvider.java
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteFactoryProvider.java
@@ -18,17 +18,21 @@ package org.eclipse.mylyn.reviews.core.spi.remote;
*/
public abstract class AbstractRemoteFactoryProvider {
- private final AbstractRemoteService service;
+ private AbstractRemoteService service;
- public AbstractRemoteFactoryProvider(JobRemoteService service) {
- this.service = service;
+ public void modelExec(Runnable runnable, boolean block) {
+ if (service != null) {
+ service.modelExec(runnable, block);
+ } else {
+ throw new RuntimeException("Internal Error: Connector must supply a service for execution.");
+ }
}
public AbstractRemoteService getService() {
return service;
}
- public void dispose() {
- service.dispose();
+ public void setService(AbstractRemoteService service) {
+ this.service = service;
}
}
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteService.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteService.java
index 212b54544..a557fe07f 100644
--- a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteService.java
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/AbstractRemoteService.java
@@ -21,17 +21,17 @@ public abstract class AbstractRemoteService {
/**
* Implementors should invoke the the
- * {@link AbstractRemoteConsumer#retrieve(org.eclipse.core.runtime.IProgressMonitor)} and
- * {@link AbstractRemoteConsumer#apply()} methods for the supplied process in the following well defined way. This
- * method is expected to return very quickly and <em>must not block</em> under any circumstances, as it must be
- * safely callable from the UI thread. Implementors should support one full cycle of invocation:
+ * {@link AbstractRemoteConsumer#pull(boolean, org.eclipse.core.runtime.IProgressMonitor)} and
+ * {@link AbstractRemoteConsumer#applyModel(boolean)} methods for the supplied process in the following well defined
+ * way. This method is expected to return very quickly and <em>must not block</em> under any circumstances, as it
+ * must be safely callable from the UI thread. Implementors should support one full cycle of invocation:
* <ol>
- * <li>The retrieve phase of the process is invoked. This invocation must be asynchronous if
+ * <li>The pull phase of the process is invoked. This invocation must be asynchronous if
* {@link AbstractRemoteConsumer#isAsynchronous()} is true.</li>
* <li>If a failure occurs or a core exception is thrown during the request phase,
* {@link AbstractRemoteConsumer#notifyDone(org.eclipse.core.runtime.IStatus)} is invoked with the exception.</li>
- * <li>Otherwise, when the request process returns, the {@link AbstractRemoteConsumer#apply()} phase of the process
- * is invoked. (In the case of the UI implementations, this might occur on the UI thread.)</li>
+ * <li>Otherwise, when the request process returns, the {@link AbstractRemoteConsumer#applyModel(boolean)} phase of
+ * the process is invoked. (In the case of the UI implementations, this might occur on the UI thread.)</li>
* <li>If a failure occurs during the create phase, notifyDone may optionally be invoked to report the failure.</li>
* <li>If both phases complete successfully,
* {@link AbstractRemoteConsumer#notifyDone(org.eclipse.core.runtime.IStatus)} is invoked on the process with an OK
@@ -39,16 +39,31 @@ public abstract class AbstractRemoteService {
* </ol>
*
* @param process
+ * The consumer process to execute
+ * @param force
+ * Invoke the pull and apply processes even if the relevant APIs indicate that they are not needed.
*/
- public abstract void execute(final AbstractRemoteConsumer process);
+ public abstract void retrieve(final AbstractRemoteConsumer process, boolean force);
/**
* Supports apply and notification services executed against a specific thread. (For example, the Remote Ui Service
* overrides this to force all model update events to occur on the UI thread, as best EMF practices require.)
*
* @param runnable
+ * @param block
+ * true if the model execution should block until complete, false if it can complete in seperate thread
*/
- public abstract void modelExec(Runnable runnable);
+ public abstract void modelExec(Runnable runnable, boolean block);
+
+ /**
+ * Supports apply and notification services executed against a specific thread. (For example, the Remote Ui Service
+ * overrides this to force all model update events to occur on the UI thread, as best EMF practices require.)
+ *
+ * @param runnable
+ */
+ public void modelExec(Runnable runnable) {
+ modelExec(runnable, true);
+ }
/**
* Returns true if any consumers are currently being managed.
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/JobRemoteService.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/JobRemoteService.java
index 6ee9859a6..ac55eea78 100644
--- a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/JobRemoteService.java
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/JobRemoteService.java
@@ -39,61 +39,65 @@ public class JobRemoteService extends AbstractRemoteService {
}
/**
- * Fully implements the {@link AbstractRemoteService#execute(AbstractRemoteConsumer)} contract:
+ * Fully implements the {@link AbstractRemoteService#retrieve(AbstractRemoteConsumer, boolean)} contract:
* <ol>
* <li>If {@link AbstractRemoteConsumer#isAsynchronous()}, creates and runs a job to
- * {@link AbstractRemoteConsumer#retrieve(org.eclipse.core.runtime.IProgressMonitor)} the remote API data.
+ * {@link AbstractRemoteConsumer#pull(boolean, org.eclipse.core.runtime.IProgressMonitor)} the remote API data.
* Otherwise, simply calls retrieve.</li>
* <li>If a failure occurs, calls {@link AbstractRemoteConsumer#notifyDone(org.eclipse.core.runtime.IStatus)}.</li>
- * <li>Invokes {@link AbstractRemoteConsumer#apply()} inside of a modelExec call, so that extending classes can
- * manage thread context.</li>
+ * <li>Invokes {@link AbstractRemoteConsumer#applyModel(boolean)} inside of a modelExec call, so that extending
+ * classes can manage thread context.</li>
* <li>(No notification occurs in the case of an error while applying.)</li>
* </ol>
*/
@Override
- public void execute(final AbstractRemoteConsumer process) {
+ public void retrieve(final AbstractRemoteConsumer process, final boolean force) {
if (process.isAsynchronous()) {
- final Job job = new Job(process.getDescription()) {
+ new Thread() { //We create a new temporary thread here just to ensure that retrieve returns as quickly as possible
@Override
- protected IStatus run(IProgressMonitor monitor) {
- try {
- process.retrieve(monitor);
- } catch (CoreException e) {
-// return e.getStatus();
- return new Status(IStatus.WARNING, "org.eclipse.mylyn.reviews.core", "Couldn't update model.",
- e);
- } catch (OperationCanceledException e) {
- return Status.CANCEL_STATUS;
- }
- return Status.OK_STATUS;
- }
- };
- job.addJobChangeListener(new JobChangeAdapter() {
- @Override
- public void done(final IJobChangeEvent event) {
- modelExec(new Runnable() {
- public void run() {
- final IStatus result = event.getResult();
- if (result.isOK()) {
- process.apply();
+ public void run() {
+ final Job job = new Job(process.getDescription()) {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ process.pull(force, monitor);
+ } catch (CoreException e) {
+ return new Status(IStatus.WARNING, "org.eclipse.mylyn.reviews.core",
+ "Couldn't update model.", e);
+ } catch (OperationCanceledException e) {
+ return Status.CANCEL_STATUS;
}
- process.notifyDone(event.getResult());
+ return Status.OK_STATUS;
+ }
+ };
+ job.addJobChangeListener(new JobChangeAdapter() {
+ @Override
+ public void done(final IJobChangeEvent event) {
+ modelExec(new Runnable() {
+ public void run() {
+ final IStatus result = event.getResult();
+ if (result.isOK()) {
+ process.applyModel(force);
+ }
+ process.notifyDone(event.getResult());
+ }
+ });
}
});
+ addJob(job);
+ job.schedule();
}
- });
- addJob(job);
- job.schedule();
+ }.start();
} else {
try {
- process.retrieve(new NullProgressMonitor());
+ process.pull(force, new NullProgressMonitor());
} catch (CoreException e) {
process.notifyDone(e.getStatus());
return;
}
modelExec(new Runnable() {
public void run() {
- process.apply();
+ process.applyModel(force);
process.notifyDone(Status.OK_STATUS);
}
});
@@ -144,7 +148,7 @@ public class JobRemoteService extends AbstractRemoteService {
}
@Override
- public void modelExec(Runnable runnable) {
+ public void modelExec(Runnable runnable, boolean block) {
runnable.run();
}
}
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/AbstractRemoteEmfFactory.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/AbstractRemoteEmfFactory.java
index 3648f851c..44a949c95 100644
--- a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/AbstractRemoteEmfFactory.java
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/AbstractRemoteEmfFactory.java
@@ -12,124 +12,268 @@
package org.eclipse.mylyn.reviews.core.spi.remote.emf;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
+import org.eclipse.mylyn.reviews.core.spi.remote.AbstractRemoteFactoryProvider;
import org.eclipse.mylyn.reviews.core.spi.remote.AbstractRemoteService;
-import org.eclipse.mylyn.reviews.core.spi.remote.emf.RemoteEmfConsumer.IObserver;
/**
* Manages a set of model objects representing remote API analogs. While the factory can be accessed directly, generally
* factory services should be requested from a consumer as this ensures that remote and local calls are handled
* appropriately.
* <p>
- * Factory users should usually obtain a fully managed {@link RemoteEmfConsumer} by calling the
- * {@link #consume(String, EObject, Object, IObserver)} method(s). They can then request pulls from the remote API and
- * be notified whenever the model objects are updated. A factory can support unlimited consumers.
+ * Factory users should usually obtain a fully managed {@link RemoteEmfConsumer} by calling the {@link #getConsumer()}
+ * method(s). They can then request model object creation, updates and retrieval from the consumer. Every model object
+ * can have one and only one consumer, even if that consumer was first obtained from the factory using only a remote key
+ * or object. This allows consumers to safely use a single consumer throughout the remote and local model object
+ * life-cycle and to obtain a consumer at any point. Consumers do not need to be disposed or managed explicitly.
* </p>
* <p>
- * Factory implementors should override the {@link AbstractRemoteEmfFactory#retrieve(Object, IProgressMonitor)},
- * {@link #create(EObject, Object)} and {@link #update(EObject, Object, Object)} methods as appropriate.
+ * Factory implementors should override the {@link AbstractRemoteEmfFactory#pull(EObject, Object, IProgressMonitor)},
+ * {@link #createModel(EObject, Object)} and {@link #updateModel(EObject, Object, Object)} methods as appropriate.
* </p>
* <p>
- * Typically, model objects are created using the {@link AbstractRemoteEmfFactory#retrieve(Object, IProgressMonitor)}
- * method. EMF objects can be also be obtained synchronously from an existing remote object using the
+ * Typically, model objects are created using the {@link AbstractRemoteEmfFactory#createModel(EObject, Object)} method.
+ * EMF objects can be also be obtained synchronously from an existing remote object using the
* {@link #get(EObject, Object)} method. Remote objects can be obtained synchronously for appropriate remote keys using
- * {@link #retrieve(Object, IProgressMonitor)}.
+ * {@link #pull(EObject, Object, IProgressMonitor)}.
* </p>
*
* @author Miles Parker
*/
public abstract class AbstractRemoteEmfFactory<EParentObjectType extends EObject, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> {
- Map<EObjectType, RemoteType> remoteForObject = new HashMap<EObjectType, RemoteType>();
+ class UniqueLocalReference<P, L> {
+ P parent;
- Map<EObjectType, RemoteKeyType> remoteKeyForObject = new HashMap<EObjectType, RemoteKeyType>();
+ L localKey;
- Map<RemoteType, EObjectType> objectForRemote = new HashMap<RemoteType, EObjectType>();
+ UniqueLocalReference(P parent, L localKey) {
+ if (parent == null || localKey == null) {
+ throw new RuntimeException("Internal Exception: Parent and local keys must be specified.");
+ }
+ this.parent = parent;
+ this.localKey = localKey;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof AbstractRemoteEmfFactory.UniqueLocalReference) {
+ @SuppressWarnings("rawtypes")
+ UniqueLocalReference reference = (UniqueLocalReference) object; //Cannot test for generic types because of erasure
+ return parent.equals(reference.parent) && localKey.equals(reference.localKey);
+ }
+ return false;
+ }
- Map<LocalKeyType, EObjectType> objectForLocalKey = new HashMap<LocalKeyType, EObjectType>();
+ @Override
+ public int hashCode() {
+ return parent.hashCode() + 31 * localKey.hashCode();
+ }
+ }
- Map<RemoteEmfConsumer.IObserver<EObjectType>, AdapterImpl> adapterForListener = new HashMap<IObserver<EObjectType>, AdapterImpl>();
+ private final Map<UniqueLocalReference<EParentObjectType, LocalKeyType>, RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>> consumerForLocalKey = new HashMap<UniqueLocalReference<EParentObjectType, LocalKeyType>, RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>>();
- AbstractRemoteService service;
+ private final EReference parentReference;
- EReference parentReference;
+ private final EAttribute localKeyAttribute;
- EAttribute localAttribute;
+ private final AbstractRemoteFactoryProvider factoryProvider;
- public AbstractRemoteEmfFactory(AbstractRemoteService service, EReference parentReference, EAttribute localAttribute) {
- this.service = service;
+ /**
+ * Constructs the factory.
+ *
+ * @param factoryProvider
+ * The associated factory provider
+ * @param parentReference
+ * The EMF reference in the parent object that points to model objects; must be available for all parent
+ * object instances, but need not be a containment reference assuming persistence is managed separately
+ * @param localKeyAttribute
+ * The EMF attribute specifying the local key; must be available for all model object instance
+ */
+ public AbstractRemoteEmfFactory(AbstractRemoteFactoryProvider factoryProvider, EReference parentReference,
+ EAttribute localKeyAttribute) {
+ this.factoryProvider = factoryProvider;
this.parentReference = parentReference;
- this.localAttribute = localAttribute;
+ this.localKeyAttribute = localKeyAttribute;
}
/**
- * Returns the remote object that corresponds to a given model object.
+ * Returns a unique consumer for a model object that corresponds to a given remote API key. May be called from any
+ * thread.
*
- * @param object
- * A model object
- * @return An object containing remotely derived state
+ * @param parentObject
+ * The object that contains or will contain the remote object type
+ * @return A key used for locating the remote object from remote API. That object does not have to exist on the
+ * remote API yet, provided appropriate remote key to local key mappings are provided.
*/
- public RemoteType getRemoteObject(EObjectType object) {
- return remoteForObject.get(object);
+ public synchronized RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> getConsumerForRemoteKey(
+ EParentObjectType parentObject, RemoteKeyType remoteKey) {
+ LocalKeyType localKey = getLocalKeyForRemoteKey(remoteKey);
+ RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consumer = findConsumer(
+ parentObject, localKey);
+ if (consumer == null) {
+ consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>(
+ this, parentObject, null, localKey, null, remoteKey);
+ assignConsumer(parentObject, localKey, consumer);
+ } else {
+ consumer.setRemoteKey(remoteKey);
+ }
+ return consumer;
}
/**
- * Returns the model object that corresponds to a given remote API object.
+ * Returns unique consumer for a model object that corresponds to a given remote API object. May be called from any
+ * thread.
*
- * @param object
- * A model object
+ * @param parentObject
+ * The object that contains or will contain the remote object type
* @return An object containing remotely derived state
*/
- public EObjectType getModelObject(RemoteType remote) {
- return objectForRemote.get(remote);
+ public synchronized RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> getConsumerForRemoteObject(
+ EParentObjectType parentObject, RemoteType remoteObject) {
+ RemoteKeyType remoteKey = getRemoteKey(remoteObject);
+ LocalKeyType localKey = getLocalKeyForRemoteKey(remoteKey);
+ RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consumer = findConsumer(
+ parentObject, localKey);
+ if (consumer == null) {
+ consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>(
+ this, parentObject, null, localKey, remoteObject, remoteKey);
+ assignConsumer(parentObject, localKey, consumer);
+ } else {
+ consumer.setRemoteObject(remoteObject);
+ }
+ return consumer;
}
/**
- * Returns the remote object that matches a given model object within a given parent.
+ * Returns unique consumer for a model object that matches a given local key.
+ * <em>Must be called from EMF safe (e.g. UI) thread.</em>
*
- * @param object
- * A model object
+ * @param parentObject
+ * The object that contains or will contain the remote object type
+ * @return A key used for locating the model object from within the model parent object (Typically an EMF id). The
+ * actual matching model object does not have to exist yet. It might for example be created as part of a
+ * subsequent retrieval based on a matching remote key.
* @return An object containing remotely derived state
*/
- public RemoteKeyType getRemoteKey(EParentObjectType parentObject, EObjectType object) {
- return remoteKeyForObject.get(object);
+ public synchronized RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> getConsumerForLocalKey(
+ EParentObjectType parentObject, LocalKeyType localKey) {
+ RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consumer = findConsumer(
+ parentObject, localKey);
+ if (consumer == null) {
+ Object parentField = parentObject.eGet(parentReference);
+ if (parentField instanceof List<?>) {
+ List<?> members = (List<?>) parentField;
+ for (Object object : members) {
+ if (object instanceof EObject) {
+ @SuppressWarnings("unchecked")
+ EObjectType eo = (EObjectType) object;
+ LocalKeyType currentKey = getLocalKey(parentObject, eo);
+ if (currentKey != null && localKey.equals(currentKey)) {
+ EObjectType modelObject = eo;
+ consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>(
+ this, parentObject, modelObject, localKey, null, null);
+ assignConsumer(parentObject, localKey, consumer);
+ break;
+ }
+ }
+ }
+ }
+ if (consumer == null) {
+ consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>(
+ this, parentObject, null, localKey, null, null);
+ assignConsumer(parentObject, localKey, consumer);
+ }
+ }
+ return consumer;
}
/**
- * Returns the local object that matches a given local key. (This will be used to support local sharing of model
- * resources.)
+ * Returns a unique consumer for a model object. <em>Must be called from EMF safe (e.g. UI) thread.</em>
*
- * @param object
- * A model object
+ * @param parentObject
+ * The object that contains the model object
+ * @param modelObject
+ * The model object itself. Must currently exist in the parent.
+ * @return A key used for locating the model object from within the model parent object (Typically an EMF id)
* @return An object containing remotely derived state
*/
- public EObjectType getLocalModelObject(LocalKeyType localKey) {
- return objectForLocalKey.get(localKey);
+ public synchronized RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> getConsumerForModel(
+ EParentObjectType parentObject, EObjectType modelObject) {
+ RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consumer = null;
+ LocalKeyType localKey = getLocalKey(parentObject, modelObject);
+ consumer = findConsumer(parentObject, localKey);
+ if (consumer == null) {
+ consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>(
+ this, parentObject, modelObject, localKey, null, null);
+ assignConsumer(parentObject, localKey, consumer);
+ }
+ return consumer;
+ }
+
+ private RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> findConsumer(
+ EParentObjectType parentObject, LocalKeyType localKey) {
+ UniqueLocalReference<EParentObjectType, LocalKeyType> key = new UniqueLocalReference<EParentObjectType, LocalKeyType>(
+ parentObject, localKey);
+ return consumerForLocalKey.get(key);
+ }
+
+ private RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> assignConsumer(
+ EParentObjectType parentObject, LocalKeyType localKey,
+ RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consumer) {
+ UniqueLocalReference<EParentObjectType, LocalKeyType> key = new UniqueLocalReference<EParentObjectType, LocalKeyType>(
+ parentObject, localKey);
+ return consumerForLocalKey.put(key, consumer);
+ }
+
+ @SuppressWarnings("unchecked")
+ public LocalKeyType getLocalKey(EParentObjectType parentObject, EObjectType modelObject) {
+ if (modelObject instanceof EObject) {
+ EObject eObject = (EObject) modelObject;
+ return (LocalKeyType) eObject.eGet(getLocalKeyAttribute()); //Cannot test for type because of erasure
+ }
+ return null;
}
/**
- * Associates a remote object to it's model object. See {@link RemoteEmfConsumer#apply()}.
+ * Override to infer a local key from the remote object. Should not usually need to be overridden -- by default
+ * returns the local key matching the remote key for the supplied object.
*
- * @param object
- * @param remote
+ * @param remoteObject
+ * The remote object to obtain the local key from
*/
- void associateObjects(EObjectType object, RemoteType remote, LocalKeyType localKey, RemoteKeyType remoteKey) {
- remoteForObject.put(object, remote);
- objectForRemote.put(remote, object);
- objectForLocalKey.put(localKey, object);
- remoteKeyForObject.put(object, remoteKey);
+ public LocalKeyType getLocalKeyForRemoteObject(RemoteType remoteObject) {
+ return getLocalKeyForRemoteKey(getRemoteKey(remoteObject));
}
/**
- * Does the creation of an object require a call to the remote API? If false, no request job is created. True by
- * default (safe case).
+ * Override to infer a local key from a remote key. This method must be properly implemented with a one to one
+ * mapping in order for consumers to function correctly.
+ *
+ * @param remoteKey
+ * The remote key to obtain the local key from
+ */
+ public abstract LocalKeyType getLocalKeyForRemoteKey(RemoteKeyType remoteKey);
+
+ /**
+ * Returns the remote object that matches a given model object within a given parent.
+ *
+ * @param object
+ * A model object
+ * @return An object containing remotely derived state
+ */
+ public abstract RemoteKeyType getRemoteKey(RemoteType remoteObject);
+
+ /**
+ * Returns true if the creation of an object requires a call to the remote API. If false, no request job is created.
+ * True by default (safe case).
*
* @return true by default
*/
@@ -140,137 +284,143 @@ public abstract class AbstractRemoteEmfFactory<EParentObjectType extends EObject
/**
* Override to perform request to remote API. This request is fully managed by remote service and could be invoked
* directly, but is typically invoked through a consumer.
+ * <em>This method may block or fail, and must not be called from UI thread.</em>
*
+ * @param parentObject
+ * The object that contains the model object
* @param remoteKey
* A unique identifier in the target API
* @param monitor
- * @return An object containing remotely derived state. (This might be a remote API object or a local object
- * containing remotly obtained data.)
+ * @return An object containing remotely derived state. (This might be a remote API object itself or any other
+ * object containing remotely obtained data.)
* @throws CoreException
*/
- protected abstract RemoteType retrieve(RemoteKeyType remoteKey, IProgressMonitor monitor) throws CoreException;
+ public abstract RemoteType pull(EParentObjectType parent, RemoteKeyType remoteKey, IProgressMonitor monitor)
+ throws CoreException;
+
+ /**
+ * Override to return true if the remote object state should be requested from the remote API. Override to return
+ * true if there is no way to check the remote model object state without retrieving the whole object. The default
+ * implementation is sufficient if the remote state is immutable -- that is, if the update method is not implemented
+ * at all.
+ *
+ * @param parentObject
+ * The object that contains the model object
+ * @param modelObject
+ * The model object to test
+ * @param remoteObject
+ * A unique identifier in the target API
+ * @param monitor
+ * @return
+ */
+ public boolean isPullNeeded(EParentObjectType parent, EObjectType object, RemoteType remote) {
+ return object == null || remote == null;
+ }
/**
* Override to create an EObject from remote object. (Consumers should use
* {@link #get(EParentObjectType parent, RemoteType remoteObject)}, which ensures that any cached objects will be
* returned instead.)
+ * <em>Must be called from EMF safe (e.g. UI) thread and should have very fast execution time.</em>
*
- * @param parent
- * the parent EMF object that the new child object will be made a member of.
+ * @param parentObject
+ * the parent EMF object that the new child object will be referenced from
* @param remoteObject
* the object representing the remote API request response
* @return a model object
*/
- protected abstract EObjectType create(EParentObjectType parent, RemoteType remoteObject);
+ protected abstract EObjectType createModel(EParentObjectType parentObject, RemoteType remoteObject);
/**
* Updates the values for the supplied EMF object based on any values that have changed in the remote object since
* the last call to {@link #retrieve(String, EObject, EReference, Object)} or {@link #update(Object)}. The object
* must have been previously retrieved using this factory.
+ * <em>Must be called from EMF safe (e.g. UI) thread and should have very fast execution time.</em>
*
- * @param object
- * a model object
- * @return true if the object has changed, false if not
+ * @param parentObject
+ * the parent EMF object that the new child object is referenced from
+ * @param modelObject
+ * The model object to update -- must currently exist in the parent
+ * @return true if the object has changed or the object delta is unknown, false otherwise
*/
- public boolean update(EParentObjectType parent, EObjectType object, RemoteType remoteObject) {
+ public boolean updateModel(EParentObjectType parentObject, EObjectType modelObject, RemoteType remoteObject) {
return false;
}
/**
- * Returns a local object for the remote object, optionally creating a new object if one does not already exist.
- * (Override {@link #create(EParentObjectType parent, RemoteType remoteObject)} to provide implementation for object
- * creation.)
+ * Override to return true if a remote object to model object update should occur, e.g. when the remote object state
+ * is more recent then the model object state. Return true by default and generally doesn't need to be overridden as
+ * most update operations should be inexpensive.
*
+ * @param parentObject
+ * The object that contains the model object
+ * @param modelObject
+ * The model object to test
* @param remoteObject
- * the object representing the remote API request response
- * @return a model object
+ * A unique identifier in the target API
+ * @param monitor
+ * @return
*/
- public final EObjectType get(EParentObjectType parent, RemoteType remoteObject, LocalKeyType localKey,
- boolean create) {
- EObjectType object = getModelObject(remoteObject);
- if (object == null && localKey != null) {
- object = getLocalModelObject(localKey);
- }
- if (object == null && create) {
- object = create(parent, remoteObject);
- associateObjects(object, remoteObject, localKey, null);
- }
- return object;
+ public boolean isUpdateModelNeeded(EParentObjectType parentObject, EObjectType modelObject, RemoteType remote) {
+ return true;
}
/**
- * Returns a local object for the remote object. (Override
- * {@link #create(EParentObjectType parent, RemoteType remoteObject)} to provide implementation for object
- * creation.)
+ * Returns the model object for the supplied key(s), assuming that model object has already been created. This
+ * method can be called from any thread, and does not require any interaction with the remote server or local model
+ * object.
*
* @param remoteObject
* the object representing the remote API request response
* @return a model object
*/
- public final EObjectType get(EParentObjectType parent, RemoteType remoteObject, LocalKeyType localKey) {
- return get(parent, remoteObject, localKey, true);
+ public synchronized final EObjectType get(EParentObjectType parentObject, LocalKeyType localKey,
+ RemoteKeyType remoteKey) {
+ RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consumer = null;
+ if (localKey != null) {
+ consumer = getConsumerForLocalKey(parentObject, localKey);
+ }
+ if (consumer == null && remoteKey != null) {
+ consumer = getConsumerForRemoteKey(parentObject, remoteKey);
+ }
+ if (consumer != null) {
+ return consumer.getModelObject();
+ }
+ return null;
}
/**
- * Returns a local object for the remote object, optionally creating a new object if one does not already exist.
- * (Override {@link #create(EParentObjectType parent, RemoteType remoteObject)} to provide implementation.)
+ * Returns the EMF reference in the parent object that refers to model objects. Returns the EMF attribute specifying
+ * the local key.
*
- * @param remoteObject
- * the object representing the remote API request response
- * @return a model object
+ * @return
*/
- public final EObjectType get(EParentObjectType parent, RemoteType remoteObject) {
- return get(parent, remoteObject, null, true);
+ public EReference getParentReference() {
+ return parentReference;
}
/**
- * Factory method to create a new EMF object factor. This is the method that most factory consumers will be
- * interested in. The method will asynchronously (as defined by remote service implementation):<li>
- * <ol>
- * Call the remote API, retrieving the results into a local (e.g. proxy) object representing the contents of the
- * remote object
- * </ol>
- * <ol>
- * Create a new EMF object and reference it from the supplied parent object.
- * </ol>
- * <ol>
- * Notify the parent object via the EMF notification mechanism. (As a by-product of the above step.)
- * </ol>
- * </li>
+ * Returns the EMF attribute specifying the local key.
*
- * @param description
- * a description of the purpose or context of this retrieval
- * @param parent
- * the object to which the retrieved object will be added
- * @param reference
- * the reference feature to use -- need not be a containment relation
- * @param key
- * the key used by the remote API to identify the object
- * @throws CoreException
+ * @return
*/
- public RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consume(
- String description, EParentObjectType parent, RemoteKeyType remoteKey, LocalKeyType localKey,
- RemoteEmfConsumer.IObserver<EObjectType> consumer) {
- return new RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>(
- description, this, parent, remoteKey, localKey, consumer);
- }
-
- public RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> consume(
- String description, EParentObjectType parent, EObjectType modelObject,
- RemoteEmfConsumer.IObserver<EObjectType> consumer) {
- return new RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>(
- description, this, parent, modelObject, consumer);
- }
-
- public EReference getParentReference() {
- return parentReference;
+ public EAttribute getLocalKeyAttribute() {
+ return localKeyAttribute;
}
- public EAttribute getLocalAttribute() {
- return localAttribute;
+ /**
+ * Returns the service used to execute model operations as supplied by the factory provider.
+ *
+ * @return
+ */
+ public AbstractRemoteService getService() {
+ return getFactoryProvider().getService();
}
- public AbstractRemoteService getService() {
- return service;
+ /**
+ * Returns the parent factory provider that provides this factory.
+ */
+ public AbstractRemoteFactoryProvider getFactoryProvider() {
+ return factoryProvider;
}
}
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/IRemoteEmfObserver.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/IRemoteEmfObserver.java
new file mode 100644
index 000000000..8d71fef53
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/IRemoteEmfObserver.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Tasktop Technologies 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:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.reviews.core.spi.remote.emf;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * The observer receives notification of events that affect the consumer model objects. This is like an Asynchronous
+ * callback, except that the notification occurs every time a remote change to the object occurs. Notifications can
+ * occur across factories or through other remote update notifications.
+ * <p>
+ * Note that the observer notifications provide different semantics not supported by EMF notifications, such as a parent
+ * add.
+ * </p>
+ */
+public interface IRemoteEmfObserver<EParentObjectType extends EObject, EObjectType> {
+
+ /**
+ * Called whenever a model object has been created from a remote object and added to a parent object.
+ *
+ * @param parentObject
+ * The parent of the supplied object
+ * @param modelObject
+ * The newly created object
+ */
+ void created(EParentObjectType parentObject, EObjectType modelObject);
+
+ /**
+ * Called whenever a model object's value has been updated from the remote object. Unlike with Set and Add EMF
+ * notifications, updates are batched so that one and only one notification occurs for a model change after all
+ * values have been updated from the remote API. Updated is also called for newly created objects, so it typically
+ * isn't neccesary to listen for {@link #created(EObject, Object)} events.
+ *
+ * @param parentObject
+ * The parent of the supplied object
+ * @param modelObject
+ * The updated object
+ */
+ void updated(EParentObjectType parentObject, EObjectType modelObject, boolean modified);
+
+ /**
+ * Called whenever a model object begins the update process, that is, after a call to
+ * {@link RemoteEmfConsumer#retrieve(boolean)}.
+ *
+ * @param parentObject
+ * The parent of the supplied object
+ * @param modelObject
+ * The updating object
+ */
+ void updating(EParentObjectType parentObject, EObjectType modelObject);
+
+ /**
+ * Called whenever a failure has occurred while attempting to retrieve a remote object.
+ *
+ * @param parentObject
+ * The parent of the supplied object
+ * @param modelObject
+ * The object for which the failure occurred
+ */
+ void failed(EParentObjectType parentObject, EObjectType modelObject, IStatus status);
+
+} \ No newline at end of file
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfConsumer.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfConsumer.java
index 735cbda6c..221c47c0f 100644
--- a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfConsumer.java
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfConsumer.java
@@ -11,6 +11,7 @@
package org.eclipse.mylyn.reviews.core.spi.remote.emf;
+import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.core.runtime.CoreException;
@@ -27,48 +28,19 @@ 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.
+ * 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 AbstractRemoteEmfFactory#consume(String, EObject, Object, IObserver)} methods, users can then
- * {@link RemoteEmfConsumer#request()} the model state to be updated from the remote API. Consumers that have not
- * directly requested an object can attach to it by calling is no longer needed in order to avoid excessive EMF
- * notification overhead.
+ * 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, RemoteType, RemoteKeyType, LocalKeyType>
extends AbstractRemoteConsumer {
- /**
- * The observer receives notification of events that affect the consumer model objects. This is like an Asynchronous
- * callback, except that the notification occurs every time a remote change to the object occurs. Notifications
- * occur regardless of the source consumer, and even across factories.
- */
- public interface IObserver<EObjectType> {
-
- void created(EObjectType object);
-
- void responded(boolean modified);
-
- void failed(IStatus status);
-
- }
-
- public class ObserverImpl<ObjectType> implements IObserver<EObjectType> {
-
- public void created(EObjectType object) {
- }
-
- public void responded(boolean changed) {
- }
-
- public void failed(IStatus status) {
- }
- }
-
- private final String description;
-
private final AbstractRemoteEmfFactory<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> factory;
private RemoteKeyType remoteKey;
@@ -81,122 +53,193 @@ public class RemoteEmfConsumer<EParentObjectType extends EObject, EObjectType, R
private LocalKeyType localKey;
- private final IObserver<EObjectType> listener;
+ Collection<IRemoteEmfObserver<EParentObjectType, EObjectType>> remoteEmfObservers;
+
+ private boolean pulling;
+
+ private boolean retrieving;
- class ConsumerAdapter extends AdapterImpl {
+ private class ConsumerAdapter extends AdapterImpl {
@Override
public void notifyChanged(Notification msg) {
- if (msg.getEventType() == RemoteNotification.REMOTE_MEMBER_CREATE) {
- if (msg.getNewValue() == modelObject) {
- listener.created(modelObject);
+ 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> 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_MEMBER_FAILURE:
+ case RemoteNotification.REMOTE_FAILURE:
+ listener.failed(parentObject, modelObject, remoteMessage.getStatus());
+ }
+ }
+ }
}
}
- if ((msg.getEventType() == RemoteNotification.REMOTE_MEMBER_UPDATE && msg.getNotifier() == parentObject && (!(modelObject instanceof EObject)))
- || (msg.getEventType() == RemoteNotification.REMOTE_UPDATE && msg.getNotifier() == modelObject)) {
- listener.responded(((RemoteNotification) msg).isModification());
- }
- if ((msg.getEventType() == RemoteNotification.REMOTE_MEMBER_FAILURE && msg.getNotifier() == parentObject && msg.getNewValue() == modelObject)
- || (msg.getEventType() == RemoteNotification.REMOTE_FAILURE && msg.getNotifier() == modelObject)) {
- listener.failed(((RemoteNotification) msg).getStatus());
- }
}
}
ConsumerAdapter adapter = new ConsumerAdapter();
- RemoteEmfConsumer(String description,
+ RemoteEmfConsumer(
AbstractRemoteEmfFactory<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> factory,
- EParentObjectType parent, RemoteKeyType remoteKey, LocalKeyType localKey, IObserver<EObjectType> consumer) {
+ EParentObjectType parent, EObjectType modelObject, LocalKeyType localKey, RemoteType remoteObject,
+ RemoteKeyType remoteKey) {
this.parentObject = parent;
- this.factory = factory;
- this.description = description;
+ this.modelObject = modelObject;
+ this.remoteObject = remoteObject;
this.remoteKey = remoteKey;
this.localKey = localKey;
- this.listener = consumer;
- parent.eAdapters().add(adapter);
- }
-
- RemoteEmfConsumer(String description,
- AbstractRemoteEmfFactory<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> factory,
- EParentObjectType parent, EObjectType object, IObserver<EObjectType> consumer) {
- this.description = description;
- this.parentObject = parent;
- this.modelObject = object;
this.factory = factory;
- this.listener = consumer;
- if (object instanceof EObject) {
- ((EObject) object).eAdapters().add(adapter);
+ if (remoteKey == null && remoteObject != null) {
+ remoteKey = factory.getRemoteKey(remoteObject);
+ }
+ if (localKey == null && modelObject != null) {
+ 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>>();
}
+ /**
+ * 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
- protected void retrieve(IProgressMonitor monitor) throws CoreException {
- parentObject.eNotify(new RemoteENotificationImpl((InternalEObject) parentObject,
- RemoteNotification.REMOTE_MEMBER_UPDATING, factory.getParentReference(), null));
- if (modelObject instanceof EObject) {
- ((EObject) modelObject).eNotify(new RemoteENotificationImpl((InternalEObject) modelObject,
- RemoteNotification.REMOTE_MEMBER_UPDATING, null, null));
+ public void pull(boolean force, IProgressMonitor monitor) throws CoreException {
+ pulling = true;
+ if (remoteObject != null && remoteKey == null) {
+ remoteKey = factory.getRemoteKey(remoteObject);
}
- if (remoteKey == null && modelObject != null) {
- remoteKey = factory.getRemoteKey(parentObject, modelObject);
- }
- try {
- remoteObject = factory.retrieve(remoteKey, monitor);
- } catch (final CoreException e) {
+ //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_FAILURE, null, null, e.getStatus()));
- } else {
+ 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()));
+ }
}
- }
- });
- throw e;
+ }, 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;
+ }
+
+ /**
+ * 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
- protected void apply() {
+ public void applyModel(boolean force) {
+ //We may not have been retrieving the model at this point
NotificationChain msgs = new NotificationChainImpl();
- subscribe();
EReference reference = factory.getParentReference();
- boolean created = false;
- if (modelObject == null || reference.isMany() && (((Collection<?>) parentObject.eGet(reference)).size() == 0)) {
- created = true;
- modelObject = factory.create(parentObject, remoteObject);
- factory.associateObjects(modelObject, remoteObject, localKey, remoteKey);
- if (reference.isMany()) {
- if (modelObject instanceof Collection) {
- ((EList<EObjectType>) parentObject.eGet(reference)).addAll((Collection<EObjectType>) modelObject);
+ boolean modified = false;
+ if (remoteObject != null) {
+ if (modelObject == null
+ || (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 {
- ((EList<EObjectType>) parentObject.eGet(reference)).add(modelObject);
+ parentObject.eSet(reference, modelObject);
+ }
+ if (modelObject instanceof EObject) {
+ ((EObject) modelObject).eSet(factory.getLocalKeyAttribute(),
+ factory.getLocalKeyForRemoteObject(remoteObject));
+ ((EObject) modelObject).eAdapters().add(adapter);
}
- } else {
- parentObject.eSet(reference, modelObject);
+ msgs.add(new RemoteENotificationImpl((InternalEObject) parentObject,
+ RemoteNotification.REMOTE_MEMBER_CREATE, reference, modelObject));
}
- if (modelObject instanceof EObject) {
- ((EObject) modelObject).eAdapters().add(adapter);
+ if (factory.isUpdateModelNeeded(parentObject, modelObject, remoteObject) || force) {
+ modified |= factory.updateModel(parentObject, modelObject, remoteObject);
}
- msgs.add(new RemoteENotificationImpl((InternalEObject) parentObject,
- RemoteNotification.REMOTE_MEMBER_CREATE, reference, modelObject));
}
- boolean update = factory.update(parentObject, modelObject, remoteObject);
- update |= created;
msgs.add(new RemoteENotificationImpl((InternalEObject) parentObject, RemoteNotification.REMOTE_MEMBER_UPDATE,
- reference, modelObject, update));
+ reference, modelObject, modified));
if (modelObject instanceof EObject) {
msgs.add(new RemoteENotificationImpl((InternalEObject) modelObject, RemoteNotification.REMOTE_UPDATE, null,
- null, update));
+ 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;
}
/**
@@ -204,49 +247,48 @@ public class RemoteEmfConsumer<EParentObjectType extends EObject, EObjectType, R
* 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.
- * If the object does not yet exist, one will be created.
* </ol>
* <ol>
- * Create a new EMF object and reference it from the supplied parent object.
+ * If the object does not yet exist, one will be created and added to the appropriate parent object.
* </ol>
* <ol>
- * Notify the parent object via the EMF notification mechanism. (As a by-product of the above step.)
+ * 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 request() {
- getFactory().getService().execute(this);
- }
-
- /**
- * Registers the consumer with the factory and obtains a model object representing the remote object. If a model
- * object does not yet exist, it will not be created. This is the method that factory observers such as views will
- * interested in. This method may be called at any time and does not trigger a remote invocation.
- */
- public void subscribe() {
- boolean modelWasNull = modelObject == null;
- if (modelObject == null) {
- modelObject = factory.getModelObject(remoteObject);
- }
- if (modelObject == null) {
- modelObject = factory.getLocalModelObject(localKey);
- }
- if (modelWasNull && modelObject instanceof EObject) {
- ((EObject) modelObject).eAdapters().add(adapter);
+ public void retrieve(boolean force) {
+ if (retrieving) {
+ return;
}
+ retrieving = true;
+ getFactory().getService().retrieve(this, force);
}
/**
- * A no-op. Typically, consumers should handle failure notifications through the {@link IObserver#failed(IStatus)}
- * method.
+ * 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;
}
/**
- * Unregisters all listeners.
+ * Unregisters all listeners and adapters.
*/
@Override
public void dispose() {
@@ -254,35 +296,143 @@ public class RemoteEmfConsumer<EParentObjectType extends EObject, EObjectType, R
if (modelObject instanceof EObject) {
((EObject) modelObject).eAdapters().remove(adapter);
}
+ synchronized (remoteEmfObservers) {
+ remoteEmfObservers.clear();
+ }
}
- @Override
- public String getDescription() {
- return description;
+ /**
+ * 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> observer) {
+ if (observer instanceof RemoteEmfObserver) {
+ RemoteEmfObserver<EParentObjectType, EObjectType> remoteEmfObserver = (RemoteEmfObserver<EParentObjectType, EObjectType>) observer;
+ if (remoteEmfObserver.getConsumer() != null && remoteEmfObserver.getConsumer() != this) {
+ remoteEmfObserver.getConsumer().removeObserver(remoteEmfObserver);
+ }
+ remoteEmfObserver.internalSetConsumer(this);
+ }
+ synchronized (remoteEmfObservers) {
+ remoteEmfObservers.add(observer);
+ }
+ }
+
+ /**
+ * 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> observer) {
+ if (observer instanceof RemoteEmfObserver) {
+ RemoteEmfObserver<EParentObjectType, EObjectType> remoteEmfObserver = (RemoteEmfObserver<EParentObjectType, EObjectType>) observer;
+ if (remoteEmfObserver.getConsumer() == this) {
+ remoteEmfObserver.internalSetConsumer(null);
+ }
+ }
+ synchronized (remoteEmfObservers) {
+ remoteEmfObservers.remove(observer);
+ }
}
+ /**
+ * Returns the factory that providing services and objects for this consumer.
+ */
public AbstractRemoteEmfFactory<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType> getFactory() {
return factory;
}
+ /**
+ * Returns true if the factory is asynchronous, false otherwise.
+ */
@Override
public boolean isAsynchronous() {
- return factory.isAsynchronous();
+ return getFactory().isAsynchronous();
}
+ /**
+ * 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;
}
- public RemoteType getRemoteObject() {
- return remoteObject;
+ /**
+ * 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;
}
- public LocalKeyType getLocalKey() {
- return localKey;
+ /**
+ * 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.getParentReference().getEReferenceType().getName()
+ + " "
+ + (localKey != null ? localKey.toString() : (remoteKey != null
+ ? ("Remote Key: " + remoteKey)
+ : "Unknown"));
}
}
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfObserver.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfObserver.java
new file mode 100644
index 000000000..c5dd9075e
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/RemoteEmfObserver.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Tasktop Technologies 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:
+ * Tasktop Technologies - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.reviews.core.spi.remote.emf;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.emf.ecore.EObject;
+
+/**
+ * A concrete implementation of {@link IRemoteEmfObserver}, providing a number of convenience methods for managing
+ * consumer interaction with observers.
+ *
+ * @author Miles Parker
+ */
+public class RemoteEmfObserver<EParentObjectType extends EObject, EObjectType> implements
+ IRemoteEmfObserver<EParentObjectType, EObjectType> {
+
+ RemoteEmfConsumer<EParentObjectType, EObjectType, ?, ?, ?> consumer;
+
+ /**
+ * Constructs an observer that listens to the supplied consumer.
+ *
+ * @param consumer
+ */
+ public RemoteEmfObserver(RemoteEmfConsumer<EParentObjectType, EObjectType, ?, ?, ?> consumer) {
+ setConsumer(consumer);
+ }
+
+ /**
+ * Constructs an observer.
+ */
+ public RemoteEmfObserver() {
+ }
+
+ public void created(EParentObjectType parentObject, EObjectType modelObject) {
+ }
+
+ public void updating(EParentObjectType parent, EObjectType object) {
+ }
+
+ public void updated(EParentObjectType parentObject, EObjectType modelObject, boolean modified) {
+ }
+
+ public void failed(EParentObjectType parentObject, EObjectType modelObject, IStatus status) {
+ }
+
+ /**
+ * Returns the consumer the observer is listening to. This value may be null if the observer was added directly to
+ * the consumer.
+ */
+ public RemoteEmfConsumer<EParentObjectType, EObjectType, ?, ?, ?> getConsumer() {
+ return consumer;
+ }
+
+ /**
+ * Sets the consumer for the given observer, adding itself to the supplied consumer and removing it from an existing
+ * consumer if any. This supports reuse of an observer when the underlying model object changes.
+ *
+ * @param consumer
+ */
+ public void setConsumer(RemoteEmfConsumer<EParentObjectType, EObjectType, ?, ?, ?> consumer) {
+ if (this.consumer != consumer) {
+ consumer.addObserver(this);
+ }
+ }
+
+ /**
+ * Non-API. Intended for use by consumer only.
+ */
+ void internalSetConsumer(RemoteEmfConsumer<EParentObjectType, EObjectType, ?, ?, ?> consumer) {
+ this.consumer = consumer;
+ }
+
+ public void dispose() {
+ if (consumer != null) {
+ consumer.removeObserver(this);
+ }
+ }
+} \ No newline at end of file
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/ReviewsRemoteFactoryProvider.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/ReviewsRemoteFactoryProvider.java
deleted file mode 100644
index 9e89565d6..000000000
--- a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/emf/ReviewsRemoteFactoryProvider.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*******************************************************************************
- * 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.List;
-
-import org.eclipse.mylyn.reviews.core.model.IFileItem;
-import org.eclipse.mylyn.reviews.core.model.IReview;
-import org.eclipse.mylyn.reviews.core.model.IReviewGroup;
-import org.eclipse.mylyn.reviews.core.model.IReviewItemSet;
-import org.eclipse.mylyn.reviews.core.model.IReviewsFactory;
-import org.eclipse.mylyn.reviews.core.spi.remote.AbstractRemoteFactoryProvider;
-import org.eclipse.mylyn.reviews.core.spi.remote.JobRemoteService;
-
-/**
- * Supports decoupling of Reviews from remote API as well as job management.
- *
- * @author Miles Parker
- */
-public abstract class ReviewsRemoteFactoryProvider extends AbstractRemoteFactoryProvider {
-
- private final IReviewGroup reviews;
-
- public ReviewsRemoteFactoryProvider(JobRemoteService service) {
- super(service);
- this.reviews = IReviewsFactory.INSTANCE.createReviewGroup();
- }
-
- public abstract AbstractRemoteEmfFactory<IReviewGroup, IReview, ?, String, String> getReviewFactory();
-
- public abstract AbstractRemoteEmfFactory<IReview, IReviewItemSet, ?, ?, String> getReviewItemSetFactory();
-
- public abstract AbstractRemoteEmfFactory<IReviewItemSet, List<IFileItem>, ?, ?, String> getReviewItemSetContentFactory();
-
- public IReviewGroup getGroup() {
- return reviews;
- }
-}
diff --git a/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/review/IReviewRemoteFactoryProvider.java b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/review/IReviewRemoteFactoryProvider.java
new file mode 100644
index 000000000..8c66a57d8
--- /dev/null
+++ b/org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote/review/IReviewRemoteFactoryProvider.java
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * 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.review;
+
+import java.util.List;
+
+import org.eclipse.mylyn.reviews.core.model.IFileItem;
+import org.eclipse.mylyn.reviews.core.model.IRepository;
+import org.eclipse.mylyn.reviews.core.model.IReview;
+import org.eclipse.mylyn.reviews.core.model.IReviewItemSet;
+import org.eclipse.mylyn.reviews.core.spi.remote.emf.AbstractRemoteEmfFactory;
+
+/**
+ * Supports decoupling of Reviews from remote API.
+ *
+ * @author Miles Parker
+ */
+public interface IReviewRemoteFactoryProvider {
+
+ AbstractRemoteEmfFactory<IRepository, IReview, ?, String, String> getReviewFactory();
+
+ AbstractRemoteEmfFactory<IReview, IReviewItemSet, ?, ?, String> getReviewItemSetFactory();
+
+ AbstractRemoteEmfFactory<IReviewItemSet, List<IFileItem>, ?, String, String> getReviewItemSetContentFactory();
+
+ IRepository getRoot();
+}

Back to the top