UNPKG

@themost/data

Version:

MOST Web Framework Codename Blueshift - Data module

1,521 lines (1,440 loc) 85.7 kB
// MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved var Symbol = require('symbol'); var {LangUtils} = require('@themost/common'); var {sprintf} = require('sprintf-js'); var Q = require('q'); var pluralize = require('pluralize'); var _ = require('lodash'); var {cloneDeep} = require('lodash'); var moment = require('moment'); var {TypeParser} = require('./types'); var parseBoolean = TypeParser.parseBoolean; var {DataModel} = require('./data-model'); var {DataContext} = require('./types'); var {XDocument} = require('@themost/xml'); // noinspection JSUnusedLocalSymbols var entityTypesProperty = Symbol('entityTypes'); // noinspection JSUnusedLocalSymbols var entityContainerProperty = Symbol('entityContainer'); var ignoreEntityTypesProperty = Symbol('ignoredEntityTypes'); var builderProperty = Symbol('builder'); var entityTypeProperty = Symbol('entityType'); // noinspection JSUnusedLocalSymbols var edmProperty = Symbol('edm'); var initializeProperty = Symbol('initialize'); var {DataConfigurationStrategy} = require('./data-configuration'); var {SchemaLoaderStrategy} = require('./data-configuration'); var {DefaultSchemaLoaderStrategy} = require('./data-configuration'); var {instanceOf} = require('./instance-of'); var {Args} = require('@themost/common'); var {hasOwnProperty} = require('./has-own-property'); /** * @enum */ function EdmType() { } EdmType.EdmBinary = 'Edm.Binary'; EdmType.EdmBoolean='Edm.Boolean'; EdmType.EdmByte='Edm.Byte'; EdmType.EdmDate='Edm.Date'; EdmType.EdmDateTimeOffset='Edm.DateTimeOffset'; EdmType.EdmDouble='Edm.Double'; EdmType.EdmDecimal='Edm.Decimal'; EdmType.EdmDuration='Edm.Duration'; EdmType.EdmGuid='Edm.Guid'; EdmType.EdmInt16='Edm.Int16'; EdmType.EdmInt32='Edm.Int32'; EdmType.EdmInt64='Edm.Int64'; EdmType.EdmSByte='Edm.SByte'; EdmType.EdmSingle='Edm.Single'; EdmType.EdmStream='Edm.Stream'; EdmType.EdmString='Edm.String'; EdmType.EdmTimeOfDay='Edm.TimeOfDay'; EdmType.EdmUntyped='Edm.Untyped'; /** * @static * @param {*} type * @returns {string} */ EdmType.CollectionOf = function(type) { return 'Collection(' + type + ')'; }; /** * @static * @param {*} type * @returns {string} */ EdmType.IsCollection = function(type) { var match = /^Collection\((.*?)\)$/.exec(type); if (match && match[1].length) { return match[1]; } }; /** * @enum */ function EdmMultiplicity() { } EdmMultiplicity.Many = 'Many'; EdmMultiplicity.One = 'One'; EdmMultiplicity.Unknown = 'Unknown'; EdmMultiplicity.ZeroOrOne = 'ZeroOrOne'; /** * @param {string} value * @returns {string|*} */ EdmMultiplicity.parse = function(value) { if (typeof value === 'string') { var re = new RegExp('^'+value+'$','ig'); return _.find(_.keys(EdmMultiplicity), function(x) { if (typeof EdmMultiplicity[x] === 'string') { return re.test(EdmMultiplicity[x]); } }); } }; /** * @enum */ function EntitySetKind() { } EntitySetKind.EntitySet = 'EntitySet'; EntitySetKind.Singleton = 'Singleton'; EntitySetKind.FunctionImport = 'FunctionImport'; EntitySetKind.ActionImport = 'ActionImport'; // noinspection JSUnusedGlobalSymbols /** * @class * @param {string} name * @constructor */ function ProcedureConfiguration(name) { this.name = name; this.parameters = []; // noinspection JSUnusedGlobalSymbols this.isBound = false; this.isComposable = false; } /** * @param type * @returns {ProcedureConfiguration} */ ProcedureConfiguration.prototype.returns = function(type) { // noinspection JSUnusedGlobalSymbols this.returnType = type; return this; }; // noinspection JSUnusedGlobalSymbols /** * @param type * @returns {ProcedureConfiguration} */ ProcedureConfiguration.prototype.returnsCollection = function(type) { // noinspection JSUnusedGlobalSymbols this.returnCollectionType = type; return this; }; /** * @param {string} name * @param {string} type * @param {boolean=} nullable * @param {boolean=} fromBody */ ProcedureConfiguration.prototype.parameter = function(name, type, nullable, fromBody) { Args.notString(name, 'Action parameter name'); Args.notString(type, 'Action parameter type'); var findRe = new RegExp('^' + name + '$' ,'ig'); var p = _.find(this.parameters, function(x) { return findRe.test(x.name); }); if (p) { p.type = type; } else { this.parameters.push({ 'name':name, 'type':type, 'nullable': _.isBoolean(nullable) ? nullable : false, 'fromBody': fromBody }); } return this; }; /** * @class * @constructor * @param {string} name * @augments ProcedureConfiguration * @extends ProcedureConfiguration */ function ActionConfiguration(name) { ActionConfiguration.super_.bind(this)(name); // noinspection JSUnusedGlobalSymbols this.isBound = false; } LangUtils.inherits(ActionConfiguration, ProcedureConfiguration); /** * @class * @constructor * @param {string} name * @augments ProcedureConfiguration */ function FunctionConfiguration(name) { FunctionConfiguration.super_.bind(this)(name); // noinspection JSUnusedGlobalSymbols this.isBound = false; } LangUtils.inherits(FunctionConfiguration, ProcedureConfiguration); /** * @class * @constructor * @param {EntityTypeConfiguration} entityType */ function EntityCollectionConfiguration(entityType) { this.actions = []; this.functions = []; this[entityTypeProperty] = entityType; } // noinspection JSUnusedGlobalSymbols /** * Creates an action that bind to this entity collection * @param {string} name * @returns ActionConfiguration */ EntityCollectionConfiguration.prototype.addAction = function(name) { /** * @type {ActionConfiguration|*} */ var a = this.hasAction(name); if (a) { return a; } a = new ActionConfiguration(name); //add current entity as parameter a.parameter('bindingParameter', 'Collection(' + this[entityTypeProperty].name + ')',true); a.isBound = true; this.actions.push(a); return a; }; /** * Checks if entity collection has an action with the given name * @param {string} name * @returns {ActionConfiguration|*} */ EntityCollectionConfiguration.prototype.hasAction = function(name) { if (_.isEmpty(name)) { return; } var findRe = new RegExp('^' + name + '$' ,'ig'); return _.find(this.actions, function(x) { return findRe.test(x.name); }); }; // noinspection JSUnusedGlobalSymbols /** * Creates an action that bind to this entity collection * @param {string} name * @returns ActionConfiguration */ EntityCollectionConfiguration.prototype.addFunction = function(name) { var a = this.hasFunction(name); if (a) { return a; } a = new FunctionConfiguration(name); a.isBound = true; a.parameter('bindingParameter', 'Collection(' + this[entityTypeProperty].name + ')',true); //add current entity as parameter this.functions.push(a); return a; }; /** * Checks if entity collection has a function with the given name * @param {string} name * @returns {ActionConfiguration|*} */ EntityCollectionConfiguration.prototype.hasFunction = function(name) { if (_.isEmpty(name)) { return; } var findRe = new RegExp('^' + name + '$' ,'ig'); return _.find(this.functions, function(x) { return findRe.test(x.name); }); }; function getOwnPropertyNames(obj) { if (typeof obj === 'undefined' || obj === null) { return []; } var ownPropertyNames = []; //get object methods var proto = obj; while(proto) { ownPropertyNames = ownPropertyNames.concat(Object.getOwnPropertyNames(proto).filter( function(x) { return ownPropertyNames.indexOf(x)<0; })); proto = Object.getPrototypeOf(proto); } if (typeof obj === 'function') { //remove caller var index = ownPropertyNames.indexOf('caller'); if (index>=0) { ownPropertyNames.splice(index,1); } index = ownPropertyNames.indexOf('arguments'); if (index>=0) { ownPropertyNames.splice(index,1); } } return ownPropertyNames; } /** * @class * @param {ODataModelBuilder} builder * @param {string} name * @constructor * @property {string} name - Gets the name of this entity type */ function EntityTypeConfiguration(builder, name) { Args.notString(name, 'Entity type name'); Object.defineProperty(this, 'name', { get:function() { return name; } }); this[builderProperty] = builder; this.property = []; this.ignoredProperty = []; this.navigationProperty = []; this.actions = []; this.functions = []; this.collection = new EntityCollectionConfiguration(this); } /** * @returns {ODataModelBuilder} */ EntityTypeConfiguration.prototype.getBuilder = function() { return this[builderProperty]; }; // noinspection JSUnusedGlobalSymbols /** * @param {string} name * @returns EntityTypeConfiguration */ EntityTypeConfiguration.prototype.derivesFrom = function(name) { Args.notString(name,'Enity type name'); this.baseType = name; return this; }; // noinspection JSUnusedGlobalSymbols /** * Creates an action that bind to this entity type * @param {string} name * @returns ActionConfiguration */ EntityTypeConfiguration.prototype.addAction = function(name) { /** * @type {ActionConfiguration|*} */ var a = this.hasAction(name); if (a) { return a; } a = new ActionConfiguration(name); //add current entity as parameter a.parameter('bindingParameter', this.name); a.isBound = true; this.actions.push(a); return a; }; /** * Checks if entity type has an action with the given name * @param {string} name * @returns {ActionConfiguration|*} */ EntityTypeConfiguration.prototype.hasAction = function(name) { if (_.isEmpty(name)) { return; } var findRe = new RegExp('^' + name + '$' ,'ig'); return _.find(this.actions, function(x) { return findRe.test(x.name); }); }; // noinspection JSUnusedGlobalSymbols /** * Creates an action that bind to this entity type * @param {string} name * @returns ActionConfiguration */ EntityTypeConfiguration.prototype.addFunction = function(name) { var a = this.hasFunction(name); if (a) { return a; } a = new FunctionConfiguration(name); a.isBound = true; a.parameter('bindingParameter', this.name); //add current entity as parameter this.functions.push(a); return a; }; /** * Checks if entity type has a function with the given name * @param {string} name * @returns {ActionConfiguration|*} */ EntityTypeConfiguration.prototype.hasFunction = function(name) { if (_.isEmpty(name)) { return; } var findRe = new RegExp('^' + name + '$' ,'ig'); return _.find(this.functions, function(x) { return findRe.test(x.name); }); }; /** * Adds a new EDM primitive property to this entity type. * @param {string} name * @param {string} type * @param {boolean=} nullable, * @returns EntityTypeConfiguration */ EntityTypeConfiguration.prototype.addProperty = function(name, type, nullable) { Args.notString(name,'Property name'); var exists =_.findIndex(this.property, function(x) { return x.name === name; }); if (exists<0) { var p = { 'name':name, 'type':type, 'nullable':_.isBoolean(nullable) ? nullable : true }; this.property.push(p); } else { _.assign(this.property[exists], { 'type':type, 'nullable':_.isBoolean(nullable) ? nullable : true }); } return this; }; // noinspection JSUnusedGlobalSymbols /** * Adds a new EDM navigation property to this entity type. * @param {string} name * @param {string} type * @param {string} multiplicity * @returns EntityTypeConfiguration */ EntityTypeConfiguration.prototype.addNavigationProperty = function(name, type, multiplicity) { Args.notString(name,'Property name'); var exists =_.findIndex(this.navigationProperty, function(x) { return x.name === name; }); var p = { 'name':name, 'type': (multiplicity==='Many') ? sprintf('Collection(%s)', type) : type }; if ((multiplicity===EdmMultiplicity.ZeroOrOne) || (multiplicity===EdmMultiplicity.Many)) { p.nullable = true; } if (exists<0) { this.navigationProperty.push(p); } else { _.assign(this.navigationProperty[exists], p); } return this; }; // noinspection JSUnusedGlobalSymbols /** * Removes the navigation property from the entity. * @param {string} name * @returns {EntityTypeConfiguration} */ EntityTypeConfiguration.prototype.removeNavigationProperty = function(name) { Args.notString(name,'Property name'); var hasProperty =_.findIndex(this.property, function(x) { return x.name === name; }); if (hasProperty>=0) { this.property.splice(hasProperty, 1); } return this; }; /** * Ignores a property from the entity * @param name * @returns {EntityTypeConfiguration} */ EntityTypeConfiguration.prototype.ignore = function(name) { Args.notString(name,'Property name'); var hasProperty =_.findIndex(this.ignoredProperty, function(x) { return x.name === name; }); if (hasProperty>=0) { return this; } this.ignoredProperty.push(name); return this; }; // noinspection JSUnusedGlobalSymbols /** * Removes the property from the entity. * @param {string} name * @returns {EntityTypeConfiguration} */ EntityTypeConfiguration.prototype.removeProperty = function(name) { Args.notString(name,'Property name'); var hasProperty =_.findIndex(this.property, function(x) { return x.name === name; }); if (hasProperty>=0) { this.property.splice(hasProperty, 1); } return this; }; // noinspection JSUnusedGlobalSymbols /** * Removes the property from the entity keys collection. * @param {string} name * @returns {EntityTypeConfiguration} */ EntityTypeConfiguration.prototype.removeKey = function(name) { Args.notString(name,'Key name'); if (this.key && _.isArray(this.key.propertyRef)) { var hasKeyIndex = _.findIndex(this.key.propertyRef, function(x) { return x.name === name; }); if (hasKeyIndex<0) { return this; } this.key.propertyRef.splice(hasKeyIndex, 1); return this; } }; // noinspection JSUnusedGlobalSymbols /** * Configures the key property(s) for this entity type. * @param {string} name * @param {string} type * @returns {EntityTypeConfiguration} */ EntityTypeConfiguration.prototype.hasKey = function(name, type) { this.addProperty(name, type, false); this.key = { propertyRef: [ { 'name': name } ] }; return this; }; // noinspection JSUnusedGlobalSymbols /** * @param {*} context * @param {*} any */ EntityTypeConfiguration.prototype.mapInstance = function(context, any) { if (any == null) { return; } if (context) { var contextLink = this.getBuilder().getContextLink(context); if (contextLink) { return _.assign({ '@odata.context':contextLink + '#' + this.name }, any); } } return any; }; // noinspection JSUnusedGlobalSymbols /** * @param {*} context * @param {string} property * @param {*} any */ EntityTypeConfiguration.prototype.mapInstanceProperty = function(context, property, any) { var builder = this.getBuilder(); if (context && typeof builder.getContextLink === 'function') { var contextLink = builder.getContextLink(context); if (contextLink) { if (context.request && context.request.url) { contextLink += '#'; contextLink += context.request.url.replace(builder.serviceRoot, ''); } return { '@odata.context':contextLink, 'value': any }; } } return { 'value': any }; }; // noinspection JSUnusedGlobalSymbols /** * * @param {*} context * @param {*} any * @returns {*} */ EntityTypeConfiguration.prototype.mapInstanceSet = function(context, any) { var result = {}; if (context) { var contextLink = this.getBuilder().getContextLink(context); if (contextLink) { result['@odata.context'] = contextLink + '#' + this.name; } } //search for total property for backward compatibility issues if (hasOwnProperty(any, 'total') && /^\+?\d+$/.test(any['total'])) { result['@odata.count'] = parseInt(any['total'], 10); } if (hasOwnProperty(any, 'count') && /^\+?\d+$/.test(any['count'])) { result['@odata.count'] = parseInt(any['count'], 10); } result['value'] = []; if (_.isArray(any)) { result['value'] = any; } //search for records property for backward compatibility issues else if (_.isArray(any.records)) { result['value'] = any.records; } else if (_.isArray(any.value)) { result['value'] = any.value; } return result; }; /** * @class * @param {ODataModelBuilder} builder * @param {string} entityType * @param {string} name */ function EntitySetConfiguration(builder, entityType, name) { Args.check(builder instanceof ODataModelBuilder, new TypeError('Invalid argument. Configuration builder must be an instance of ODataModelBuilder class')); Args.notString(entityType, 'Entity Type'); Args.notString(name, 'EntitySet Name'); this[builderProperty] = builder; this[entityTypeProperty] = entityType; //ensure entity type if (!this[builderProperty].hasEntity(this[entityTypeProperty])) { this[builderProperty].addEntity(this[entityTypeProperty]); } this.name = name; this.kind = EntitySetKind.EntitySet; //use the given name as entity set URL by default this.url = name; Object.defineProperty(this,'entityType', { get: function() { if (!this[builderProperty].hasEntity(this[entityTypeProperty])) { return this[builderProperty].addEntity(this[entityTypeProperty]); } return this[builderProperty].getEntity(this[entityTypeProperty]); } }); this.hasContextLink( /** * @this EntitySetConfiguration * @param context * @returns {string|*} */ function(context) { var thisBuilder = this.getBuilder(); if (_.isNil(thisBuilder)) { return; } if (typeof thisBuilder.getContextLink !== 'function') { return; } //get builder context link var builderContextLink = thisBuilder.getContextLink(context); if (builderContextLink) { //add hash for entity set return builderContextLink + '#' + this.name; } }); } // noinspection JSUnusedGlobalSymbols EntitySetConfiguration.prototype.hasUrl = function(url) { Args.notString(url, 'Entity Resource Path'); this.url = url; }; // noinspection JSUnusedGlobalSymbols EntitySetConfiguration.prototype.getUrl = function() { return this.url; }; /** * @returns {ODataModelBuilder} */ EntitySetConfiguration.prototype.getBuilder = function() { return this[builderProperty]; }; // noinspection JSUnusedGlobalSymbols /** * @returns {*} */ EntitySetConfiguration.prototype.getEntityTypePropertyList = function() { var result = {}; _.forEach(this.entityType.property, function(x) { result[x.name] = x; }); var baseEntityType = this.getBuilder().getEntity(this.entityType.baseType); while (baseEntityType) { _.forEach(baseEntityType.property, function(x) { result[x.name] = x; }); baseEntityType = this.getBuilder().getEntity(baseEntityType.baseType); } return result; }; // noinspection JSUnusedGlobalSymbols /** * @param {string} name * @param {boolean=} deep * @returns {*} */ EntitySetConfiguration.prototype.getEntityTypeProperty = function(name, deep) { var re = new RegExp('^' + name + '$','ig'); var p = _.find(this.entityType.property, function(x) { return re.test(x.name); }); if (p) { return p; } var deep_ = _.isBoolean(deep) ? deep : true; if (deep_) { var baseEntityType = this.getBuilder().getEntity(this.entityType.baseType); while (baseEntityType) { p = _.find(baseEntityType.property, function(x) { return re.test(x.name); }); if (p) { return p; } baseEntityType = this.getBuilder().getEntity(baseEntityType.baseType); } } }; // noinspection JSUnusedGlobalSymbols /** * @returns {*} */ EntitySetConfiguration.prototype.getEntityTypeIgnoredPropertyList = function() { var result = [].concat(this.entityType.ignoredProperty); var baseEntityType = this.getBuilder().getEntity(this.entityType.baseType); while (baseEntityType) { result.push.apply(result, baseEntityType.ignoredProperty); baseEntityType = this.getBuilder().getEntity(baseEntityType.baseType); } return result; }; // noinspection JSUnusedGlobalSymbols /** * @param {string} name * @param {boolean=} deep * @returns {*} */ EntitySetConfiguration.prototype.getEntityTypeNavigationProperty = function(name, deep) { var re = new RegExp('^' + name + '$','ig'); var p = _.find(this.entityType.navigationProperty, function(x) { return re.test(x.name); }); if (p) { return p; } var deep_ = _.isBoolean(deep) ? deep : true; if (deep_) { var baseEntityType = this.getBuilder().getEntity(this.entityType.baseType); while (baseEntityType) { p = _.find(baseEntityType.navigationProperty, function(x) { return re.test(x.name); }); if (p) { return p; } baseEntityType = this.getBuilder().getEntity(baseEntityType.baseType); } } }; // noinspection JSUnusedGlobalSymbols /** * @returns {*} */ EntitySetConfiguration.prototype.getEntityTypeNavigationPropertyList = function() { var result = []; _.forEach(this.entityType.navigationProperty, function(x) { result[x.name] = x; }); var baseEntityType = this.getBuilder().getEntity(this.entityType.baseType); while (baseEntityType) { _.forEach(baseEntityType.navigationProperty, function(x) { result[x.name] = x; }); baseEntityType = this.getBuilder().getEntity(baseEntityType.baseType); } return result; }; // noinspection JSUnusedGlobalSymbols /** * @param contextLinkFunc */ EntitySetConfiguration.prototype.hasContextLink = function(contextLinkFunc) { // noinspection JSUnusedGlobalSymbols this.getContextLink = contextLinkFunc; }; // noinspection JSUnusedGlobalSymbols /** * * @param {Function} idLinkFunc */ EntitySetConfiguration.prototype.hasIdLink = function(idLinkFunc) { // noinspection JSUnusedGlobalSymbols this.getIdLink = idLinkFunc; }; // noinspection JSUnusedGlobalSymbols /** * * @param {Function} readLinkFunc */ EntitySetConfiguration.prototype.hasReadLink = function(readLinkFunc) { // noinspection JSUnusedGlobalSymbols this.getReadLink = readLinkFunc; }; // noinspection JSUnusedGlobalSymbols /** * * @param {Function} editLinkFunc */ EntitySetConfiguration.prototype.hasEditLink = function(editLinkFunc) { // noinspection JSUnusedGlobalSymbols this.getEditLink = editLinkFunc; }; // noinspection JSUnusedGlobalSymbols /** * @param {*} context * @param {*} any */ EntitySetConfiguration.prototype.mapInstance = function(context, any) { if (any == null) { return; } if (context) { var contextLink = this.getContextLink(context); if (contextLink) { return _.assign({ '@odata.context':contextLink + '/$entity' }, any); } } return any; }; // noinspection JSUnusedGlobalSymbols /** * @param {*} context * @param {string} property * @param {*} any */ EntitySetConfiguration.prototype.mapInstanceProperty = function(context, property, any) { var builder = this.getBuilder(); if (context && typeof builder.getContextLink === 'function') { var contextLink = builder.getContextLink(context); if (contextLink) { if (context.request && context.request.url) { contextLink += '#'; contextLink += context.request.url.replace(builder.serviceRoot, ''); } return { '@odata.context':contextLink, 'value': any }; } } return { 'value': any }; }; // noinspection JSUnusedGlobalSymbols /** * * @param {*} context * @param {*} any * @returns {*} */ EntitySetConfiguration.prototype.mapInstanceSet = function(context, any) { var result = {}; if (context) { var contextLink = this.getContextLink(context); if (contextLink) { result['@odata.context'] = contextLink; } } //search for total property for backward compatibility issues if (hasOwnProperty(any, 'total') && /^\+?\d+$/.test(any['total'])) { result['@odata.count'] = parseInt(any['total'], 10); } else if (hasOwnProperty(any, 'count') && /^\+?\d+$/.test(any['count'])) { result['@odata.count'] = parseInt(any['count'], 10); } if (hasOwnProperty(any, 'skip') && /^\+?\d+$/.test(any['skip'])) { result['@odata.skip'] = parseInt(any['skip'], 10); } result['value'] = []; if (_.isArray(any)) { result['value'] = any; } //search for records property for backward compatibility issues else if (_.isArray(any.records)) { result['value'] = any.records; } else if (_.isArray(any.value)) { result['value'] = any.value; } return result; }; /** * @class * @param {*} builder * @param {string} entityType * @param {string} name * @constructor * @augments EntitySetConfiguration * @extends EntitySetConfiguration */ function SingletonConfiguration(builder, entityType, name) { SingletonConfiguration.super_.bind(this)(builder, entityType, name); this.kind = EntitySetKind.Singleton; } LangUtils.inherits(SingletonConfiguration, EntitySetConfiguration); /** * @param {SchemaConfiguration} schema * @param {string} name * @returns {string} */ function setQualifiedName(schema, name) { // if name is already qualified e.g. ServiceModel.Account if (/\./g.test(name)) { // do nothing return name; } // get namespace or alias var namespace = schema.namespace || schema.alias; if (namespace == null) { return name; } // validate Collection(EntityType) expression var match = /^Collection\(([a-zA-Z0-9._]+)\)$/ig.exec(name); if (match) { // convert expression if (/\./g.test(match[1])) { return `Collection(${match[1]})`; } else { return `Collection(${namespace}.${match[1]})`; } } // otherwise return qualified name return `${namespace}.${name}`; } /** * Converts schema configuration to an edm document * @private * @this ODataModelBuilder * @param {SchemaConfiguration} schema * @returns {XDocument} */ function schemaToEdmDocument(schema) { var doc = new XDocument(); var rootElement = doc.createElement('edmx:Edmx'); rootElement.setAttribute('xmlns:edmx', 'http://docs.oasis-open.org/odata/ns/edmx'); rootElement.setAttribute('Version','4.0'); doc.appendChild(rootElement); var dataServicesElement = doc.createElement('edmx:DataServices'); var schemaElement = doc.createElement('Schema'); schemaElement.setAttribute('xmlns', 'http://docs.oasis-open.org/odata/ns/edm'); if (schema.namespace) { schemaElement.setAttribute('Namespace', schema.namespace); } if (schema.alias != null) { schemaElement.setAttribute('Alias', schema.alias); } var actionElements = [], functionElements = []; //append edmx:DataServices > Schema dataServicesElement.appendChild(schemaElement); _.forEach(schema.entityType, /** * * @param {EntityTypeConfiguration} entityType */ function(entityType) { //search for bound actions _.forEach(entityType.actions.concat(entityType.collection.actions), function(action) { var actionElement = doc.createElement('Action'); actionElement.setAttribute('Name', action.name); actionElement.setAttribute('IsBound', true); if (action.isComposable) { actionElement.setAttribute('IsComposable', action.isComposable); } _.forEach(action.parameters, function(parameter) { var paramElement = doc.createElement('Parameter'); paramElement.setAttribute('Name', parameter.name); paramElement.setAttribute('Type', setQualifiedName(schema, parameter.type)); var nullable = _.isBoolean(parameter.nullable) ? parameter.nullable : false; if (!nullable) { paramElement.setAttribute('Nullable', nullable); } //append Action > Parameter actionElement.appendChild(paramElement) }); if (action.returnType || action.returnCollectionType) { var returnTypeElement = doc.createElement('ReturnType'); var returnType = action.returnType; if (action.returnCollectionType) { returnType = action.returnCollectionType; returnTypeElement.setAttribute('Type', sprintf('Collection(%s)', setQualifiedName(schema, returnType))); } else { returnTypeElement.setAttribute('Type', setQualifiedName(schema, returnType)); } returnTypeElement.setAttribute('Nullable', true); actionElement.appendChild(returnTypeElement); } actionElements.push(actionElement); }); //search for bound functions _.forEach(entityType.functions.concat(entityType.collection.functions), function(func) { var functionElement = doc.createElement('Function'); functionElement.setAttribute('Name', func.name); functionElement.setAttribute('IsBound', true); if (func.isComposable) { functionElement.setAttribute('IsComposable', func.isComposable); } _.forEach(func.parameters, function(parameter) { var paramElement = doc.createElement('Parameter'); paramElement.setAttribute('Name', parameter.name); paramElement.setAttribute('Type', setQualifiedName(schema, parameter.type)); var nullable = _.isBoolean(parameter.nullable) ? parameter.nullable : false; if (!nullable) { paramElement.setAttribute('Nullable', nullable); } //append Function > Parameter functionElement.appendChild(paramElement) }); if (func.returnType || func.returnCollectionType) { var returnTypeElement = doc.createElement('ReturnType'); var returnType = func.returnType; if (func.returnCollectionType) { returnType = func.returnCollectionType; returnTypeElement.setAttribute('Type', sprintf('Collection(%s)', setQualifiedName(schema, returnType))); } else { returnTypeElement.setAttribute('Type', setQualifiedName(schema, returnType)); } returnTypeElement.setAttribute('Nullable', true); functionElement.appendChild(returnTypeElement); } functionElements.push(functionElement); }); //create element Schema > EntityType var entityTypeElement = doc.createElement('EntityType'); entityTypeElement.setAttribute('Name', entityType.name); entityTypeElement.setAttribute('OpenType', true); if (entityType.baseType) { entityTypeElement.setAttribute('BaseType', setQualifiedName(schema, entityType.baseType)); } if (entityType.implements) { var implementsAnnotation = doc.createElement('Annotation'); implementsAnnotation.setAttribute('Term', 'DataModel.OData.Core.V1.Implements'); implementsAnnotation.setAttribute('String', entityType.implements); entityTypeElement.appendChild(implementsAnnotation); } if (entityType.key && entityType.key.propertyRef) { var keyElement = doc.createElement('Key'); _.forEach(entityType.key.propertyRef, function(key) { var keyRefElement = doc.createElement('PropertyRef'); keyRefElement.setAttribute('Name',key.name); keyElement.appendChild(keyRefElement); }); entityTypeElement.appendChild(keyElement); } //enumerate properties _.forEach(entityType.property, function(x) { var propertyElement = doc.createElement('Property'); propertyElement.setAttribute('Name',x.name); propertyElement.setAttribute('Type', setQualifiedName(schema, x.type)); if (_.isBoolean(x.nullable) && (x.nullable===false)) { propertyElement.setAttribute('Nullable',false); } // add annotations if (x.immutable) { var immutableAnnotation = doc.createElement('Annotation'); immutableAnnotation.setAttribute('Term', 'Org.OData.Core.V1.Immutable'); immutableAnnotation.setAttribute('Bool', 'true'); propertyElement.appendChild(immutableAnnotation); } if (x.computed) { var computedAnnotation = doc.createElement('Annotation'); computedAnnotation.setAttribute('Term', 'Org.OData.Core.V1.Computed'); computedAnnotation.setAttribute('Bool', 'true'); propertyElement.appendChild(computedAnnotation); } entityTypeElement.appendChild(propertyElement); }); //enumerate navigation properties _.forEach(entityType.navigationProperty, function(x) { var propertyElement = doc.createElement('NavigationProperty'); propertyElement.setAttribute('Name',x.name); propertyElement.setAttribute('Type',setQualifiedName(schema, x.type)); if (!x.nullable) { propertyElement.setAttribute('Nullable',false); } if (x.immutable) { var immutableAnnotation = doc.createElement('Annotation'); immutableAnnotation.setAttribute('Term', 'Org.OData.Core.V1.Immutable'); immutableAnnotation.setAttribute('Bool', 'true'); propertyElement.appendChild(immutableAnnotation); } if (x.computed) { var computedAnnotation = doc.createElement('Annotation'); computedAnnotation.setAttribute('Term', 'Org.OData.Core.V1.Computed'); computedAnnotation.setAttribute('Bool', 'true'); propertyElement.appendChild(computedAnnotation); } entityTypeElement.appendChild(propertyElement); }); //append Schema > EntityType schemaElement.appendChild(entityTypeElement); }); //append action elements to schema _.forEach(actionElements, function(actionElement) { schemaElement.appendChild(actionElement); }); //append function elements to schema _.forEach(functionElements, function(functionElement) { schemaElement.appendChild(functionElement); }); //create Schema > EntityContainer var entityContainerElement = doc.createElement('EntityContainer'); entityContainerElement.setAttribute('Name', schema.entityContainer.name || 'DefaultContainer'); _.forEach(schema.entityContainer.entitySet, /** * @param {EntitySetConfiguration} child */ function(child) { var childElement = doc.createElement(child.kind); childElement.setAttribute('Name', child.name); if ((child.kind === EntitySetKind.EntitySet) || (child.kind === EntitySetKind.Singleton)) { childElement.setAttribute('EntityType', setQualifiedName(schema, child.entityType.name)); } var childAnnotation = doc.createElement('Annotation'); childAnnotation.setAttribute('Term', 'Org.OData.Core.V1.ResourcePath'); childAnnotation.setAttribute('String', child.getUrl()); childElement.appendChild(childAnnotation); //append Schema > EntityContainer > (EntitySet, Singleton, FunctionImport) entityContainerElement.appendChild(childElement); }); //append Schema > EntityContainer schemaElement.appendChild(entityContainerElement); //append edmx:Edmx > edmx:DataServices rootElement.appendChild(dataServicesElement); return doc; } /** * @classdesc Represents the OData model builder of an HTTP application * @property {string} serviceRoot - Gets or sets the service root URI * @param {ConfigurationBase} configuration * @class */ function ODataModelBuilder(configuration) { this[entityTypesProperty] = {}; this[ignoreEntityTypesProperty] = []; this[entityContainerProperty] = []; this.defaultNamespace = null; this.defaultAlias = null; /** * @returns {ConfigurationBase} */ this.getConfiguration = function() { return configuration; }; if (configuration != null) { this.defaultNamespace = configuration.getSourceAt('settings/builder/defaultNamespace'); } if (configuration != null) { this.defaultAlias = configuration.getSourceAt('settings/builder/defaultAlias'); } var serviceRoot_; var self = this; Object.defineProperty(this,'serviceRoot', { get:function() { return serviceRoot_; }, set: function(value) { serviceRoot_ = value; if (typeof self.getContextLink === 'undefined') { //set context link builder function self.hasContextLink(function(context) { var req = context.request; var p = /\/$/g.test(serviceRoot_) ? serviceRoot_ + '$metadata' : serviceRoot_ + '/' + '$metadata'; if (req) { return (req.protocol||'http') + '://' + req.headers.host + p; } return p; }); } } }) } /** * Gets a registered entity type * @param {string} name * @returns {EntityTypeConfiguration|*} */ ODataModelBuilder.prototype.getEntity = function(name) { if (_.isNil(name)) { return; } Args.notString(name, 'Entity type name'); return this[entityTypesProperty][name]; }; /** * Registers an entity type * @param {string} name * @returns {EntityTypeConfiguration} */ ODataModelBuilder.prototype.addEntity = function(name) { if (!this.hasEntity(name)) { this[entityTypesProperty][name] = new EntityTypeConfiguration(this, name); } return this.getEntity(name) }; // noinspection JSUnusedGlobalSymbols /** * @param {*} entityType * @param {string} name * @returns SingletonConfiguration|* */ ODataModelBuilder.prototype.addSingleton = function(entityType, name) { if (!this.hasSingleton(name)) { this[entityContainerProperty].push(new SingletonConfiguration(this, entityType, name)); } return this.getSingleton(name); }; /** * Gets an entity set * @param name * @returns {SingletonConfiguration} */ ODataModelBuilder.prototype.getSingleton =function(name) { Args.notString(name, 'Singleton Name'); var re = new RegExp('^' + name + '$','ig'); return _.find(this[entityContainerProperty], function(x) { return re.test(x.name) && x.kind === EntitySetKind.Singleton; }); }; /** * @param {string} name * @returns {SingletonConfiguration|*} */ ODataModelBuilder.prototype.hasSingleton = function(name) { var findRe = new RegExp('^' + name + '$' ,'ig'); return _.findIndex(this[entityContainerProperty], function(x) { return findRe.test(x.name) && x.kind === EntitySetKind.Singleton; })>=0; }; /** * Checks if the given entity set exists in entity container * @param {string} name * @returns {boolean} */ ODataModelBuilder.prototype.hasEntitySet = function(name) { var findRe = new RegExp('^' + name + '$' ,'ig'); return _.findIndex(this[entityContainerProperty], function(x) { return findRe.test(x.name) && x.kind === EntitySetKind.EntitySet; })>=0; }; // noinspection JSUnusedGlobalSymbols /** * Registers an entity type * @param {string} entityType * @param {string} name * @returns {EntitySetConfiguration} */ ODataModelBuilder.prototype.addEntitySet = function(entityType, name) { if (!this.hasEntitySet(name)) { this[entityContainerProperty].push(new EntitySetConfiguration(this, entityType, name)); } return this.getEntitySet(name); }; /** * Registers an entity type * @param {string} name * @returns {boolean} */ ODataModelBuilder.prototype.removeEntitySet = function(name) { var findRe = new RegExp('^' + name + '$' ,'ig'); var index = _.findIndex(this[entityContainerProperty], function(x) { return findRe.test(x.name) && x.kind === EntitySetKind.EntitySet; }); if (index>=0) { this[entityContainerProperty].splice(index,1); return true; } return false; }; /** * Gets an entity set * @param name * @returns {EntitySetConfiguration} */ ODataModelBuilder.prototype.getEntitySet = function(name) { Args.notString(name, 'EntitySet Name'); var re = new RegExp('^' + name + '$','ig'); return _.find(this[entityContainerProperty], function(x) { return re.test(x.name) && x.kind === EntitySetKind.EntitySet; }); }; /** * Gets an entity set based on the given entity name * @param {string} entityName * @returns {EntitySetConfiguration} */ ODataModelBuilder.prototype.getEntityTypeEntitySet = function(entityName) { Args.notString(entityName, 'Entity Name'); var re = new RegExp('^' + entityName + '$','ig'); return _.find(this[entityContainerProperty], function(x) { return x.entityType && re.test(x.entityType.name); }); }; /** * Ignores the entity type with the given name * @param {string} name * @returns {ODataModelBuilder} */ ODataModelBuilder.prototype.ignore = function(name) { var hasEntity = this[ignoreEntityTypesProperty].indexOf(name); if (hasEntity < 0) { this[ignoreEntityTypesProperty].push(name); } return this; }; // noinspection JSUnusedGlobalSymbols /** * Checks if the given entity type exists in entity's collection * @param {string} name * @returns {boolean} */ ODataModelBuilder.prototype.hasEntity = function(name) { return hasOwnProperty(this[entityTypesProperty], name); }; /** * Creates and returns a structure based on the configuration performed using this builder * @returns {Promise} */ ODataModelBuilder.prototype.getEdm = function() { var self = this; return Q.promise(function(resolve, reject) { try{ var schema = { namespace: self.defaultNamespace, alias: self.defaultAlias, entityType:[], entityContainer: { 'name':'DefaultContainer', 'entitySet':[] } }; //get entity types by excluding ignored entities var keys = _.filter(_.keys(self[entityTypesProperty]), function(x) { return self[ignoreEntityTypesProperty].indexOf(x)<0; }); //enumerate entity types _.forEach(keys, function(key) { schema.entityType.push(self[entityTypesProperty][key]); }); //apply entity sets schema.entityContainer.entitySet.push.apply(schema.entityContainer.entitySet, self[entityContainerProperty]); return resolve(schema); } catch(err) { return reject(err); } }); }; /** * Returns entity based on the configuration performed using this builder in * @returns {SchemaConfiguration} */ ODataModelBuilder.prototype.getEdmSync = function() { var self = this; /** * @type {SchemaConfiguration} */ var schema = { entityType:[], entityContainer: { 'name':'DefaultContainer', 'entitySet':[] } }; //get entity types by excluding ignored entities var keys = _.filter(_.keys(self[entityTypesProperty]), function(x) { return self[ignoreEntityTypesProperty].indexOf(x)<0; }); //enumerate entity types _.forEach(keys, function(key) { schema.entityType.push(self[entityTypesProperty][key]); }); //apply entity sets schema.entityContainer.entitySet.push.apply(schema.entityContainer.entitySet, self[entityContainerProperty]); return schema; }; // noinspection JSUnusedGlobalSymbols /** * @param {boolean=} all * @returns {ODataModelBuilder} */ ODataModelBuilder.prototype.clean = function(all) { delete this[edmProperty]; if (typeof all === 'boolean' && all === true) { delete this[initializeProperty]; this[entityTypesProperty] = {}; this[ignoreEntityTypesProperty] = []; this[entityContainerProperty] = []; } return this; }; // noinspection JSUnusedGlobalSymbols /** * Creates and returns an XML structure based on the configuration performed using this builder * @returns {Promise<XDocument>} */ ODataModelBuilder.prototype.getEdmDocument = function() { var self = this; return Q.promise(function(resolve, reject) { try{ return self.getEdm().then(function(schema) { var doc = schemaToEdmDocument.bind(self)(schema); return resolve(doc); }).catch(function(err) { return reject(err); }); } catch(err) { return reject(err); } }); }; // noinspection JSUnusedGlobalSymbols /** * Returns an XML structure based on the configuration performed using this builder * @returns {XDocument} */ ODataModelBuilder.prototype.getEdmDocumentSync = function() {