Merge pull request #255 from nasa/open215

[Creation] Add creation capability
This commit is contained in:
akhenry 2015-11-06 16:00:12 -08:00
commit 51968954a9
13 changed files with 369 additions and 132 deletions

View File

@ -1,9 +1,4 @@
{
"configuration": {
"paths": {
"uuid": "uuid"
}
},
"extensions": {
"routes": [
{
@ -159,7 +154,7 @@
"provides": "creationService",
"type": "provider",
"implementation": "creation/CreationService.js",
"depends": [ "persistenceService", "now", "$q", "$log" ]
"depends": [ "$q", "$log" ]
}
],
"runs": [
@ -178,16 +173,6 @@
"copyright": "Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)",
"license": "license-mit",
"link": "https://github.com/sindresorhus/screenfull.js/blob/gh-pages/license"
},
{
"name": "Math.uuid.js",
"version": "1.4",
"description": "Unique identifer generation (code adapted.)",
"author": "Robert Kieffer",
"website": "https://github.com/broofa/node-uuid",
"copyright": "Copyright (c) 2010 Robert Kieffer",
"license": "license-mit",
"link": "http://opensource.org/licenses/MIT"
}
]
}

View File

@ -25,8 +25,8 @@
* Module defining CreateService. Created by vwoeltje on 11/10/14.
*/
define(
["uuid"],
function (uuid) {
[],
function () {
"use strict";
var NON_PERSISTENT_WARNING =
@ -42,11 +42,9 @@ define(
* @memberof platform/commonUI/browse
* @constructor
*/
function CreationService(persistenceService, now, $q, $log) {
this.persistenceService = persistenceService;
function CreationService($q, $log) {
this.$q = $q;
this.$log = $log;
this.now = now;
}
/**
@ -70,26 +68,17 @@ define(
*/
CreationService.prototype.createObject = function (model, parent) {
var persistence = parent.getCapability("persistence"),
newObject = parent.useCapability("instantiation", model),
newObjectPersistence = newObject.getCapability("persistence"),
self = this;
// Persist the new domain object's model; it will be fully
// constituted as a domain object when loaded back, as all
// domain object models are.
function doPersist(space, id, model) {
return self.persistenceService.createObject(
space,
id,
model
).then(function () { return id; });
}
// Add the newly-created object's id to the parent's
// composition, so that it will subsequently appear
// as a child contained by that parent.
function addToComposition(id, parent, parentPersistence) {
function addToComposition() {
var compositionCapability = parent.getCapability('composition'),
addResult = compositionCapability &&
compositionCapability.add(id);
compositionCapability.add(newObject);
return self.$q.when(addResult).then(function (result) {
if (!result) {
@ -97,7 +86,7 @@ define(
return undefined;
}
return parentPersistence.persist().then(function () {
return persistence.persist().then(function () {
return result;
});
});
@ -105,21 +94,13 @@ define(
// We need the parent's persistence capability to determine
// what space to create the new object's model in.
if (!persistence) {
if (!persistence || !newObjectPersistence) {
self.$log.warn(NON_PERSISTENT_WARNING);
return self.$q.reject(new Error(NON_PERSISTENT_WARNING));
}
// We create a new domain object in three sequential steps:
// 1. Get a new UUID for the object
// 2. Create a model with that ID in the persistence space
// 3. Add that ID to
return self.$q.when(uuid()).then(function (id) {
model.persisted = self.now();
return doPersist(persistence.getSpace(), id, model);
}).then(function (id) {
return addToComposition(id, parent, persistence);
});
// Persist the new object, then add it to composition.
return newObjectPersistence.persist().then(addToComposition);
};

View File

@ -30,9 +30,7 @@ define(
"use strict";
describe("The creation service", function () {
var mockPersistenceService,
mockNow,
mockQ,
var mockQ,
mockLog,
mockParentObject,
mockNewObject,
@ -40,7 +38,9 @@ define(
mockPersistenceCapability,
mockCompositionCapability,
mockContextCapability,
mockCreationCapability,
mockCapabilities,
mockNewPersistenceCapability,
creationService;
function mockPromise(value) {
@ -60,11 +60,6 @@ define(
}
beforeEach(function () {
mockPersistenceService = jasmine.createSpyObj(
"persistenceService",
[ "createObject" ]
);
mockNow = jasmine.createSpy('now');
mockQ = { when: mockPromise, reject: mockReject };
mockLog = jasmine.createSpyObj(
"$log",
@ -76,7 +71,7 @@ define(
);
mockNewObject = jasmine.createSpyObj(
"newObject",
[ "getId" ]
[ "getId", "getCapability", "useCapability" ]
);
mockMutationCapability = jasmine.createSpyObj(
"mutation",
@ -94,19 +89,22 @@ define(
"context",
["getPath"]
);
mockCreationCapability = jasmine.createSpyObj(
"creation",
["instantiate", "invoke"]
);
mockCapabilities = {
mutation: mockMutationCapability,
persistence: mockPersistenceCapability,
composition: mockCompositionCapability,
context: mockContextCapability
context: mockContextCapability,
instantiation: mockCreationCapability
};
mockPersistenceService.createObject.andReturn(
mockPromise(true)
mockNewPersistenceCapability = jasmine.createSpyObj(
"new-persistence",
[ "persist", "getSpace" ]
);
mockNow.andReturn(12321);
mockParentObject.getCapability.andCallFake(function (key) {
return mockCapabilities[key];
});
@ -115,9 +113,16 @@ define(
});
mockParentObject.getId.andReturn('parentId');
mockPersistenceCapability.persist.andReturn(
mockPromise(true)
);
mockNewObject.getId.andReturn('newId');
mockNewObject.getCapability.andCallFake(function (c) {
return c === 'persistence' ?
mockNewPersistenceCapability : undefined;
});
mockPersistenceCapability.persist
.andReturn(mockPromise(true));
mockNewPersistenceCapability.persist
.andReturn(mockPromise(true));
mockMutationCapability.invoke.andReturn(mockPromise(true));
mockPersistenceCapability.getSpace.andReturn("testSpace");
@ -125,10 +130,12 @@ define(
mockPromise([mockNewObject])
);
mockCompositionCapability.add.andReturn(mockPromise(true));
mockCreationCapability.instantiate.andReturn(mockNewObject);
mockCreationCapability.invoke.andCallFake(function (model) {
return mockCreationCapability.instantiate(model);
});
creationService = new CreationService(
mockPersistenceService,
mockNow,
mockQ,
mockLog
);
@ -137,21 +144,18 @@ define(
it("allows new objects to be created", function () {
var model = { someKey: "some value" };
creationService.createObject(model, mockParentObject);
expect(mockPersistenceService.createObject).toHaveBeenCalledWith(
"testSpace",
jasmine.any(String), // the object id; generated UUID
model
);
expect(mockCreationCapability.instantiate)
.toHaveBeenCalledWith(model);
});
it("adds new id's to the parent's composition", function () {
it("adds new objects to the parent's composition", function () {
var model = { someKey: "some value" },
parentModel = { composition: ["notAnyUUID"] };
creationService.createObject(model, mockParentObject);
// Verify that a new ID was added
expect(mockCompositionCapability.add)
.toHaveBeenCalledWith(jasmine.any(String));
.toHaveBeenCalledWith(mockNewObject);
});
it("provides the newly-created object", function () {
@ -207,11 +211,6 @@ define(
expect(mockLog.error).toHaveBeenCalled();
});
it("attaches a 'persisted' timestamp", function () {
var model = { someKey: "some value" };
creationService.createObject(model, mockParentObject);
expect(model.persisted).toEqual(mockNow());
});
});
}

View File

@ -2,6 +2,11 @@
"name": "Open MCT Web Core",
"description": "Defines core concepts of Open MCT Web.",
"sources": "src",
"configuration": {
"paths": {
"uuid": "uuid"
}
},
"extensions": {
"versions": [
{
@ -33,7 +38,7 @@
"provides": "objectService",
"type": "provider",
"implementation": "objects/DomainObjectProvider.js",
"depends": [ "modelService", "capabilityService", "$q" ]
"depends": [ "modelService", "instantiate" ]
},
{
"provides": "capabilityService",
@ -193,6 +198,11 @@
"key": "delegation",
"implementation": "capabilities/DelegationCapability.js",
"depends": [ "$q" ]
},
{
"key": "instantiation",
"implementation": "capabilities/InstantiationCapability.js",
"depends": [ "$injector" ]
}
],
"services": [
@ -213,6 +223,11 @@
"key": "contextualize",
"implementation": "services/Contextualize.js",
"depends": [ "$log" ]
},
{
"key": "instantiate",
"implementation": "services/Instantiate.js",
"depends": [ "capabilityService" ]
}
],
"roots": [
@ -235,6 +250,18 @@
"value": [],
"description": "An array of additional persistence spaces to load models from."
}
],
"licenses": [
{
"name": "Math.uuid.js",
"version": "1.4",
"description": "Unique identifer generation (code adapted.)",
"author": "Robert Kieffer",
"website": "https://github.com/broofa/node-uuid",
"copyright": "Copyright (c) 2010 Robert Kieffer",
"license": "license-mit",
"link": "http://opensource.org/licenses/MIT"
}
]
}
}

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* 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*/
define(
['../objects/DomainObjectImpl', 'uuid'],
function (DomainObjectImpl, uuid) {
'use strict';
/**
* Implements the `instantiation` capability. This allows new domain
* objects to be instantiated.
*
* @constructor
* @memberof platform/core
* @param $injector Angular's `$injector`
*/
function InstantiationCapability($injector) {
this.$injector = $injector;
}
/**
* Instantiate a new domain object with the provided model.
*
* This domain object will have been simply instantiated; it will not
* have been persisted, nor will it have been added to the
* composition of the object which exposed this capability.
*
* @returns {DomainObject} the new domain object
*/
InstantiationCapability.prototype.instantiate = function (model) {
// 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);
};
/**
* Alias of `create`.
* @see {platform/core.CreationCapability#create}
*/
InstantiationCapability.prototype.invoke =
InstantiationCapability.prototype.instantiate;
return InstantiationCapability;
}
);

View File

@ -27,8 +27,8 @@
* @namespace platform/core
*/
define(
["./DomainObjectImpl"],
function (DomainObjectImpl) {
[],
function () {
"use strict";
/**
@ -57,62 +57,36 @@ define(
*
* @param {ModelService} modelService the service which shall
* provide models (persistent state) for domain objects
* @param {CapabilityService} capabilityService the service
* which provides capabilities (dynamic behavior)
* for domain objects.
* @param {Function} instantiate a service to instantiate new
* domain object instances
* @param $q Angular's $q, for promise consolidation
* @memberof platform/core
* @constructor
*/
function DomainObjectProvider(modelService, capabilityService, $q) {
function DomainObjectProvider(modelService, instantiate, $q) {
this.modelService = modelService;
this.capabilityService = capabilityService;
this.$q = $q;
this.instantiate = instantiate;
}
DomainObjectProvider.prototype.getObjects = function getObjects(ids) {
var modelService = this.modelService,
capabilityService = this.capabilityService,
$q = this.$q;
// Given a models object (containing key-value id-model pairs)
// create a function that will look up from the capability
// service based on id; for handy mapping below.
function capabilityResolver(models) {
return function (id) {
var model = models[id];
return model ?
capabilityService.getCapabilities(model) :
undefined;
};
}
instantiate = this.instantiate;
// Assemble the results from the model service and the
// capability service into one value, suitable to return
// from this service. Note that ids are matched to capabilities
// by index.
function assembleResult(ids, models, capabilities) {
// from this service.
function assembleResult(models) {
var result = {};
ids.forEach(function (id, index) {
if (models[id]) {
// Create the domain object
result[id] = new DomainObjectImpl(
id,
models[id],
capabilities[index]
);
result[id] = instantiate(models[id], id);
}
});
return result;
}
return modelService.getModels(ids).then(function (models) {
return $q.all(
ids.map(capabilityResolver(models))
).then(function (capabilities) {
return assembleResult(ids, models, capabilities);
});
});
return modelService.getModels(ids).then(assembleResult);
};
return DomainObjectProvider;

View File

@ -0,0 +1,54 @@
/*****************************************************************************
* 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*/
define(
['../objects/DomainObjectImpl', 'uuid'],
function (DomainObjectImpl, uuid) {
'use strict';
/**
* The `instantiate` service allows new domain object instances to be
* created. These objects are not persisted to any back-end or
* placed anywhere in the object hierarchy by default.
*
* Usage: `instantiate(model, [id])`
*
* ...returns a new instance of a domain object with the specified
* model. An identifier may be provided; if omitted, one will be
* generated instead.
*
* @constructor
* @memberof platform/core
* @param $injector Angular's `$injector`
*/
function Instantiate(capabilityService) {
return function (model, id) {
var capabilities = capabilityService.getCapabilities(model);
id = id || uuid();
return new DomainObjectImpl(id, model, capabilities);
};
}
return Instantiate;
}
);

View File

@ -0,0 +1,67 @@
/*****************************************************************************
* 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,xdescribe,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/capabilities/InstantiationCapability"],
function (InstantiationCapability) {
'use strict';
describe("The 'instantiation' capability", function () {
var mockInjector,
mockInstantiate,
instantiation;
beforeEach(function () {
mockInjector = jasmine.createSpyObj("$injector", ["get"]);
mockInstantiate = jasmine.createSpy("instantiate");
mockInjector.get.andCallFake(function (key) {
return key === 'instantiate' ?
mockInstantiate : undefined;
});
instantiation = new InstantiationCapability(mockInjector);
});
it("aliases 'instantiate' as 'invoke'", function () {
expect(instantiation.invoke).toBe(instantiation.instantiate);
});
it("uses the instantiate service to create domain objects", function () {
var mockDomainObject = jasmine.createSpyObj('domainObject', [
'getId',
'getModel',
'getCapability',
'useCapability',
'hasCapability'
]), testModel = { someKey: "some value" };
mockInstantiate.andReturn(mockDomainObject);
expect(instantiation.instantiate(testModel))
.toBe(mockDomainObject);
expect(mockInstantiate).toHaveBeenCalledWith(testModel);
});
});
}
);

View File

@ -25,14 +25,16 @@
* DomainObjectProviderSpec. Created by vwoeltje on 11/6/14.
*/
define(
["../../src/objects/DomainObjectProvider"],
function (DomainObjectProvider) {
[
"../../src/objects/DomainObjectProvider",
"../../src/objects/DomainObjectImpl"
],
function (DomainObjectProvider, DomainObjectImpl) {
"use strict";
describe("The domain object provider", function () {
var mockModelService,
mockCapabilityService,
mockQ,
mockInstantiate,
provider;
function mockPromise(value) {
@ -57,18 +59,15 @@ define(
"modelService",
[ "getModels" ]
);
mockCapabilityService = jasmine.createSpyObj(
"capabilityService",
[ "getCapabilities" ]
);
mockQ = {
when: mockPromise,
all: mockAll
};
mockInstantiate = jasmine.createSpy("instantiate");
mockInstantiate.andCallFake(function (model, id) {
return new DomainObjectImpl(id, model, {});
});
provider = new DomainObjectProvider(
mockModelService,
mockCapabilityService,
mockQ
mockInstantiate
);
});
@ -86,10 +85,11 @@ define(
result;
mockModelService.getModels.andReturn(mockPromise({ a: model }));
result = provider.getObjects(ids).testValue;
expect(mockInstantiate).toHaveBeenCalledWith(model, 'a');
expect(result.a.getId()).toEqual("a");
expect(result.a.getModel()).toEqual(model);
});
});
}
);
);

View File

@ -0,0 +1,81 @@
/*****************************************************************************
* 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,xdescribe,expect,beforeEach,waitsFor,jasmine*/
define(
["../../src/services/Instantiate"],
function (Instantiate) {
'use strict';
describe("The 'instantiate' service", function () {
var mockCapabilityService,
mockCapabilityConstructor,
mockCapabilityInstance,
mockCapabilities,
testModel,
instantiate,
domainObject;
beforeEach(function () {
mockCapabilityService = jasmine.createSpyObj(
'capabilityService',
['getCapabilities']
);
mockCapabilityConstructor = jasmine.createSpy('capability');
mockCapabilityInstance = {};
mockCapabilityService.getCapabilities.andReturn({
something: mockCapabilityConstructor
});
mockCapabilityConstructor.andReturn(mockCapabilityInstance);
testModel = { someKey: "some value" };
instantiate = new Instantiate(mockCapabilityService);
domainObject = instantiate(testModel);
});
it("loads capabilities from the capability service", function () {
expect(mockCapabilityService.getCapabilities)
.toHaveBeenCalledWith(testModel);
});
it("exposes loaded capabilities from the created object", function () {
expect(domainObject.getCapability('something'))
.toBe(mockCapabilityInstance);
expect(mockCapabilityConstructor)
.toHaveBeenCalledWith(domainObject);
});
it("exposes the provided model", function () {
expect(domainObject.getModel()).toEqual(testModel);
});
it("provides unique identifiers", function () {
expect(domainObject.getId()).toEqual(jasmine.any(String));
expect(instantiate(testModel).getId())
.not.toEqual(domainObject.getId());
});
});
}
);

View File

@ -9,6 +9,7 @@
"capabilities/ContextualDomainObject",
"capabilities/CoreCapabilityProvider",
"capabilities/DelegationCapability",
"capabilities/InstantiationCapability",
"capabilities/MetadataCapability",
"capabilities/MutationCapability",
"capabilities/PersistenceCapability",
@ -25,6 +26,7 @@
"objects/DomainObjectProvider",
"services/Contextualize",
"services/Instantiate",
"services/Now",
"services/Throttle",
"services/Topic",

View File

@ -46,7 +46,7 @@ require.config({
'es6-promise': 'platform/framework/lib/es6-promise-2.0.0.min',
'moment': 'platform/telemetry/lib/moment.min',
'moment-duration-format': 'platform/features/clock/lib/moment-duration-format',
'uuid': 'platform/commonUI/browse/lib/uuid'
'uuid': 'platform/core/lib/uuid'
},
shim: {