@themost/data
Version:
MOST Web Framework Codename Blueshift - Data module
1,521 lines (1,440 loc) • 85.7 kB
JavaScript
// 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() {