| /******************************************************************************* |
| * 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.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.ecore.EAttribute; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.mylyn.reviews.core.spi.remote.AbstractRemoteService; |
| |
| /** |
| * 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 #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#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#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 #pull(EObject, Object, IProgressMonitor)}. |
| * </p> |
| * |
| * @author Miles Parker |
| */ |
| public abstract class AbstractRemoteEmfFactory<EParentObjectType extends EObject, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> { |
| |
| class UniqueLocalReference<P, L> { |
| P parent; |
| |
| L localKey; |
| |
| 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; |
| } |
| |
| @Override |
| public int hashCode() { |
| return parent.hashCode() + 31 * localKey.hashCode(); |
| } |
| } |
| |
| private final Map<UniqueLocalReference<EParentObjectType, LocalKeyType>, RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>> consumerForLocalKey = new HashMap<UniqueLocalReference<EParentObjectType, LocalKeyType>, RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>>(); |
| |
| private final EReference parentReference; |
| |
| private final EAttribute localKeyAttribute; |
| |
| private final AbstractRemoteEmfFactoryProvider<?, ?> factoryProvider; |
| |
| /** |
| * 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(AbstractRemoteEmfFactoryProvider<?, ?> factoryProvider, EReference parentReference, |
| EAttribute localKeyAttribute) { |
| this.factoryProvider = factoryProvider; |
| this.parentReference = parentReference; |
| this.localKeyAttribute = localKeyAttribute; |
| } |
| |
| /** |
| * Returns a unique consumer for a model object that corresponds to a given remote API key. May be called from any |
| * thread. |
| * |
| * @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 RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> getConsumerForRemoteKey( |
| EParentObjectType parentObject, RemoteKeyType remoteKey) { |
| LocalKeyType localKey = getLocalKeyForRemoteKey(remoteKey); |
| synchronized (consumerForLocalKey) { |
| RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> consumer = null; |
| if (localKey != null) { |
| consumer = getConsumerForLocalKey(parentObject, localKey); |
| } |
| if (consumer == null) { |
| consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>( |
| this, parentObject, null, localKey, null, remoteKey); |
| assignConsumer(parentObject, localKey, consumer); |
| } else { |
| consumer.setRemoteKey(remoteKey); |
| } |
| return consumer; |
| } |
| } |
| |
| /** |
| * Returns unique consumer for a model object that corresponds to a given remote API object. May be called from any |
| * thread. |
| * |
| * @param parentObject |
| * The object that contains or will contain the remote object type |
| * @return An object containing remotely derived state |
| */ |
| public RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> getConsumerForRemoteObject( |
| EParentObjectType parentObject, RemoteType remoteObject) { |
| synchronized (consumerForLocalKey) { |
| RemoteKeyType remoteKey = getRemoteKey(remoteObject); |
| LocalKeyType localKey = getLocalKeyForRemoteKey(remoteKey); |
| RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> consumer = findConsumer( |
| parentObject, localKey); |
| if (consumer == null) { |
| consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>( |
| this, parentObject, null, localKey, remoteObject, remoteKey); |
| assignConsumer(parentObject, localKey, consumer); |
| } else { |
| consumer.setRemoteObject(remoteObject); |
| } |
| return consumer; |
| } |
| } |
| |
| /** |
| * 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 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 RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> getConsumerForLocalKey( |
| EParentObjectType parentObject, LocalKeyType localKey) { |
| RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> consumer = null; |
| synchronized (consumerForLocalKey) { |
| consumer = findConsumer(parentObject, localKey); |
| } |
| if (consumer == null) { |
| EObjectType eo = open(parentObject, localKey); |
| synchronized (consumerForLocalKey) { |
| if (eo != null) { |
| EObjectType modelObject = eo; |
| consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>( |
| this, parentObject, modelObject, localKey, null, null); |
| assignConsumer(parentObject, localKey, consumer); |
| } |
| if (consumer == null) { |
| consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>( |
| this, parentObject, null, localKey, null, null); |
| assignConsumer(parentObject, localKey, consumer); |
| } |
| } |
| } |
| return consumer; |
| } |
| |
| class ObjectFinder implements Runnable { |
| EObjectType foundObject; |
| |
| EParentObjectType parentObject; |
| |
| LocalKeyType localKey; |
| |
| private ObjectFinder(EParentObjectType parentObject, LocalKeyType localKey) { |
| super(); |
| this.parentObject = parentObject; |
| this.localKey = localKey; |
| } |
| |
| @Override |
| public void run() { |
| Object parentField = parentObject.eGet(parentReference); |
| if (parentField instanceof List<?>) { |
| List<?> members = (List<?>) parentField; |
| for (Object object : members) { |
| if (object instanceof EObject) { |
| LocalKeyType currentKey = getLocalKey(parentObject, (EObjectType) object); |
| if (currentKey != null && localKey.equals(currentKey)) { |
| foundObject = (EObjectType) object; |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| protected EObjectType open(EParentObjectType parentObject, LocalKeyType localKey) { |
| ObjectFinder finder = new ObjectFinder(parentObject, localKey); |
| getService().modelExec(finder, true); |
| return finder.foundObject; |
| } |
| |
| /** |
| * Returns a unique consumer for a model object. <em>Must be called from EMF safe (e.g. UI) thread.</em> |
| * |
| * @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 RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> getConsumerForModel( |
| EParentObjectType parentObject, EObjectType modelObject) { |
| synchronized (consumerForLocalKey) { |
| RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> consumer = null; |
| LocalKeyType localKey = getLocalKey(parentObject, modelObject); |
| if (localKey != null) { |
| consumer = findConsumer(parentObject, localKey); |
| } |
| if (consumer == null) { |
| consumer = new RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType>( |
| this, parentObject, modelObject, localKey, null, null); |
| assignConsumer(parentObject, localKey, consumer); |
| } |
| return consumer; |
| } |
| } |
| |
| private RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> findConsumer( |
| EParentObjectType parentObject, LocalKeyType localKey) { |
| UniqueLocalReference<EParentObjectType, LocalKeyType> key = new UniqueLocalReference<EParentObjectType, LocalKeyType>( |
| parentObject, localKey); |
| return consumerForLocalKey.get(key); |
| } |
| |
| private RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> assignConsumer( |
| EParentObjectType parentObject, |
| LocalKeyType localKey, |
| RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> consumer) { |
| UniqueLocalReference<EParentObjectType, LocalKeyType> key = new UniqueLocalReference<EParentObjectType, LocalKeyType>( |
| parentObject, localKey); |
| return consumerForLocalKey.put(key, consumer); |
| } |
| |
| public void removeConsumer( |
| RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> consumer) { |
| EParentObjectType parentObject = consumer.getParentObject(); |
| LocalKeyType localKey = consumer.getLocalKey(); |
| if (parentObject != null && localKey != null) { |
| UniqueLocalReference<EParentObjectType, LocalKeyType> key = new UniqueLocalReference<EParentObjectType, LocalKeyType>( |
| parentObject, localKey); |
| consumerForLocalKey.remove(key); |
| } |
| } |
| |
| @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; |
| } |
| |
| /** |
| * 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 remoteObject |
| * The remote object to obtain the local key from |
| */ |
| public LocalKeyType getLocalKeyForRemoteObject(RemoteType remoteObject) { |
| return getLocalKeyForRemoteKey(getRemoteKey(remoteObject)); |
| } |
| |
| /** |
| * 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); |
| |
| /** |
| * Override to infer a remote key from a local key. This method is optional but should be supplied whenever it is |
| * possible to infer the remote key. |
| * |
| * @param localKey |
| * The local key to discover remote key from, null if unable to infer one |
| */ |
| public RemoteKeyType getRemoteKeyForLocalKey(EParentObjectType parentObject, LocalKeyType localKey) { |
| RemoteType remoteObject = getRemoteObjectForLocalKey(parentObject, localKey); |
| if (remoteObject != null) { |
| return getRemoteKey(remoteObject); |
| } |
| return null; |
| } |
| |
| /** |
| * Override to find a remote object from a local obejct. This method is optional but should be supplied whenever it |
| * is possible to infer the remote key. |
| * |
| * @param localKey |
| * The local key to discover remote key from, null if unable to infer one |
| */ |
| public RemoteType getRemoteObjectForLocalKey(EParentObjectType parentObject, LocalKeyType localKey) { |
| return null; |
| } |
| |
| /** |
| * 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 |
| */ |
| public boolean isAsynchronous() { |
| return true; |
| } |
| |
| /** |
| * 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 itself or any other |
| * object containing remotely obtained data.) |
| * @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 perform actual push 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 |
| * @throws CoreException |
| */ |
| public abstract void push(RemoteType remoteObject, IProgressMonitor monitor) throws CoreException; |
| |
| /** |
| * Override to return true if the remote object state has changed and needs to update to remote API. |
| * |
| * @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 isPushNeeded(EParentObjectType parent, EObjectType object, RemoteType remote) { |
| return false; |
| } |
| |
| /** |
| * 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 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 createModel(EParentObjectType parentObject, RemoteType remoteObject); |
| |
| /** |
| * Override to return true if a remote object should be created. |
| * |
| * @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 isCreateModelNeeded(EParentObjectType parentObject, EObjectType modelObject) { |
| return modelObject == null; |
| } |
| |
| /** |
| * 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 #pull(EObject, Object, IProgressMonitor)} or |
| * {@link #updateModel(EObject, Object, 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 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 updateModel(EParentObjectType parentObject, EObjectType modelObject, RemoteType remoteObject) { |
| return false; |
| } |
| |
| /** |
| * Updates the values for the remote object based on any values that have changed in model object since the last |
| * call to {@link #updateRemote(Object)}. |
| * <em>Must be called from EMF safe (e.g. UI) thread and should have very fast execution time.</em> |
| * |
| * @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 remote object has changed or the object delta is unknown, false otherwise |
| */ |
| public boolean updateRemote(EParentObjectType parentObject, EObjectType modelObject, RemoteType remoteObject) { |
| return false; |
| } |
| |
| /** |
| * 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 |
| * A unique identifier in the target API |
| * @param monitor |
| * @return |
| */ |
| public boolean isUpdateModelNeeded(EParentObjectType parentObject, EObjectType modelObject, RemoteType remote) { |
| return true; |
| } |
| |
| /** |
| * 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 parentObject, LocalKeyType localKey, RemoteKeyType remoteKey) { |
| RemoteEmfConsumer<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> 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 the EMF reference in the parent object that refers to model objects. Returns the EMF attribute specifying |
| * the local key. |
| * |
| * @return |
| */ |
| public EReference getParentReference() { |
| return parentReference; |
| } |
| |
| /** |
| * Returns the EMF attribute specifying the local key. |
| * |
| * @return |
| */ |
| public EAttribute getLocalKeyAttribute() { |
| return localKeyAttribute; |
| } |
| |
| /** |
| * Returns the service used to execute model operations as supplied by the factory provider. |
| * |
| * @return |
| */ |
| public AbstractRemoteService getService() { |
| return getFactoryProvider().getService(); |
| } |
| |
| /** |
| * Returns the parent factory provider that provides this factory. |
| */ |
| public AbstractRemoteEmfFactoryProvider<?, ?> getFactoryProvider() { |
| return factoryProvider; |
| } |
| |
| public abstract ObjectCurrentType getModelCurrentValue(EParentObjectType parentObject, EObjectType object); |
| |
| public String getModelDescription(EParentObjectType parentObject, EObjectType object, LocalKeyType localKey) { |
| return getParentReference().getEReferenceType().getName() + " " + localKey; |
| } |
| |
| } |