/******************************************************************************* * Copyright (c) 2013, 2014 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.Collection; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.mylyn.reviews.core.spi.remote.AbstractRemoteConsumer; import org.eclipse.osgi.util.NLS; /** * Manages the interaction between a remote API and a local EMF object. There can be only one instance of a consumer for * a given model object or remote object per factory. * <p> * After obtaining a consumer using one of the {@link RemoteEmfConsumer} <i>AbstractRemoteEmfFactory#getConsumer()</i> * methods, call {@link RemoteEmfConsumer#retrieve(boolean)} to request an update. Any registered * {@link RemoteEmfObserver}s will then receive an {@link RemoteEmfObserver#updated(boolean)} event regardless of * whether or not the actual state changed. * * @author Miles Parker */ public class RemoteEmfConsumer<EParentObjectType extends EObject, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> extends AbstractRemoteConsumer { private final AbstractRemoteEmfFactory<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> factory; private RemoteKeyType remoteKey; private RemoteType remoteObject; private final EParentObjectType parentObject; private EObjectType modelObject; private LocalKeyType localKey; private final Collection<RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType>> remoteEmfObservers; private boolean pulling; private boolean retrieving; boolean userJob; boolean systemJob; //Can set to false for running with in another job boolean asynchronous = true; private IStatus lastStatus = Status.OK_STATUS; RemoteEmfConsumer( AbstractRemoteEmfFactory<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> factory, final EParentObjectType parent, final EObjectType modelObject, LocalKeyType localKey, RemoteType remoteObject, RemoteKeyType remoteKey) { this.parentObject = parent; this.modelObject = modelObject; this.remoteObject = remoteObject; this.remoteKey = remoteKey; this.localKey = localKey; this.factory = factory; if (remoteKey == null && remoteObject != null) { this.remoteKey = factory.getRemoteKey(remoteObject); } if (localKey == null && modelObject != null) { this.localKey = factory.getLocalKey(null, modelObject); } remoteEmfObservers = new CopyOnWriteArrayList<RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType>>(); } public void notifyObservers(final RemoteNotification notification) { getFactory().getService().modelExec(new Runnable() { @Override public void run() { for (RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> listener : remoteEmfObservers) { switch (notification.getType()) { case RemoteNotification.REMOTE_UPDATE: listener.updated(notification.isModification()); break; case RemoteNotification.REMOTE_UPDATING: listener.updating(); break; } } } }); } /** * Pulls the results from the factory, populating the remote object with the latest state from the remote API. * Blocks until the remote API call completes. Does nothing if a retrieval is already occurring. * <em>This method must not be called from the UI thread.</em> * * @param force * pull from remote even when API doesn't require * @param monitor * @throws CoreException */ @Override public void pull(boolean force, IProgressMonitor monitor) throws CoreException { pulling = true; if (remoteObject != null && remoteKey == null) { remoteKey = factory.getRemoteKey(remoteObject); } if (remoteKey == null && localKey != null) { remoteKey = factory.getRemoteKeyForLocalKey(parentObject, localKey); } //Pull when "needed" or forced, but not when we don't have a remote key as that would be pointless. if ((factory.isPullNeeded(parentObject, modelObject, remoteObject) || force == true) && remoteKey != null) { notifyObservers(RemoteNotification.createUpdatingNotification()); try { remoteObject = factory.pull(parentObject, remoteKey, monitor); if (localKey == null) { localKey = factory.getLocalKeyForRemoteObject(remoteObject); } pulling = false; } catch (final CoreException e) { lastStatus = e.getStatus(); notifyObservers(RemoteNotification.createUpdateNotification(false)); throw e; } lastStatus = Status.OK_STATUS; } 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 public void applyModel(boolean force) { EReference reference = factory.getParentReference(); boolean modified = false; if (remoteObject != null) { if (modelObject == null || factory.isCreateModelNeeded(parentObject, modelObject) || (reference.isMany() && (((Collection<?>) parentObject.eGet(reference)).size() == 0))) { modified = true; modelObject = factory.createModel(parentObject, remoteObject); if (reference.isMany()) { if (modelObject instanceof Collection) { ((EList<EObjectType>) parentObject.eGet(reference)) .addAll((Collection<EObjectType>) modelObject); } else { ((EList<EObjectType>) parentObject.eGet(reference)).add(modelObject); } } else { parentObject.eSet(reference, modelObject); } if (modelObject instanceof EObject) { ((EObject) modelObject).eSet(factory.getLocalKeyAttribute(), factory.getLocalKeyForRemoteObject(remoteObject)); } } if (factory.isUpdateModelNeeded(parentObject, modelObject, remoteObject) || force) { modified |= factory.updateModel(parentObject, modelObject, remoteObject); } } retrieving = false; notifyObservers(RemoteNotification.createUpdateNotification(modified)); } /** * 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 RemoteEmfObserver#updated(boolean)} has been called. */ public boolean isRetrieving() { return retrieving; } /** * Performs a complete remote request, result application and listener notification against the factory. This is the * method primary factory consumers will be interested in. The method will asynchronously (as defined by remote * service implementation): * <li> * <ol> * Notify any registered {@link RemoteEmfObserver}s that the object is {@link RemoteEmfObserver#updating()}. * </ol> * <ol> * Call the remote API, retrieving the results into a local object representing the contents of the remote object. * </ol> * <ol> * If the object does not yet exist, one will be created and added to the appropriate parent object. * </ol> * <ol> * Notify objects of any changes via the standard EMF notification mechanisms. (As a by-product of the above step.) * </ol> * <ol> * Notify any registered {@link RemoteEmfObserver}s of object creation or update. (An update is notified even if * object state does not change.) * </ol> * </li> * * @param force * Forces pull and update, even if factory methods * {@link AbstractRemoteEmfFactory#isPullNeeded(EObject, Object, Object)} and/or * {@link AbstractRemoteEmfFactory#isUpdateModelNeeded(EObject, Object, Object)} return false. */ public void retrieve(boolean force) { if (retrieving) { return; } retrieving = true; getFactory().getService().retrieve(this, force || !lastStatus.isOK()); } /** * Handles notification from the service that a retrieval has completed. */ @Override public void notifyDone(IStatus status) { retrieving = false; release(); } /** * Unregisters all listeners and adapters. */ @Override public void dispose() { retrieving = false; remoteEmfObservers.clear(); getFactory().removeConsumer(this); if (getModelObject() instanceof EObject) { getFactory().getFactoryProvider().close((EObject) getModelObject()); } modelObject = null; remoteObject = null; } /** * Adds an observer to this consumer. Updates the consumer field for {@link RemoteEmfObserver}s. * * @param observer * The observer to add */ public void addObserver( RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> observer) { if (observer != null) { RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> remoteEmfObserver = observer; if (remoteEmfObserver.getConsumer() != null && remoteEmfObserver.getConsumer() != this) { remoteEmfObserver.getConsumer().removeObserver(remoteEmfObserver); } remoteEmfObserver.internalSetConsumer(this); 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( RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> observer) { if (observer != null) { RemoteEmfObserver<EParentObjectType, EObjectType, LocalKeyType, ObjectCurrentType> remoteEmfObserver = observer; if (remoteEmfObserver.getConsumer() == this) { remoteEmfObserver.internalSetConsumer(null); } remoteEmfObservers.remove(observer); } release(); } public void release() { if (remoteEmfObservers.size() == 0 && !retrieving) { dispose(); } } public void updateObservers() { notifyObservers(RemoteNotification.createUpdateNotification(false)); } /** * Returns the factory that providing services and objects for this consumer. */ public AbstractRemoteEmfFactory<EParentObjectType, EObjectType, LocalKeyType, RemoteType, RemoteKeyType, ObjectCurrentType> getFactory() { return factory; } public void open() { getFactory().open(parentObject, localKey); } public void save() { if (getModelObject() instanceof EObject) { getFactory().getFactoryProvider().save(); getFactory().getFactoryProvider().save((EObject) getModelObject()); } } /** * Returns the parent object for this consumer. */ public EParentObjectType getParentObject() { return parentObject; } /** * Returns the model object for this consumer, if one has been obtained through the {@link #retrieve(boolean)} * method or supplied when any object obtained this consumer. */ public EObjectType getModelObject() { return modelObject; } /** * Returns the local key supplied by the consumer, or the local remote key if it can be inferred from the remote key * or remote object. */ public LocalKeyType getLocalKey() { if (localKey != null) { return localKey; } else if (remoteKey != null) { return getFactory().getLocalKeyForRemoteKey(remoteKey); } else if (remoteObject != null) { return getFactory().getLocalKeyForRemoteObject(remoteObject); } return null; } /** * Returns the remote key for this consumer. */ public RemoteKeyType getRemoteKey() { return remoteKey; } /** * Returns the remote object that maps to this consumer's model object, local key or remote key, if one has been * supplied or obtained using the remote key. * * @return */ public RemoteType getRemoteObject() { return remoteObject; } /** * Should only be called by RemoteEmfFactory. * * @param remoteObject */ void setRemoteObject(RemoteType remoteObject) { if (!factory.getLocalKeyForRemoteObject(remoteObject).equals(getLocalKey())) { throw new RuntimeException( "Internal Error. Tried to set a remote object that doesn't match existing local key or object."); //$NON-NLS-1$ } 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."); //$NON-NLS-1$ } this.remoteKey = remoteKey; } @Override public String getDescription() { return NLS.bind(Messages.RemoteEmfConsumer_Retrieving_X, factory.getModelDescription(getParentObject(), getModelObject(), getLocalKey())); } @Override public boolean isUserJob() { return userJob; } public void setUiJob(boolean userJob) { this.userJob = userJob; } @Override public boolean isSystemJob() { return systemJob; } public void setSystemJob(boolean systemJob) { this.systemJob = systemJob; } @Override public boolean isAsynchronous() { return getFactory().isAsynchronous() && asynchronous; } public void setAsynchronous(boolean asynchronous) { this.asynchronous = asynchronous; } public IStatus getStatus() { return lastStatus; } }