diff --git a/platform/containment/README.md b/platform/containment/README.md new file mode 100644 index 0000000000..aceef4d963 --- /dev/null +++ b/platform/containment/README.md @@ -0,0 +1,2 @@ +Implements support for rules which determine which objects are allowed +to contain other objects, typically by type. \ No newline at end of file diff --git a/platform/containment/src/CapabilityTable.js b/platform/containment/src/CapabilityTable.js new file mode 100644 index 0000000000..6a4f4a1e54 --- /dev/null +++ b/platform/containment/src/CapabilityTable.js @@ -0,0 +1,53 @@ +/*global define*/ + + +define( + [], + function () { + "use strict"; + + /** + * Build a table indicating which types are expected to expose + * which capabilities. + */ + function CapabilityTable(typeService, capabilityService) { + var table = {}; + + // Build an initial model for a type + function buildModel(type) { + var model = Object.create(type.getInitialModel() || {}); + model.type = type.getKey(); + return model; + } + + // Get capabilities expected for this type + function getCapabilities(type) { + return capabilityService.getCapabilities(buildModel(type)); + } + + // Populate the lookup table for this type's capabilities + function addToTable(type) { + var typeKey = type.getKey(); + Object.keys(getCapabilities(type)).forEach(function (key) { + table[key] = table[key] || {}; + table[key][typeKey] = true; + }); + } + + // Build the table + (typeService.listTypes() || []).forEach(addToTable); + + return { + /** + * Check if a type is expected to expose a specific + * capability. + */ + hasCapability: function (typeKey, capabilityKey) { + return (table[capabilityKey] || {})[typeKey]; + } + }; + } + + return CapabilityTable; + } +); \ No newline at end of file diff --git a/platform/containment/src/CompositionPolicy.js b/platform/containment/src/CompositionPolicy.js new file mode 100644 index 0000000000..b052df0a4f --- /dev/null +++ b/platform/containment/src/CompositionPolicy.js @@ -0,0 +1,29 @@ +/*global define*/ + +define( + ['./ContainmentTable'], + function (ContainmentTable) { + "use strict"; + + /** + * Defines composition policy as driven by type metadata. + */ + function CompositionPolicy(typeService, capabilityService) { + // We're really just wrapping the containment table and rephrasing + // it as a policy decision. + var table = new ContainmentTable(typeService, capabilityService); + + return { + /** + * Is the type identified by the candidate allowed to + * contain the type described by the context? + */ + allow: function (candidate, context) { + return table.canContain(candidate, context); + } + }; + } + + return CompositionPolicy; + } +); \ No newline at end of file diff --git a/platform/containment/src/ContainmentTable.js b/platform/containment/src/ContainmentTable.js new file mode 100644 index 0000000000..b408fe677f --- /dev/null +++ b/platform/containment/src/ContainmentTable.js @@ -0,0 +1,98 @@ +/*global define*/ + +define( + ['./CapabilityTable'], + function (CapabilityTable) { + "use strict"; + + // Symbolic value for the type table for cases when any type + // is allowed to be contained. + var ANY = true; + + /** + * Supports composition policy by maintaining a table of + * domain object types, to determine if they can contain + * other domain object types. This is determined at application + * start time (plug-in support means this cannot be determined + * prior to that, but we don't want to redo these calculations + * every time policy is checked.) + */ + function ContainmentTable(typeService, capabilityService) { + var types = typeService.listTypes(), + capabilityTable = new CapabilityTable(typeService, capabilityService), + table = {}; + + // Check if one type can contain another + function canContain(containerType, containedType) { + } + + // Add types which have all these capabilities to the set + // of allowed types + function addToSetByCapability(set, has) { + has = Array.isArray(has) ? has : [has]; + types.forEach(function (type) { + var typeKey = type.getKey(); + set[typeKey] = has.map(function (capabilityKey) { + return capabilityTable.hasCapability(typeKey, capabilityKey); + }).reduce(function (a, b) { + return a && b; + }, true); + }); + } + + // Add this type (or type description) to the set of allowed types + function addToSet(set, type) { + // Is this a simple case of an explicit type identifier? + if (typeof type === 'string') { + // If so, add it to the set of allowed types + set[type] = true; + } else { + // Otherwise, populate that set based on capabilities + addToSetByCapability(set, (type || {}).has || []); + } + } + + // Add to the lookup table for this type + function addToTable(type) { + var key = type.getKey(), + definition = type.getDefinition() || {}, + contains = definition.contains; + + // Check for defined containment restrictions + if (contains === undefined) { + // If not, accept anything + table[key] = ANY; + } else { + // Start with an empty set... + table[key] = {}; + // ...cast accepted types to array if necessary... + contains = Array.isArray(contains) ? contains : [contains]; + // ...and add all containment rules to that set + contains.forEach(function (c) { + addToSet(table[key], c); + }); + } + } + + // Build the table + types.forEach(addToTable); + + return { + /** + * Check if domain objects of one type can contain domain + * objects of another type. + * @returns {boolean} true if allowable + */ + canContain: function (containerType, containedType) { + var set = table[containerType.getKey()] || {}; + // Recognize either the symbolic value for "can contain + // anything", or lookup the specific type from the set. + return (set === ANY) || set[containedType.getKey()]; + } + }; + + } + + return ContainmentTable; + } +); \ No newline at end of file