Skip to content

Commit 6176ca2

Browse files
authored
Merge pull request #1084 from nasa/transaction-clearing-1059
[Persistence] Clear transactions selectively
2 parents c5041db + eb6ddb5 commit 6176ca2

File tree

10 files changed

+343
-134
lines changed

10 files changed

+343
-134
lines changed

platform/commonUI/edit/bundle.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ define([
4242
"./src/representers/EditToolbarRepresenter",
4343
"./src/capabilities/EditorCapability",
4444
"./src/capabilities/TransactionCapabilityDecorator",
45+
"./src/services/TransactionManager",
4546
"./src/services/TransactionService",
4647
"./src/creation/CreateMenuController",
4748
"./src/creation/LocatorController",
@@ -80,6 +81,7 @@ define([
8081
EditToolbarRepresenter,
8182
EditorCapability,
8283
TransactionCapabilityDecorator,
84+
TransactionManager,
8385
TransactionService,
8486
CreateMenuController,
8587
LocatorController,
@@ -222,8 +224,7 @@ define([
222224
"policyService",
223225
"dialogService",
224226
"creationService",
225-
"copyService",
226-
"transactionService"
227+
"copyService"
227228
],
228229
"priority": "mandatory"
229230
},
@@ -321,7 +322,7 @@ define([
321322
"implementation": TransactionCapabilityDecorator,
322323
"depends": [
323324
"$q",
324-
"transactionService"
325+
"transactionManager"
325326
],
326327
"priority": "fallback"
327328
},
@@ -406,6 +407,15 @@ define([
406407
"key": "locator",
407408
"template": locatorTemplate
408409
}
410+
],
411+
"services": [
412+
{
413+
"key": "transactionManager",
414+
"implementation": TransactionManager,
415+
"depends": [
416+
"transactionService"
417+
]
418+
}
409419
]
410420
}
411421
});

platform/commonUI/edit/src/actions/SaveAsAction.js

+18-18
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ define([
4444
dialogService,
4545
creationService,
4646
copyService,
47-
transactionService,
4847
context
4948
) {
5049
this.domainObject = (context || {}).domainObject;
@@ -55,7 +54,6 @@ define([
5554
this.dialogService = dialogService;
5655
this.creationService = creationService;
5756
this.copyService = copyService;
58-
this.transactionService = transactionService;
5957
}
6058

6159
/**
@@ -113,9 +111,8 @@ define([
113111
var self = this,
114112
domainObject = this.domainObject,
115113
copyService = this.copyService,
116-
transactionService = this.transactionService,
117-
cancelOldTransaction,
118-
dialog = new SaveInProgressDialog(this.dialogService);
114+
dialog = new SaveInProgressDialog(this.dialogService),
115+
toUndirty = [];
119116

120117
function doWizardSave(parent) {
121118
var wizard = self.createWizard(parent);
@@ -147,27 +144,31 @@ define([
147144
}
148145

149146
function allowClone(objectToClone) {
150-
return (objectToClone.getId() === domainObject.getId()) ||
151-
objectToClone.getCapability('location').isOriginal();
147+
var allowed =
148+
(objectToClone.getId() === domainObject.getId()) ||
149+
objectToClone.getCapability('location').isOriginal();
150+
if (allowed) {
151+
toUndirty.push(objectToClone);
152+
}
153+
return allowed;
152154
}
153155

154156
function cloneIntoParent(parent) {
155157
return copyService.perform(domainObject, parent, allowClone);
156158
}
157159

158-
function commitEditingAfterClone(clonedObject) {
159-
return domainObject.getCapability("editor").save()
160-
.then(resolveWith(clonedObject));
160+
function undirty(object) {
161+
return object.getCapability('persistence').refresh();
161162
}
162163

163-
function restartTransaction(object) {
164-
cancelOldTransaction = transactionService.restartTransaction();
165-
return object;
164+
function undirtyOriginals(object) {
165+
return Promise.all(toUndirty.map(undirty))
166+
.then(resolveWith(object));
166167
}
167168

168-
function doCancelOldTransaction(object) {
169-
cancelOldTransaction();
170-
return object;
169+
function commitEditingAfterClone(clonedObject) {
170+
return domainObject.getCapability("editor").save()
171+
.then(resolveWith(clonedObject));
171172
}
172173

173174
function onFailure() {
@@ -179,10 +180,9 @@ define([
179180
.then(doWizardSave)
180181
.then(showBlockingDialog)
181182
.then(getParent)
182-
.then(restartTransaction)
183183
.then(cloneIntoParent)
184+
.then(undirtyOriginals)
184185
.then(commitEditingAfterClone)
185-
.then(doCancelOldTransaction)
186186
.then(hideBlockingDialog)
187187
.catch(onFailure);
188188
};

platform/commonUI/edit/src/capabilities/TransactionalPersistenceCapability.js

+12-31
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,21 @@ define(
3333
* called.
3434
* @memberof platform/commonUI/edit/capabilities
3535
* @param $q
36-
* @param transactionService
36+
* @param transactionManager
3737
* @param persistenceCapability
3838
* @param domainObject
3939
* @constructor
4040
*/
4141
function TransactionalPersistenceCapability(
4242
$q,
43-
transactionService,
43+
transactionManager,
4444
persistenceCapability,
4545
domainObject
4646
) {
47-
this.transactionService = transactionService;
47+
this.transactionManager = transactionManager;
4848
this.persistenceCapability = persistenceCapability;
4949
this.domainObject = domainObject;
5050
this.$q = $q;
51-
this.persistPending = false;
5251
}
5352

5453
/**
@@ -57,34 +56,14 @@ define(
5756
* @returns {*}
5857
*/
5958
TransactionalPersistenceCapability.prototype.persist = function () {
60-
var self = this;
59+
var wrappedPersistence = this.persistenceCapability;
6160

62-
function onCommit() {
63-
return self.persistenceCapability.persist().then(function (result) {
64-
self.persistPending = false;
65-
return result;
66-
});
67-
}
68-
69-
function onCancel() {
70-
if (self.domainObject.getModel().persisted !== undefined) {
71-
//Fetch clean model from persistence
72-
return self.persistenceCapability.refresh().then(function (result) {
73-
self.persistPending = false;
74-
return result;
75-
});
76-
} else {
77-
self.persistPending = false;
78-
//Model is undefined in persistence, so return undefined.
79-
return self.$q.when(undefined);
80-
}
81-
}
82-
83-
if (this.transactionService.isActive()) {
84-
if (!this.persistPending) {
85-
this.transactionService.addToTransaction(onCommit, onCancel);
86-
this.persistPending = true;
87-
}
61+
if (this.transactionManager.isActive()) {
62+
this.transactionManager.addToTransaction(
63+
this.domainObject.getId(),
64+
wrappedPersistence.persist.bind(wrappedPersistence),
65+
wrappedPersistence.refresh.bind(wrappedPersistence)
66+
);
8867
//Need to return a promise from this function
8968
return this.$q.when(true);
9069
} else {
@@ -93,6 +72,8 @@ define(
9372
};
9473

9574
TransactionalPersistenceCapability.prototype.refresh = function () {
75+
this.transactionManager
76+
.clearTransactionsFor(this.domainObject.getId());
9677
return this.persistenceCapability.refresh();
9778
};
9879

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*****************************************************************************
2+
* Open MCT, Copyright (c) 2014-2016, United States Government
3+
* as represented by the Administrator of the National Aeronautics and Space
4+
* Administration. All rights reserved.
5+
*
6+
* Open MCT is licensed under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
* http://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* Open MCT includes source code licensed under additional open source
18+
* licenses. See the Open Source Licenses file (LICENSES.md) included with
19+
* this source code distribution or the Licensing information page available
20+
* at runtime from the About dialog for additional information.
21+
*****************************************************************************/
22+
23+
define([], function () {
24+
/**
25+
* Manages transactions to support the TransactionalPersistenceCapability.
26+
* This assumes that all commit/cancel callbacks for a given domain
27+
* object are equivalent, and only need to be added once to any active
28+
* transaction. Violating this assumption may cause unexpected behavior.
29+
* @constructor
30+
* @memberof platform/commonUI/edit
31+
*/
32+
function TransactionManager(transactionService) {
33+
this.transactionService = transactionService;
34+
this.clearTransactionFns = {};
35+
}
36+
37+
/**
38+
* Check if a transaction is currently active.
39+
* @returns {boolean} true if there is a transaction active
40+
*/
41+
TransactionManager.prototype.isActive = function () {
42+
return this.transactionService.isActive();
43+
};
44+
45+
/**
46+
* Check if callbacks associated with this domain object have already
47+
* been added to the active transaction.
48+
* @private
49+
* @param {string} id the identifier of the domain object to check
50+
* @returns {boolean} true if callbacks have been added
51+
*/
52+
TransactionManager.prototype.isScheduled = function (id) {
53+
return !!this.clearTransactionFns[id];
54+
};
55+
56+
/**
57+
* Add callbacks associated with this domain object to the active
58+
* transaction. Both callbacks are expected to return promises that
59+
* resolve when their associated behavior is complete.
60+
*
61+
* If callbacks associated with this domain object have already been
62+
* added to the active transaction, this call will be ignored.
63+
*
64+
* @param {string} id the identifier of the associated domain object
65+
* @param {Function} onCommit behavior to invoke when committing transaction
66+
* @param {Function} onCancel behavior to invoke when cancelling transaction
67+
*/
68+
TransactionManager.prototype.addToTransaction = function (
69+
id,
70+
onCommit,
71+
onCancel
72+
) {
73+
var release = this.releaseClearFn.bind(this, id);
74+
75+
function chain(promiseFn, nextFn) {
76+
return function () {
77+
return promiseFn().then(nextFn);
78+
};
79+
}
80+
81+
if (!this.isScheduled(id)) {
82+
this.clearTransactionFns[id] =
83+
this.transactionService.addToTransaction(
84+
chain(onCommit, release),
85+
chain(onCancel, release)
86+
);
87+
}
88+
};
89+
90+
/**
91+
* Remove any callbacks associated with this domain object from the
92+
* active transaction.
93+
* @param {string} id the identifier for the domain object
94+
*/
95+
TransactionManager.prototype.clearTransactionsFor = function (id) {
96+
if (this.isScheduled(id)) {
97+
this.clearTransactionFns[id]();
98+
this.releaseClearFn(id);
99+
}
100+
};
101+
102+
/**
103+
* Release the cached "remove from transaction" function that has been
104+
* stored in association with this domain object.
105+
* @param {string} id the identifier for the domain object
106+
* @private
107+
*/
108+
TransactionManager.prototype.releaseClearFn = function (id) {
109+
delete this.clearTransactionFns[id];
110+
};
111+
112+
return TransactionManager;
113+
});

platform/commonUI/edit/src/services/TransactionService.js

+10-30
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ define(
8181
//Log error because this is a programming error if it occurs.
8282
this.$log.error("No transaction in progress");
8383
}
84+
85+
return function () {
86+
this.onCommits = this.onCommits.filter(function (callback) {
87+
return callback !== onCommit;
88+
});
89+
this.onCancels = this.onCancels.filter(function (callback) {
90+
return callback !== onCancel;
91+
});
92+
}.bind(this);
8493
};
8594

8695
/**
@@ -140,38 +149,9 @@ define(
140149
});
141150
};
142151

143-
/**
144-
* Clear and restart the active transaction.
145-
*
146-
* This neither cancels nor commits the active transaction;
147-
* instead, it returns a function that can be used to cancel that
148-
* transaction.
149-
*
150-
* @returns {Function} a function to cancel the prior transaction
151-
* @private
152-
*/
153-
TransactionService.prototype.restartTransaction = function () {
154-
var oldOnCancels = this.onCancels;
155-
156-
this.onCommits = [];
157-
this.onCancels = [];
158-
159-
return function () {
160-
while (oldOnCancels.length > 0) {
161-
var onCancel = oldOnCancels.pop();
162-
try {
163-
onCancel();
164-
} catch (error) {
165-
this.$log.error("Error cancelling transaction.");
166-
}
167-
}
168-
};
169-
};
170-
171152
TransactionService.prototype.size = function () {
172153
return this.onCommits.length;
173154
};
174155

175156
return TransactionService;
176-
}
177-
);
157+
});

0 commit comments

Comments
 (0)