Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 7f00384381a1904dd731acb8d3c83ed9b4069491 (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
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
/*****************************************************************************
 * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, 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:
 *  Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
 *  Christian W. Damus (CEA) - bugs 429826, 408491, 433320
 *  Christian W. Damus - bugs 451557, 457560, 461629, 463564, 466997, 465416, 485220
 *
 *****************************************************************************/
package org.eclipse.papyrus.infra.core.utils;

import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.transaction.RecordingCommand;
import org.eclipse.emf.transaction.RollbackException;
import org.eclipse.emf.transaction.Transaction;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.impl.InternalTransactionalEditingDomain;
import org.eclipse.emf.transaction.impl.TransactionImpl;
import org.eclipse.papyrus.infra.core.resource.ReadOnlyAxis;
import org.eclipse.papyrus.infra.tools.util.IExecutorService;
import org.eclipse.papyrus.infra.tools.util.IProgressCallable;
import org.eclipse.papyrus.infra.tools.util.IProgressRunnable;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;


/**
 * This helper can be used to run (safe) transactions outside the CommandStack
 *
 * @author Camille Letavernier
 *
 */
public class TransactionHelper {

	// Refactoring needed. The sasheditor contentprovider should have dependencies to infra.EMF...

	public static final String TRANSACTION_OPTION_READ_ONLY_AXIS = "papyrus.read_only_axis"; //$NON-NLS-1$

	/**
	 * @deprecated Since 1.1 M7 read-only state of resources is always cached and the cache lifecycle is not tied to transactions.
	 */
	@Deprecated
	public static final String TRANSACTION_OPTION_NO_READ_ONLY_CACHE = "papyrus.no_read_only_cache"; //$NON-NLS-1$

	public static final String TRANSACTION_OPTION_INTERACTIVE = "papyrus.interactive"; //$NON-NLS-1$

	public static final String TRANSACTION_OPTION_MERGE_NESTED_READ = "papyrus.merge_nested_read"; //$NON-NLS-1$

	/**
	 * @since 2.0
	 */
	public static void run(EditingDomain domain, Runnable writeOperation) throws InterruptedException, RollbackException {
		if (domain instanceof TransactionalEditingDomain) {
			run((TransactionalEditingDomain) domain, writeOperation);
		} else {
			writeOperation.run();
		}
	}

	/**
	 * @since 2.0
	 */
	public static void run(TransactionalEditingDomain domain, final Runnable writeOperation) throws InterruptedException, RollbackException {
		if (domain instanceof InternalTransactionalEditingDomain) {
			run((InternalTransactionalEditingDomain) domain, writeOperation);
		} else {
			// Shouldn't happen, as all TransactionalEditingDomain implementations should also implement InternalTransactionalEditingDomain
			domain.getCommandStack().execute(new RecordingCommand(domain) {

				@Override
				protected void doExecute() {
					writeOperation.run();
				}
			});
		}
	}

	/**
	 * @since 2.0
	 */
	public static void run(InternalTransactionalEditingDomain domain, Runnable writeOperation) throws InterruptedException, RollbackException {
		Transaction transaction = domain.startTransaction(false, Collections.emptyMap());
		try {
			writeOperation.run();
		} finally {
			transaction.commit();
		}
	}

	/**
	 * Queries whether an editing {@code domain} has been disposed.
	 *
	 * @param domain
	 *            an editing domain
	 *
	 * @return whether the {@code domain} is {@code null} (which presumably implies disposed) or has been disposed
	 */
	public static boolean isDisposed(TransactionalEditingDomain domain) {
		// A disposed editing domain (and only a disposed editing domain) has no command stack
		return (domain == null) || (domain.getCommandStack() == null);
	}

	/**
	 * Merges the read-only {@code axis} option into an existing map of {@code options}.
	 *
	 * @param options
	 *            an existing (non-{@code null}) options map
	 * @param axis
	 *            the axis option to merge
	 * @return the augmented {@code options}
	 */
	public static Map<String, Object> mergeReadOnlyAxisOption(Map<String, Object> options, ReadOnlyAxis axis) {
		return mergeReadOnlyAxisOption(options, Collections.singleton(axis));
	}

	/**
	 * Merges the read-only {@code axes} option into an existing map of {@code options}.
	 *
	 * @param options
	 *            an existing (non-{@code null}) options map
	 * @param axes
	 *            the axes option to merge
	 * @return the augmented {@code options}
	 */
	public static Map<String, Object> mergeReadOnlyAxisOption(Map<String, Object> options, Set<ReadOnlyAxis> axes) {
		options.put(TRANSACTION_OPTION_READ_ONLY_AXIS, axes);
		return options;
	}

	/**
	 * Adds the read-only {@code axis} option to a transaction's {@code options}.
	 *
	 * @param options
	 *            an options map, which may be {@code null} or immutable
	 * @param axis
	 *            the axis option to add
	 * @return a new map based on the {@code options} and including the {@code axis}
	 */
	public static Map<String, Object> addReadOnlyAxisOption(Map<String, ?> options, ReadOnlyAxis axis) {
		return addReadOnlyAxisOption(options, Collections.singleton(axis));
	}

	/**
	 * Adds the read-only {@code axes} option to a transaction's {@code options}.
	 *
	 * @param options
	 *            an options map, which may be {@code null} or immutable
	 * @param axes
	 *            the axes option to add
	 * @return a new map based on the {@code options} and including the {@code axes}
	 */
	public static Map<String, Object> addReadOnlyAxisOption(Map<String, ?> options, Set<ReadOnlyAxis> axes) {
		Map<String, Object> result = (options == null) ? Maps.<String, Object> newHashMap() : Maps.newHashMap(options);
		result.put(TRANSACTION_OPTION_READ_ONLY_AXIS, axes);
		return result;
	}

	/**
	 * Creates a new mutable transaction options map with a read-only {@code axis}.
	 *
	 * @param axis
	 *            the axis option
	 * @return a new mutable map including the {@code axis}
	 */
	public static Map<String, Object> readOnlyAxisOption(ReadOnlyAxis axis) {
		return readOnlyAxisOption(Collections.singleton(axis));
	}

	/**
	 * Creates a new mutable transaction options map with a read-only {@code axes}.
	 *
	 * @param axes
	 *            the axes option
	 * @return a new mutable map including the {@code axes}
	 */
	public static Map<String, Object> readOnlyAxisOption(Set<ReadOnlyAxis> axes) {
		return addReadOnlyAxisOption(null, axes);
	}

	/**
	 * Queries the read-only axes to be enforced by a {@code transaction}.
	 *
	 * @param transaction
	 *            a transaction
	 * @return its read-only axes, which are {@linkplain ReadOnlyAxis#anyAxis() all of them} by default if the option is absent
	 */
	@SuppressWarnings("unchecked")
	public static Set<ReadOnlyAxis> getReadOnlyAxisOption(Transaction transaction) {
		Set<ReadOnlyAxis> result;

		Object value = transaction.getOptions().get(TRANSACTION_OPTION_READ_ONLY_AXIS);
		if (value instanceof Set<?>) {
			result = (Set<ReadOnlyAxis>) value;
		} else if (value instanceof Iterable<?>) {
			result = Sets.immutableEnumSet((Iterable<ReadOnlyAxis>) value);
		} else {
			result = ReadOnlyAxis.anyAxis();
		}

		return result;
	}

	/**
	 * Merges the option to disable read-only state caching into an existing map of {@code options}.
	 *
	 * @param options
	 *            an existing (non-{@code null}) options map
	 * @param disableCache
	 *            whether to disable read-only caching
	 * @return the augmented {@code options}
	 * @deprecated Since 1.1 M7 read-only state of resources is always cached and the cache lifecycle is not tied to transactions.
	 */
	@Deprecated
	public static Map<String, Object> mergeDisableReadOnlyOption(Map<String, Object> options, boolean disableCache) {
		options.put(TRANSACTION_OPTION_NO_READ_ONLY_CACHE, disableCache);
		return options;
	}

	/**
	 * Adds the option to disable read-only state caching to a transaction's {@code options}.
	 *
	 * @param options
	 *            an options map, which may be {@code null} or immutable
	 * @param disableCache
	 *            whether to disable read-only caching
	 * @return a new map based on the {@code options} and including the {@code disableCache} option
	 * @deprecated Since 1.1 M7 read-only state of resources is always cached and the cache lifecycle is not tied to transactions.
	 */
	@Deprecated
	public static Map<String, Object> addDisableReadOnlyCacheOption(Map<String, ?> options, boolean disableCache) {
		Map<String, Object> result = (options == null) ? Maps.<String, Object> newHashMap() : Maps.newHashMap(options);
		result.put(TRANSACTION_OPTION_NO_READ_ONLY_CACHE, disableCache);
		return result;
	}

	/**
	 * Creates a new mutable transaction options map with the option to disable caching of read-only state for objects and resources.
	 *
	 * @param disableCache
	 *            whether to disable read-only caching
	 * @return a new mutable map including the {@code disableCache} option
	 * @deprecated Since 1.1 M7 read-only state of resources is always cached and the cache lifecycle is not tied to transactions.
	 */
	@Deprecated
	public static Map<String, Object> disableReadOnlyCacheOption(boolean disableCache) {
		return addDisableReadOnlyCacheOption(null, disableCache);
	}

	/**
	 * Queries whether a {@code transaction} is running with caching of read-only state of objects and resources disabled.
	 *
	 * @param transaction
	 *            a transaction
	 * @return {@code true} if the {@code transaction} has the {@linkplain #TRANSACTION_OPTION_NO_READ_ONLY_CACHE interactive option} set {@code true}; {@code false}, otherwise (including the default case of no option set)
	 * @deprecated Since 1.1 M7 read-only state of resources is always cached and the cache lifecycle is not tied to transactions.
	 */
	@Deprecated
	public static boolean isReadOnlyCacheDisabled(Transaction transaction) {
		Object value = transaction.getOptions().get(TRANSACTION_OPTION_NO_READ_ONLY_CACHE);
		return (value instanceof Boolean) ? (Boolean) value : false;
	}

	/**
	 * Merges the option to merge nested read-only transactions with parent write transactions into an existing map of {@code options}.
	 * This option on a write transaction affects this behaviour of subsequent child read-only transactions.
	 *
	 * @param options
	 *            an existing (non-{@code null}) options map
	 * @param mergeReadOnly
	 *            whether to merge read-only transactions into parent write transactions
	 * @return the augmented {@code options}
	 */
	public static Map<String, Object> mergeMergeReadOnlyOption(Map<String, Object> options, boolean mergeReadOnly) {
		options.put(TRANSACTION_OPTION_MERGE_NESTED_READ, mergeReadOnly);
		return options;
	}

	/**
	 * Adds the option to merge nested read-only transactions with parent write transactions to a transaction's {@code options}.
	 * This option on a write transaction affects this behaviour of subsequent child read-only transactions.
	 *
	 * @param options
	 *            an options map, which may be {@code null} or immutable
	 * @param mergeReadOnly
	 *            whether to merge read-only transactions into parent write transactions
	 * @return a new map based on the {@code options} and including the {@code mergeReadOnly} option
	 */
	public static Map<String, Object> addMergeReadOnlyOption(Map<String, ?> options, boolean mergeReadOnly) {
		Map<String, Object> result = (options == null) ? Maps.<String, Object> newHashMap() : Maps.newHashMap(options);
		result.put(TRANSACTION_OPTION_MERGE_NESTED_READ, mergeReadOnly);
		return result;
	}

	/**
	 * Creates a new mutable transaction options map with the option to merge nested read-only transactions with parent write transactions.
	 * This option on a write transaction affects this behaviour of subsequent child read-only transactions.
	 *
	 * @param mergeReadOnly
	 *            whether to merge read-only transactions into parent write transactions
	 * @return a new mutable map including the {@code mergeReadOnly} option
	 */
	public static Map<String, Object> mergeReadOnlyOption(boolean mergeReadOnly) {
		return addMergeReadOnlyOption(null, mergeReadOnly);
	}

	/**
	 * Queries whether a {@code transaction} is running with merging of nested read-only transactions with parent write transactions enabled.
	 *
	 * @param transaction
	 *            a transaction
	 * @return {@code true} if the {@code transaction} has the {@linkplain #TRANSACTION_OPTION_MERGE_NESTED_READ interactive option} set {@code true}; {@code false}, otherwise (including the default case of no option set)
	 */
	public static boolean isMergeReadOnly(Transaction transaction) {
		Object value = transaction.getOptions().get(TRANSACTION_OPTION_MERGE_NESTED_READ);
		return (value instanceof Boolean) ? (Boolean) value : false;
	}

	/**
	 * Merges the {@code interactive} transaction option into an existing map of {@code options}.
	 *
	 * @param options
	 *            an existing (non-{@code null}) options map
	 * @param interactive
	 *            whether the transaction is in an user-interactive context
	 * @return the augmented {@code options}
	 */
	public static Map<String, Object> mergeInteractiveOption(Map<String, Object> options, boolean interactive) {
		options.put(TRANSACTION_OPTION_INTERACTIVE, interactive);
		return options;
	}

	/**
	 * Adds the {@code interactive} option option to a transaction's {@code options}.
	 *
	 * @param options
	 *            an options map, which may be {@code null} or immutable
	 * @param interactive
	 *            whether the transaction is in an user-interactive context
	 * @return a new map based on the {@code options} and including the {@code interactive} option
	 */
	public static Map<String, Object> addInteractiveOption(Map<String, ?> options, boolean interactive) {
		Map<String, Object> result = (options == null) ? Maps.<String, Object> newHashMap() : Maps.newHashMap(options);
		result.put(TRANSACTION_OPTION_INTERACTIVE, interactive);
		return result;
	}

	/**
	 * Creates a new mutable transaction options map with an {@code interactive} option.
	 *
	 * @param interactive
	 *            whether the transaction is in an user-interactive context
	 * @return a new mutable map including the {@code interactive} option
	 */
	public static Map<String, Object> interactiveOption(boolean interactive) {
		return addInteractiveOption(null, interactive);
	}

	/**
	 * Queries whether a {@code transaction} is running in a user-interactive context. In practice, this means that it would be appropriate to
	 * prompt the user to make resources/objects writable if necessary.
	 *
	 * @param transaction
	 *            a transaction
	 * @return {@code false} if the {@code transaction} has the {@linkplain #TRANSACTION_OPTION_INTERACTIVE interactive option} set {@code false}; {@code true}, otherwise (including the default case of no option set)
	 */
	public static boolean isInteractive(Transaction transaction) {
		Object value = transaction.getOptions().get(TRANSACTION_OPTION_INTERACTIVE);
		return (value instanceof Boolean) ? (Boolean) value : true;
	}

	/**
	 * Queries whether a {@code transaction} is one that is executing or that executed triggers, or is perhaps
	 * the read-only post-commit transaction that tells listeners about the changes made by a trigger transaction.
	 * 
	 * @param transaction
	 *            a transaction
	 * @return whether it is a trigger transaction
	 */
	public static boolean isTriggerTransaction(Transaction transaction) {
		Object value = transaction.getOptions().get(TransactionImpl.OPTION_IS_TRIGGER_TRANSACTION);
		return (value instanceof Boolean) ? (Boolean) value : false;
	}

	/**
	 * Creates an {@link Executor} that executes {@link Runnable}s at the pre-commit phase of the active write
	 * transaction of the specified editing {@code domain} or at some other time if no write transaction is active.
	 * 
	 * @param domain
	 *            a transactional editing domain. May not be {@code null}
	 * @param fallback
	 *            an executor to use for scheduling tasks when the {@code domain} does not have a
	 *            write transaction open. May not be {@code null}
	 */
	public static Executor createTransactionExecutor(TransactionalEditingDomain domain, Executor fallback) {
		return createTransactionExecutor(domain, fallback, null, null);
	}

	/**
	 * Creates an {@link Executor} that executes {@link Runnable}s at the pre-commit phase of the active write
	 * transaction of the specified editing {@code domain} or at some other time if no write transaction is active.
	 * The specified {@code policy}, if any, may determine whether for some task the {@code fallback} should be preferred
	 * over the transaction executor or vice-versa (such as to handle special requirements like tasks needing to run
	 * on the UI thread).
	 * 
	 * @param domain
	 *            a transactional editing domain. May not be {@code null}
	 * @param fallback
	 *            an executor to use for scheduling tasks when the {@code domain} does not have a
	 *            write transaction open. May not be {@code null}
	 * @param policy
	 *            an optional executor selection policy (may be {@code null}). The policy is queried for ranking of both
	 *            the transaction executor and the {@code fallback}, unless there is no transaction active, which always excludes the
	 *            transaction executor. In the event of a tie, the transaction executor is always selected
	 */
	public static Executor createTransactionExecutor(TransactionalEditingDomain domain, Executor fallback, IExecutorPolicy policy) {
		return createTransactionExecutor(domain, fallback, policy, null);
	}

	/**
	 * Creates an {@link Executor} that executes {@link Runnable}s at the pre-commit phase of the active write
	 * transaction of the specified editing {@code domain} or at some other time if no write transaction is active.
	 * 
	 * @param domain
	 *            a transactional editing domain. May not be {@code null}
	 * @param fallback
	 *            an executor to use for scheduling tasks when the {@code domain} does not have a
	 *            write transaction open. May not be {@code null}
	 * @param options
	 *            a map of options to apply to the nested transaction in which tasks are executed. May be {@code null} if not needed
	 */
	public static Executor createTransactionExecutor(TransactionalEditingDomain domain, Executor fallback, Map<?, ?> options) {
		return createTransactionExecutor(domain, fallback, null, options);
	}

	/**
	 * Creates an {@link Executor} that executes {@link Runnable}s at the pre-commit phase of the active write
	 * transaction of the specified editing {@code domain} or at some other time if no write transaction is active.
	 * The specified {@code policy}, if any, may determine whether for some task the {@code fallback} should be preferred
	 * over the transaction executor or vice-versa (such as to handle special requirements like tasks needing to run
	 * on the UI thread).
	 * 
	 * @param domain
	 *            a transactional editing domain. May not be {@code null}
	 * @param fallback
	 *            an executor to use for scheduling tasks when the {@code domain} does not have a
	 *            write transaction open. May not be {@code null}
	 * @param policy
	 *            an optional executor selection policy (may be {@code null}). The policy is queried for ranking of both
	 *            the transaction executor and the {@code fallback}, unless there is no transaction active, which always excludes the
	 *            transaction executor. In the event of a tie, the transaction executor is always selected
	 * @param options
	 *            a map of options to apply to the nested transaction in which tasks are executed. May be {@code null} if not needed
	 */
	public static Executor createTransactionExecutor(TransactionalEditingDomain domain, Executor fallback, IExecutorPolicy policy, Map<?, ?> options) {
		if ((domain == null) || (fallback == null)) {
			throw new NullPointerException();
		}

		return new TransactionPrecommitExecutor(domain, fallback, policy, options);
	}

	/**
	 * Create a privileged progress runnable, which is like a regular {@linkplain TransactionalEditingDomain#createPrivilegedRunnable(Runnable)
	 * privileged runnable} except that it is given a progress monitor for progress reporting.
	 *
	 * @param domain
	 *            an editing domain
	 * @param runnable
	 *            a progress runnable that is to borrow the {@code domain}'s active transaction on the modal context thread
	 * @return the privileged runnable, ready to pass into the {@link IExecutorService} or other such API
	 * @since 2.0
	 */
	public static IProgressRunnable createPrivilegedRunnable(TransactionalEditingDomain domain, final IProgressRunnable runnable) {
		IProgressMonitor monitorHolder[] = { null };

		Runnable privileged = domain.createPrivilegedRunnable(() -> runnable.run(monitorHolder[0]));

		return monitor -> {
			monitorHolder[0] = monitor;
			privileged.run();
		};
	}

	/**
	 * Create a privileged progress callable, which is like a {@linkplain TransactionalEditingDomain#createPrivilegedRunnable(Runnable)
	 * privileged runnable} except that it is given a progress monitor for progress reporting and it computes a result.
	 *
	 * @param callable
	 *            an editing domain
	 * @param callable
	 *            a progress callable that is to borrow the {@code domain}'s active transaction on the modal context thread
	 * @return the privileged callable, ready to pass into the {@link IExecutorService} or other such API
	 * @since 2.0
	 */
	public static <V> IProgressCallable<V> createPrivilegedCallable(TransactionalEditingDomain domain, final IProgressCallable<V> callable) {
		IProgressMonitor monitorHolder[] = { null };
		AtomicReference<V> resultHolder = new AtomicReference<V>();
		Exception failHolder[] = { null };

		Runnable privileged = domain.createPrivilegedRunnable(() -> {
			try {
				resultHolder.set(callable.call(monitorHolder[0]));
			} catch (Exception e) {
				failHolder[0] = e;
			}
		});

		return monitor -> {
			monitorHolder[0] = monitor;

			privileged.run();

			if (failHolder[0] != null) {
				throw failHolder[0];
			}

			return resultHolder.get();
		};
	}
}

Back to the top