Skip to main content
summaryrefslogtreecommitdiffstats
blob: 44a949c9518a2972f840c550b76eff50cb3c237c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
/*******************************************************************************
 * 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.AbstractRemoteFactoryProvider;
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, RemoteType, RemoteKeyType, LocalKeyType> {

	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, RemoteType, RemoteKeyType, LocalKeyType>> consumerForLocalKey = new HashMap<UniqueLocalReference<EParentObjectType, LocalKeyType>, RemoteEmfConsumer<EParentObjectType, EObjectType, RemoteType, RemoteKeyType, LocalKeyType>>();

	private final EReference parentReference;

	private final EAttribute localKeyAttribute;

	private final AbstractRemoteFactoryProvider 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(AbstractRemoteFactoryProvider 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 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 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 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 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 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 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 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;
	}

	/**
	 * 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);

	/**
	 * 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 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);

	/**
	 * 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 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;
	}

	/**
	 * 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 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 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 AbstractRemoteFactoryProvider getFactoryProvider() {
		return factoryProvider;
	}
}

Back to the top