generator-stui5
Version:
UI5 boilerplate generator with Yeoman.io
1,292 lines (1,217 loc) • 110 kB
JavaScript
/*
* @copyright@
*/
// Provides class sap.ui.core.util.MockServer for mocking a server
sap.ui
.define(
[ 'jquery.sap.global', 'sap/ui/Device', 'sap/ui/base/ManagedObject', 'sap/ui/thirdparty/sinon' ],
function(jQuery, Device, ManagedObject, sinon) {
"use strict";
if (!!Device.browser.internet_explorer) {
jQuery.sap.require("sap.ui.thirdparty.sinon-ie");
}
/*global URI *///declare unusual global vars for JSLint/SAPUI5 validation
/**
* Creates a mocked server. This helps to mock all or some backend calls, e.g. for OData/JSON Models or simple XHR calls, without
* changing the application code. This class can also be used for qunit tests.
*
* @param {string} [sId] id for the new server object; generated automatically if no non-empty id is given
* Note: this can be omitted, no matter whether <code>mSettings</code> will be given or not!
* @param {object} [mSettings] optional map/JSON-object with initial property values, aggregated objects etc. for the new object
* @param {object} [oScope] scope object for resolving string based type and formatter references in bindings
*
* @class Class to mock a server
* @extends sap.ui.base.ManagedObject
* @abstract
* @author SAP SE
* @version @version@
* @public
* @name sap.ui.core.util.MockServer
*/
var MockServer = ManagedObject.extend("sap.ui.core.util.MockServer", /** @lends sap.ui.core.util.MockServer.prototype */
{
constructor : function(sId, mSettings, oScope) {
ManagedObject.apply(this, arguments);
MockServer._aServers.push(this);
},
metadata : {
properties : {
/**
* Setter for property <code>rootUri</code>. All request path URI are prefixed with this root URI if set.
*
* Default value is empty/<code>undefined</code>
* @param {string} rootUri new value for property <code>rootUri</code>
* @public
* @name sap.ui.core.util.MockServer#setRootUri
* @function
*/
/**
* Getter for property <code>rootUri</code>.
*
* Default value is empty/<code>undefined</code>
*
* @return {string} the value of property <code>rootUri</code>
* @public
* @name sap.ui.core.util.MockServer#getRootUri
* @function
*/
rootUri : "string",
/**
* Setter for property <code>requests</code>.
*
* Default value is is <code>[]</code>
*
* Each array entry should consist of an array with the following properties / values:
*
* <ul>
* <li><b>method <string>: "GET"|"POST"|"DELETE|"PUT"</b>
* <br>
* (any HTTP verb)
* </li>
* <li><b>path <string>: "/path/to/resource"</b>
* <br>
* The path is converted to a regular expression, so it can contain normal regular expression syntax.
* All regular expression groups are forwarded as arguments to the <code>response</code> function.
* In addition to this, parameters can be written in this notation: <code>:param</code>. These placeholder will be replaced by regular expression groups.
* </li>
* <li><b>response <function>: function(xhr, param1, param2, ...) { }</b>
* <br>
* The xhr object can be used to respond on the request. Supported methods are:
* <br>
* <code>xhr.respond(iStatusCode, mHeaders, sBody)</code>
* <br>
* <code>xhr.respondJSON(iStatusCode, mHeaders, oJsonObjectOrString)</code>. By default a JSON header is set for response header
* <br>
* <code>xhr.respondXML(iStatusCode, mHeaders, sXmlString)</code>. By default a XML header is set for response header
* <br>
* <code>xhr.respondFile(iStatusCode, mHeaders, sFileUrl)</code>. By default the mime type of the file is set for response header
* </li>
* </ul>
*
* @param {object[]} requests new value for property <code>requests</code>
* @public
* @name sap.ui.core.util.MockServer#setRequests
* @function
*/
/**
* Getter for property <code>requests</code>.
*
* Default value is <code>[]</code>
*
* @return {object[]} the value of property <code>rootUri</code>
* @public
* @name sap.ui.core.util.MockServer#getRequests
* @function
*/
requests : {
type : "object[]",
defaultValue : []
}
}
},
_oServer : null,
_aFilter : null,
_oMockdata : null,
_oMetadata : null,
_sMetadataUrl : null,
_sMockdataBaseUrl : null,
_mEntitySets : null
});
/**
* Starts the server.
* @public
* @name sap.ui.core.util.MockServer#start
* @function
*/
MockServer.prototype.start = function() {
this._oServer = MockServer._getInstance();
this._aFilters = [];
var aRequests = this.getRequests();
var iLength = aRequests.length;
for ( var i = 0; i < iLength; i++) {
var oRequest = aRequests[i];
this._addRequestHandler(oRequest.method, oRequest.path, oRequest.response);
}
};
/**
* Stops the server.
* @public
* @name sap.ui.core.util.MockServer#stop
* @function
*/
MockServer.prototype.stop = function() {
if (this.isStarted()) {
this._removeAllRequestHandlers();
this._removeAllFilters();
this._oServer = null;
}
};
/**
* Returns whether the server is started or not.
*
* @return {boolean} whether the server is started or not.
* @public
* @name sap.ui.core.util.MockServer#isStarted
* @function
*/
MockServer.prototype.isStarted = function() {
return !!this._oServer;
};
/**
* Applies the OData system query option string on the given array
* @param {object} oFilteredData
* @param {string} sQuery string in the form {query}={value}
* @param {string} sEntitySetName the name of the entitySet the oFilteredData belongs to
* @private
* @name sap.ui.core.util.MockServer#_applyQueryOnCollection
* @function
*/
MockServer.prototype._applyQueryOnCollection = function(oFilteredData, sQuery, sEntitySetName) {
var aQuery = sQuery.split('=');
var sODataQueryValue = aQuery[1];
if (sODataQueryValue === "")
return;
if (sODataQueryValue.lastIndexOf(',') === sODataQueryValue.length) {
jQuery.sap.log.error("The URI is violating the construction rules defined in the Data Services specification!");
throw new Error("400");
}
switch (aQuery[0]) {
case "$top":
if (!(new RegExp(/^\d+$/).test(sODataQueryValue))) {
jQuery.sap.log.error("Invalid system query options value!");
throw new Error("400");
}
oFilteredData.results = oFilteredData.results.slice(0, sODataQueryValue);
break;
case "$skip":
if (!(new RegExp(/^\d+$/).test(sODataQueryValue))) {
jQuery.sap.log.error("Invalid system query options value!");
throw new Error("400");
}
oFilteredData.results = oFilteredData.results.slice(sODataQueryValue, oFilteredData.results.length);
break;
case "$orderby":
oFilteredData.results = this._getOdataQueryOrderby(oFilteredData.results, sODataQueryValue);
break;
case "$filter":
oFilteredData.results = this._recursiveOdataQueryFilter(oFilteredData.results, sODataQueryValue);
break;
case "$select":
oFilteredData.results = this._getOdataQuerySelect(oFilteredData.results, sODataQueryValue);
break;
case "$inlinecount":
var iCount = this._getOdataInlineCount(oFilteredData.results, sODataQueryValue);
if (iCount) {
oFilteredData.__count = iCount;
}
break;
case "$expand":
oFilteredData.results = this._getOdataQueryExpand(oFilteredData.results, sODataQueryValue, sEntitySetName);
break;
case "$format":
oFilteredData.results = this._getOdataQueryFormat(oFilteredData.results, sODataQueryValue);
break;
default:
jQuery.sap.log.error("Invalid system query options value!");
throw new Error("400");
}
};
/**
* Applies the OData system query option string on the given entry
* @param {object} oEntry
* @param {string} sQuery string of the form {query}={value}
* @param {string} sEntitySetName the name of the entitySet the oEntry belongs to
* @private
* @name sap.ui.core.util.MockServer#_applyQueryOnEntry
* @function
*/
MockServer.prototype._applyQueryOnEntry = function(oEntry, sQuery, sEntitySetName) {
var aQuery = sQuery.split('=');
var sODataQueryValue = aQuery[1];
if (sODataQueryValue === "")
return;
if (sODataQueryValue.lastIndexOf(',') === sODataQueryValue.length) {
jQuery.sap.log.error("The URI is violating the construction rules defined in the Data Services specification!");
throw new Error("400");
}
switch (aQuery[0]) {
case "$filter":
return this._recursiveOdataQueryFilter([ oEntry ], sODataQueryValue)[0];
case "$select":
return this._getOdataQuerySelect([ oEntry ], sODataQueryValue)[0];
case "$expand":
return this._getOdataQueryExpand([ oEntry ], sODataQueryValue, sEntitySetName)[0];
case "$format":
return this._getOdataQueryFormat([ oEntry ], sODataQueryValue);
default:
jQuery.sap.log.error("Invalid system query options value!");
throw new Error("400");
}
};
/**
* Applies the Orderby OData system query option string on the given array
* @param {object} aDataSet
* @param {string} sODataQueryValue a comma separated list of property navigation paths to sort by, where each property navigation path terminates on a primitive property
* @private
* @name sap.ui.core.util.MockServer#_getOdataQueryOrderby
* @function
*/
MockServer.prototype._getOdataQueryOrderby = function(aDataSet, sODataQueryValue) {
// sort properties lookup
var aProperties = sODataQueryValue.split(',');
var that = this;
//trim all properties
jQuery.each(aProperties, function(i, sPropertyName) {
aProperties[i] = that._trim(sPropertyName);
});
var fnComparator = function compare(a, b) {
for ( var i = 0; i < aProperties.length; i++) {
// sort order lookup asc / desc
var aSort = aProperties[i].split(' ');
// by default the sort is in asc order
var iSorter = 1;
if (aSort.length > 1) {
switch (aSort[1]) {
case 'asc':
iSorter = 1;
break;
case 'desc':
iSorter = -1;
break;
default:
jQuery.sap.log.error("Invalid sortorder '" + aSort[1] + "' detected!");
throw new Error("400");
}
}
// support for 1 level complex type property
var sPropName, sComplexType;
var iComplexType = aSort[0].indexOf("/");
if (iComplexType !== -1) {
sPropName = aSort[0].substring(iComplexType + 1);
sComplexType = aSort[0].substring(0, iComplexType);
if (!a[sComplexType].hasOwnProperty(sPropName)) {
jQuery.sap.log.error("Property " + sPropName + " not found!");
throw new Error("400");
}
if (a[sComplexType][sPropName] < b[sComplexType][sPropName])
return -1 * iSorter;
if (a[sComplexType][sPropName] > b[sComplexType][sPropName])
return 1 * iSorter;
} else {
sPropName = aSort[0];
if (!a.hasOwnProperty(sPropName)) {
jQuery.sap.log.error("Property " + sPropName + " not found!");
throw new Error("400");
}
if (a[sPropName] < b[sPropName])
return -1 * iSorter;
if (a[sPropName] > b[sPropName])
return 1 * iSorter;
}
}
return 0;
};
return aDataSet.sort(fnComparator);
};
/**
* Removes duplicate entries from the given array
* @param {object} aDataSet
* @private
* @name sap.ui.core.util.MockServer#_arrayUnique
* @function
*/
MockServer.prototype._arrayUnique = function(array) {
var a = array.concat();
for ( var i = 0; i < a.length; ++i) {
for ( var j = i + 1; j < a.length; ++j) {
if (a[i] === a[j])
a.splice(j--, 1);
}
}
return a;
};
/**
* Returns the indices of the first brackets appearance, excluding brackets of $filter reserved functions
* @param {string} sString
* @private
* @name sap.ui.core.util.MockServer#_getBracketIndices
* @function
*/
MockServer.prototype._getBracketIndices = function(sString) {
var aStack = [];
var bReserved = false;
var iStartIndex, iEndIndex = 0;
for ( var character = 0; character < sString.length; character++) {
if (sString[character] === '(') {
if (/[substringof|endswith|startswith]$/.test(sString.substring(0, character))) {
bReserved = true;
} else {
aStack.push(sString[character]);
if (iStartIndex === undefined) {
iStartIndex = character;
}
}
} else if (sString[character] === ')') {
if (!bReserved) {
aStack.pop();
iEndIndex = character;
if (aStack.length === 0)
return {
start : iStartIndex,
end : iEndIndex
};
} else {
bReserved = false;
}
}
}
return {
start : iStartIndex,
end : iEndIndex
};
};
/**
* Applies the $filter OData system query option string on the given array.
* This function is called recursively on expressions in brackets.
* @param {string} sString
* @private
* @name sap.ui.core.util.MockServer#_getBracketIndices
* @function
*/
MockServer.prototype._recursiveOdataQueryFilter = function(aDataSet, sODataQueryValue) {
// check for wrapping brackets, e.g. (A), (A op B), (A op (B)), (((A)))
var oIndices = this._getBracketIndices(sODataQueryValue);
if (oIndices.start === 0 && oIndices.end === sODataQueryValue.length - 1) {
sODataQueryValue = this._trim(sODataQueryValue.substring(oIndices.start + 1, oIndices.end));
return this._recursiveOdataQueryFilter(aDataSet, sODataQueryValue);
}
// find brackets that are not related to the reserved words
var rExp = /([^substringof|endswith|startswith]|^)\((.*)\)/;
if (rExp.test(sODataQueryValue)) {
var sBracketed = sODataQueryValue.substring(oIndices.start, oIndices.end + 1);
var rExp1 = new RegExp("(.*) +(or|and) +(" + this._trim(this._escapeStringForRegExp(sBracketed)) + ".*)");
if (oIndices.start === 0) {
rExp1 = new RegExp("(" + this._trim(this._escapeStringForRegExp(sBracketed)) + ") +(or|and) +(.*)");
}
var aExp1Parts = rExp1.exec(sODataQueryValue);
var sExpression = aExp1Parts[1];
var sOperator = aExp1Parts[2];
var sExpression2 = aExp1Parts[3];
var aSet1 = this._recursiveOdataQueryFilter(aDataSet, sExpression);
var aSet2, aParts;
if (sOperator === "or") {
aSet2 = this._recursiveOdataQueryFilter(aDataSet, sExpression2);
return this._arrayUnique(aSet1.concat(aSet2));
}
if (sOperator === "and") {
return this._recursiveOdataQueryFilter(aSet1, sExpression2);
}
} else {
//there are only brackets with the reserved words
// e.g. A or B and C or D
aParts = sODataQueryValue.split(/ +and | or +/);
// base case
if (aParts.length === 1) {
// IE8 handling
if (sODataQueryValue.match(/ +and | or +/)) {
throw new Error("400");
}
return this._getOdataQueryFilter(aDataSet, this._trim(sODataQueryValue));
}
var aResult = this._recursiveOdataQueryFilter(aDataSet, aParts[0]);
var rRegExp;
for ( var i = 1; i < aParts.length; i++) {
rRegExp = new RegExp(this._trim(this._escapeStringForRegExp(aParts[i - 1])) + " +(and|or) +"
+ this._trim(this._escapeStringForRegExp(aParts[i])));
sOperator = rRegExp.exec(sODataQueryValue)[1];
if (sOperator === "or") {
aSet2 = this._recursiveOdataQueryFilter(aDataSet, aParts[i]);
aResult = this._arrayUnique(aResult.concat(aSet2));
}
if (sOperator === "and") {
aResult = this._recursiveOdataQueryFilter(aResult, aParts[i]);
}
}
return aResult;
}
};
/**
* Applies the Filter OData system query option string on the given array
* @param {object} aDataSet
* @param {string} sODataQueryValue a boolean expression
* @private
* @name sap.ui.core.util.MockServer#_getOdataQueryFilter
* @function
*/
MockServer.prototype._getOdataQueryFilter = function(aDataSet, sODataQueryValue) {
if (aDataSet.length === 0)
return aDataSet;
var rExp = new RegExp("(.*) (eq|ne|gt|lt|le|ge) (.*)");
var rExp2 = new RegExp("(endswith|startswith|substringof)\\((.*)");
var sODataFilterMethod = null;
var aODataFilterValues = rExp.exec(sODataQueryValue);
if (aODataFilterValues) {
sODataFilterMethod = aODataFilterValues[2];
} else {
aODataFilterValues = rExp2.exec(sODataQueryValue);
if (aODataFilterValues) {
sODataFilterMethod = aODataFilterValues[1];
} else {
throw new Error("400");
}
}
var that = this;
var fnGetFilteredData = function(bValue, iValueIndex, iPathIndex, fnSelectFilteredData) {
var aODataFilterValues, sValue;
if (!bValue) { //e.g eq, ne, gt, lt, le, ge
aODataFilterValues = rExp.exec(sODataQueryValue);
sValue = that._trim(aODataFilterValues[iValueIndex + 1]);
var sPath = that._trim(aODataFilterValues[iPathIndex + 1]);
} else { //e.g.substringof, startswith, endswith
var rStringFilterExpr = new RegExp("(substringof|startswith|endswith)\\(([^,\\)]*),(.*)\\)");
aODataFilterValues = rStringFilterExpr.exec(sODataQueryValue);
sValue = that._trim(aODataFilterValues[iValueIndex + 2]);
sPath = that._trim(aODataFilterValues[iPathIndex + 2]);
}
//TODO do the check using the property type and not value
//fix for filtering on date time properties
if (sValue.indexOf("datetime") === 0) {
sValue = that._getJsonDate(sValue);
}
// fix for filtering on boolean properties
else if (sValue === "true") {sValue = true;}
else if (sValue === "false") {sValue = false;}
//fix for filtering on properties of type number
else if(that._isValidNumber(sValue)) {
sValue = parseFloat(sValue);
}
//fix for filtering on properties of type string
else if((sValue.charAt(0) === "'") && (sValue.charAt(sValue.length - 1) === "'")){
sValue = sValue.substr(1, sValue.length - 2);
}
// support for 1 level complex type property
var iComplexType = sPath.indexOf("/");
if (iComplexType !== -1) {
var sPropName = sPath.substring(iComplexType + 1);
var sComplexType = sPath.substring(0, iComplexType);
if (!aDataSet[0][sComplexType].hasOwnProperty(sPropName)) {
jQuery.sap.log.error("Property " + sPropName + " not found!");
throw new Error("400");
}
return fnSelectFilteredData(sPath, sValue, sComplexType, sPropName);
} else {
//check if sPath exists as property of the entityset
if (!aDataSet[0].hasOwnProperty(sPath)) {
jQuery.sap.log.error("Property " + sPath + " not found for " + aDataSet[0].__metadata.type + "!");
throw new Error("400");
}
return fnSelectFilteredData(sPath, sValue);
}
};
switch (sODataFilterMethod) {
case "substringof":
return fnGetFilteredData(true, 0, 1, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName].indexOf(sValue) !== -1);
}
return (oMockData[sPath].indexOf(sValue) !== -1);
});
});
case "startswith":
return fnGetFilteredData(true, 1, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName].indexOf(sValue) === 0);
}
return (oMockData[sPath].indexOf(sValue) === 0);
});
});
case "endswith":
return fnGetFilteredData(true, 1, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName].indexOf(sValue) === (oMockData[sComplexType][sPropName].length - sValue.length));
}
return (oMockData[sPath].indexOf(sValue) === (oMockData[sPath].length - sValue.length));
});
});
case "eq":
return fnGetFilteredData(false, 2, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName] === sValue);
}
return (oMockData[sPath] === sValue);
});
});
case "ne":
return fnGetFilteredData(false, 2, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName] !== sValue);
}
return (oMockData[sPath] !== sValue);
});
});
case "gt":
return fnGetFilteredData(false, 2, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName] > sValue);
}
return (oMockData[sPath] > sValue);
});
});
case "lt":
return fnGetFilteredData(false, 2, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName] < sValue);
}
return (oMockData[sPath] < sValue);
});
});
case "ge":
return fnGetFilteredData(false, 2, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName] >= sValue);
}
return (oMockData[sPath] >= sValue);
});
});
case "le":
return fnGetFilteredData(false, 2, 0, function(sPath, sValue, sComplexType, sPropName) {
return jQuery.grep(aDataSet, function(oMockData) {
if (sComplexType && sPropName) {
return (oMockData[sComplexType][sPropName] <= sValue);
}
return (oMockData[sPath] <= sValue);
});
});
default:
jQuery.sap.log.error("Invalid $filter operator '" + sODataFilterMethod + "'!");
throw new Error("400");
}
};
/**
* Applies the Select OData system query option string on the given array
* @param {object} aDataSet
* @param {string} sODataQueryValue a comma separated list of property paths, qualified action names, qualified function names, or the star operator (*)
* @private
* @name sap.ui.core.util.MockServer#_getOdataQuerySelect
* @function
*/
MockServer.prototype._getOdataQuerySelect = function(aDataSet, sODataQueryValue) {
var that = this;
var sPropName, sComplexType;
var aProperties = sODataQueryValue.split(',');
var aSelectedDataSet = [];
var oPushedObject;
var fnCreatePushedEntry = function(aProperties, oData, oPushedObject) {
if (oData["__metadata"]) {
oPushedObject["__metadata"] = oData["__metadata"];
}
jQuery.each(aProperties, function(i, sPropertyName) {
var iComplexType = sPropertyName.indexOf("/");
// this is a complex type property
if (iComplexType !== -1) {
sPropName = sPropertyName.substring(iComplexType + 1);
sComplexType = sPropertyName.substring(0, iComplexType);
if (!oPushedObject[sComplexType]) {
oPushedObject[sComplexType] = {};
}
oPushedObject[sComplexType] = fnCreatePushedEntry([sPropName], oData[sComplexType], oPushedObject[sComplexType]);
// this is a simple property
} else {
if (oData && !oData.hasOwnProperty(sPropertyName)) {
jQuery.sap.log.error("Resource not found for the selection clause '" + sPropertyName + "'!");
throw new Error("404");
}
oPushedObject[sPropertyName] = oData[sPropertyName];
}
});
return oPushedObject;
};
// in case of $select=* return the data as is
if (jQuery.inArray("*", aProperties) !== -1) {
return aDataSet;
}
// trim all properties
jQuery.each(aProperties, function(i, sPropertyName) {
aProperties[i] = that._trim(sPropertyName);
});
// for each entry in the dataset create a new object that contains only the properties in $select clause
jQuery.each(aDataSet, function(iIndex, oData) {
oPushedObject = {};
aSelectedDataSet.push(fnCreatePushedEntry(aProperties, oData, oPushedObject));
});
return aSelectedDataSet;
};
/**
* Applies the InlineCount OData system query option string on the given array
* @param {object} aDataSet
* @param {string} sODataQueryValue a value of allpages, or a value of none
* @private
* @name sap.ui.core.util.MockServer#_getOdataInlineCount
* @function
*/
MockServer.prototype._getOdataInlineCount = function(aDataSet, sODataQueryValue) {
var aProperties = sODataQueryValue.split(',');
if (aProperties.length !== 1 || (aProperties[0] !== 'none' && aProperties[0] !== 'allpages')) {
jQuery.sap.log.error("Invalid system query options value!");
throw new Error("400");
}
if (aProperties[0] === 'none') {
return;
}
return aDataSet.length;
};
/**
* Applies the Format OData system query option
* @param {string} sODataQueryValue
* @private
* @name sap.ui.core.util.MockServer#_getOdataQueryFormat
* @function
*/
MockServer.prototype._getOdataQueryFormat = function(aDataSet, sODataQueryValue) {
if (sODataQueryValue !== 'json') {
jQuery.sap.log.error("Unsupported format value. Only json format is supported!");
throw new Error("400");
}
return aDataSet;
};
/**
* Applies the Expand OData system query option string on the given array
* @param {object} aDataSet
* @param {string} sODataQueryValue a comma separated list of navigation property paths
* @param {string} sEntitySetName the name of the entitySet the aDataSet belongs to
* @private
* @name sap.ui.core.util.MockServer#_getOdataQueryExpand
* @function
*/
MockServer.prototype._getOdataQueryExpand = function(aDataSet, sODataQueryValue, sEntitySetName) {
var that = this;
var aNavProperties = sODataQueryValue.split(',');
//trim all nav properties
jQuery.each(aNavProperties, function(i, sPropertyName) {
aNavProperties[i] = that._trim(sPropertyName);
});
var oEntitySetNavProps = that._mEntitySets[sEntitySetName].navprops;
jQuery.each(aDataSet, function(iIndex, oRecord) {
jQuery.each(aNavProperties, function(iIndex, sNavPropFull) {
var aNavProps = sNavPropFull.split("/");
var sNavProp = aNavProps[0];
if (!oRecord[sNavProp]) {
throw new Error("404");
}
//check if an expanded operation was already executed. for 1:* check results . otherwise, check if there is __deferred for clean start.
var aNavEntry = oRecord[sNavProp].results || oRecord[sNavProp];
if (!aNavEntry || !!aNavEntry.__deferred) {
aNavEntry = jQuery.extend(true, [], that._resolveNavigation(sEntitySetName, oRecord, sNavProp));
}
if (!!aNavEntry && aNavProps.length > 1) {
var sRestNavProps = aNavProps.splice(1, aNavProps.length).join("/");
aNavEntry = that._getOdataQueryExpand(aNavEntry, sRestNavProps,
oEntitySetNavProps[sNavProp].to.entitySet);
}
if (oEntitySetNavProps[sNavProp].to.multiplicity === "*") {
oRecord[sNavProp] = {
results : aNavEntry
};
} else {
oRecord[sNavProp] = aNavEntry[0] ? aNavEntry[0] : {};
}
});
});
return aDataSet;
};
/**
* Refreshes the service metadata document and the mockdata
*
* @private
* @name sap.ui.core.util.MockServer#_refreshData
* @function
*/
MockServer.prototype._refreshData = function() {
// load the metadata
this._loadMetadata(this._sMetadataUrl);
// here we need to analyse the EDMX and identify the entity sets
this._mEntitySets = this._findEntitySets(this._oMetadata);
if (!this._sMockdataBaseUrl) {
// load the mockdata
this._generateMockdata(this._mEntitySets, this._oMetadata);
} else {
// check the mockdata base URL to end with a slash
if (!jQuery.sap.endsWith(this._sMockdataBaseUrl, "/") && !jQuery.sap.endsWith(this._sMockdataBaseUrl, ".json")) {
this._sMockdataBaseUrl += "/";
}
// load the mockdata
this._loadMockdata(this._mEntitySets, this._sMockdataBaseUrl);
}
};
/**
* Returns the root URI without query or hash parameters
* @return {string} the root URI without query or hash parameters
* @name sap.ui.core.util.MockServer#_getRootUri
* @function
*/
MockServer.prototype._getRootUri = function() {
var sUri = this.getRootUri();
sUri = sUri && /([^?#]*)([?#].*)?/.exec(sUri)[1]; // remove URL parameters or anchors
return sUri;
};
/**
* Loads the service metadata for the given url
* @param {string} sMetadataUrl url to the service metadata document
* @return {XMLDocument} the xml document object
* @private
* @name sap.ui.core.util.MockServer#_loadMetadata
* @function
*/
MockServer.prototype._loadMetadata = function(sMetadataUrl) {
// load the metadata
var oMetadata = jQuery.sap.sjax({
url : sMetadataUrl,
dataType : "xml"
}).data;
jQuery.sap.assert(oMetadata !== undefined, "The metadata for url \"" + sMetadataUrl + "\" could not be found!");
this._oMetadata = oMetadata;
return oMetadata;
};
/**
* find the entity sets in the metadata XML document
* @param {XMLDocument} oMetadata the metadata XML document
* @return {map} map of entity sets
* @private
* @name sap.ui.core.util.MockServer#_findEntitySets
* @function
*/
MockServer.prototype._findEntitySets = function(oMetadata) {
// here we need to analyse the EDMX and identify the entity sets
var mEntitySets = {};
var oPrincipals = jQuery(oMetadata).find("Principal");
var oDependents = jQuery(oMetadata).find("Dependent");
jQuery(oMetadata).find("EntitySet").each(function(iIndex, oEntitySet) {
var $EntitySet = jQuery(oEntitySet);
// split the namespace and the name of the entity type (namespace could have dots inside)
var aEntityTypeParts = /((.*)\.)?(.*)/.exec($EntitySet.attr("EntityType"));
mEntitySets[$EntitySet.attr("Name")] = {
"name" : $EntitySet.attr("Name"),
"schema" : aEntityTypeParts[2],
"type" : aEntityTypeParts[3],
"keys" : [],
"keysType" : {},
"navprops" : {}
};
});
// helper function to find the entity set and property reference
// for the given role name
var fnResolveNavProp = function(sRole, bFrom) {
var aRoleEnd = jQuery(oMetadata).find("End[Role=" + sRole + "]");
var sEntitySet;
var sMultiplicity;
jQuery.each(aRoleEnd, function(i, oValue) {
if (!!jQuery(oValue).attr("EntitySet")) {
sEntitySet = jQuery(oValue).attr("EntitySet");
} else {
sMultiplicity = jQuery(oValue).attr("Multiplicity");
}
});
var aPropRef = [];
var oPrinDeps = (bFrom) ? oPrincipals : oDependents;
jQuery(oPrinDeps).each(function(iIndex, oPrinDep) {
if (sRole === (jQuery(oPrinDep).attr("Role"))) {
jQuery(oPrinDep).children("PropertyRef").each(function(iIndex, oPropRef) {
aPropRef.push(jQuery(oPropRef).attr("Name"));
});
return false;
}
});
return {
"role" : sRole,
"entitySet" : sEntitySet,
"propRef" : aPropRef,
"multiplicity" : sMultiplicity
};
};
// find the keys and the navigation properties of the entity types
jQuery.each(mEntitySets, function(sEntitySetName, oEntitySet) {
// find the keys
var $EntityType = jQuery(oMetadata).find("EntityType[Name=" + oEntitySet.type + "]");
var aKeys = jQuery($EntityType).find("PropertyRef");
jQuery.each(aKeys, function(iIndex, oPropRef) {
var sKeyName = jQuery(oPropRef).attr("Name");
oEntitySet.keys.push(sKeyName);
oEntitySet.keysType[sKeyName] = jQuery($EntityType).find("Property[Name=" + sKeyName + "]").attr("Type");
});
// resolve the navigation properties
var aNavProps = jQuery(oMetadata).find("EntityType[Name=" + oEntitySet.type + "] NavigationProperty");
jQuery.each(aNavProps, function(iIndex, oNavProp) {
var $NavProp = jQuery(oNavProp);
oEntitySet.navprops[$NavProp.attr("Name")] = {
"name" : $NavProp.attr("Name"),
"from" : fnResolveNavProp($NavProp.attr("FromRole"), true),
"to" : fnResolveNavProp($NavProp.attr("ToRole"), false)
};
});
});
// return the entity sets
return mEntitySets;
};
/**
* find the entity types in the metadata XML document
* @param {XMLDocument} oMetadata the metadata XML document
* @return {map} map of entity types
* @private
* @name sap.ui.core.util.MockServer#_findEntityTypes
* @function
*/
MockServer.prototype._findEntityTypes = function(oMetadata) {
var mEntityTypes = {};
jQuery(oMetadata).find("EntityType").each(function(iIndex, oEntityType) {
var $EntityType = jQuery(oEntityType);
mEntityTypes[$EntityType.attr("Name")] = {
"name" : $EntityType.attr("Name"),
"properties" : [],
"keys" : []
};
$EntityType.find("Property").each(function(iIndex, oProperty) {
var $Property = jQuery(oProperty);
var type = $Property.attr("Type");
mEntityTypes[$EntityType.attr("Name")].properties.push({
"schema" : type.substring(0, type.lastIndexOf(".")),
"type" : type.substring(type.lastIndexOf(".") + 1),
"name" : $Property.attr("Name"),
"precision" : $Property.attr("Precision"),
"scale" : $Property.attr("Scale")
});
});
$EntityType.find("PropertyRef").each(function(iIndex, oKey) {
var $Key = jQuery(oKey);
var sPropertyName = $Key.attr("Name");
mEntityTypes[$EntityType.attr("Name")].keys.push(sPropertyName);
});
});
return mEntityTypes;
};
/**
* find the complex types in the metadata XML document
* @param {XMLDocument} oMetadata the metadata XML document
* @return {map} map of complex types
* @private
* @name sap.ui.core.util.MockServer#_findComplexTypes
* @function
*/
MockServer.prototype._findComplexTypes = function(oMetadata) {
var mComplexTypes = {};
jQuery(oMetadata).find("ComplexType").each(function(iIndex, oComplexType) {
var $ComplexType = jQuery(oComplexType);
mComplexTypes[$ComplexType.attr("Name")] = {
"name" : $ComplexType.attr("Name"),
"properties" : []
};
$ComplexType.find("Property").each(function(iIndex, oProperty) {
var $Property = jQuery(oProperty);
var type = $Property.attr("Type");
mComplexTypes[$ComplexType.attr("Name")].properties.push({
"schema" : type.substring(0, type.lastIndexOf(".")),
"type" : type.substring(type.lastIndexOf(".") + 1),
"name" : $Property.attr("Name"),
"precision" : $Property.attr("Precision"),
"scale" : $Property.attr("Scale")
});
});
});
return mComplexTypes;
};
/**
* creates a key string for the given keys and entry
* @param {object} oEntitySet the entity set info
* @param {object} oEntry entity set entry which contains the keys as properties
* @return {string} the keys string
* @private
* @name sap.ui.core.util.MockServer#_createKeysString
* @function
*/
MockServer.prototype._createKeysString = function(oEntitySet, oEntry) {
// creates the key string for an entity
var that = this;
var sKeys = "";
if (oEntry) {
jQuery.each(oEntitySet.keys, function(iIndex, sKey) {
if (sKeys) {
sKeys += ",";
}
var oKeyValue = oEntry[sKey];
if (oEntitySet.keysType[sKey] === "Edm.String") {
oKeyValue = "'" + oKeyValue + "'";
} else if (oEntitySet.keysType[sKey] === "Edm.DateTime") {
oKeyValue = that._getDateTime(oKeyValue);
} else if (oEntitySet.keysType[sKey] === "Edm.Guid") {
oKeyValue = "guid'" + oKeyValue + "'";
}
if (oEntitySet.keys.length === 1) {
sKeys += oKeyValue;
return sKeys;
}
sKeys += sKey + "=" + oKeyValue;
});
}
return sKeys;
};
/**
* loads the mock data for the given entity sets and tries to load them from
* the files inside the given base url. The name of the JSON files containing the
* mock data should be the same as the name of the underlying entity type. As
* an alternative you could also specify the url to a single JSON file containing
* the mock data for all entity types.
* @param {map} mEntitySets map of entity sets
* @param {string} sBaseUrl the base url which contains the mock data in JSON files or if the url is pointing to a JSON file containing all entity types
* @return {array} the mockdata arary containing the data for the entity sets
* @private
* @name sap.ui.core.util.MockServer#_loadMockdata
* @function
*/
MockServer.prototype._loadMockdata = function(mEntitySets, sBaseUrl) {
// load the entity sets (map the entity type data to the entity set)
var that = this, mEntityTypesData = {};
this._oMockdata = {};
// load the entity types data
if (jQuery.sap.endsWith(sBaseUrl, ".json")) {
// all entity types are in one file
var oResponse = jQuery.sap.sjax({
url : sBaseUrl,
dataType : "json"
});
if (oResponse.success) {
mEntityTypesData = oResponse.data;
} else {
jQuery.sap.log.error("The mockdata for all the entity types could not be found at \"" + sBaseUrl + "\"!");
}
} else {
// load the entity types individually
jQuery.each(mEntitySets, function(sEntitySetName, oEntitySet) {
if (!mEntityTypesData[oEntitySet.type]) {
var sEntityTypeUrl = sBaseUrl + oEntitySet.type + ".json";
var oResponse = jQuery.sap.sjax({
url : sEntityTypeUrl,
dataType : "json"
});
if (oResponse.success) {
if (!!oResponse.data.d) {
if (!!oResponse.data.d.results) {
mEntityTypesData[oEntitySet.type] = oResponse.data.d.results;
} else {
jQuery.sap.log.error("The mockdata format for entity type \"" + oEntitySet.type
+ "\" invalid");
}
} else {
mEntityTypesData[oEntitySet.type] = oResponse.data;
}
} else {
if (oResponse.status === "parsererror") {
jQuery.sap.log.error("The mockdata for entity type \"" + oEntitySet.type
+ "\" could not be loaded due to a parsing error!");
} else {
jQuery.sap.log.error("The mockdata for entity type \"" + oEntitySet.type
+ "\" could not be found at \"" + sBaseUrl + "\"!");
if (!!that._bGenerateMissingMockData) {
var mEntitySet = {};
mEntitySet[oEntitySet.name] = oEntitySet;
mEntityTypesData[oEntitySet.type] = that._generateODataMockdataForEntitySet(mEntitySet,
that._oMetadata)[oEntitySet.name];
}
}
}
}
});
}
// create the mock data for the entity sets and enhance the mock data with metadata
jQuery.each(mEntitySets, function(sEntitySetName, oEntitySet) {
// TODO: should we clone here or not? right now we clone because of unique metadata for
// individual entity sets otherwise the data of the entity types would be a
// reference and thus it overrides the metadata from the other entity type.
// this happens especially then when we have two entity sets for the same
// entity type => maybe we move the metdata generation to the response creation!
that._oMockdata[sEntitySetName] = [];
if (mEntityTypesData[oEntitySet.type]) {
jQuery.each(mEntityTypesData[oEntitySet.type], function(iIndex, oEntity) {
that._oMockdata[sEntitySetName].push(jQuery.extend(true, {}, oEntity));
});
}
// enhance with OData metadata if exists
if (that._oMockdata[sEntitySetName].length > 0) {
that._enhanceWithMetadata(oEntitySet, that._oMockdata[sEntitySetName]);
}
});
// return the new mockdata
return this._oMockdata;
};
/**
* enhances the mock data for the given entity set with the necessary metadata.
* Important is at least to have a metadata entry incl. uri for the entry and
* for the navigation property it is required to have a deferred infor in case
* of not expanding it.
* @param {object} oEntitySet the entity set info
* @param {object} oMockData mock data for the entity set
* @private
* @name sap.ui.core.util.MockServer#_enhanceWithMetadata
* @function
*/
MockServer.prototype._enhanceWithMetadata = function(oEntitySet, oMockData) {
if (oMockData) {
var that = this, sRootUri = this._getRootUri(), sEntitySetName = oEntitySet && oEntitySet.name;
jQuery.each(oMockData, function(iIndex, oEntry) {
// add the metadata for the entry (type is pointing to the EntityType which is required by datajs to resolve properties)
oEntry.__metadata = {
id : sRootUri + sEntitySetName + "(" + that._createKeysString(oEntitySet, oEntry) + ")",
type : oEntitySet.schema + "." + oEntitySet.type,
uri : sRootUri + sEntitySetName + "(" + that._createKeysString(oEntitySet, oEntry) + ")"
};
// add the navigation properties
jQuery.each(oEntitySet.navprops, function(sKey, oNavProp) {
oEntry[sKey] = {
__deferred : {
uri : sRootUri + sEntitySetName + "(" + that._createKeysString(oEntitySet, oEntry) + ")/"
+ sKey
}
};
});
});
}
};
/**
* verify entitytype keys type ((e.g. Int, String, SByte, Time, DateTimeOffset, Decimal, Double, Single, Boolean, DateTime)
* @param {oEntitySet} the entity set for verification
* @param {aRequestedKeys} aRequestedKeys the requested Keys
* @return boolean
* @private
* @name sap.ui.core.util.MockServer#_isRequestedKeysValid
* @function
*/
MockServer.prototype._isRequestedKeysValid = function(oEntitySet, aRequestedKeys) {
// If the Entry has a single key Property the predicate may include only the value of the key Property
if (aRequestedKeys.length === 1) {
var aSplitEq = aRequestedKeys[0].split('=');
if (this._trim(aSplitEq[0]) !== oEntitySet.keys[0]) {
aRequestedKeys = [ oEntitySet.keys[0] + "=" + aRequestedKeys[0] ];
}
}
for ( var i = 0; i < aRequestedKeys.length; i++) {
var sKey = this._trim(aRequestedKeys[i].substring(0, aRequestedKeys[i].indexOf('=')));
var sRequestValue = this._trim(aRequestedKeys[i].substring(aRequestedKeys[i].indexOf('=') + 1));
var sFirstChar = sRequestValue.charAt(0);
var sLastChar = sRequestValue.charAt(sRequestValue.length - 1);
if (oEntitySet.keysType[sKey] === "Edm.String") {
if (sFirstChar !== "'" || sLastChar !== "'") {
return false;
}
} else if (oEntitySet.keysType[sKey] === "Edm.DateTime") {
if (sFirstChar === "'" || sLastChar !== "'") {
return false;
}
} else if (oEntitySet.keysType[sKey] === "Edm.Guid") {
if (sFirstChar === "'" || sLastChar !== "'") {
return false;
}
} else {
if (sFirstChar === "'" || sLastChar === "'") {
return false;
}
}
}
return true;
};
/**
* Takes a string '<poperty1>=<value1>, <poperty2>=<value2>,...' and creates an
* object (hash map) out of it.
*
* @param {sKeys}
* the string of porperty/value pairs
* @param {object}
* object consisting of the parsed properties
*/
MockServer.prototype._parseKeys = function(sKeys, oEntitySet) {
var oResult = {}; // default is an empty hash map
var aProps = sKeys.split(",");
var sKeyName, sKeyValue, aPair;
for ( var i = 0; i < aProps.length; i++) {
aPair = aProps[i].split("=");
if (aPair.length === 1 && oEntitySet.keys.length === 1) {
sKeyName = oEntitySet.keys[0];
sKeyValue = aPair[0];
} else {
if (aPair.length === 2) {
sKeyName = aPair[0];
sKeyValue = aPair[1];
}
}
if (sKeyValue.indexOf('\'') === 0) {
oResult[sKeyName] = sKeyValue.slice(1, sKeyValue.length - 1);
} else {
oResult[sKeyName] = sKeyValue;
}
}
return oResult;
};
/**
* Generate mock value for a specific property type. String value will be
* based on the property name and an index Integer / Decimal value will be
* generated randomly Date / Time / DateTime value will also be generated
* randomly
*
* @param {string}
* sKey the property name
* @param {string}
* sType the property type without the Edm prefix
* @param {map}
* mComplexTypes map of the complex types
* @return {object} the mocked value
* @private
* @name sap.ui.core.util.MockServer#_generatePropertyValue
* @function
*/
MockServer.prototype._generatePropertyValue = function(sKey, sType, mComplexTypes, iIndexParameter) {
var iIndex = iIndexParameter;
if (!iIndex) {
iIndex = Math.floor(Math.random() * 10000) + 101;
}
switch (sType) {
case "String":
return sKey + " " + iIndex;
case "DateTime":
var date = new Date();
date.setFullYear(2000 + Math.floor(Math.random() * 20));
date.setDate(Math.floor(Math.random() * 30));
date.setMonth(Math.floor(Math.random() * 12));
date.setMilliseconds(0);
return "/Date(" + date.getTime() + ")/";
case "Int16":
case "Int32":
case "Int64":
return Math.floor(Math.random() * 10000);
case "Decimal":
return Math.floor(Math.random() * 1000000) / 100;
case "Boolean":
return Math.random() < .5;
case "Byte":
return Math.floor(Math.random() * 10);
case "Double":
return Math.random() * 10;
case "Single":
return Math.random() * 1000000000;
case "SByte":
return Math.floor(Math.random() * 10);
case "Time":
return Math.floor(Math.random() * 23) + ":" + Math.floor(Math.random() * 59) + ":"
+ Math.floor(Math.random() * 59);
case "Guid":
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
case "Binary":
var nMask = Math.floor(-2147483648 + Math.random() * 4294967295);
for ( var n