diff options
Diffstat (limited to 'org.eclipse.mylyn.reviews.core/src/org/eclipse/mylyn/reviews/core/spi/remote')
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(); +} |