Merge branch 'timeline-save-770b'

This commit is contained in:
Pete Richards
2016-03-24 13:06:53 -07:00
10 changed files with 233 additions and 36 deletions

View File

@ -93,27 +93,23 @@ define(
return wizard.populateObjectFromInput(formValue, newObject); return wizard.populateObjectFromInput(formValue, newObject);
} }
function addToParent (populatedObject) { function persistAndReturn(domainObject) {
parentObject.getCapability('composition').add(populatedObject); return domainObject.getCapability('persistence')
return parentObject.getCapability('persistence').persist().then(function(){ .persist()
return parentObject; .then(function () {
return domainObject;
}); });
} }
function save(object) { function addToParent (populatedObject) {
/* parentObject.getCapability('composition').add(populatedObject);
It's necessary to persist the new sub-object in order return persistAndReturn(parentObject);
that it can be retrieved for composition in the parent.
Future refactoring that allows temporary objects to be
retrieved from object services will make this unnecessary.
*/
return object.getCapability('editor').save(true);
} }
return this.dialogService return this.dialogService
.getUserInput(wizard.getFormStructure(false), wizard.getInitialFormValue()) .getUserInput(wizard.getFormStructure(false), wizard.getInitialFormValue())
.then(populateObjectFromInput) .then(populateObjectFromInput)
.then(save) .then(persistAndReturn)
.then(addToParent); .then(addToParent);
}; };

View File

@ -27,6 +27,7 @@ define([
"./src/models/StaticModelProvider", "./src/models/StaticModelProvider",
"./src/models/RootModelProvider", "./src/models/RootModelProvider",
"./src/models/ModelAggregator", "./src/models/ModelAggregator",
"./src/models/ModelCacheService",
"./src/models/PersistedModelProvider", "./src/models/PersistedModelProvider",
"./src/models/CachingModelDecorator", "./src/models/CachingModelDecorator",
"./src/models/MissingModelDecorator", "./src/models/MissingModelDecorator",
@ -58,6 +59,7 @@ define([
StaticModelProvider, StaticModelProvider,
RootModelProvider, RootModelProvider,
ModelAggregator, ModelAggregator,
ModelCacheService,
PersistedModelProvider, PersistedModelProvider,
CachingModelDecorator, CachingModelDecorator,
MissingModelDecorator, MissingModelDecorator,
@ -182,7 +184,10 @@ define([
{ {
"provides": "modelService", "provides": "modelService",
"type": "decorator", "type": "decorator",
"implementation": CachingModelDecorator "implementation": CachingModelDecorator,
"depends": [
"cacheService"
]
}, },
{ {
"provides": "modelService", "provides": "modelService",
@ -319,6 +324,7 @@ define([
"key": "persistence", "key": "persistence",
"implementation": PersistenceCapability, "implementation": PersistenceCapability,
"depends": [ "depends": [
"cacheService",
"persistenceService", "persistenceService",
"identifierService", "identifierService",
"notificationService", "notificationService",
@ -354,6 +360,10 @@ define([
} }
], ],
"services": [ "services": [
{
"key": "cacheService",
"implementation": ModelCacheService
},
{ {
"key": "now", "key": "now",
"implementation": Now "implementation": Now
@ -384,7 +394,8 @@ define([
"implementation": Instantiate, "implementation": Instantiate,
"depends": [ "depends": [
"capabilityService", "capabilityService",
"identifierService" "identifierService",
"cacheService"
] ]
} }
], ],

View File

@ -46,6 +46,7 @@ define(
* @implements {Capability} * @implements {Capability}
*/ */
function PersistenceCapability( function PersistenceCapability(
cacheService,
persistenceService, persistenceService,
identifierService, identifierService,
notificationService, notificationService,
@ -56,6 +57,7 @@ define(
this.modified = domainObject.getModel().modified; this.modified = domainObject.getModel().modified;
this.domainObject = domainObject; this.domainObject = domainObject;
this.cacheService = cacheService;
this.identifierService = identifierService; this.identifierService = identifierService;
this.persistenceService = persistenceService; this.persistenceService = persistenceService;
this.notificationService = notificationService; this.notificationService = notificationService;
@ -130,6 +132,7 @@ define(
domainObject = this.domainObject, domainObject = this.domainObject,
model = domainObject.getModel(), model = domainObject.getModel(),
modified = model.modified, modified = model.modified,
cacheService = this.cacheService,
persistenceService = this.persistenceService, persistenceService = this.persistenceService,
persistenceFn = model.persisted !== undefined ? persistenceFn = model.persisted !== undefined ?
this.persistenceService.updateObject : this.persistenceService.updateObject :
@ -146,6 +149,9 @@ define(
getKey(domainObject.getId()), getKey(domainObject.getId()),
domainObject.getModel() domainObject.getModel()
]).then(function(result){ ]).then(function(result){
if (result) {
cacheService.remove(domainObject.getId());
}
return rejectIfFalsey(result, self.$q); return rejectIfFalsey(result, self.$q);
}).catch(function(error){ }).catch(function(error){
return notifyOnError(error, domainObject, self.notificationService, self.$q); return notifyOnError(error, domainObject, self.notificationService, self.$q);

View File

@ -35,9 +35,8 @@ define(
* @param {ModelService} modelService this service to decorate * @param {ModelService} modelService this service to decorate
* @implements {ModelService} * @implements {ModelService}
*/ */
function CachingModelDecorator(modelService) { function CachingModelDecorator(cacheService, modelService) {
this.cache = {}; this.cacheService = cacheService;
this.cached = {};
this.modelService = modelService; this.modelService = modelService;
} }
@ -51,17 +50,16 @@ define(
} }
CachingModelDecorator.prototype.getModels = function (ids) { CachingModelDecorator.prototype.getModels = function (ids) {
var cache = this.cache, var cacheService = this.cacheService,
cached = this.cached,
neededIds = ids.filter(function notCached(id) { neededIds = ids.filter(function notCached(id) {
return !cached[id]; return !cacheService.has(id);
}); });
// Update the cached instance of a model to a new value. // Update the cached instance of a model to a new value.
// We update in-place to ensure there is only ever one instance // We update in-place to ensure there is only ever one instance
// of any given model exposed by the modelService as a whole. // of any given model exposed by the modelService as a whole.
function updateModel(id, model) { function updateModel(id, model) {
var oldModel = cache[id]; var oldModel = cacheService.get(id);
// Same object instance is a possibility, so don't copy // Same object instance is a possibility, so don't copy
if (oldModel === model) { if (oldModel === model) {
@ -71,7 +69,7 @@ define(
// If we'd previously cached an undefined value, or are now // If we'd previously cached an undefined value, or are now
// seeing undefined, replace the item in the cache entirely. // seeing undefined, replace the item in the cache entirely.
if (oldModel === undefined || model === undefined) { if (oldModel === undefined || model === undefined) {
cache[id] = model; cacheService.put(id, model);
return model; return model;
} }
@ -91,15 +89,15 @@ define(
// Store the provided models in our cache // Store the provided models in our cache
function cacheAll(models) { function cacheAll(models) {
Object.keys(models).forEach(function (id) { Object.keys(models).forEach(function (id) {
cache[id] = cached[id] ? var model = cacheService.has(id) ?
updateModel(id, models[id]) : models[id]; updateModel(id, models[id]) : models[id];
cached[id] = true; cacheService.put(id, model);
}); });
} }
// Expose the cache (for promise chaining) // Expose the cache (for promise chaining)
function giveCache() { function giveCache() {
return cache; return cacheService.all();
} }
// Look up if we have unknown IDs // Look up if we have unknown IDs
@ -110,7 +108,7 @@ define(
} }
// Otherwise, just expose the cache directly // Otherwise, just expose the cache directly
return fastPromise(cache); return fastPromise(cacheService.all());
}; };
return CachingModelDecorator; return CachingModelDecorator;

View File

@ -0,0 +1,83 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define*/
define([], function () {
'use strict';
/**
* Provides a cache for domain object models which exist in memory,
* but may or may not exist in backing persistene stores.
* @constructor
* @memberof platform/core
*/
function ModelCacheService() {
this.cache = {};
}
/**
* Put a domain object model in the cache.
* @param {string} id the domain object's identifier
* @param {object} model the domain object's model
*/
ModelCacheService.prototype.put = function (id, model) {
this.cache[id] = model;
};
/**
* Retrieve a domain object model from the cache.
* @param {string} id the domain object's identifier
* @returns {object} the domain object's model
*/
ModelCacheService.prototype.get = function (id) {
return this.cache[id];
};
/**
* Check if a domain object model is in the cache.
* @param {string} id the domain object's identifier
* @returns {boolean} true if present; false if not
*/
ModelCacheService.prototype.has = function (id) {
return this.cache.hasOwnProperty(id);
};
/**
* Remove a domain object model from the cache.
* @param {string} id the domain object's identifier
*/
ModelCacheService.prototype.remove = function (id) {
delete this.cache[id];
};
/**
* Retrieve all cached domain object models. These are given
* as an object containing key-value pairs, where keys are
* domain object identifiers and values are domain object models.
* @returns {object} all domain object models
*/
ModelCacheService.prototype.all = function () {
return this.cache;
};
return ModelCacheService;
});

View File

@ -44,10 +44,15 @@ define(
* @param {IdentifierService} identifierService service to generate * @param {IdentifierService} identifierService service to generate
* new identifiers * new identifiers
*/ */
function Instantiate(capabilityService, identifierService) { function Instantiate(
capabilityService,
identifierService,
cacheService
) {
return function (model, id) { return function (model, id) {
var capabilities = capabilityService.getCapabilities(model); var capabilities = capabilityService.getCapabilities(model);
id = id || identifierService.generate(); id = id || identifierService.generate();
cacheService.put(id, model);
return new DomainObjectImpl(id, model, capabilities); return new DomainObjectImpl(id, model, capabilities);
}; };
} }

View File

@ -36,6 +36,7 @@ define(
mockDomainObject, mockDomainObject,
mockIdentifier, mockIdentifier,
mockNofificationService, mockNofificationService,
mockCacheService,
mockQ, mockQ,
id = "object id", id = "object id",
model, model,
@ -81,6 +82,10 @@ define(
"notificationService", "notificationService",
["error"] ["error"]
); );
mockCacheService = jasmine.createSpyObj(
"cacheService",
[ "get", "put", "remove", "all" ]
);
mockDomainObject = { mockDomainObject = {
getId: function () { return id; }, getId: function () { return id; },
@ -96,6 +101,7 @@ define(
mockIdentifierService.parse.andReturn(mockIdentifier); mockIdentifierService.parse.andReturn(mockIdentifier);
mockIdentifier.getSpace.andReturn(SPACE); mockIdentifier.getSpace.andReturn(SPACE);
persistence = new PersistenceCapability( persistence = new PersistenceCapability(
mockCacheService,
mockPersistenceService, mockPersistenceService,
mockIdentifierService, mockIdentifierService,
mockNofificationService, mockNofificationService,
@ -170,6 +176,11 @@ define(
expect(mockQ.reject).not.toHaveBeenCalled(); expect(mockQ.reject).not.toHaveBeenCalled();
expect(mockNofificationService.error).not.toHaveBeenCalled(); expect(mockNofificationService.error).not.toHaveBeenCalled();
}); });
it("removes the model from the cache", function () {
persistence.persist();
expect(mockCacheService.remove).toHaveBeenCalledWith(id);
});
}); });
describe("unsuccessful persistence", function() { describe("unsuccessful persistence", function() {
var sadPromise = { var sadPromise = {

View File

@ -22,8 +22,11 @@
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/ /*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define( define(
["../../src/models/CachingModelDecorator"], [
function (CachingModelDecorator) { "../../src/models/CachingModelDecorator",
"../../src/models/ModelCacheService"
],
function (CachingModelDecorator, ModelCacheService) {
"use strict"; "use strict";
describe("The caching model decorator", function () { describe("The caching model decorator", function () {
@ -67,7 +70,10 @@ define(
b: { someOtherKey: "some other value" } b: { someOtherKey: "some other value" }
}; };
mockModelService.getModels.andReturn(asPromise(testModels)); mockModelService.getModels.andReturn(asPromise(testModels));
decorator = new CachingModelDecorator(mockModelService); decorator = new CachingModelDecorator(
new ModelCacheService(),
mockModelService
);
}); });
it("loads models from its wrapped model service", function () { it("loads models from its wrapped model service", function () {

View File

@ -0,0 +1,69 @@
/*****************************************************************************
* Open MCT Web, Copyright (c) 2014-2015, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT Web is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT Web includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
define(['../../src/models/ModelCacheService'], function (ModelCacheService) {
'use strict';
describe("ModelCacheService", function () {
var testIds,
testModels,
cacheService;
beforeEach(function () {
testIds = [ 'a', 'b', 'c', 'd' ];
testModels = testIds.reduce(function (models, id) {
models[id] = { someKey: "some value for " + id };
return models;
}, {});
cacheService = new ModelCacheService();
});
describe("when populated with models", function () {
beforeEach(function () {
testIds.forEach(function (id) {
cacheService.put(id, testModels[id]);
});
});
it("indicates that it has these models", function () {
testIds.forEach(function (id) {
expect(cacheService.has(id)).toBe(true);
});
});
it("provides all of these models", function () {
expect(cacheService.all()).toEqual(testModels);
});
it("allows models to be retrieved", function () {
testIds.forEach(function (id) {
expect(cacheService.get(id)).toEqual(testModels[id]);
});
});
it("allows models to be removed", function () {
cacheService.remove('a');
expect(cacheService.has('a')).toBe(false);
});
});
});
});

View File

@ -32,8 +32,7 @@ define(
mockIdentifierService, mockIdentifierService,
mockCapabilityConstructor, mockCapabilityConstructor,
mockCapabilityInstance, mockCapabilityInstance,
mockCapabilities, mockCacheService,
mockIdentifier,
idCounter, idCounter,
testModel, testModel,
instantiate, instantiate,
@ -62,11 +61,17 @@ define(
"some-id-" + (idCounter += 1); "some-id-" + (idCounter += 1);
}); });
mockCacheService = jasmine.createSpyObj(
'cacheService',
[ 'get', 'put', 'remove', 'all' ]
);
testModel = { someKey: "some value" }; testModel = { someKey: "some value" };
instantiate = new Instantiate( instantiate = new Instantiate(
mockCapabilityService, mockCapabilityService,
mockIdentifierService mockIdentifierService,
mockCacheService
); );
domainObject = instantiate(testModel); domainObject = instantiate(testModel);
}); });
@ -92,6 +97,13 @@ define(
expect(instantiate(testModel).getId()) expect(instantiate(testModel).getId())
.not.toEqual(domainObject.getId()); .not.toEqual(domainObject.getId());
}); });
it("caches the instantiated model", function () {
expect(mockCacheService.put).toHaveBeenCalledWith(
domainObject.getId(),
testModel
);
});
}); });
} }