UNPKG

@themost/data

Version:
1,608 lines (1,517 loc) 79.6 kB
/** * @licence * MOST Web Framework * A JavaScript Web Framework * http://themost.io * * Copyright (c) 2014, Kyriakos Barbounakis k.barbounakis@gmail.com, Anthi Oikonomou anthioikonomou@gmail.com * * Released under the BSD-3-Clause license * Date: 2017-11-10 */ /// var Symbol = require('symbol'); var LangUtils = require('@themost/common/utils').LangUtils; var sprintf = require('sprintf').sprintf; var Q = require('q'); var pluralize = require('pluralize'); var _ = require('lodash'); var moment = require('moment'); var parseBoolean = require('./types').parsers.parseBoolean; var DataModel = require('./data-model').DataModel; var DataContext = require('./types').DataContext; var XDocument = require('@themost/xml/index').XDocument; // 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').DataConfigurationStrategy; var SchemaLoaderStrategy = require('./data-configuration').SchemaLoaderStrategy; var DefaultSchemaLoaderStrategy = require('./data-configuration').DefaultSchemaLoaderStrategy; function Args() { // } /** * Checks the expression and throws an exception if the condition is not met. * @param {*} expr * @param {string} message */ Args.check = function(expr, message) { Args.notNull(expr,"Expression"); if (typeof expr === 'function') { expr.call() } var res; if (typeof expr === 'function') { res = !(expr.call()); } else { res = (!expr); } if (res) { var err = new Error(message); err.code = "ECHECK"; throw err; } }; /** * * @param {*} arg * @param {string} name */ Args.notNull = function(arg,name) { if (typeof arg === 'undefined' || arg === null) { var err = new Error(name + " may not be null or undefined"); err.code = "ENULL"; throw err; } }; /** * @param {*} arg * @param {string} name */ Args.notString = function(arg, name) { if (typeof arg !== 'string') { var err = new Error(name + " must be a string"); err.code = "EARG"; throw err; } }; /** * @param {*} arg * @param {string} name */ Args.notFunction = function(arg, name) { if (typeof arg !== 'function') { var err = new Error(name + " must be a function"); err.code = "EARG"; throw err; } }; /** * @param {*} arg * @param {string} name */ Args.notNumber = function(arg, name) { if (typeof arg !== 'string') { var err = new Error(name + " must be number"); err.code = "EARG"; throw err; } }; /** * @param {string|*} arg * @param {string} name */ Args.notEmpty = function(arg,name) { Args.notNull(arg,name); Args.notString(arg,name); if (arg.length === 0) { var err = new Error(name + " may not be empty"); err.code = "EEMPTY"; return err; } }; /** * @param {number|*} arg * @param {string} name */ Args.notNegative = function(arg,name) { Args.notNumber(arg,name); if (arg<0) { var err = new Error(name + " may not be negative"); err.code = "ENEG"; return err; } }; /** * @param {number|*} arg * @param {string} name */ Args.positive = function(arg,name) { Args.notNumber(arg,name); if (arg<=0) { var err = new Error(name + " may not be negative or zero"); err.code = "EPOS"; return err; } }; /** * @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"; /** * @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 (any.hasOwnProperty("total") && /^\+?\d+$/.test(any["total"])) { result["@odata.count"] = parseInt(any["total"]); } if (any.hasOwnProperty("count") && /^\+?\d+$/.test(any["count"])) { result["@odata.count"] = parseInt(any["count"]); } 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 (any.hasOwnProperty("total") && /^\+?\d+$/.test(any["total"])) { result["@odata.count"] = parseInt(any["total"]); } else if (any.hasOwnProperty("count") && /^\+?\d+$/.test(any["count"])) { result["@odata.count"] = parseInt(any["count"]); } if (any.hasOwnProperty("skip") && /^\+?\d+$/.test(any["skip"])) { result["@odata.skip"] = parseInt(any["skip"]); } 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); /** * 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); } 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", 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)", returnType)); } else { returnTypeElement.setAttribute("Type", 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", 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)", returnType)); } else { returnTypeElement.setAttribute("Type", 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", entityType.baseType); } 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",x.type); if (_.isBoolean(x.nullable) && (x.nullable===false)) { propertyElement.setAttribute("Nullable",false); } entityTypeElement.appendChild(propertyElement); }); //enumerate navigation properties _.forEach(entityType.navigationProperty, function(x) { var propertyElement = doc.createElement('NavigationProperty'); propertyElement.setAttribute("Name",x.name); propertyElement.setAttribute("Type",x.type); if (!x.nullable) { propertyElement.setAttribute("Nullable",false); } 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", 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] = []; /** * @returns {ConfigurationBase} */ this.getConfiguration = function() { return configuration; }; 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 this[entityTypesProperty].hasOwnProperty(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 = { 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) { 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() { /** * get schema configuration * @type {SchemaConfiguration} */ var schema = this.getEdmSync(); // convert schema to edm document return schemaToEdmDocument.bind(this)(schema); }; // noinspection JSUnusedGlobalSymbols /** * @param {Function} contextLinkFunc */ ODataModelBuilder.prototype.hasContextLink = function(contextLinkFunc) { this.getContextLink = contextLinkFunc; }; // noinspection JSUnusedGlobalSymbols /** * * @param jsonFormatterFunc */ ODataModelBuilder.prototype.hasJsonFormatter = function(jsonFormatterFunc) { this.jsonFormatter = jsonFormatterFunc; }; /** * @param {EntitySetConfiguration} entitySet * @param {*} context * @param {*} instance * @param {*=} options * @returns * */ ODataModelBuilder.prototype.jsonFormatter = function(context, entitySet, instance, options) { var self = this; var defaults = _.assign({ addContextAttribute:true, addCountAttribute:false }, options); var entityProperty = entitySet.getEntityTypePropertyList(); var entityNavigationProperty