From a63e0533993cf263e84cfda9f92298aafe45f7a8 Mon Sep 17 00:00:00 2001 From: Pete Richards Date: Fri, 17 Jun 2016 16:54:32 -0700 Subject: [PATCH 1/2] [ObjectAPI] Draft new Object API Rought prototype of new object API. --- bower.json | 3 +- main.js | 3 +- src/MCT.js | 9 +- src/api/api.js | 9 +- src/api/objects/LegacyObjectAPIInterceptor.js | 70 ++++++++++++ src/api/objects/ObjectAPI.js | 92 ++++++++++++++++ src/api/objects/README.md | 101 ++++++++++++++++++ src/api/objects/bundle.js | 49 +++++++++ src/api/objects/object-utils.js | 67 ++++++++++++ 9 files changed, 396 insertions(+), 7 deletions(-) create mode 100644 src/api/objects/LegacyObjectAPIInterceptor.js create mode 100644 src/api/objects/ObjectAPI.js create mode 100644 src/api/objects/README.md create mode 100644 src/api/objects/bundle.js create mode 100644 src/api/objects/object-utils.js diff --git a/bower.json b/bower.json index c10b8e49e2..fecb559734 100644 --- a/bower.json +++ b/bower.json @@ -19,6 +19,7 @@ "comma-separated-values": "^3.6.4", "FileSaver.js": "^0.0.2", "zepto": "^1.1.6", - "eventemitter3": "^1.2.0" + "eventemitter3": "^1.2.0", + "lodash": "3.10.1" } } diff --git a/main.js b/main.js index 561c6e8b79..045a8142ab 100644 --- a/main.js +++ b/main.js @@ -35,7 +35,8 @@ requirejs.config({ "screenfull": "bower_components/screenfull/dist/screenfull.min", "text": "bower_components/text/text", "uuid": "bower_components/node-uuid/uuid", - "zepto": "bower_components/zepto/zepto.min" + "zepto": "bower_components/zepto/zepto.min", + "lodash": "bower_components/lodash/lodash" }, "shim": { "angular": { diff --git a/src/MCT.js b/src/MCT.js index 3b66611451..92dc4f5391 100644 --- a/src/MCT.js +++ b/src/MCT.js @@ -1,8 +1,13 @@ define([ 'EventEmitter', 'legacyRegistry', - './api/api' -], function (EventEmitter, legacyRegistry, api) { + './api/api', + './api/objects/bundle' +], function ( + EventEmitter, + legacyRegistry, + api +) { function MCT() { EventEmitter.call(this); this.legacyBundle = { extensions: {} }; diff --git a/src/api/api.js b/src/api/api.js index b64b1f578d..d5be08293c 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1,9 +1,12 @@ define([ - './Type' + './Type', + './objects/ObjectAPI' ], function ( - Type + Type, + ObjectAPI ) { return { - Type: Type + Type: Type, + Objects: ObjectAPI }; }); diff --git a/src/api/objects/LegacyObjectAPIInterceptor.js b/src/api/objects/LegacyObjectAPIInterceptor.js new file mode 100644 index 0000000000..26bfa22ec2 --- /dev/null +++ b/src/api/objects/LegacyObjectAPIInterceptor.js @@ -0,0 +1,70 @@ +define([ + './object-utils', + './ObjectAPI' +], function ( + utils, + ObjectAPI +) { + function ObjectServiceProvider(objectService, instantiate) { + this.objectService = objectService; + this.instantiate = instantiate; + } + + ObjectServiceProvider.prototype.save = function (object) { + var key = object.key, + keyString = utils.makeKeyString(key), + newObject = this.instantiate(utils.toOldFormat(object), keyString); + + return object.getCapability('persistence') + .persist() + .then(function () { + return utils.toNewFormat(object, key); + }); + }; + + ObjectServiceProvider.prototype.delete = function (object) { + // TODO! + }; + + ObjectServiceProvider.prototype.get = function (key) { + var keyString = utils.makeKeyString(key); + return this.objectService.getObjects([keyString]) + .then(function (results) { + var model = JSON.parse(JSON.stringify(results[keyString].getModel())); + return utils.toNewFormat(model, key); + }); + }; + + // Injects new object API as a decorator so that it hijacks all requests. + // Object providers implemented on new API should just work, old API should just work, many things may break. + function LegacyObjectAPIInterceptor(ROOTS, instantiate, objectService) { + this.getObjects = function (keys) { + var results = {}, + promises = keys.map(function (keyString) { + var key = utils.parseKeyString(keyString); + return ObjectAPI.get(key) + .then(function (object) { + object = utils.toOldFormat(object) + results[keyString] = instantiate(object, keyString); + }); + }); + + return Promise.all(promises) + .then(function () { + return results; + }); + }; + + ObjectAPI._supersecretSetFallbackProvider( + new ObjectServiceProvider(objectService, instantiate) + ); + + ROOTS.forEach(function (r) { + ObjectAPI.addRoot(utils.parseKeyString(r.id)); + }); + + return this; + } + + return LegacyObjectAPIInterceptor; +}); diff --git a/src/api/objects/ObjectAPI.js b/src/api/objects/ObjectAPI.js new file mode 100644 index 0000000000..f66ad35a77 --- /dev/null +++ b/src/api/objects/ObjectAPI.js @@ -0,0 +1,92 @@ +define([ + 'lodash', + './object-utils' +], function ( + _, + utils +) { + + /** + Object API. Intercepts the existing object API while also exposing + A new Object API. + + MCT.objects.get('mine') + .then(function (root) { + console.log(root); + MCT.objects.getComposition(root) + .then(function (composition) { + console.log(composition) + }) + }); + */ + + var Objects = {}, + ROOT_REGISTRY = [], + PROVIDER_REGISTRY = {}, + FALLBACK_PROVIDER; + + Objects._supersecretSetFallbackProvider = function (p) { + FALLBACK_PROVIDER = p; + }; + + + + // Root provider is hardcoded in; can't be skipped. + var RootProvider = { + 'get': function () { + return Promise.resolve({ + name: 'The root object', + type: 'root', + composition: ROOT_REGISTRY + }); + } + }; + + // Retrieve the provider for a given key. + function getProvider(key) { + if (key.identifier === 'ROOT') { + return RootProvider; + } + return PROVIDER_REGISTRY[key.namespace] || FALLBACK_PROVIDER; + }; + + Objects.addProvider = function (namespace, provider) { + PROVIDER_REGISTRY[namespace] = provider; + }; + + [ + 'save', + 'delete', + 'get' + ].forEach(function (method) { + Objects[method] = function () { + var key = arguments[0], + provider = getProvider(key); + + if (!provider) { + throw new Error('No Provider Matched'); + } + + if (!provider[method]) { + throw new Error('Provider does not support [' + method + '].'); + } + + return provider[method].apply(provider, arguments); + }; + }); + + Objects.addRoot = function (key) { + ROOT_REGISTRY.unshift(key); + }; + + Objects.removeRoot = function (key) { + ROOT_REGISTRY = ROOT_REGISTRY.filter(function (k) { + return ( + k.identifier !== key.identifier || + k.namespace !== key.namespace + ); + }); + }; + + return Objects; +}); diff --git a/src/api/objects/README.md b/src/api/objects/README.md new file mode 100644 index 0000000000..a7317944f9 --- /dev/null +++ b/src/api/objects/README.md @@ -0,0 +1,101 @@ +# Object API - Overview + +The object API provides methods for fetching domain objects. + +# Keys +Keys are a composite identifier that is used to create and persist objects. Ex: +```javascript +{ + namespace: 'elastic', + identifier: 'myIdentifier' +} +``` + +In old MCT days, we called this an "id", and we encoded it in a single string. +The above key would encode into the identifier, `elastic:myIdentifier`. + +When interacting with the API you will be dealing with key objects. + +# Configuring the Object API + +The following methods should be used before calling run. They allow you to +configure the persistence space of MCT. + +* `MCT.objects.addRoot(key)` -- add a "ROOT" to Open MCT by specifying it's + key. +* `MCT.objects.removeRoot(key)` -- Remove a "ROOT" from Open MCT by key. +* `MCT.objects.addProvider(namespace, provider)` -- register an object provider + for a specific namespace. See below for documentation on the provider + interface. + +# Using the object API + +The object API provides methods for getting, saving, and deleting objects. + +* MCT.objects.get(key) -> returns promise for an object +* MCT.objects.save(object) -> returns promise that is resolved when object + has been saved +* MCT.objects.delete(object) -> returns promise that is resolved when object has + been deleted + +## Configuration Example: Adding a groot + +The following example adds a new root object for groot and populates it with +some pieces of groot. + +```javascript + +var ROOT_KEY = { + namespace: 'groot', + identifier: 'groot' +}; + +var GROOT_ROOT = { + name: 'I am groot', + type: 'folder', + composition: [ + { + namespace: 'groot', + identifier: 'arms' + }, + { + namespace: 'groot', + identifier: 'legs' + }, + { + namespace: 'groot', + identifier: 'torso' + } + ] +}; + +var GrootProvider = { + get: function (key) { + if (key.identifier === 'groot') { + return Promise.resolve(GROOT_ROOT); + } + return Promise.resolve({ + name: 'Groot\'s ' + key.identifier + }); + } +}; + +MCT.objects.addRoot(ROOT_KEY); + +MCT.objects.addProvider('groot', GrootProvider); + +MCT.run(); +``` + +### Making a custom provider: + +All methods on the provider interface are optional, so you do not need +to modify them. + +* `provider.get(key)` -> promise for a domain object. +* `provider.save(domainObject)` -> returns promise that is fulfilled when object + has been saved. +* `provider.delete(domainObject)` -> returns promise that is fulfilled when + object has been deleted. + + diff --git a/src/api/objects/bundle.js b/src/api/objects/bundle.js new file mode 100644 index 0000000000..d9cdb8a0e8 --- /dev/null +++ b/src/api/objects/bundle.js @@ -0,0 +1,49 @@ +/***************************************************************************** + * 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([ + './LegacyObjectAPIInterceptor', + 'legacyRegistry' +], function ( + LegacyObjectAPIInterceptor, + legacyRegistry +) { + legacyRegistry.register('src/api/objects', { + name: 'Object API', + description: 'The public Objects API', + extensions: { + components: [ + { + provides: "objectService", + type: "decorator", + priority: "mandatory", + implementation: LegacyObjectAPIInterceptor, + depends: [ + "roots[]", + "instantiate" + ] + } + ] + } + }); +}); diff --git a/src/api/objects/object-utils.js b/src/api/objects/object-utils.js new file mode 100644 index 0000000000..66e7287a50 --- /dev/null +++ b/src/api/objects/object-utils.js @@ -0,0 +1,67 @@ +define([ + +], function ( + +) { + + // take a key string and turn it into a key object + // 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'} + var parseKeyString = function (key) { + if (typeof key === 'object') { + return key; + } + var namespace = '', + identifier = key; + for (var i = 0, escaped = false, len=key.length; i < len; i++) { + if (key[i] === ":" && !escaped) { + namespace = key.slice(0, i); + identifier = key.slice(i + 1); + break; + } + } + return { + namespace: namespace, + identifier: identifier + }; + }; + + // take a key and turn it into a key string + // {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root' + var makeKeyString = function (key) { + if (typeof key === 'string') { + return key; + } + if (!key.namespace) { + return key.identifier; + } + return [ + key.namespace.replace(':', '\\:'), + key.identifier.replace(':', '\\:') + ].join(':'); + }; + + // Converts composition to use key strings instead of keys + var toOldFormat = function (model) { + delete model.key; + if (model.composition) { + model.composition = model.composition.map(makeKeyString); + } + return model; + }; + + // converts composition to use keys instead of key strings + var toNewFormat = function (model, key) { + model.key = key; + if (model.composition) { + model.composition = model.composition.map(parseKeyString); + } + return model; + }; + + return { + toOldFormat: toOldFormat, + toNewFormat: toNewFormat, + makeKeyString: makeKeyString, + parseKeyString: parseKeyString + }; +}); From d475d767d5f6d43e5d7e24336add6c64a46bcd68 Mon Sep 17 00:00:00 2001 From: Pete Richards Date: Fri, 17 Jun 2016 17:05:05 -0700 Subject: [PATCH 2/2] add grootprovider --- index.html | 6 +++-- src/api/objects/README.md | 5 ++-- tutorials/grootprovider/groots.js | 44 +++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 tutorials/grootprovider/groots.js diff --git a/index.html b/index.html index 301ade4506..91fb7bd9df 100644 --- a/index.html +++ b/index.html @@ -31,12 +31,14 @@