Merged from master

This commit is contained in:
Henry
2015-11-25 09:04:11 -08:00
parent 7849803a5d
commit 268a2c2427
64 changed files with 3200 additions and 231 deletions

View File

@@ -72,8 +72,7 @@
"persistenceService",
"$q",
"now",
"PERSISTENCE_SPACE",
"ADDITIONAL_PERSISTENCE_SPACES"
"PERSISTENCE_SPACE"
]
},
{
@@ -115,6 +114,12 @@
"type": "provider",
"implementation": "views/ViewProvider.js",
"depends": [ "views[]", "$log" ]
},
{
"provides": "identifierService",
"type": "provider",
"implementation": "identifiers/IdentifierProvider.js",
"depends": [ "PERSISTENCE_SPACE" ]
}
],
"types": [
@@ -183,7 +188,7 @@
{
"key": "persistence",
"implementation": "capabilities/PersistenceCapability.js",
"depends": [ "persistenceService", "PERSISTENCE_SPACE" ]
"depends": [ "persistenceService", "identifierService" ]
},
{
"key": "metadata",
@@ -202,7 +207,7 @@
{
"key": "instantiation",
"implementation": "capabilities/InstantiationCapability.js",
"depends": [ "$injector" ]
"depends": [ "$injector", "identifierService" ]
}
],
"services": [
@@ -245,11 +250,6 @@
{
"key": "PERSISTENCE_SPACE",
"value": "mct"
},
{
"key": "ADDITIONAL_PERSISTENCE_SPACES",
"value": [],
"description": "An array of additional persistence spaces to load models from."
}
],
"licenses": [

View File

@@ -22,8 +22,8 @@
/*global define,Promise*/
define(
['../objects/DomainObjectImpl', 'uuid'],
function (DomainObjectImpl, uuid) {
['../objects/DomainObjectImpl'],
function (DomainObjectImpl) {
'use strict';
/**
@@ -33,9 +33,12 @@ define(
* @constructor
* @memberof platform/core
* @param $injector Angular's `$injector`
* @implements {Capability}
*/
function InstantiationCapability($injector) {
function InstantiationCapability($injector, identifierService, domainObject) {
this.$injector = $injector;
this.identifierService = identifierService;
this.domainObject = domainObject;
}
/**
@@ -45,19 +48,26 @@ define(
* have been persisted, nor will it have been added to the
* composition of the object which exposed this capability.
*
* @param {object} the model for the new domain object
* @returns {DomainObject} the new domain object
*/
InstantiationCapability.prototype.instantiate = function (model) {
var parsedId =
this.identifierService.parse(this.domainObject.getId()),
space = parsedId.getDefinedSpace(),
id = this.identifierService.generate(space);
// Lazily initialize; instantiate depends on capabilityService,
// which depends on all capabilities, including this one.
this.instantiateFn = this.instantiateFn ||
this.$injector.get("instantiate");
return this.instantiateFn(model);
return this.instantiateFn(model, id);
};
/**
* Alias of `create`.
* @see {platform/core.CreationCapability#create}
* Alias of `instantiate`.
* @see {platform/core.CreationCapability#instantiate}
*/
InstantiationCapability.prototype.invoke =
InstantiationCapability.prototype.instantiate;

View File

@@ -44,12 +44,16 @@ define(
* @constructor
* @implements {Capability}
*/
function PersistenceCapability(persistenceService, space, domainObject) {
function PersistenceCapability(
persistenceService,
identifierService,
domainObject
) {
// Cache modified timestamp
this.modified = domainObject.getModel().modified;
this.domainObject = domainObject;
this.space = space;
this.identifierService = identifierService;
this.persistenceService = persistenceService;
}
@@ -63,6 +67,11 @@ define(
};
}
function getKey(id) {
var parts = id.split(":");
return parts.length > 1 ? parts.slice(1).join(":") : id;
}
/**
* Persist any changes which have been made to this
* domain object's model.
@@ -87,7 +96,7 @@ define(
// ...and persist
return persistenceFn.apply(persistenceService, [
this.getSpace(),
domainObject.getId(),
getKey(domainObject.getId()),
domainObject.getModel()
]);
};
@@ -130,7 +139,8 @@ define(
* be used to persist this object
*/
PersistenceCapability.prototype.getSpace = function () {
return this.space;
var id = this.domainObject.getId();
return this.identifierService.parse(id).getSpace();
};
return PersistenceCapability;

View File

@@ -0,0 +1,86 @@
/*****************************************************************************
* 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';
var SEPARATOR = ":";
/**
* Provides an interface for interpreting domain object identifiers;
* in particular, parses out persistence space/key pairs associated
* with the domain object.
*
* @memberof platform/core
* @constructor
* @param {string} id the domain object identifier
* @param {string} defaultSpace the persistence space to use if
* one is not encoded in the identifier
*/
function Identifier(id, defaultSpace) {
var separatorIndex = id.indexOf(SEPARATOR);
if (separatorIndex > -1) {
this.key = id.substring(separatorIndex + 1);
this.space = id.substring(0, separatorIndex);
this.definedSpace = this.space;
} else {
this.key = id;
this.space = defaultSpace;
this.definedSpace = undefined;
}
}
/**
* Get the key under which the identified domain object's model
* should be persisted, within its persistence space.
* @returns {string} the key within its persistence space
*/
Identifier.prototype.getKey = function () {
return this.key;
};
/**
* Get the space in which the identified domain object's model should
* be persisted.
* @returns {string} the persistence space
*/
Identifier.prototype.getSpace = function () {
return this.space;
};
/**
* Get the persistence space, if any, which has been explicitly
* encoded in this domain object's identifier. Returns undefined
* if no such space has been specified.
* @returns {string} the persistence space, or undefined
*/
Identifier.prototype.getDefinedSpace = function () {
return this.definedSpace;
};
return Identifier;
}
);

View File

@@ -0,0 +1,66 @@
/*****************************************************************************
* 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(
["uuid", "./Identifier"],
function (uuid, Identifier) {
'use strict';
/**
* Parses and generates domain object identifiers.
* @param {string} defaultSpace the default persistence space
* @constructor
* @memberof {platform/core}
*/
function IdentifierProvider(defaultSpace) {
this.defaultSpace = defaultSpace;
}
/**
* Generate a new domain object identifier. A persistence space
* may optionally be included; if not specified, no space will
* be encoded into the identifier.
* @param {string} [space] the persistence space to encode
* in this identifier
* @returns {string} a new domain object identifier
*/
IdentifierProvider.prototype.generate = function (space) {
var id = uuid();
if (space !== undefined) {
id = space + ":" + id;
}
return id;
};
/**
* Parse a domain object identifier to examine its component
* parts (e.g. its persistence space.)
* @returns {platform/core.Identifier} the parsed identifier
*/
IdentifierProvider.prototype.parse = function (id) {
return new Identifier(id, this.defaultSpace);
};
return IdentifierProvider;
}
);

View File

@@ -33,6 +33,15 @@ define(
* A model service which reads domain object models from an external
* persistence service.
*
* Identifiers will be interpreted as follows:
* * If no colon is present, the model will be read from the default
* persistence space.
* * If a colon is present, everything before the first colon will be
* taken to refer to the persistence space, and everything after
* will be taken to be that model's key within this space. (If
* no such space exists within the `persistenceService`, that
* identifier will simply be ignored.)
*
* @memberof platform/core
* @constructor
* @implements {ModelService}
@@ -41,39 +50,26 @@ define(
* @param $q Angular's $q service, for working with promises
* @param {function} now a function which provides the current time
* @param {string} space the name of the persistence space(s)
* from which models should be retrieved.
* @param {string} spaces additional persistence spaces to use
* from which models should be retrieved by default
*/
function PersistedModelProvider(persistenceService, $q, now, space, spaces) {
function PersistedModelProvider(persistenceService, $q, now, space) {
this.persistenceService = persistenceService;
this.$q = $q;
this.spaces = [space].concat(spaces || []);
this.now = now;
}
// Take the most recently modified model, for cases where
// multiple persistence spaces return models.
function takeMostRecent(modelA, modelB) {
return (!modelB || modelB.modified === undefined) ? modelA :
(!modelA || modelA.modified === undefined) ? modelB :
modelB.modified > modelA.modified ? modelB :
modelA;
this.defaultSpace = space;
}
PersistedModelProvider.prototype.getModels = function (ids) {
var persistenceService = this.persistenceService,
$q = this.$q,
spaces = this.spaces,
space = this.space,
now = this.now;
now = this.now,
defaultSpace = this.defaultSpace,
parsedIds;
// Load a single object model from any persistence spaces
function loadModel(id) {
return $q.all(spaces.map(function (space) {
return persistenceService.readObject(space, id);
})).then(function (models) {
return models.reduce(takeMostRecent);
});
function loadModel(parsedId) {
return persistenceService
.readObject(parsedId.space, parsedId.key);
}
// Ensure that models read from persistence have some
@@ -88,24 +84,43 @@ define(
}
// Package the result as id->model
function packageResult(models) {
function packageResult(parsedIds, models) {
var result = {};
ids.forEach(function (id, index) {
parsedIds.forEach(function (parsedId, index) {
var id = parsedId.id;
if (models[index]) {
result[id] = addPersistedTimestamp(models[index]);
result[id] = models[index];
}
});
return result;
}
// Filter out "namespaced" identifiers; these are
// not expected to be found in database. See WTD-659.
ids = ids.filter(function (id) {
return id.indexOf(":") === -1;
function loadModels(parsedIds) {
return $q.all(parsedIds.map(loadModel))
.then(function (models) {
return packageResult(
parsedIds,
models.map(addPersistedTimestamp)
);
});
}
function restrictToSpaces(spaces) {
return parsedIds.filter(function (parsedId) {
return spaces.indexOf(parsedId.space) !== -1;
});
}
parsedIds = ids.map(function (id) {
var parts = id.split(":");
return (parts.length > 1) ?
{ id: id, space: parts[0], key: parts.slice(1).join(":") } :
{ id: id, space: defaultSpace, key: id };
});
// Give a promise for all persistence lookups...
return $q.all(ids.map(loadModel)).then(packageResult);
return persistenceService.listSpaces()
.then(restrictToSpaces)
.then(loadModels);
};
return PersistedModelProvider;

View File

@@ -22,8 +22,8 @@
/*global define,Promise*/
define(
['../objects/DomainObjectImpl', 'uuid'],
function (DomainObjectImpl, uuid) {
['../objects/DomainObjectImpl'],
function (DomainObjectImpl) {
'use strict';
/**
@@ -39,12 +39,15 @@ define(
*
* @constructor
* @memberof platform/core
* @param $injector Angular's `$injector`
* @param {CapabilityService} capabilityService the service which will
* provide instantiated domain objects with their capabilities
* @param {IdentifierService} identifierService service to generate
* new identifiers
*/
function Instantiate(capabilityService) {
function Instantiate(capabilityService, identifierService) {
return function (model, id) {
var capabilities = capabilityService.getCapabilities(model);
id = id || uuid();
id = id || identifierService.generate();
return new DomainObjectImpl(id, model, capabilities);
};
}

View File

@@ -28,19 +28,40 @@ define(
describe("The 'instantiation' capability", function () {
var mockInjector,
mockIdentifierService,
mockInstantiate,
mockIdentifier,
mockDomainObject,
instantiation;
beforeEach(function () {
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
mockInstantiate = jasmine.createSpy("instantiate");
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
[ 'parse', 'generate' ]
);
mockIdentifier = jasmine.createSpyObj(
'identifier',
[ 'getSpace', 'getKey', 'getDefinedSpace' ]
);
mockDomainObject = jasmine.createSpyObj(
'domainObject',
[ 'getId', 'getCapability', 'getModel' ]
);
mockInjector.get.andCallFake(function (key) {
return key === 'instantiate' ?
mockInstantiate : undefined;
});
mockIdentifierService.parse.andReturn(mockIdentifier);
mockIdentifierService.generate.andReturn("some-id");
instantiation = new InstantiationCapability(mockInjector);
instantiation = new InstantiationCapability(
mockInjector,
mockIdentifierService,
mockDomainObject
);
});
@@ -59,7 +80,8 @@ define(
mockInstantiate.andReturn(mockDomainObject);
expect(instantiation.instantiate(testModel))
.toBe(mockDomainObject);
expect(mockInstantiate).toHaveBeenCalledWith(testModel);
expect(mockInstantiate)
.toHaveBeenCalledWith(testModel, jasmine.any(String));
});
});

View File

@@ -31,7 +31,9 @@ define(
describe("The persistence capability", function () {
var mockPersistenceService,
mockIdentifierService,
mockDomainObject,
mockIdentifier,
id = "object id",
model = { someKey: "some value"},
SPACE = "some space",
@@ -50,6 +52,14 @@ define(
"persistenceService",
[ "updateObject", "readObject", "createObject", "deleteObject" ]
);
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
[ 'parse', 'generate' ]
);
mockIdentifier = jasmine.createSpyObj(
'identifier',
[ 'getSpace', 'getKey', 'getDefinedSpace' ]
);
mockDomainObject = {
getId: function () { return id; },
getModel: function () { return model; },
@@ -61,9 +71,11 @@ define(
model = mutator(model) || model;
}
});
mockIdentifierService.parse.andReturn(mockIdentifier);
mockIdentifier.getSpace.andReturn(SPACE);
persistence = new PersistenceCapability(
mockPersistenceService,
SPACE,
mockIdentifierService,
mockDomainObject
);
});

View File

@@ -0,0 +1,58 @@
/*****************************************************************************
* 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/identifiers/IdentifierProvider"],
function (IdentifierProvider) {
'use strict';
describe("IdentifierProvider", function () {
var defaultSpace,
provider;
beforeEach(function () {
defaultSpace = "some-default-space";
provider = new IdentifierProvider(defaultSpace);
});
it("generates unique identifiers", function () {
expect(provider.generate())
.not.toEqual(provider.generate());
});
it("allows spaces to be specified for generated identifiers", function () {
var specificSpace = "some-specific-space",
id = provider.generate(specificSpace);
expect(id).toEqual(jasmine.any(String));
expect(provider.parse(id).getDefinedSpace())
.toEqual(specificSpace);
});
it("parses identifiers using the default space", function () {
expect(provider.parse("some-unprefixed-id").getSpace())
.toEqual(defaultSpace);
});
});
}
);

View File

@@ -0,0 +1,82 @@
/*****************************************************************************
* 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/identifiers/Identifier"],
function (Identifier) {
'use strict';
describe("A parsed domain object identifier", function () {
var id,
defaultSpace,
identifier;
beforeEach(function () {
defaultSpace = "someDefaultSpace";
});
describe("when space is encoded", function () {
var idSpace, idKey, spacedId;
beforeEach(function () {
idSpace = "a-specific-space";
idKey = "a-specific-key";
id = idSpace + ":" + idKey;
identifier = new Identifier(id, defaultSpace);
});
it("provides the encoded space", function () {
expect(identifier.getSpace()).toEqual(idSpace);
});
it("provides the key within that space", function () {
expect(identifier.getKey()).toEqual(idKey);
});
it("provides the defined space", function () {
expect(identifier.getDefinedSpace()).toEqual(idSpace);
});
});
describe("when space is not encoded", function () {
beforeEach(function () {
id = "a-generic-id";
identifier = new Identifier(id, defaultSpace);
});
it("provides the default space", function () {
expect(identifier.getSpace()).toEqual(defaultSpace);
});
it("provides the id as the key", function () {
expect(identifier.getKey()).toEqual(id);
});
it("provides no defined space", function () {
expect(identifier.getDefinedSpace()).toEqual(undefined);
});
});
});
}
);

View File

@@ -33,13 +33,12 @@ define(
var mockQ,
mockPersistenceService,
SPACE = "space0",
spaces = [ "space1" ],
modTimes,
mockNow,
provider;
function mockPromise(value) {
return {
return (value || {}).then ? value : {
then: function (callback) {
return mockPromise(callback(value));
},
@@ -78,13 +77,14 @@ define(
persisted: 0
});
});
mockPersistenceService.listSpaces
.andReturn(mockPromise([SPACE]));
provider = new PersistedModelProvider(
mockPersistenceService,
mockQ,
mockNow,
SPACE,
spaces
SPACE
);
});
@@ -103,25 +103,6 @@ define(
});
it("reads object models from multiple spaces", function () {
var models;
modTimes.space1 = {
'x': 12321
};
provider.getModels(["a", "x", "zz"]).then(function (m) {
models = m;
});
expect(models).toEqual({
a: { space: SPACE, id: "a", persisted: 0 },
x: { space: 'space1', id: "x", modified: 12321, persisted: 0 },
zz: { space: SPACE, id: "zz", persisted: 0 }
});
});
it("ensures that persisted timestamps are present", function () {
var mockCallback = jasmine.createSpy("callback"),
testModels = {

View File

@@ -29,18 +29,27 @@ define(
describe("The 'instantiate' service", function () {
var mockCapabilityService,
mockIdentifierService,
mockCapabilityConstructor,
mockCapabilityInstance,
mockCapabilities,
mockIdentifier,
idCounter,
testModel,
instantiate,
domainObject;
beforeEach(function () {
idCounter = 0;
mockCapabilityService = jasmine.createSpyObj(
'capabilityService',
['getCapabilities']
);
mockIdentifierService = jasmine.createSpyObj(
'identifierService',
[ 'parse', 'generate' ]
);
mockCapabilityConstructor = jasmine.createSpy('capability');
mockCapabilityInstance = {};
mockCapabilityService.getCapabilities.andReturn({
@@ -48,9 +57,17 @@ define(
});
mockCapabilityConstructor.andReturn(mockCapabilityInstance);
mockIdentifierService.generate.andCallFake(function (space) {
return (space ? (space + ":") : "") +
"some-id-" + (idCounter += 1);
});
testModel = { someKey: "some value" };
instantiate = new Instantiate(mockCapabilityService);
instantiate = new Instantiate(
mockCapabilityService,
mockIdentifierService
);
domainObject = instantiate(testModel);
});

View File

@@ -15,6 +15,9 @@
"capabilities/PersistenceCapability",
"capabilities/RelationshipCapability",
"identifiers/Identifier",
"identifiers/IdentifierProvider",
"models/ModelAggregator",
"models/MissingModelDecorator",
"models/PersistedModelProvider",