Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: b8423baf54d8f232b13d74d35ba591a05693dbdc (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
/*****************************************************************************
 * Copyright (c) 2011, 2015 Atos Origin, CEA, 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:
 *  Mathieu Velten (Atos Origin) mathieu.velten@atosorigin.com - Initial API and implementation
 *  Christian W. Damus (CEA) - Support object-level read/write controls (CDO)
 *  Christian W. Damus (CEA) - bug 323802
 *  Christian W. Damus (CEA) - bug 429826
 *  Christian W. Damus (CEA) - bug 422257
 *  Christian W. Damus (CEA) - bug 415639
 *  Christian W. Damus - bug 399859
 *  Christian W. Damus - bug 461629
 *  Christian W. Damus - bug 465416
 *
 *****************************************************************************/
package org.eclipse.papyrus.infra.emf.readonly;

import static org.eclipse.papyrus.infra.core.utils.TransactionHelper.isInteractive;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.transaction.NotificationFilter;
import org.eclipse.emf.transaction.RollbackException;
import org.eclipse.emf.transaction.RunnableWithResult;
import org.eclipse.emf.transaction.Transaction;
import org.eclipse.emf.transaction.TransactionalCommandStack;
import org.eclipse.emf.transaction.impl.InternalTransaction;
import org.eclipse.emf.transaction.impl.TransactionChangeRecorder;
import org.eclipse.emf.workspace.IWorkspaceCommandStack;
import org.eclipse.emf.workspace.ResourceUndoContext;
import org.eclipse.papyrus.infra.core.resource.IReadOnlyHandler2;
import org.eclipse.papyrus.infra.core.resource.IRollbackStatus;
import org.eclipse.papyrus.infra.core.resource.ReadOnlyAxis;
import org.eclipse.papyrus.infra.core.resource.ResourceAdapter;
import org.eclipse.papyrus.infra.core.resource.RollbackStatus;
import org.eclipse.papyrus.infra.core.utils.TransactionHelper;
import org.eclipse.papyrus.infra.emf.edit.domain.PapyrusTransactionalEditingDomain;
import org.eclipse.papyrus.infra.onefile.model.IPapyrusFile;
import org.eclipse.papyrus.infra.onefile.model.PapyrusModelHelper;
import org.eclipse.papyrus.infra.onefile.utils.OneFileUtils;


public class PapyrusROTransactionalEditingDomain extends PapyrusTransactionalEditingDomain {

	public PapyrusROTransactionalEditingDomain(AdapterFactory adapterFactory, TransactionalCommandStack stack, ResourceSet resourceSet) {
		super(adapterFactory, stack, resourceSet);

		if (stack instanceof IWorkspaceCommandStack) {
			resourceSet.eAdapters().add(createResourceUndoContextHandler());
		}
	}

	@Override
	public boolean isReadOnly(Resource resource) {
		return isReadOnly(ReadOnlyAxis.anyAxis(), resource);
	}

	public boolean isReadOnly(EObject eObject) {
		return isReadOnly(ReadOnlyAxis.anyAxis(), eObject);
	}

	@Override
	protected TransactionChangeRecorder createChangeRecorder(ResourceSet rset) {
		// Ensure that the ControlledResourceTracker gets in ahead of the change recorder so that it processes
		// notifications pertaining to sub-model unit structure, first, to ensure correct determination of
		// read-only state for cross-model-referenced objects
		ControlledResourceTracker.getInstance(this);

		return doCreateChangeRecorder(rset);
	}

	protected TransactionChangeRecorder doCreateChangeRecorder(ResourceSet rset) {
		return new TransactionChangeRecorder(this, rset) {

			@Override
			protected void appendNotification(Notification notification) {
				// Append to the transaction first
				super.appendNotification(notification);

				if (!NotificationFilter.READ.matches(notification)) {
					// Check whether we are modifying a read-only object
					assertNotReadOnly(notification.getNotifier());
				} else {
					// Maybe we resolved a cross-resource containment proxy
					handleCrossResourceContainmentProxy(notification);
				}
			}
		};
	}

	protected void handleCrossResourceContainmentProxy(Notification notification) {
		// If it's not an EReference, then it's a feature-map EAttribute from an unknown-schema AnyType
		// and it won't contain cross-resource containment references (at least, not that we could tell)
		if ((notification.getEventType() == Notification.RESOLVE) && (notification.getFeature() instanceof EReference)) {
			EReference reference = (EReference) notification.getFeature();
			if (reference.isContainment()) {
				InternalEObject newValue = (InternalEObject) notification.getNewValue();
				if (newValue.eDirectResource() != null) {
					ControlledResourceTracker.getInstance(this).handleCrossResourceContainment(newValue);
				}
			}
		}
	}

	protected void assertNotReadOnly(Object object) {
		InternalTransaction tx = getActiveTransaction();

		// If there's no transaction, then there will be nothing to roll back. And if it's unprotected, let the client do whatever.
		// And, of course, don't interfere with rollback! Finally, if we're already going to roll back, don't bother
		if ((tx != null) && !tx.isRollingBack() //
				&& !Boolean.TRUE.equals(tx.getOptions().get(Transaction.OPTION_UNPROTECTED)) //
				&& !willRollBack(tx)) {

			final Set<ReadOnlyAxis> axes = TransactionHelper.getReadOnlyAxisOption(tx);
			boolean readOnly;

			// Check for Resource first because CDO resources *are* EObjects
			if (object instanceof Resource) {
				Resource.Internal resource = (Resource.Internal) object;
				if (resource.isLoading()) {
					// We must be able to modify read-only resources in order to load them
					return;
				}
				// If it's not an interactive transaction, don't try to make the resource writable because that would prompt the user
				readOnly = isReadOnly(axes, resource) && !(isInteractive(tx) && makeWritable(axes, resource));
			} else if (object instanceof EObject) {
				EObject eObject = (EObject) object;
				// If it's not an interactive transaction, don't try to make the object writable because that would prompt the user
				readOnly = isReadOnly(axes, eObject) && !(isInteractive(tx) && makeWritable(axes, eObject));
			} else {
				// If it's not an EMF-managed object, we don't care
				readOnly = false;
			}

			if (readOnly) {
				String message = "Attempt to modify object(s) in a read-only model."; //$NON-NLS-1$
				Collection<?> offenders = Collections.singleton(object);
				tx.abort(new RollbackStatus(Activator.PLUGIN_ID, IRollbackStatus.READ_ONLY_OBJECT, message, offenders));
			}
		}
	}

	private boolean willRollBack(Transaction tx) {
		IStatus status = tx.getStatus();
		return (status != null) && (status.getSeverity() >= IStatus.ERROR);
	}

	protected boolean isReadOnly(Set<ReadOnlyAxis> axes, Resource resource) {
		if ((resource != null) && (resource.getURI() != null)) {
			return ReadOnlyManager.getReadOnlyHandler(this).anyReadOnly(axes, new URI[] { resource.getURI() }).get();
		}
		return false;
	}

	protected boolean isReadOnly(Set<ReadOnlyAxis> axes, EObject eObject) {
		return ReadOnlyManager.getReadOnlyHandler(this).isReadOnly(axes, eObject).get();
	}

	protected boolean makeWritable(Set<ReadOnlyAxis> axes, Resource resource) {
		URI[] uris = getCompositeModelURIs(resource.getURI());
		IReadOnlyHandler2 handler = ReadOnlyManager.getReadOnlyHandler(this);

		if (!handler.canMakeWritable(axes, uris).or(false)) {
			return false;
		}

		return handler.makeWritable(axes, uris).get();
	}

	protected boolean makeWritable(Set<ReadOnlyAxis> axes, EObject object) {
		boolean result;

		IReadOnlyHandler2 handler = ReadOnlyManager.getReadOnlyHandler(this);

		if (!handler.canMakeWritable(axes, object).or(false)) {
			result = false;
		} else {
			result = handler.makeWritable(axes, object).get();
		}

		return result;
	}

	/**
	 * Obtains the complete set of URIs for members of the composite model resource of which the given URI is one member.
	 *
	 * @param memberURI
	 *            a member of a composite Papyrus model
	 *
	 * @return the complete set of member resources (which could just be the original {@code memberURI})
	 */
	protected URI[] getCompositeModelURIs(URI memberURI) {
		URI[] result = null;

		if (memberURI.isPlatformResource()) {
			// We don't have object-level read-only state in the workspace (perhaps in CDO repositories)
			IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(memberURI.trimFragment().toPlatformString(true)));
			if ((file != null) && file.exists()) {
				IPapyrusFile composite = PapyrusModelHelper.getPapyrusModelFactory().createIPapyrusFile(file);
				Set<URI> memberURIs = new HashSet<URI>();
				for (IFile member : OneFileUtils.getAssociatedFiles(composite)) {
					memberURIs.add(URI.createPlatformResourceURI(member.getFullPath().toString(), true));
				}
				result = memberURIs.toArray(new URI[memberURIs.size()]);
			}
		}

		if (result == null) {
			result = new URI[] { memberURI };
		}

		return result;
	}

	@Override
	public void dispose() {
		try {
			super.dispose();
		} finally {
			resourceSet = null;
			adapterFactory = null;
			ReadOnlyManager.roHandlers.remove(this);
		}
	}

	/**
	 * Overrides the inherited method to support an {@linkplain TransactionHelper#TRANSACTION_OPTION_MERGE_NESTED_READ option} to merge nested read-only transactions into parent write transactions.
	 */
	@Override
	public Object runExclusive(Runnable read) throws InterruptedException {

		Transaction active = getActiveTransaction();
		Transaction tx = null;

		if ((active == null) || !(active.isActive() && isReadOnlyCompatible(active))) {
			// only need to start a new transaction if we don't already have
			// exclusive read-only access
			tx = startTransaction(true, null);
		}

		final RunnableWithResult<?> rwr = (read instanceof RunnableWithResult) ? (RunnableWithResult<?>) read : null;

		try {
			read.run();
		} finally {
			if ((tx != null) && (tx.isActive())) {
				// commit the transaction now
				try {
					tx.commit();

					if (rwr != null) {
						rwr.setStatus(Status.OK_STATUS);
					}
				} catch (RollbackException e) {
					Activator.log.error("Read-only transaction was rolled back.", e); //$NON-NLS-1$

					if (rwr != null) {
						rwr.setStatus(e.getStatus());
					}
				}
			}
		}

		return (rwr != null) ? rwr.getResult() : null;
	}

	private boolean isReadOnlyCompatible(Transaction parentTransaction) {
		return (parentTransaction.isReadOnly() || TransactionHelper.isMergeReadOnly(parentTransaction))
				&& (parentTransaction.getOwner() == Thread.currentThread());
	}

	protected Adapter createResourceUndoContextHandler() {
		return new ResourceUndoContextHandler(getCommandStack());
	}

	//
	// Nested types
	//

	protected class ResourceUndoContextHandler extends ResourceAdapter {

		private final IOperationHistory history;

		protected ResourceUndoContextHandler(CommandStack stack) {
			super();

			history = ((IWorkspaceCommandStack) stack).getOperationHistory();
		}

		@Override
		protected void handleResourceUnloaded(Resource resource) {
			// Purge the resource undo context
			IUndoContext resourceContext = new ResourceUndoContext(PapyrusROTransactionalEditingDomain.this, resource);
			history.dispose(resourceContext, true, true, true);
		}
	}
}

Back to the top