UNPKG

generator-stui5

Version:
1,292 lines (1,217 loc) 110 kB
/* * @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