1 // Copyright (c) 2009-2014 SAP SE, All Rights Reserved
  2 /**
  3  * @fileOverview An API which allows applications to talk to a surrounding shell.
  4  * <p>
  5  * <b>Note:</b> Including this file automatically performs <b>domain relaxation</b>.<br>
  6  * Since version 1.26.3, the way how domain relaxation is performed can be
  7  * configured by setting the property <code>domainRelaxation</code> in the global variable
  8  * <code>sap-ui2-shell-api-config</code> before including the script. The property
  9  * <code>domainRelaxation</code> has to be an object with the following properties:<br>
 10  * <ul>
 11  *  <li><code>integrated</code>:
 12  *  domain relaxation used, when application is started inside of an iframe,
 13  *  a frameset or a popup window</li>
 14  *  <li><code>standalone</code>:
 15  *  domain relaxation used, when application runs standalone</li>
 16  * <li><code>maxrelax</code>
 17  *  maximal relaxation, for "auto" and "maximal"</li>
 18  * </ul>
 19  * <br>
 20  * Possible values for integrated/standalone:<br>
 21  * <ul>
 22  *   <li><code>"none"</code>    : No domain relaxation will be done</li>
 23  *   <li><code>"auto"</code>    : Domain relaxation will automatically adapt to the parent window.
 24  *               If no matching relaxation can be found, domain relaxation is
 25  *               "maximal". When running standalone this defaults to "minimal".</li>
 26  *   <li><code>"minimal"</code> : Only the first part (hostname) of the domain will be removed</li>
 27  *   <li><code>"maximal"</code> : Remove as much as possible, without relaxing to the top-level domain</li>
 28  * </ul>
 29  * <br>
 30  * Possible value for maxrelax:
 31  *   Integer, that determines the number of domain parts, that have to be kept.
 32  *   E.g. 2 means "sap.com", 3 means "sub.sap.com"
 33  * <p>
 34  * Example:<br>
 35  * <code>
 36  * <pre>
 37  * window["sap-ui2-shell-api-config"] = {
 38  *   domainRelaxation : {
 39  *       integrated : "auto",
 40  *       standalone : "none",
 41  *       maxrelax : 2
 42  *   }
 43  * }
 44  *</pre>
 45  *</code>
 46  * <p>
 47  * The default behavior is:
 48  * <ul>
 49  *  <li>integrated: "auto"</li>
 50  *  <li>standalone: "minimal"</li>
 51  *  <li>maxrelax: 2</li>
 52  * </ul>
 53  * This means that when including the script without configuration, domain relaxation of 1 level is
 54  * usually applied. This is also the default for all versions before 1.26.3.<br>
 55  * <p>
 56  * <b>Note:</b> Including this file automatically transports the user's settings from an ABAP
 57  * backend to SAPUI5, e.g. preferred language incl. its right-to-left status, date, time, and
 58  * number formats, theme and possibly others (in future). For this to take proper effect, this file
 59  * <b>must</b> be included <b>before</b> the SAPUI5 bootstrap. <b>Beware!</b> This has an effect
 60  * on SAPUI5's bootstrap sequence with two known consequences:
 61  * <ol>
 62  * <li> You must not refer to SAPUI5 code other than <code>sap.ui.getCore().attachInit()</code>
 63  * until SAPUI5's core is properly initialized.
 64  * <li> You cannot include style sheets directly from the HTML page and override SAPUI5 style
 65  * classes in them, because SAPUI5's bootstrap is delayed and their standard style sheets are
 66  * included only <b>after</b> the page has loaded completely. Use
 67  * <code>jQuery.sap.includeStyleSheet()</code> from your code and obey the first rule.
 68  * <b>Override SAPUI5 style classes with caution!</b> Be sure not to break the built-in theming
 69  * support, e.g. by hard-coding custom colors.
 70  * </ol>
 71  */
 72 
 73 this.sap = this.sap || {};
 74 
 75 // Error (from services/sap/ui2/srvc/error.js) *********************************
 76 // Copyright (c) 2009-2014 SAP SE, All Rights Reserved
 77 /**
 78  * @fileOverview An error object which logs the error message immediately.
 79  */
 80 
 81 this.sap = this.sap || {};
 82 
 83 (function () {
 84   "use strict";
 85   /*global jQuery, sap */
 86 
 87   // namespace "sap.ui2.srvc" **************************************************
 88   sap.ui2 = sap.ui2 || {};
 89   sap.ui2.srvc = sap.ui2.srvc || {};
 90 
 91   // Note: jQuery might not yet be available!
 92   if (typeof jQuery === 'function' && jQuery.sap) {
 93     jQuery.sap.declare("sap.ui2.srvc.error");
 94   }
 95 
 96   /**
 97    * Creates an <code>Error</code> object and logs the error message immediately.
 98    *
 99    * @param {string} sMessage
100    *   the error message
101    * @param {string} [sComponent]
102    *   the error component to log
103    *
104    * @class An error that is written to the log.
105    * @constructor
106    * @since 1.2.0
107    */
108   sap.ui2.srvc.Error = function (sMessage, sComponent) {
109     // see also redundant declaration in utils.js which has to be in sync
110     var that = new Error(sMessage); // reuse Error constructor to benefit from it (e.g. stack)
111     that.name = "sap.ui2.srvc.Error";
112     sap.ui2.srvc.log.error(sMessage, null, sComponent);
113     return that;
114   };
115   // to avoid (new Error()) instanceof sap.ui2.srvc.Error === true we do not set the prototype,
116   // we also tolerate that (new sap.ui2.srvc.Error()) instanceof sap.ui2.srvc.Error === false now
117   // sap.ui2.srvc.Error.prototype = Error.prototype;
118 
119 }());
120 
121 // Utils (from services/sap/ui2/srvc/utils.js) *********************************
122 // Copyright (c) 2009-2014 SAP SE, All Rights Reserved
123 /**
124  * @fileOverview This file contains miscellaneous utility functions.
125  */
126 
127 this.sap = this.sap || {};
128 
129 (function () {
130   "use strict";
131   /*global window,console, DOMParser, jQuery, location, sap, setTimeout, URI, XMLHttpRequest */
132 
133   // ensure that Function.prototype.bind is available, even with iOS 5
134   // utils.js is used with startup service, shell API and page building services
135   if (!Function.prototype.bind) {
136     /**
137      * Replacement for ECMAScript 5 feature which might still be missing.
138      *
139      * @param {object} oThis
140      *  The value to be passed as the <code>this</code> parameter to the target
141      *  function when the bound function is called. The value is ignored if the
142      *  bound function is constructed using the <code>new</code> operator.
143      * @param {...object} aVarArgs
144      *  Arguments to prepend to arguments provided to the bound function when
145      *  invoking the target function.
146      *
147      * @see <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind">bind in ECMAScript 5</a>
148      */
149     Function.prototype.bind = function (oThis) {
150       if (typeof this !== "function") {
151         // closest thing possible to the ECMAScript 5 internal IsCallable function
152         throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
153       }
154 
155       var aArgs = Array.prototype.slice.call(arguments, 1),
156         fToBind = this,
157         /** @ignore */ // no, JSDoc, this is not a global function!
158         NOP = function () {/* no-op c'tor */},
159         /** @ignore */ // no, JSDoc, this is not a global function!
160         fBound = function () {
161           return fToBind.apply(
162             // passing "window" as "this" has been removed (cf. "use strict";)
163             this instanceof NOP ? this : oThis,
164             aArgs.concat(Array.prototype.slice.call(arguments))
165           );
166         };
167       NOP.prototype = this.prototype;
168       fBound.prototype = new NOP();
169       return fBound;
170     };
171   }
172 
173   // namespace "sap.ui2.srvc" **************************************************
174   sap.ui2 = sap.ui2 || {};
175   sap.ui2.srvc = sap.ui2.srvc || {};
176   if (sap.ui2.srvc.log) {
177     return; // It's OK. Don't load twice.
178   }
179 
180   // cache for GET requests
181   var oCache;
182 
183   if (typeof jQuery === 'function' && jQuery.sap) {
184     jQuery.sap.declare("sap.ui2.srvc.utils");
185   }
186 
187   // "private static" methods **************************************************
188 
189   /**
190    * Tells whether the package <code>jQuery.sap.log</code> currently exists.
191    *
192    * @returns {boolean}
193    */
194   function jQuerySapLogExists() {
195     return typeof jQuery === 'function' && jQuery.sap && jQuery.sap.log;
196   }
197 
198   /**
199    * Formats the message for a simple output to <code>window.console</code>.
200    * Mimics SAPUI5 log behavior for the three last parts:
201    * <code>
202    * var logText = oLogEntry.date + " " + oLogEntry.time + " " + sWindowName +
203    *    oLogEntry.message + " - " + oLogEntry.details + " " + oLogEntry.component;
204    * </code>
205    */
206   function formatMessage(sMessage, sDetails, sComponent) {
207     return (sMessage || "") + " - " + (sDetails || "") + " " + (sComponent || "");
208   }
209 
210   // "public static" methods ***************************************************
211 
212   /**
213    * @namespace The namespace for functions which log messages even if SAPUI5 is not present.
214    * @since 1.3.0
215    */
216   sap.ui2.srvc.log = {
217     /**
218      * Wrapper function for <code>jQuery.sap.log.debug()</code>. Writes a simple log message to
219      * the console if SAPUI5 is not present.
220      *
221      * @param {string} sMessage
222      *   the log message
223      * @param {string} sDetails
224      *   the message details
225      * @param {string} sComponent
226      *   the component which issues the message
227      * @since 1.3.0
228      */
229     debug: function (sMessage, sDetails, sComponent) {
230       if (jQuerySapLogExists()) {
231         jQuery.sap.log.debug(sMessage, sDetails, sComponent);
232         return;
233       }
234       if (typeof console === "object") {
235         if (typeof console.debug === "function") { // e.g. Chrome
236           console.debug(formatMessage(sMessage, sDetails, sComponent));
237         } else { // e.g. IE9
238           console.log(formatMessage(sMessage, sDetails, sComponent));
239         }
240       }
241     },
242 
243     /**
244      * Wrapper function for <code>jQuery.sap.log.error()</code>. Writes a simple error message to
245      * the console if SAPUI5 is not present.
246      *
247      * @param {string} sMessage
248      *   the log message
249      * @param {string} sDetails
250      *   the message details
251      * @param {string} sComponent
252      *   the component which issues the message
253      * @since 1.3.0
254      */
255     error: function (sMessage, sDetails, sComponent) {
256       if (jQuerySapLogExists()) {
257         jQuery.sap.log.error(sMessage, sDetails, sComponent);
258         return;
259       }
260       if (typeof console === "object") {
261         console.error(formatMessage(sMessage, sDetails, sComponent));
262       }
263     },
264 
265     /**
266      * Wrapper function for <code>jQuery.sap.log.info()</code>. Writes a simple info message to
267      * the console if SAPUI5 is not present.
268      *
269      * @param {string} sMessage
270      *   the log message
271      * @param {string} sDetails
272      *   the message details
273      * @param {string} sComponent
274      *   the component which issues the message
275      * @since 1.3.0
276      */
277     info: function (sMessage, sDetails, sComponent) {
278       if (jQuerySapLogExists()) {
279         jQuery.sap.log.info(sMessage, sDetails, sComponent);
280         return;
281       }
282       if (typeof console === "object") {
283         console.info(formatMessage(sMessage, sDetails, sComponent));
284       }
285     },
286 
287     /**
288      * Wrapper function for <code>jQuery.sap.log.warning()</code>. Writes a simple warning message
289      * to the console if SAPUI5 is not present.
290      *
291      * @param {string} sMessage
292      *   the log message
293      * @param {string} sDetails
294      *   the message details
295      * @param {string} sComponent
296      *   the component which issues the message
297      * @since 1.3.0
298      */
299     warning: function (sMessage, sDetails, sComponent) {
300       if (jQuerySapLogExists()) {
301         jQuery.sap.log.warning(sMessage, sDetails, sComponent);
302         return;
303       }
304       if (typeof console === "object") {
305         console.warn(formatMessage(sMessage, sDetails, sComponent));
306       }
307     }
308   };
309 
310   /**
311    * Makes the given relative URL absolute. URLs containing host and/or protocol
312    * and URLs with an absolute path remain unchanged. The URL is in no way
313    * normalized; the function simply cuts off the file name from the base and
314    * appends the relative URL.
315    *
316    * @param {string} sUrl
317    *   the (possibly server-relative) URL
318    * @param {string} [sBase=location.href]
319    *   the base URL; it <b>must</b> at least be server-absolute
320    * @returns {string}
321    *   the absolute URL
322    * @since 1.2.0
323    */
324   sap.ui2.srvc.absoluteUrl = function (sUrl, sBase) {
325     /*jslint regexp: true */
326 
327     // default base is the page location
328     sBase = sBase || location.href;
329     // base must be absolute
330     if (sBase.indexOf('://') < 0 && sBase.charAt(0) !== '/') {
331       throw new sap.ui2.srvc.Error("Illegal base URL: " + sBase, "sap.ui2.srvc");
332     }
333     // do not change empty or absolute URL
334     if (!sUrl || sUrl.indexOf('://') >= 0 || sUrl.charAt(0) === '/') {
335       return this.addCacheBusterTokenUsingUshellConfig(sUrl);
336     }
337     if (sBase.search(/^([^:]*:)?\/\/[^\/]+$/) < 0) {
338       // not a pure server URL -> cut off the file name
339       sBase = sBase.replace(/\/[^\/]*$/, '');
340     }
341     // append the relative path
342     return this.addCacheBusterTokenUsingUshellConfig(sBase + '/' + sUrl);
343   };
344 
345   /**
346    * Calls the given success handler (a)synchronously. Errors thrown in the success handler are
347    * caught and the error message is reported to the error handler; if an error stack is
348    * available, it is logged.
349    *
350    * @param {function ()} fnSuccess
351    *   no-args success handler
352    * @param {function (string)} [fnFailure]
353    *   error handler, taking an error message; MUST NOT throw any error itself!
354    * @param {boolean} [bAsync=false]
355    *   whether the call shall be asynchronously
356    * @since 1.2.0
357    */
358   sap.ui2.srvc.call = function (fnSuccess, fnFailure, bAsync) {
359     // see also redundant declaration in sap.ushell.utils.call which has to be in sync
360     var sMessage;
361 
362     if (bAsync) {
363       setTimeout(function () {
364         sap.ui2.srvc.call(fnSuccess, fnFailure, false);
365       }, 0);
366       return;
367     }
368 
369     try {
370       fnSuccess();
371     } catch (e) {
372       sMessage = e.message || e.toString();
373       sap.ui2.srvc.log.error("Call to success handler failed: " + sMessage,
374           e.stack, //may be undefined: only supported in Chrome, FF; as of now not in Safari, IE
375           "sap.ui2.srvc");
376       if (fnFailure) {
377         fnFailure(sMessage);
378       }
379     }
380   };
381 
382   /**
383    * GETs the given URL (as XML if indicated) and hands it to the given
384    * success handler. As this is a root cause for asynchronous behaviour,
385    * special precautions are taken: errors thrown in the success handler are
386    * caught and reported to the error handler!
387    *
388    * @param {string} sUrl
389    *   URL for GET request
390    * @param {boolean} bXml
391    *   whether the handler expects XML instead of plain text
392    * @param {function (DOMDocument|string)} fnSuccess
393    *   success handler, taking a DOM document or text string
394    * @param {function (string, string)} fnFailure
395    *   error handler, taking an error message and (if http status is not OK) the GET response as
396    *   text; MUST NOT throw any error itself!
397    * @param {object} [oXHR]
398    *   the XMLHttpRequest object which may be predefined (e.g. by setting request headers). If
399    *   <code>undefined</code>, a new XMLHttpRequest object is created.
400    * @param {boolean} [bCache]
401    *   whether the response is cached for further calls (since 1.8.1). XML responses cannot be
402    *   cached. An <code>sap.ui2.srvc.Error</code> is thrown if both <code>bXml</code> and
403    *   <code>bCache</code> are set to <code>true</code>.
404    * @since 1.2.0
405    */
406   sap.ui2.srvc.get = function (sUrl, bXml, fnSuccess, fnFailure, oXHR, bCache) {
407     if (typeof fnSuccess !== "function") {
408       throw new sap.ui2.srvc.Error("Missing success handler", "sap.ui2.srvc");
409     }
410     if (typeof fnFailure !== "function") {
411       throw new sap.ui2.srvc.Error("Missing error handler", "sap.ui2.srvc");
412     }
413     if (bXml && bCache) {
414       throw new sap.ui2.srvc.Error("Caching of XML responses not supported", "sap.ui2.srvc");
415     }
416     if (typeof sap.ui2.srvc.addCacheBusterTokenUsingUshellConfig === "function") {
417       sUrl = sap.ui2.srvc.addCacheBusterTokenUsingUshellConfig(sUrl);
418     }
419     oXHR = oXHR || new XMLHttpRequest();
420 
421     /**
422      * @private
423      */
424     oXHR.onreadystatechange = function () {
425       var oResult, oXml;
426       // Note: "this" refers to oXHR according to W3C
427       if (this.readyState !== /*DONE*/4) {
428         return; // not yet DONE
429       }
430       sap.ui2.srvc.get.pending -= 1;
431       if (this.status !== /*OK*/200) {
432         // HTTP status not OK
433         sap.ui2.srvc.log.error("Error " + this.status + " in response for URL " + sUrl,
434           null, "sap.ui2.srvc");
435         fnFailure(sUrl + ": " + this.status + " " + this.statusText, this.responseText);
436         return;
437       }
438 
439       sap.ui2.srvc.log.debug("Received response for URL " + sUrl, null, "sap.ui2.srvc");
440       if (bXml) {
441         oXml = this.responseXML;
442         if (oXml === null || !oXml.documentElement) {
443           // in FF it is null, in IE it is a document with only an error message
444           fnFailure(sUrl + ": no valid XML");
445           return;
446         }
447         oResult = oXml;
448       } else {
449         oResult = this.responseText;
450         if (bCache) {
451           oCache.put(sUrl, oResult);
452         }
453       }
454       sap.ui2.srvc.call(fnSuccess.bind(null, oResult), fnFailure);
455     };
456 
457     if (!bXml && oCache.containsKey(sUrl)) {
458       sap.ui2.srvc.log.debug("Return cached response for URL " + sUrl, null, "sap.ui2.srvc");
459       sap.ui2.srvc.call(fnSuccess.bind(null, oCache.get(sUrl)), fnFailure);
460     } else {
461       try {
462         oXHR.open("GET", sUrl, /*asynchronously*/true);
463         oXHR.send();
464         sap.ui2.srvc.get.pending += 1;
465         sap.ui2.srvc.log.debug("Sent request to URL " + sUrl, null, "sap.ui2.srvc");
466       } catch (e) {
467         sap.ui2.srvc.log.error("Error '" + (e.message || e) + "' in request to URL " + sUrl,
468           null, "sap.ui2.srvc");
469         throw e;
470       }
471     }
472   };
473 
474   /**
475    * Gets an URL and adds the given cache buster token to it if no other token is already
476    * contained. In case the URL is no valid URL the token is not added.
477    *
478    * @param {string} sUrl
479    *  e.g. "/sap/bc/ui5_ui5/application/path"
480    *  URL to be changed
481    * @param {regEx} oPattern
482    *  e.g /^\/sap\/bc\/ui5_ui5\//
483    *  RegExp to determine if sUrl matches and needs to be extended by the cache buster token
484    *  sToken
485    * @param {string} sReplacement
486    *  e.g. "/sap/bc/ui5_ui5/[CacheBusterToken]/"
487    *  The part of sUrl matched by oPattern will be exchanged by this. Before that is done sToken
488    *  is inserted in sReplacement at the position indicated by [CacheBusterToken]
489    *  The replacement may refer to capture groups of oPattern
490    * @param {string} sToken
491    *  e.g. "~201412132350000~"
492    *  token to be inserted in sUrl. It will be inserted as indicated in the final constructed URL!
493    * @returns {string}
494    *  - if sUrl did not matched oPattern: unchanged sUrl
495    *  - if sUrl matched oPattern: sUrl enhanced with sToken,
496    *         e.g. "/sap/bc/ui5_ui5/~201412132350000~/application/path"
497    *
498    * @private
499    */
500   sap.ui2.srvc.addCacheBusterToken = function (sUrl, oPattern, sReplacement, sToken) {
501     if (oPattern.test(sUrl)) { //url matches the pattern
502       sUrl = sUrl.replace(oPattern, sReplacement);
503       //replace the token placeholder globally in the final url (!)
504       // (also allow the token to be added elsewhere)
505       sUrl = sUrl.replace(/\[CacheBusterToken\]/g, sToken);
506     }
507     return sUrl;
508   };
509 
510   /**
511    * Removes a cache buster token (if available) of an Url and normalizes the url afterwards
512    * @param {string} sUrl
513    * @returns {string}
514    *   normalized url (without a cache buster token)
515    * @since 1.28.1
516    *
517    * @private
518    */
519   sap.ui2.srvc.removeCBAndNormalizeUrl = function (sUrl) {
520     var oUrl,
521       sUrlModified = "/";
522 
523     if (typeof sUrl !== "string" || sUrl === "") {
524       return sUrl;
525     }
526 
527     // TODO URI may not be defined in use cases without UI5
528     oUrl = new URI(sUrl);
529     if (/\/~[\w\-]+~[A-Z0-9]?\//.test(sUrl)) {
530       // Url contains a cache buster token
531       // 1) Removing the cache buster token
532       // 1.1) Splitting the url into its path segments
533       // 1.2) Removing the segment representing the cache buster token
534       oUrl.segment().forEach(function (sSegment) {
535         if (!(/~[\w\-]+~[A-Z0-9]?/.test(sSegment))) {
536           // Building url
537           sUrlModified += sSegment + "/";
538         }
539       });
540 
541       // Removing the last slash of the built url
542       if (sUrlModified.charAt(sUrlModified.length - 1) === "/") {
543         sUrlModified = sUrlModified.substr(0, sUrlModified.length - 1);
544       }
545     } else {
546       sUrlModified = sUrl;
547     }
548 
549     // Normalizing url
550     oUrl = new URI(sUrlModified);
551     oUrl.normalizePathname();
552 
553     return oUrl.path();
554   };
555 
556   /**
557    * Gets an URL and adds the given cache buster token to it if no other token is already
558    * contained. The rules to be applied are coming from the ushell configuration:
559    *  sap-ushell-config.cacheBusting.patterns
560    * The rules are applied by there order property (lowest first) and the modified URL is returned
561    * as soon as the first rule matched.
562    *
563    * @param {string} sUrl
564    *  e.g. "/sap/bc/ui5_ui5/application/path"
565    *  URL to be changed
566    * @returns {string}
567    *  - if sUrl already contained a cache buster token (e.g. ~00000~): unchanged sUrl
568    *  - if sUrl did not match any pattern: unchanged sUrl
569    *  - if sUrl matched pattern: sUrl enhanced with sToken,
570    *         e.g. "/sap/bc/ui5_ui5/~201412132350000~/application/path"
571    *  - if the modified sUrl (normalized and cache buster token was removed)
572    *    is found as an attribute of the config
573    *    (window["sap-ushell-config"].cacheBusting.urls),
574    *    the cache buster token which is defined as the value of this attribute
575    *    is going to be returned.
576    *
577    * @private
578    */
579   sap.ui2.srvc.addCacheBusterTokenUsingUshellConfig = function (sUrl) {
580     //TODO move to sap.ushell.utils
581     var oCacheBusting = window["sap-ushell-config"] &&
582         window["sap-ushell-config"].cacheBusting,
583       oPatterns = oCacheBusting && oCacheBusting.patterns,
584       sCacheBusterUrl = sUrl,
585       aParameterMap = [],
586       sSapUshellNoCb,
587       aRules = [];
588 
589     aParameterMap = sap.ui2.srvc.getParameterMap();
590     sSapUshellNoCb = aParameterMap["sap-ushell-nocb"] && aParameterMap["sap-ushell-nocb"][0];
591 
592     // When URL disables Cache Busting return URL without cache busting token
593     if (sSapUshellNoCb === 'true' || sSapUshellNoCb === 'X') {
594       return sUrl;
595     }
596 
597     // don't continue if the string is empty or a token is already present,
598     // either as token (e.g.: /~0123_-Abc~/)
599     // this case happens during navigation, because this method is both called from
600     // NavTargetResolution service as well as from the stubbed jQuery.sap.registerModulePath method
601     // or as =~xxxxxx~
602     // syntax for application cache-buster contains now an additional scope qualifier that can be
603     // either empty, "R" for resource, "5" for UI5 app, "W" for web app and "C"  for custom
604     // see ABAP class /UI5/CL_UI5_APP_HTTP_HANDLER for details
605     if (!oCacheBusting
606             || typeof sUrl !== "string"
607             || sUrl === ""
608             || /\/~[\w\-]+~[A-Z0-9]?\//.test(sUrl)      // matches intermediate segment with cb-token
609             || /\/~[\w\-]+~[A-Z0-9]?$/.test(sUrl)) {    // matches last segment with cb-token (no trailing slash)
610       return sUrl;
611     }
612 
613     if (oCacheBusting && oCacheBusting.urls) {
614       // Removing the last slash of the input url
615       if (sUrl.charAt(sUrl.length - 1) === "/") {
616         sUrl = sUrl.substr(0, sUrl.length - 1);
617       }
618       // Config contains the modified url (without a slash at the end)
619       if (oCacheBusting.urls.hasOwnProperty(sUrl)) {
620         return sUrl + "/" + oCacheBusting.urls[sUrl].cacheBusterToken;
621       }
622       // Config contains the modified url (having a slash at the end)
623       if (oCacheBusting.urls.hasOwnProperty(sUrl + "/")) {
624         return sUrl + "/" + oCacheBusting.urls[sUrl + "/"].cacheBusterToken;
625       }
626     }
627 
628     if (!oPatterns) {
629       return sUrl;
630     }
631 
632     // put rules in aRules and sort them by oRule.order
633     Object.keys(oPatterns).forEach(function (sPattern) {
634       if (oPatterns.hasOwnProperty(sPattern)) {
635         var oRule = oPatterns[sPattern];
636         // the property name is the pattern to be used, copy it to the object itself for later
637         oRule.pattern = new RegExp(sPattern);
638         aRules.push(oRule);
639       }
640     });
641     aRules.sort(function (oRule1, oRule2) { return oRule1.order - oRule2.order; });
642 
643     // apply rules
644     aRules.every(function (oRule) { // use every to be able to break
645       if (oRule.pattern.test(sUrl)) {
646         if (!oRule.cacheBusterToken) {
647           oRule.cacheBusterToken = oCacheBusting.cacheBusterToken;
648         }
649 
650         //url matches the pattern, note that this is not redundant
651         //one can define patterns without a replacement to match and end the matching process!
652         sCacheBusterUrl = sap.ui2.srvc.addCacheBusterToken(sUrl, oRule.pattern, oRule.replacement,
653           oRule.cacheBusterToken);
654         // break as soon as first rule matches (irrespective of alteration)
655         return false;
656       }
657       return true;
658     });
659 
660     return sCacheBusterUrl;
661   };
662 
663   /**
664    * Clear cache for GET requests.
665    *
666    * @since 1.8.1
667    */
668   sap.ui2.srvc.get.clearCache = function () {
669     oCache = new sap.ui2.srvc.Map();
670   };
671 
672   /**
673    * Number of pending XHR requests.
674    *
675    * @type {number}
676    */
677   sap.ui2.srvc.get.pending = 0;
678 
679   /**
680    * Gets the device's form factor. Based on <code>sap.ui.Device.system</code> from SAPUI5.
681    * @returns {string}
682    *   the device's form factor ("desktop", "tablet" or "phone")
683    * @since 1.19.1
684    */
685   sap.ui2.srvc.getFormFactor = function () {
686    // see also redundant declaration in sap.ushell.utils.getFormFactor which has to be in sync
687     var oSystem = sap.ui.Device.system;
688 
689     return oSystem.desktop ? oSystem.SYSTEMTYPE.DESKTOP :
690         (oSystem.tablet ? oSystem.SYSTEMTYPE.TABLET :
691             (oSystem.phone ? oSystem.SYSTEMTYPE.PHONE : undefined));
692   };
693 
694   /**
695    * Returns a map of all search parameters present in the given search string
696    * or this window's current URL. To be precise, <code>location.search</code>
697    * is used as a default and any given search string must use the same syntax
698    * (start with a "?" and not include a "#").
699    *
700    * @param {string} [sSearchString=location.search]
701    *   search string starting with a "?" (unless empty) and not including a "#"
702    * @returns {object}
703    *   a <code>map<string, string[]></code> from key to array of values
704    * @since 1.2.0
705    *
706    * @see <a href="http://java.sun.com/javaee/5/docs/api/javax/servlet/ServletRequest.html#getParameterMap()">
707    * javax.servlet.ServletRequest#getParameterMap()</a>
708    * @see <a href="https://sapui5.hana.ondemand.com/sdk/docs/api/symbols/jQuery.sap.util.UriParameters.html">
709    * Interface jQuery.sap.util.UriParameters</a>
710    */
711   sap.ui2.srvc.getParameterMap = function (sSearchString) {
712     var i,
713       n,
714       mResult = {},
715       sKey,
716       sValue,
717       iIndexOfEquals,
718       aKeyValuePairs,
719       // Note: location.search starts with "?" if not empty
720       sSearch = arguments.length > 0 ? sSearchString : location.search;
721 
722     if (sSearch && sSearch.charAt(0) !== "?") {
723       throw new sap.ui2.srvc.Error("Illegal search string " + sSearch, "sap.ui2.srvc");
724     }
725     if (!sSearch || sSearch === "?") {
726       return {}; // Note: split("") would return [""]
727     }
728 
729     // Note: W3C recommends that servers support ";" as well as "&"
730     //       (http://www.w3.org/TR/1999/REC-html401-19991224/appendix/notes.html#h-B.2.2)
731     // http://unixpapa.com/js/querystring.html advocates this on the client-side also!
732     aKeyValuePairs = sSearch.substring(1).replace(/\+/g, ' ').split(/[&;]/);
733 
734     for (i = 0, n = aKeyValuePairs.length; i < n; i += 1) {
735       // decode key/value pair at first "=" character
736       sKey = aKeyValuePairs[i];
737       sValue = ""; // Note: empty value may be omitted altogether
738       iIndexOfEquals = sKey.indexOf("=");
739       if (iIndexOfEquals >= 0) {
740         sValue = sKey.slice(iIndexOfEquals + 1);
741         sValue = decodeURIComponent(sValue);
742         sKey = sKey.slice(0, iIndexOfEquals);
743       }
744       sKey = decodeURIComponent(sKey);
745 
746       // map key to value(s)
747       // Note: beware of inherited functions!
748       if (!Object.prototype.hasOwnProperty.call(mResult, sKey)) {
749         mResult[sKey] = [];
750       }
751       mResult[sKey].push(sValue);
752     }
753 
754     return mResult;
755   };
756 
757   /**
758    * Returns the value of the given URL's GET parameter with the given name, properly decoded.
759    * Returns "" if no such parameter can be found.
760    *
761    * @param {string} sUrl
762    *   any URL
763    * @param {string} sName
764    *   the name of the GET parameter we are looking for
765    * @returns {string}
766    *   the parameter value, properly decoded
767    *
768    * @private
769    * @since 1.17.0
770    */
771   sap.ui2.srvc.getParameterValue = function (sUrl, sName) {
772     var oParameterMap, iQueryIndex;
773 
774     if (typeof sName !== "string") {
775       // avoid surprises when sName would later be converted into a string
776       throw new sap.ui2.srvc.Error("Missing parameter name", "sap.ui2.srvc");
777     }
778 
779     sUrl = sUrl.split('#')[0];
780     iQueryIndex = sUrl.indexOf("?");
781     if (iQueryIndex >= 0) {
782       oParameterMap = sap.ui2.srvc.getParameterMap(sUrl.slice(iQueryIndex));
783       if (oParameterMap[sName]) {
784         return oParameterMap[sName][0];
785       }
786     }
787     return "";
788   };
789 
790   /**
791    * Tells whether the given value is an array.
792    *
793    * @param {object} o
794    *   any value
795    * @returns {boolean}
796    *   <code>true</code> if and only if the given value is an array
797    * @since 1.2.0
798    */
799   sap.ui2.srvc.isArray = function (o) {
800     // see Crockford page 61
801     return Object.prototype.toString.apply(o) === '[object Array]';
802   };
803 
804   /**
805    * Parses the given XML string and returns it as a document.
806    *
807    * @param {string} sXml
808    *   the XML
809    * @returns {DOMDocument}
810    *   a DOM document, or <code>null</code> in case of missing or empty XML string
811    * @throws {Error}
812    *   in case of invalid XML string
813    * @since 1.2.0
814    */
815   sap.ui2.srvc.parseXml = function (sXml) {
816     var oXml;
817     if (!sXml || typeof sXml !== "string") {
818       return null;
819     }
820     oXml = new DOMParser().parseFromString(sXml, "text/xml");
821     if (oXml.getElementsByTagName("parsererror").length) { // Chrome, Firefox
822       throw new sap.ui2.srvc.Error("Invalid XML: " + sXml, "sap.ui2.srvc");
823     }
824     return oXml;
825   };
826 
827   /**
828    * Serves as a marker for functions that are to be exposed in QUnit tests. Calls to this function
829    * are expected to be placed directly before the named function declaration (even <b>after</b>
830    * the JSDoc). The function itself does nothing.
831    *
832    * @param {object} o
833    *   the object to which this function will be attached in tests; must not be <code>this</code>
834    *   (use <code>that</code> instead)
835    * @since 1.3.0
836    */
837   sap.ui2.srvc.testPublishAt = function (o) {
838     // intentionally left blank
839   };
840 
841   // "public classes" **********************************************************
842 
843   if (sap.ui2.srvc.Error === undefined) {
844     sap.ui2.srvc.Error = function (sMessage, sComponent) {
845       // see also redundant declaration in error.js which has to be in sync
846       var that = new Error(sMessage); // reuse Error constructor to benefit from it (e.g. stack)
847       that.name = "sap.ui2.srvc.Error";
848       sap.ui2.srvc.log.error(sMessage, null, sComponent);
849       return that;
850     };
851     // to avoid (new Error()) instanceof sap.ui2.srvc.Error === true we do not set the prototype,
852     // we also tolerate that (new sap.ui2.srvc.Error()) instanceof sap.ui2.srvc.Error === false now
853     // sap.ui2.srvc.Error.prototype = Error.prototype;
854   }
855 
856   /**
857    * Creates an empty map.
858    * @class A mapping from arbitrary string(!) keys (including "get" or "hasOwnProperty") to values
859    * of any type.
860    * @since 1.5.0
861    */
862   sap.ui2.srvc.Map = function () {
863     this.entries = {};
864   };
865 
866   /**
867    * Associates the specified value with the specified key in this map. If the map previously
868    * contained a