@themost/data
Version:
MOST Web Framework 2.0 - ORM module
1,608 lines (1,517 loc) • 79.6 kB
JavaScript
/**
* @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