/*!
 * ${copyright}
 */
/*eslint-disable max-len */
/**
 * OData-based DataBinding
 *
 * @namespace
 * @name sap.ui.model.odata
 * @public
 */

// Provides class sap.ui.model.odata.ODataModel
sap.ui.define([
	"./CountMode",
	"./ODataContextBinding",
	"./ODataListBinding",
	"./ODataMetadata",
	"./ODataPropertyBinding",
	"./ODataTreeBinding",
	"./ODataUtils",
	"sap/base/assert",
	"sap/base/Log",
	"sap/base/security/encodeURL",
	"sap/base/util/each",
	"sap/base/util/extend",
	"sap/base/util/isEmptyObject",
	"sap/base/util/isPlainObject",
	"sap/base/util/merge",
	"sap/base/util/uid",
	"sap/ui/model/BindingMode",
	"sap/ui/model/Context",
	"sap/ui/model/FilterProcessor",
	"sap/ui/model/Model",
	"sap/ui/model/odata/ODataAnnotations",
	"sap/ui/model/odata/ODataMetaModel",
	"sap/ui/thirdparty/datajs",
	"sap/ui/thirdparty/URI"
], function(CountMode, ODataContextBinding, ODataListBinding, ODataMetadata, ODataPropertyBinding,
		ODataTreeBinding, ODataUtils, assert, Log, encodeURL, each, extend, isEmptyObject,
		isPlainObject, merge, uid, BindingMode, Context, FilterProcessor, Model, ODataAnnotations,
		ODataMetaModel, OData, URI) {
	"use strict";

	/**
	 * Constructor for a new ODataModel.
	 *
	 * @param {string} [sServiceUrl] base uri of the service to request data from; additional URL parameters appended here will be appended to every request
	 * 								can be passed with the mParameters object as well: [mParameters.serviceUrl] A serviceURl is required!
	 * @param {object} [mParameters] (optional) a map which contains the following parameter properties:
	 * @param {boolean} [mParameters.json] if set true request payloads will be JSON, XML for false (default = false),
	 * @param {string} [mParameters.user] <b>Deprecated</b> for security reasons. Use strong server
	 *   side authentication instead. UserID for the service.
	 * @param {string} [mParameters.password] <b>Deprecated</b> for security reasons. Use strong
	 *   server side authentication instead. Password for the service.
	 * @param {Object<string,string>} [mParameters.headers] a map of custom headers like {"myHeader":"myHeaderValue",...},
	 * @param {boolean} [mParameters.tokenHandling] enable/disable XCSRF-Token handling (default = true),
	 * @param {boolean} [mParameters.withCredentials] experimental - true when user credentials are to be included in a cross-origin request. Please note that this works only if all requests are asynchronous.
	 * @param {object} [mParameters.loadMetadataAsync] (optional) determined if the service metadata request is sent synchronous or asynchronous. Default is false.
	 * @param [mParameters.maxDataServiceVersion] (default = '2.0') please use the following string format e.g. '2.0' or '3.0'.
	 * 									OData version supported by the ODataModel: '2.0',
	 * @param {boolean} [mParameters.useBatch] when true all requests will be sent in batch requests (default = false),
	 * @param {boolean} [mParameters.refreshAfterChange] enable/disable automatic refresh after change operations: default = true,
	 * @param  {string|string[]} [mParameters.annotationURI] The URL (or an array of URLs) from which the annotation metadata should be loaded,
	 * @param {boolean} [mParameters.loadAnnotationsJoined] Whether or not to fire the metadataLoaded-event only after annotations have been loaded as well,
	 * @param {Object<string,string>} [mParameters.serviceUrlParams] map of URL parameters - these parameters will be attached to all requests,
	 * @param {Object<string,string>} [mParameters.metadataUrlParams] map of URL parameters for metadata requests - only attached to $metadata request.
	 * @param {string} [mParameters.defaultCountMode] sets the default count mode for the model. If not set, sap.ui.model.odata.CountMode.Both is used.
	 * @param {Object<string,string>} [mParameters.metadataNamespaces] a map of namespaces (name => URI) used for parsing the service metadata.
	 * @param {boolean} [mParameters.skipMetadataAnnotationParsing] Whether to skip the automated loading of annotations from the metadata document. Loading annotations from metadata does not have any effects (except the lost performance by invoking the parser) if there are not annotations inside the metadata document
	 *
	 * @class
	 * Model implementation for OData format
	 *
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @public
	 * @deprecated As of version 1.48, please use {@link sap.ui.model.odata.v2.ODataModel} instead.
	 * @alias sap.ui.model.odata.ODataModel
	 * @extends sap.ui.model.Model
	 */
	var ODataModel = Model.extend("sap.ui.model.odata.ODataModel", /** @lends sap.ui.model.odata.ODataModel.prototype */ {

		constructor : function(sServiceUrl, bJSON, sUser, sPassword, mHeaders, bTokenHandling, bWithCredentials, bLoadMetadataAsync) {
			Model.apply(this, arguments);

			var bUseBatch,
				bRefreshAfterChange,
				sMaxDataServiceVersion,
				sAnnotationURI = null,
				bLoadAnnotationsJoined,
				mMetadataNamespaces,
				sDefaultCountMode,
				mServiceUrlParams,
				mMetadataUrlParams,
				bSkipMetadataAnnotationParsing,
				that = this;

			if (typeof (sServiceUrl) === "object") {
				bJSON = sServiceUrl;
				sServiceUrl = bJSON.serviceUrl;
			}

			if (typeof bJSON === "object") {
				sUser = bJSON.user;
				sPassword = bJSON.password;
				mHeaders = bJSON.headers;
				bTokenHandling = bJSON.tokenHandling;
				bLoadMetadataAsync = bJSON.loadMetadataAsync;
				bWithCredentials = bJSON.withCredentials;
				sMaxDataServiceVersion = bJSON.maxDataServiceVersion;
				bUseBatch = bJSON.useBatch;
				bRefreshAfterChange = bJSON.refreshAfterChange;
				sAnnotationURI = bJSON.annotationURI;
				bLoadAnnotationsJoined = bJSON.loadAnnotationsJoined;
				sDefaultCountMode = bJSON.defaultCountMode;
				mMetadataNamespaces = bJSON.metadataNamespaces;
				mServiceUrlParams = bJSON.serviceUrlParams;
				mMetadataUrlParams = bJSON.metadataUrlParams;
				bSkipMetadataAnnotationParsing = bJSON.skipMetadataAnnotationParsing;
				bJSON = bJSON.json;
			}

			this.oServiceData = {};
			this.sDefaultBindingMode = BindingMode.OneWay;
			this.mSupportedBindingModes = {"OneWay": true, "OneTime": true, "TwoWay":true};
			this.mUnsupportedFilterOperators = {"Any": true, "All": true};
			this.bCountSupported = true;
			this.bJSON = bJSON;
			this.bCache = true;
			this.aPendingRequestHandles = [];
			this.oRequestQueue = {};
			this.aBatchOperations = [];
			this.oHandler = undefined;
			this.bTokenHandling = bTokenHandling !== false;
			this.bWithCredentials = bWithCredentials === true;
			this.bUseBatch = bUseBatch === true;
			this.bRefreshAfterChange = bRefreshAfterChange !== false;
			this.sMaxDataServiceVersion = sMaxDataServiceVersion;
			this.bLoadMetadataAsync = !!bLoadMetadataAsync;
			this.bLoadAnnotationsJoined = bLoadAnnotationsJoined === undefined ? true : bLoadAnnotationsJoined;
			this.sAnnotationURI = sAnnotationURI;
			this.sDefaultCountMode = sDefaultCountMode || CountMode.Both;
			this.oMetadataLoadEvent = null;
			this.oMetadataFailedEvent = null;
			this.bSkipMetadataAnnotationParsing = bSkipMetadataAnnotationParsing;

			// prepare variables for request headers, data and metadata
			this.oHeaders = {};
			this.setHeaders(mHeaders);
			this.oData = {};
			this.oMetadata = null;
			this.oAnnotations = null;
			this.aUrlParams = [];

			// determine the service base url and the url parameters
			if (sServiceUrl.indexOf("?") == -1) {
				this.sServiceUrl = sServiceUrl;
			} else {
				var aUrlParts = sServiceUrl.split("?");
				this.sServiceUrl = aUrlParts[0];
				if (aUrlParts[1]) {
					this.aUrlParams.push(aUrlParts[1]);
				}
			}

			if (sap.ui.getCore().getConfiguration().getStatistics()) {
				// add statistics parameter to every request (supported only on Gateway servers)
				this.aUrlParams.push("sap-statistics=true");
			}

			// Remove trailing slash (if any)
			this.sServiceUrl = this.sServiceUrl.replace(/\/$/, "");

			// Get/create service specific data container or create one if it doesn't exist
			var sMetadataUrl = this._createRequestUrl("$metadata", undefined, mMetadataUrlParams);
			if (!ODataModel.mServiceData[sMetadataUrl]) {
				ODataModel.mServiceData[sMetadataUrl] = {};
			}
			this.oServiceData = ODataModel.mServiceData[sMetadataUrl];


			// Get CSRF token, if already available
			if (this.bTokenHandling && this.oServiceData.securityToken) {
				this.oHeaders["x-csrf-token"] = this.oServiceData.securityToken;
			}

			// store user and password
			this.sUser = sUser;
			this.sPassword = sPassword;

			this.oHeaders["Accept-Language"] = sap.ui.getCore().getConfiguration().getLanguageTag();

			if (!this.oServiceData.oMetadata) {
				//create Metadata object
				this.oServiceData.oMetadata = new ODataMetadata(sMetadataUrl, {
					async: this.bLoadMetadataAsync,
					user: this.sUser,
					password: this.sPassword,
					headers: this.mCustomHeaders,
					namespaces: mMetadataNamespaces,
					withCredentials: this.bWithCredentials
				});
			}
			this.oMetadata = this.oServiceData.oMetadata;

			this.pAnnotationsLoaded = this.oMetadata.loaded();

			if (this.sAnnotationURI || !this.bSkipMetadataAnnotationParsing) {
				// Make sure the annotation parser object is already created and can be used by the MetaModel
				var oAnnotations = this._getAnnotationParser();

				if (!this.bSkipMetadataAnnotationParsing) {
					if (!this.bLoadMetadataAsync) {
						//Synchronous metadata load --> metadata should already be available, try to stay synchronous
						// Don't fire additional events for automatic metadata annotations parsing, but if no annotation URL exists, fire the event
						this.addAnnotationXML(this.oMetadata.sMetadataBody, !!this.sAnnotationURI);
					} else {
						this.pAnnotationsLoaded = this.oMetadata.loaded().then(function(bSuppressEvents, mParams) {
							//Don't fire additional events for automatic metadata annotations parsing, but if no annotation URL exists, fire the event
							if (this.bDestroyed) {
								return Promise.reject();
							}
							return this.addAnnotationXML(mParams["metadataString"], bSuppressEvents);
						}.bind(this, !!this.sAnnotationURI));
					}
				}

				if (this.sAnnotationURI) {
					if (this.bLoadMetadataAsync) {
						this.pAnnotationsLoaded = this.pAnnotationsLoaded
							.then(oAnnotations.addUrl.bind(oAnnotations, this.sAnnotationURI));
					} else {
						this.pAnnotationsLoaded = Promise.all([
							this.pAnnotationsLoaded,
							oAnnotations.addUrl(this.sAnnotationURI)
						]);
					}
				}
			}

			if (mServiceUrlParams) {
				// new URL params used -> add to ones from sServiceUrl
				// do this after the Metadata request is created to not put the serviceUrlParams on this one
				this.aUrlParams = this.aUrlParams.concat(ODataUtils._createUrlParamsArray(mServiceUrlParams));
			}


			this.onMetadataLoaded = function(oEvent){
				that._initializeMetadata();
				that.initialize();
			};
			this.onMetadataFailed = function(oEvent) {
				that.fireMetadataFailed(oEvent.getParameters());
			};

			if (!this.oMetadata.isLoaded()) {
				this.oMetadata.attachLoaded(this.onMetadataLoaded);
				this.oMetadata.attachFailed(this.onMetadataFailed);
			}
			if (this.oMetadata.isFailed()){
				this.refreshMetadata();
			}
			if (this.oMetadata.isLoaded()) {
				this._initializeMetadata(true);
			}


			// set the header for the accepted content types
			if (this.bJSON) {
				if (this.sMaxDataServiceVersion === "3.0") {
					this.oHeaders["Accept"] = "application/json;odata=fullmetadata";
				} else {
					this.oHeaders["Accept"] = "application/json";
				}
				this.oHandler = OData.jsonHandler;
			} else {
				this.oHeaders["Accept"] = "application/atom+xml,application/atomsvc+xml,application/xml";
				this.oHandler = OData.atomHandler;
			}


			// the max version number the client can accept in a response
			this.oHeaders["MaxDataServiceVersion"] = "2.0";
			if (this.sMaxDataServiceVersion) {
				this.oHeaders["MaxDataServiceVersion"] = this.sMaxDataServiceVersion;
			}

			// set version to 2.0 because 1.0 does not support e.g. skip/top, inlinecount...
			// states the version of the Open Data Protocol used by the client to generate the request.
			this.oHeaders["DataServiceVersion"] = "2.0";

		},
		metadata : {

			publicMethods : ["create", "remove", "update", "submitChanges", "getServiceMetadata", "read", "hasPendingChanges", "refresh", "refreshMetadata", "resetChanges",
							 "isCountSupported", "setCountSupported", "setDefaultCountMode", "getDefaultCountMode", "forceNoCache", "setProperty",
							 "getSecurityToken", "refreshSecurityToken", "setHeaders", "getHeaders", "setUseBatch"]
		}
	});

	//
	ODataModel.M_EVENTS = {
			RejectChange: "rejectChange",
			/**
			 * Event is fired if the metadata document was successfully loaded
			 */
			MetadataLoaded: "metadataLoaded",

			/**
			 * Event is fired if the metadata document has failed to load
			 */
			MetadataFailed: "metadataFailed",

			/**
			 * Event is fired if the annotations document was successfully loaded
			 */
			AnnotationsLoaded: "annotationsLoaded",

			/**
			 * Event is fired if the annotations document has failed to load
			 */
			AnnotationsFailed: "annotationsFailed"

	};


	// Keep a map of service specific data, which can be shared across different model instances
	// on the same OData service
	ODataModel.mServiceData = {
	};

	ODataModel.prototype.fireRejectChange = function(mParameters) {
		this.fireEvent("rejectChange", mParameters);
		return this;
	};

	ODataModel.prototype.attachRejectChange = function(oData, fnFunction, oListener) {
		this.attachEvent("rejectChange", oData, fnFunction, oListener);
		return this;
	};

	ODataModel.prototype.detachRejectChange = function(fnFunction, oListener) {
		this.detachEvent("rejectChange", fnFunction, oListener);
		return this;
	};

	/**
	 * Fires the "metadataLoaded" event if the metadata and the annotations are loaded.
	 *
	 * @param {boolean} bDelayEvent
	 *   Whether the <code>fireMetadataLoaded</code>-event should be fired with a delay
	 * @private
	 */
	ODataModel.prototype._initializeMetadata = function(bDelayEvent) {
		var that = this;
		this.bUseBatch = this.bUseBatch || this.oMetadata.getUseBatch();
		var doFire = function(bDelay){
			if (bDelay) {
				that.metadataLoadEvent = setTimeout(doFire.bind(that), 0);
			} else if (that.oMetadata) {
				that.fireMetadataLoaded({metadata: that.oMetadata});
				Log.debug("ODataModel fired metadataloaded");
			}
		};

		if (this.sAnnotationURI && this.bLoadAnnotationsJoined) {
			// In case of joined loading, wait for the annotations before firing the event
			// This is also tested in the fireMetadataLoaded-method and no event is fired in case
			// of joined loading.
			if (this.oAnnotations && (this.oAnnotations.bInitialized || this.oAnnotations.isFailed())) {
				doFire(!this.bLoadMetadataAsync);
			} else {
				this.oAnnotations.attachEventOnce("loaded", function() {
					doFire(true);
				});
			}
		} else {
			// In case of synchronous or asynchronous non-joined loading, or if no annotations are
			// loaded at all, the events are fired individually
				doFire(bDelayEvent);
		}
	};

	/**
	 * The <code>annotationsLoaded</code> event is fired after the annotations document was successfully loaded.
	 *
	 * @name sap.ui.model.odata.ODataModel#annotationsLoaded
	 * @event
	 * @param {sap.ui.base.Event} oEvent
	 * @public
	 */

	/**
	 * Fires event {@link #event:annotationsLoaded annotationsLoaded} to attached listeners.
	 *
	 * @param {object} [oParameters] Parameters to pass along with the event
	 * @param {sap.ui.model.odata.ODataAnnotations} [oParameters.annotations]  the annotations object.
	 *
	 * @return {this} <code>this</code> to allow method chaining
	 * @protected
	 */
	ODataModel.prototype.fireAnnotationsLoaded = function(oParameters) {
		if (!this.bLoadMetadataAsync) {
			setTimeout(this.fireEvent.bind(this, "annotationsLoaded", oParameters), 0);
		} else {
			this.fireEvent("annotationsLoaded", oParameters);
		}
		return this;
	};

	/**
	 * Attaches event handler <code>fnFunction</code> to the {@link #event:annotationsLoaded annotationsLoaded} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * @param {object}
	 *            [oData] An application-specific payload object that will be passed to the event handler
	 *            along with the event object when firing the event
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object to call the event handler with. Defaults to this
	 *            <code>sap.ui.model.odata.ODataModel</code> itself
	 *
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.attachAnnotationsLoaded = function(oData, fnFunction, oListener) {
		this.attachEvent("annotationsLoaded", oData, fnFunction, oListener);
		return this;
	};

	/**
	 * Detaches event handler <code>fnFunction</code> from the {@link #event:annotationsLoaded annotationsLoaded} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object on which the given function had to be called
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.detachAnnotationsLoaded = function(fnFunction, oListener) {
		this.detachEvent("annotationsLoaded", fnFunction, oListener);
		return this;
	};

	/**
	 * The <code>annotationsFailed</code> event is fired when loading the annotations document failed.
	 *
	 * @name sap.ui.model.odata.ODataModel#annotationsFailed
	 * @event
	 * @param {sap.ui.base.Event} oEvent
	 * @public
	 */

	/**
	 * Fires event {@link #event:annotationsFailed annotationsFailed} to attached listeners.
	 *
	 * @param {object} [oParameters] Parameters to pass along with the event
	 * @param {string} [oParameters.message]  A text that describes the failure.
	 * @param {string} [oParameters.statusCode]  HTTP status code returned by the request (if available)
	 * @param {string} [oParameters.statusText] The status as a text, details not specified, intended only for diagnosis output
	 * @param {string} [oParameters.responseText] Response that has been received for the request ,as a text string
	 *
	 * @return {this} <code>this</code> to allow method chaining
	 * @protected
	 */
	ODataModel.prototype.fireAnnotationsFailed = function(oParameters) {
		if (!this.bLoadMetadataAsync) {
			setTimeout(this.fireEvent.bind(this, "annotationsFailed", oParameters), 0);
		} else {
			this.fireEvent("annotationsFailed", oParameters);
		}
		Log.debug("ODataModel fired annotationsFailed");
		return this;
	};

	/**
	 * Attaches event handler <code>fnFunction</code> to the {@link #event:annotationsFailed annotationsFailed} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * @param {object}
	 *            [oData] An application-specific payload object that will be passed to the event handler
	 *            along with the event object when firing the event
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object to call the event handler with. Defaults to this
	 *            <code>sap.ui.model.odata.ODataModel</code> itself
	 *
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.attachAnnotationsFailed = function(oData, fnFunction, oListener) {
		this.attachEvent("annotationsFailed", oData, fnFunction, oListener);
		return this;
	};

	/**
	 * Detaches event handler <code>fnFunction</code> from the {@link #event:annotationsFailed annotationsFailed} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * The passed function and listener object must match the ones used for event registration.
	 *
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object on which the given function had to be called
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.detachAnnotationsFailed = function(fnFunction, oListener) {
		this.detachEvent("annotationsFailed", fnFunction, oListener);
		return this;
	};

	/**
	 * The <code>metadataLoaded</code> event is fired after the metadata document was successfully loaded.
	 *
	 * @name sap.ui.model.odata.ODataModel#metadataLoaded
	 * @event
	 * @param {sap.ui.base.Event} oEvent
	 * @public
	 */

	/**
	 * Fires event {@link #event:metadataLoaded metadataLoaded} to attached listeners.
	 *
	 * @param {object} [oParameters] Parameters to pass along with the event
	 * @param {sap.ui.model.odata.ODataMetadata} [oParameters.metadata] The metadata object.
	 *
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @protected
	 */
	ODataModel.prototype.fireMetadataLoaded = function(oParameters) {
		this.fireEvent("metadataLoaded", oParameters);
		return this;
	};

	/**
	 * Attaches event handler <code>fnFunction</code> to the {@link #event:metadataLoaded metadataLoaded} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * @param {object}
	 *            [oData] An application-specific payload object that will be passed to the event handler
	 *            along with the event object when firing the event
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object to call the event handler with. Defaults to this
	 *            <code>sap.ui.model.odata.ODataModel</code> itself
	 *
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.attachMetadataLoaded = function(oData, fnFunction, oListener) {
		this.attachEvent("metadataLoaded", oData, fnFunction, oListener);
		return this;
	};

	/**
	 * Detaches event handler <code>fnFunction</code> from the {@link #event:metadataLoaded metadataLoaded} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * The passed function and listener object must match the ones used for event registration.
	 *
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object on which the given function had to be called
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.detachMetadataLoaded = function(fnFunction, oListener) {
		this.detachEvent("metadataLoaded", fnFunction, oListener);
		return this;
	};

	/**
	 * The <code>metadataFailed</code> event is fired when loading the metadata document failed.
	 *
	 * @name sap.ui.model.odata.ODataModel#metadataFailed
	 * @event
	 * @param {sap.ui.base.Event} oEvent
	 * @public
	 */

	/**
	 * Fires event {@link #event:metadataFailed metadataFailed} to attached listeners.
	 *
	 * @param {object} [oParameters] Parameters to pass along with the event
	 * @param {string} [oParameters.message] A text that describes the failure.
	 * @param {string} [oParameters.statusCode] HTTP status code returned by the request (if available)
	 * @param {string} [oParameters.statusText] The status as a text, details not specified, intended only for diagnosis output
	 * @param {string} [oParameters.responseText] Response that has been received for the request ,as a text string
	 *
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @protected
	 */
	ODataModel.prototype.fireMetadataFailed = function(oParameters) {
		this.fireEvent("metadataFailed", oParameters);
		return this;
	};

	/**
	 * Attaches event handler <code>fnFunction</code> to the {@link #event:metadataFailed metadataFailed} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * @param {object}
	 *            [oData] An application-specific payload object that will be passed to the event handler
	 *            along with the event object when firing the event
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object to call the event handler with. Defaults to this
	 *            <code>sap.ui.model.odata.ODataModel</code> itself
	 *
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.attachMetadataFailed = function(oData, fnFunction, oListener) {
		this.attachEvent("metadataFailed", oData, fnFunction, oListener);
		return this;
	};

	/**
	 * Detaches event handler <code>fnFunction</code> from the {@link #event:metadataFailed metadataFailed} event of this
	 * <code>sap.ui.model.odata.ODataModel</code>.
	 *
	 * The passed function and listener object must match the ones used for event registration.
	 *
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object on which the given function had to be called
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	ODataModel.prototype.detachMetadataFailed = function(fnFunction, oListener) {
		this.detachEvent("metadataFailed", fnFunction, oListener);
		return this;
	};

	/**
	 * Refreshes the metadata for model, e.g. in case the first request for metadata has failed.
	 *
	 * @public
	 */
	ODataModel.prototype.refreshMetadata = function(){
		if (this.oMetadata && this.oMetadata.refresh){
			this.oMetadata.refresh();
		}
	};

	/**
	 * Creates a request URL using the supplied parameters.
	 *
	 * @param {string} sPath The path to the property
	 * @param {sap.ui.model.Context} oContext The context of the property
	 * @param {object} oUrlParams Additional query parameters
	 * @param {boolean} bBatch Whether a batch request should be sent
	 * @param {boolean} [bCache=true] Force no caching if false
	 *
	 * @returns {string} The created URL
	 * @private
	 */
	ODataModel.prototype._createRequestUrl = function(sPath, oContext, oUrlParams, bBatch, bCache) {

		// create the url for the service
		var aUrlParams,
			sResolvedPath,
			sUrlParams,
			sUrl = "";

		//we need to handle url params that can be passed from the manual CRUD methods due to compatibility
		if (sPath && sPath.indexOf('?') != -1 ) {
			sUrlParams = sPath.substr(sPath.indexOf('?') + 1);
			sPath = sPath.substr(0, sPath.indexOf('?'));
		}

		sResolvedPath = this._normalizePath(sPath, oContext);

		if (!bBatch) {
			sUrl = this.sServiceUrl + sResolvedPath;
		} else {
			sUrl = sResolvedPath.substr(sResolvedPath.indexOf('/') + 1);
		}

		aUrlParams = ODataUtils._createUrlParamsArray(oUrlParams);

		if (this.aUrlParams) {
			aUrlParams = aUrlParams.concat(this.aUrlParams);
		}
		if (sUrlParams) {
			aUrlParams.push(sUrlParams);
		}
		if (aUrlParams.length > 0) {
			sUrl += "?" + aUrlParams.join("&");
		}
		if (bCache === undefined) {
			bCache = true;
		}
		if (bCache === false) {

			var timeStamp = Date.now();
			// try replacing _= if it is there
			var ret = sUrl.replace( /([?&])_=[^&]*/, "$1_=" + timeStamp );
			// if nothing was replaced, add timestamp to the end
			sUrl = ret + ( ( ret === sUrl ) ? ( /\?/.test( sUrl ) ? "&" : "?" ) + "_=" + timeStamp : "" );
		}

		return sUrl;
	};

	/**
	 * Does a request using the service URL and configuration parameters provided in the model's
	 * constructor and sets the response data into the model. This request is performed
	 * asynchronously.
	 *
	 * @param {string} sPath
	 *   A string containing the path to the data which should be retrieved; the path is appended
	 *   to the <code>sServiceUrl</code> which was specified in the model constructor
	 * @param {string[]} aParams
	 *   Additional query parameters
	 * @param {function} [fnSuccess]
	 *   Callback function which is called when the data has been successfully retrieved and stored
	 *   in the model
	 * @param {function} [fnError]
	 *   Callback function which is called when the request failed
	 * @param {boolean} [bCache=true]
	 *   Force no caching if false
	 * @param {function} [fnHandleUpdate]
	 *   Function to handle an update with
	 * @param {function} [fnCompleted]
	 *   Function to call after the request is completed; called after <code>fnSuccess</code>
	 *
	 * @private
	 */
	ODataModel.prototype._loadData = function(sPath, aParams, fnSuccess, fnError, bCache,
			fnHandleUpdate, fnCompleted){
		// create a request object for the data request
		var oRequestHandle,
			aResults = [],
			sUrl = this._createRequestUrl(sPath, null, aParams, null, bCache || this.bCache),
			oRequest = this._createRequest(sUrl, "GET", true),
			that = this;

		function _handleSuccess(oData, oResponse) {

			var oResultData = oData,
				mChangedEntities = {};

			// no data response
			if (oResponse.statusCode == 204) {
				if (fnSuccess) {
					fnSuccess(null);
				}
				if (fnCompleted) {
					fnCompleted(null);
				}
				that.fireRequestCompleted({url : oRequest.requestUri, type : "GET", async : oRequest.async,
					info: "Accept headers:" + that.oHeaders["Accept"], infoObject : {acceptHeaders: that.oHeaders["Accept"]}, success: true});

				return undefined;
			}

			// no data available
			if (!oResultData) {
				Log.fatal("The following problem occurred: No data was retrieved by service: " + oResponse.requestUri);
				that.fireRequestCompleted({url : oRequest.requestUri, type : "GET", async : oRequest.async,
					info: "Accept headers:" + that.oHeaders["Accept"], infoObject : {acceptHeaders: that.oHeaders["Accept"]},  success: false});
				return false;
			}

			if (that.bUseBatch) { // process batch response
				// check if errors occurred in the batch
				var aErrorResponses = that._getBatchErrors(oData);
				if (aErrorResponses.length > 0) {
					// call handle error with the first error.
					_handleError(aErrorResponses[0]);
					return false;
				}

				if (oResultData.__batchResponses && oResultData.__batchResponses.length > 0) {
					oResultData = oResultData.__batchResponses[0].data;
				} else {
					Log.fatal("The following problem occurred: No data was retrieved by service: " + oResponse.requestUri);
				}
			}

			aResults = aResults.concat(oResultData.results);
			// check if not all requested data was loaded
			if (oResultData.__next) {
				// replace request uri with next uri to retrieve additional data
				var oURI = new URI(oResultData.__next);
				oRequest.requestUri = oURI.absoluteTo(oResponse.requestUri).toString();
				_submit(oRequest);
			} else {
				// all data is read so merge all data
				if (oResultData.results) {
					var vValue, vKey;
					for (vKey in aResults) {
						vValue = aResults[vKey];

						// Prevent never-ending loop
						if (aResults === vValue) {
							continue;
						}

						oResultData.results[vKey] = vValue;
					}
				}

				// broken implementations need this
				if (oResultData.results && !Array.isArray(oResultData.results)) {
					oResultData = oResultData.results;
				}
				// adding the result data to the data object
				that._importData(oResultData, mChangedEntities);

				// reset change key if refresh was triggered on that entry
				if (that.sChangeKey && mChangedEntities) {
					var sEntry = that.sChangeKey.substr(that.sChangeKey.lastIndexOf('/') + 1);
					if (mChangedEntities[sEntry]) {
						delete that.oRequestQueue[that.sChangeKey];
						that.sChangeKey = null;
					}
				}

				if (fnSuccess) {
					fnSuccess(oResultData);
				}
				that.checkUpdate(false, false, mChangedEntities);
				if (fnCompleted) {
					fnCompleted(oResultData);
				}
				that.fireRequestCompleted({url : oRequest.requestUri, type : "GET", async : oRequest.async,
					info: "Accept headers:" + that.oHeaders["Accept"], infoObject : {acceptHeaders: that.oHeaders["Accept"]}, success: true});
			}

			return undefined;
		}

		function _handleError(oError) {
			// If error is a 403 with XSRF token "Required" reset token and retry sending request
			if (that.bTokenHandling && oError.response) {
				var sToken = that._getHeader("x-csrf-token", oError.response.headers);
				if (!oRequest.bTokenReset && oError.response.statusCode == '403' && sToken && sToken.toLowerCase() == "required") {
					that.resetSecurityToken();
					oRequest.bTokenReset = true;
					_submit();
					return;
				}
			}

			var mParameters = that._handleError(oError);

			if (fnError) {
				fnError(oError, oRequestHandle && oRequestHandle.bAborted);
			}

			that.fireRequestCompleted({url : oRequest.requestUri, type : "GET", async : oRequest.async,
				info: "Accept headers:" + that.oHeaders["Accept"], infoObject : {acceptHeaders: that.oHeaders["Accept"]}, success: false, errorobject: mParameters});

			// Don't fire RequestFailed for intentionally aborted requests; fire event if we have no (OData.read fails before handle creation)
			if (!oRequestHandle || !oRequestHandle.bAborted) {
				mParameters.url = oRequest.requestUri;
				that.fireRequestFailed(mParameters);
			}
		}

		/**
		 * this method is used to retrieve all desired data. It triggers additional read requests if the server paging size
		 * permits to return all the requested data. This could only happen for servers with support for OData > 2.0.
		 */
		function _submit(){
			// execute the request and use the metadata if available

			if (that.bUseBatch) {
				that.updateSecurityToken();
				// batch requests only need the path without the service URL
				// extract query of url and combine it with the path...
				var sUriQuery = URI.parse(oRequest.requestUri).query;
				//var sRequestUrl = sPath.replace(/\/$/, ""); // remove trailing slash if any
				//sRequestUrl += sUriQuery ? "?" + sUriQuery : "";
				var sRequestUrl = that._createRequestUrl(sPath, null, sUriQuery, that.bUseBatch);
				oRequest = that._createRequest(sRequestUrl, "GET", true);
				// Make sure requests not requiring a CSRF token don't send one.
				if (that.bTokenHandling) {
					delete oRequest.headers["x-csrf-token"];
				}
				var oBatchRequest = that._createBatchRequest([oRequest],true);
				oRequestHandle = that._request(oBatchRequest, _handleSuccess, _handleError, OData.batchHandler, undefined, that.getServiceMetadata());
			} else {
				oRequestHandle = that._request(oRequest, _handleSuccess, _handleError, that.oHandler, undefined, that.getServiceMetadata());
			}

			if (fnHandleUpdate) {
				// Create a wrapper for the request handle to be able to differentiate
				// between intentionally aborted requests and failed requests
				var oWrappedHandle = {
					abort: function() {
						oRequestHandle.bAborted = true;
						oRequestHandle.abort();
					}
				};
				fnHandleUpdate(oWrappedHandle);
			}
		}

		// Make sure requests not requiring a CSRF token don't send one.
		if (that.bTokenHandling) {
			delete oRequest.headers["x-csrf-token"];
		}
		this.fireRequestSent({url : oRequest.requestUri, type : "GET", async : oRequest.async,
			info: "Accept headers:" + this.oHeaders["Accept"], infoObject : {acceptHeaders: this.oHeaders["Accept"]}});
		_submit();
	};

	/**
	 * Imports the data to the internal storage. Nested entries are processed recursively, moved to
	 * the canonic location and referenced from the parent entry. Keys are collected in a map for
	 * updating bindings.
	 *
	 * @param {object} oData
	 *   The data
	 * @param {Object<string,boolean>} mKeys
	 *   Keys used to update affected bindings
	 *
	 * @returns {string[]|string}
	 *   Returns an array of keys if the data has nested data or a single key if it doesn't have
	 *   nested data
	 */
	ODataModel.prototype._importData = function(oData, mKeys) {
		var that = this,
		aList, sKey, oResult, oEntry;
		if (oData.results) {
			aList = [];
			each(oData.results, function(i, entry) {
				aList.push(that._importData(entry, mKeys));
			});
			return aList;
		} else {
			sKey = this._getKey(oData);
			oEntry = this.oData[sKey];
			if (!oEntry) {
				oEntry = oData;
				this.oData[sKey] = oEntry;
			}
			each(oData, function(sName, oProperty) {
				if (oProperty && (oProperty.__metadata && oProperty.__metadata.uri || oProperty.results) && !oProperty.__deferred) {
					oResult = that._importData(oProperty, mKeys);
					if (Array.isArray(oResult)) {
						oEntry[sName] = { __list: oResult };
					} else {
						oEntry[sName] = { __ref: oResult };
					}
				} else if (!oProperty || !oProperty.__deferred) { //do not store deferred navprops
					oEntry[sName] = oProperty;
				}
			});
			mKeys[sKey] = true;
			return sKey;
		}
	};

	/**
	 * Remove references of navigation properties created in <code>importData</code> function.
	 *
	 * @param {object} oData Data imported in <code>importData</code>
	 *
	 * @returns {object|object[]} The data with references of navigation properties removed
	 */
	ODataModel.prototype._removeReferences = function(oData){
		var that = this, aList;
		if (oData.results) {
			aList = [];
			each(oData.results, function(i, entry) {
				aList.push(that._removeReferences(entry));
			});
			return aList;
		} else {
			each(oData, function(sPropName, oCurrentEntry) {
				if (oCurrentEntry) {
					if (oCurrentEntry["__ref"] || oCurrentEntry["__list"]) {
						delete oData[sPropName];
					}
				}
			});
			return oData;
		}
	};

	/**
	 * Restore references of navigation properties created in <code>importData</code> function.
	 *
	 * @param {object} oData Data imported in <code>importData</code>
	 *
	 * @returns {object|object[]} The data with references of navigation properties restored
	 */
	ODataModel.prototype._restoreReferences = function(oData){
		var that = this,
		aList,
		aResults = [];
		if (oData.results) {
			aList = [];
			each(oData.results, function(i, entry) {
				aList.push(that._restoreReferences(entry));
			});
			return aList;
		} else {
			each(oData, function(sPropName, oCurrentEntry) {
				if (oCurrentEntry && oCurrentEntry["__ref"]) {
					var oChildEntry = that._getObject("/" + oCurrentEntry["__ref"]);
					assert(oChildEntry, "ODataModel inconsistent: " + oCurrentEntry["__ref"] + " not found!");
					if (oChildEntry) {
						delete oCurrentEntry["__ref"];
						oData[sPropName] = oChildEntry;
						// check recursively for found child entries
						that._restoreReferences(oChildEntry);
					}
				} else if (oCurrentEntry && oCurrentEntry["__list"]) {
					each(oCurrentEntry["__list"], function(j, sEntry) {
						var oChildEntry = that._getObject("/" + oCurrentEntry["__list"][j]);
						assert(oChildEntry, "ODataModel inconsistent: " +  oCurrentEntry["__list"][j] + " not found!");
						if (oChildEntry) {
							aResults.push(oChildEntry);
							// check recursively for found child entries
							that._restoreReferences(oChildEntry);
						}
					});
					delete oCurrentEntry["__list"];
					oCurrentEntry.results = aResults;
					aResults = [];
				}
			});
			return oData;
		}
	};

	/**
	 * removes all existing data from the model
	 */
	ODataModel.prototype.removeData = function(){
		this.oData = {};
	};

	/**
	 * Initialize the model.
	 * This will call initialize on all bindings. This is done if metadata is loaded asynchronously.
	 *
	 * @private
	 */
	ODataModel.prototype.initialize = function() {
		// Call initialize on all bindings in case metadata was not available when they were created
		var aBindings = this.getBindings();
		each(aBindings, function(iIndex, oBinding) {
			oBinding.initialize();
		});
	};

	/**
	 * Refresh the model.
	 *
	 * This will check all bindings for updated data and update the controls if data has been changed.
	 *
	 * @param {boolean} [bForceUpdate=false] Force update of controls
	 * @param {boolean} [bRemoveData=false] If set to true then the model data will be removed/cleared.
	 * 					Please note that the data might not be there when calling e.g. getProperty too early before the refresh call returned.
	 *
	 * @public
	 */
	ODataModel.prototype.refresh = function(bForceUpdate, bRemoveData) {
		// Call refresh on all bindings instead of checkUpdate to properly reset cached data in bindings
		if (bRemoveData) {
			this.removeData();
		}
		this._refresh(bForceUpdate);
	};

	ODataModel.prototype._refresh = function(bForceUpdate, mChangedEntities, mEntityTypes) {
		// Call refresh on all bindings instead of checkUpdate to properly reset cached data in bindings
		var aBindings = this.getBindings();
		each(aBindings, function(iIndex, oBinding) {
			oBinding.refresh(bForceUpdate, mChangedEntities, mEntityTypes);
		});
	};


	/**
	 * Private method iterating the registered bindings of this model instance and initiating their
	 * check for an update.
	 *
	 * @param {boolean} [bForceUpdate]
	 *   Whether change events is fired regardless of the bindings state
	 * @param {boolean} bAsync
	 *   Whether the check is done asynchronously
	 * @param {object} mChangedEntities
	 *   A map of changed entities
	 * @param {boolean} bMetaModelOnly
	 *   Whether only metamodel bindings are updated
	 *
	 * @private
	 */
	ODataModel.prototype.checkUpdate = function(bForceUpdate, bAsync, mChangedEntities, bMetaModelOnly) {
		if (bAsync) {
			if (!this.sUpdateTimer) {
				this.sUpdateTimer = setTimeout(function() {
					this.checkUpdate(bForceUpdate, false, mChangedEntities);
				}.bind(this), 0);
			}
			return;
		}
		if (this.sUpdateTimer) {
			clearTimeout(this.sUpdateTimer);
			this.sUpdateTimer = null;
		}
		var aBindings = this.getBindings();
		each(aBindings, function(iIndex, oBinding) {
			if (!bMetaModelOnly || this.isMetaModelPath(oBinding.getPath())) {
				oBinding.checkUpdate(bForceUpdate, mChangedEntities);
			}
		}.bind(this));
	};

	/*
	 * @see sap.ui.model.Model.prototype.bindProperty
	 */
	ODataModel.prototype.bindProperty = function(sPath, oContext, mParameters) {
		var oBinding = new ODataPropertyBinding(this, sPath, oContext, mParameters);
		return oBinding;
	};

	/**
	 * Creates a new list binding for this model.
	 *
	 * @param {string} sPath
	 *   Binding path, either absolute or relative to a given <code>oContext</code>
	 * @param {sap.ui.model.Context} [oContext]
	 *   Binding context referring to this model
	 * @param {sap.ui.model.Sorter|sap.ui.model.Sorter[]} [aSorters]
	 *   Initial sort order, can be either a sorter or an array of sorters
	 * @param {sap.ui.model.Filter|sap.ui.model.Filter[]} [aFilters]
	 *   Predefined filter/s, can be either a filter or an array of filters
	 * @param {object} [mParameters]
	 *   Map which contains additional parameters for the binding
	 * @param {string} [mParameters.expand]
	 *   Value for the OData <code>$expand</code> query parameter which should be included in the
	 *   request
	 * @param {string} [mParameters.select]
	 *   Value for the OData <code>$select</code> query parameter which should be included in the
	 *   request
	 * @param {Object<string,string>} [mParameters.custom]
	 *   Optional map of custom query parameters (name/value pairs); names of custom parameters must
	 *   not start with <code>$</code>
	 * @param {sap.ui.model.odata.CountMode} [mParameters.countMode]
	 *   Defines the count mode of the new binding; if not specified, the default count mode of this
	 *   model will be applied
	 *
	 * @returns {sap.ui.model.ListBinding} A new list binding object
	 *
	 * @see sap.ui.model.Model.prototype.bindList
	 * @public
	 */
	ODataModel.prototype.bindList = function(sPath, oContext, aSorters, aFilters, mParameters) {
		var oBinding = new ODataListBinding(this, sPath, oContext, aSorters, aFilters, mParameters);
		return oBinding;
	};

	/*
	 * @see sap.ui.model.Model.prototype.bindTree
	 */
	ODataModel.prototype.bindTree = function(sPath, oContext, aFilters, mParameters) {
		var oBinding = new ODataTreeBinding(this, sPath, oContext, aFilters, mParameters);
		return oBinding;
	};

	/*
	 * Creates a binding context for the given path
	 * If the data of the context is not yet available, it can not be created, but first the
	 * entity needs to be fetched from the server asynchronously. In case no callback function
	 * is provided, the request will not be triggered.
	 *
	 * @see sap.ui.model.Model.prototype.createBindingContext
	 */
	ODataModel.prototype.createBindingContext =
			function(sPath, oContext, mParameters, fnCallBack, bReload) {
		var sFullPath = this.resolve(sPath, oContext);
			bReload = !!bReload;

		// optional parameter handling
		if (typeof oContext == "function") {
			fnCallBack = oContext;
			oContext = null;
		}
		if (typeof mParameters == "function") {
			fnCallBack = mParameters;
			mParameters = null;
		}

		// if path cannot be resolved, call the callback function and return null
		if (!sFullPath) {
			if (fnCallBack) {
				fnCallBack(null);
			}
			return null;
		}

		// try to resolve path, send a request to the server if data is not available yet
		// if we have set forceUpdate in mParameters we send the request even if the data is available
		var oData = this._getObject(sPath, oContext),
			sKey,
			oNewContext,
			that = this;

		if (!bReload) {
			bReload = this._isReloadNeeded(sFullPath, oData, mParameters);
		}

		if (!bReload) {
			sKey = this._getKey(oData);
			oNewContext = this.getContext('/' + sKey);
			if (fnCallBack) {
				fnCallBack(oNewContext);
			}
			return oNewContext;
		}

		if (fnCallBack) {
			var bIsRelative = !sPath.startsWith("/");
			if (sFullPath) {
				var aParams = [],
					sCustomParams = this.createCustomParams(mParameters);
				if (sCustomParams) {
					aParams.push(sCustomParams);
				}
				this._loadData(sFullPath, aParams, function(oData) {
					sKey = oData ? that._getKey(oData) : undefined;
					if (sKey && oContext && bIsRelative) {
						var sContextPath = oContext.getPath();
						// remove starting slash
						sContextPath = sContextPath.substr(1);
						// when model is refreshed, parent entity might not be available yet
						if (that.oData[sContextPath]) {
							that.oData[sContextPath][sPath] = {__ref: sKey};
						}
					}
					oNewContext = that.getContext('/' + sKey);
					fnCallBack(oNewContext);
				}, function() {
					fnCallBack(null); // error - notify to recreate contexts
				});
			} else {
				fnCallBack(null); // error - notify to recreate contexts
			}
		}

		return undefined;
	};

	/**
	 * Checks if data based on <code>select</code>, <code>expand</code> parameters is already loaded
	 * or not. In case it couldn't be found we should reload the data so we return true.
	 *
	 * @param {string} sFullPath The path to the data
	 * @param {object} oData The data to check for completeness
	 * @param {object} [mParameters] Value of <code>select</code> and/or <code>expand</code>
	 *
	 * @returns {boolean} Whether a reload is needed
	 */
	ODataModel.prototype._isReloadNeeded = function(sFullPath, oData, mParameters) {
		var oDataObject, i, sNavProps, sPropKey, bReloadNeeded, sSelectProps,
			aNavProps = [],
			aSelectProps = [];

		// no valid path --> no reload
		if (!sFullPath) {
			return false;
		}

		// no data --> reload needed
		if (!oData) {
			return true;
		}

		//Split the Navigation-Properties (or multi-level chains) which should be expanded
		if (mParameters && mParameters["expand"]) {
			sNavProps = mParameters["expand"].replace(/\s/g, "");
			aNavProps = sNavProps.split(',');
		}

		//Split the Navigation properties again, if there are multi-level properties chained together by "/"
		//The resulting aNavProps array will look like this: ["a", ["b", "c/d/e"], ["f", "g/h"], "i"]
		if (aNavProps) {
			for (i = 0; i < aNavProps.length; i++) {
				var chainedPropIndex = aNavProps[i].indexOf("/");
				if (chainedPropIndex !== -1) {
					//cut of the first nav property of the chain
					var chainedPropFirst = aNavProps[i].slice(0, chainedPropIndex);
					var chainedPropRest = aNavProps[i].slice(chainedPropIndex + 1);
					//keep track of the newly splitted nav prop chain
					aNavProps[i] = [chainedPropFirst, chainedPropRest];
				}
			}
		}

		//Iterate all nav props and follow the given expand-chain
		for (i = 0; i < aNavProps.length; i++) {
			var navProp = aNavProps[i];

			//check if the navProp was split into multiple parts (meaning it's an array)
			//e.g. ["Orders", "Products/Suppliers"]
			if (Array.isArray(navProp)) {

				var oFirstNavProp = oData[navProp[0]];
				var sNavPropRest = navProp[1];

				//first nav prop in the chain is either undefined or deferred -> reload needed
				if (!oFirstNavProp || (oFirstNavProp && oFirstNavProp.__deferred)) {
					return true;
				} else if (oFirstNavProp) {
					if (oFirstNavProp.__list && oFirstNavProp.__list.length > 0) {
						//Follow all keys in the __list collection by recursively calling
						//this function to check if all linked properties are loaded.
						//This is basically a depth-first search.
						for (var iNavIndex = 0; iNavIndex < oFirstNavProp.__list.length;
								iNavIndex++) {
							sPropKey = "/" + oFirstNavProp.__list[iNavIndex];
							oDataObject = this.getObject(sPropKey);
							bReloadNeeded =
								this._isReloadNeeded(sPropKey, oDataObject, {expand: sNavPropRest});
							if (bReloadNeeded) {
								//if a single nav-prop path is not loaded -> reload needed
								return true;
							}
						}
					} else if (oFirstNavProp.__ref) {
						//the first nav-prop is not a __list
						//but only a reference to a single entry (__ref)
						sPropKey = "/" + oFirstNavProp.__ref;
						oDataObject = this.getObject(sPropKey);
						bReloadNeeded =
							this._isReloadNeeded(sPropKey, oDataObject, {expand: sNavPropRest});
						if (bReloadNeeded) {
							return true;
						}
					}
				}
			} else if (oData[navProp] === undefined
					|| (oData[navProp] && oData[navProp].__deferred)) {
				//only one single Part, e.g. "Orders"
				return true;
			}
		}

		if (mParameters && mParameters["select"]) {
			sSelectProps = mParameters["select"].replace(/\s/g, "");
			aSelectProps = sSelectProps.split(',');
		}

		for (i = 0; i < aSelectProps.length; i++) {
			// reload data if select property not available
			if (oData[aSelectProps[i]] === undefined) {
				return true;
			}
		}

		if (aSelectProps.length == 0) {
			// check if all props exist and are already loaded...
			// only a subset of props may already be loaded before and now we want to load all.
			var oEntityType = this.oMetadata._getEntityTypeByPath(sFullPath);
			if (!oEntityType) {
				// if no entity type could be found we decide not to reload
				return false;
			} else {
				for (i = 0; i < oEntityType.property.length; i++) {
					if (oData[oEntityType.property[i].name] === undefined) {
						return true;
					}
				}
			}
		}
		return false;
	};

	/*
	 * @see sap.ui.model.Model.prototype.destroyBindingContext
	 */
	ODataModel.prototype.destroyBindingContext = function(oContext) {
	};

	/**
	 * Create URL parameters from custom parameters.
	 *
	 * @param {object} mParameters Parameters to build URL parameters from
	 *
	 * @returns {string} The parameters to use in a URL
	 * @private
	 */
	ODataModel.prototype.createCustomParams = function(mParameters) {
		var sName, sParamName,
			aCustomParams = [],
			mCustomQueryOptions,
			mSupportedParams = {
				expand: true,
				select: true
			};

		for (sParamName in mParameters) {
			if (sParamName in mSupportedParams) {
				aCustomParams.push("$" + sParamName + "=" + encodeURL(mParameters[sParamName]));
			}
			if (sParamName == "custom") {
				mCustomQueryOptions = mParameters[sParamName];
				for (sName in mCustomQueryOptions) {
					if (sName.indexOf("$") == 0) {
						Log.warning("Trying to set OData parameter " + sName + " as custom query option!");
					} else {
						aCustomParams.push(sName + "=" + encodeURL(mCustomQueryOptions[sName]));
					}
				}
			}
		}
		return aCustomParams.join("&");
	};

	/*
	 * @see sap.ui.model.Model.prototype.bindContext
	 */
	ODataModel.prototype.bindContext = function(sPath, oContext, mParameters) {
		var oBinding = new ODataContextBinding(this, sPath, oContext, mParameters);
		return oBinding;
	};

	/**
	 * Sets whether this OData service supports <code>$count</code> on its collections.
	 *
	 * @param {boolean} bCountSupported
	 *   Whether this OData service supports <code>$count</code> on its collections
	 * @deprecated As of version 1.20, please use {@link #setDefaultCountMode} instead.
	 * @public
	 */
	ODataModel.prototype.setCountSupported = function(bCountSupported) {
		this.bCountSupported = bCountSupported;
	};

	/**
	 * Returns whether this model supports <code>$count</code> on its collections.
	 *
	 * @returns {boolean} Whether this model supports <code>$count</code> on its collections
	 *
	 * @deprecated As of version 1.20, please use {@link #getDefaultCountMode} instead.
	 * @public
	 */
	ODataModel.prototype.isCountSupported = function() {
		return this.bCountSupported;
	};

	/**
	 * Sets the default mode how to retrieve the item count for a collection in this model.
	 *
	 * The count can be determined in the following ways
	 * <ul>
	 * <li>by sending a separate <code>$count</code> request</li>
	 * <li>by adding parameter <code>$inlinecount=allpages</code> to one or all data requests</li>
	 * <li>a combination of the previous two</li>
	 * <li>not at all (questions about the size of the collection can't be answered then)</li>
	 * </ul>
	 * See {@link sap.ui.model.odata.CountMode} for all enumeration values and more details.
	 *
	 * Note that a call to this method does not modify the count mode for existing list bindings,
	 * only bindings that are created afterwards will use the new mode when no mode is defined at their creation.
	 *
	 * If no default count mode is set for an <code>ODataModel</code> (v1), the mode <code>Both</code>
	 * will be used for compatibility reasons.
	 *
	 * @param {sap.ui.model.odata.CountMode} sCountMode The new default count mode for this model
	 * @since 1.20
	 * @public
	 */
	ODataModel.prototype.setDefaultCountMode = function(sCountMode) {
		this.sDefaultCountMode = sCountMode;
	};

	/**
	 * Returns the default count mode for retrieving the count of collections.
	 *
	 * @returns {sap.ui.model.odata.CountMode}
	 *   The default count mode for retrieving the count of collections
	 *
	 * @since 1.20
	 * @public
	 */
	ODataModel.prototype.getDefaultCountMode = function() {
		return this.sDefaultCountMode;
	};


	/**
	 * Returns the key part from the entry URI or the given context.
	 *
	 * @param {object|sap.ui.model.Context} oObject The object or context to get the key from
	 * @param {boolean} bDecode Whether the URI decoding should be applied on the key
	 *
	 * @returns {string} The key part from the entry URI
	 *
	 * @private
	 */
	ODataModel.prototype._getKey = function(oObject, bDecode) {
		var sKey, sURI;
		if (oObject instanceof Context) {
			sKey = oObject.getPath().substr(1);
		} else if (oObject && oObject.__metadata && oObject.__metadata.uri) {
			sURI = oObject.__metadata.uri;
			sKey = sURI.substr(sURI.lastIndexOf("/") + 1);
		}
		if (bDecode) {
			sKey = decodeURIComponent(sKey);
		}
		return sKey;
	};

	/**
	 * Returns the key part from the entry URI or the given context or object.
	 *
	 * @param {object|sap.ui.model.Context} oObject The context or object
	 * @param {boolean} bDecode Whether the URI decoding should be applied on the key
	 *
	 * @returns {string} The key part from the entry URI
	 *
	 * @public
	 */
	ODataModel.prototype.getKey = function(oObject, bDecode) {
		return this._getKey(oObject, bDecode);
	};

	/**
	 * Creates the key from the given collection name and property map.
	 *
	 * @param {string} sCollection
	 *   The name of the collection
	 * @param {object} oKeyProperties
	 *   The object containing at least all the key properties of the entity type
	 * @param {boolean} bDecode
	 *   Whether the URI decoding should be applied on the key
	 *
	 * @returns {string} The created key
	 *
	 * @public
	 */
	ODataModel.prototype.createKey = function(sCollection, oKeyProperties, bDecode) {
		var oEntityType = this.oMetadata._getEntityTypeByPath(sCollection),
			sKey = sCollection,
			that = this,
			sName,
			sValue,
			oProperty;
		assert(oEntityType, "Could not find entity type of collection \"" + sCollection + "\" in service metadata!");
		sKey += "(";
		if (oEntityType.key.propertyRef.length == 1) {
			sName = oEntityType.key.propertyRef[0].name;
			assert(sName in oKeyProperties, "Key property \"" + sName + "\" is missing in object!");
			oProperty = this.oMetadata._getPropertyMetadata(oEntityType, sName);
			sValue = ODataUtils.formatValue(oKeyProperties[sName], oProperty.type);
			sKey += bDecode ? sValue : encodeURIComponent(sValue);
		} else {
			each(oEntityType.key.propertyRef, function(i, oPropertyRef) {
				if (i > 0) {
					sKey += ",";
				}
				sName = oPropertyRef.name;
				assert(sName in oKeyProperties, "Key property \"" + sName + "\" is missing in object!");
				oProperty = that.oMetadata._getPropertyMetadata(oEntityType, sName);
				sValue = ODataUtils.formatValue(oKeyProperties[sName], oProperty.type);
				sKey += sName;
				sKey += "=";
				sKey += bDecode ? sValue : encodeURIComponent(sValue);
			});
		}
		sKey += ")";
		return sKey;
	};

	/**
	 * Returns the value for the property with the given <code>sPath</code>. If the path points to a
	 * navigation property which has been loaded via <code>$expand</code> then the
	 * <code>bIncludeExpandEntries</code> parameter determines if the navigation property should be
	 * included in the returned value or not.
	 * Please note that this currently works for 1..1 navigation properties only.
	 *
	 * @param {string} sPath
	 *   The path/name of the property
	 * @param {object} [oContext]
	 *   The context if available to access the property value
	 * @param {boolean} [bIncludeExpandEntries]
	 *   This parameter should be set when a URI or custom parameter with a <code>$expand</code>
	 *   System Query Option was used to retrieve associated entries embedded/inline. If true then
	 *   the getProperty function returns a desired property value/entry and includes the associated
	 *   expand entries (if any). If false the associated/expanded entry properties are removed and
	 *   not included in the desired entry as properties at all. This is useful for performing
	 *   updates on the base entry only. Note: A copy and not a reference of the entry will be
	 *   returned.
	 *
	 * @return {object} The value of the property
	 *
	 * @public
	 */
	ODataModel.prototype.getProperty = function(sPath, oContext, bIncludeExpandEntries) {
		var oValue = this._getObject(sPath, oContext);

		// same behavior as before
		if (bIncludeExpandEntries == null || bIncludeExpandEntries == undefined) {
			return oValue;
		}

		// if value is a plain value and not an object we return directly
		if (!isPlainObject(oValue)) {
			return oValue;
		}

		// do a value copy or the changes to that value will be modified in the model as well (reference)
		oValue = merge({}, oValue);

		if (bIncludeExpandEntries == true) {
			// include expand entries
			return this._restoreReferences(oValue);
		} else {
			// remove expanded references
			return this._removeReferences(oValue);
		}
	};

	/**
	 * Gets the object for the given path and context.
	 *
	 * @param {string} sPath A relative or absolute path
	 * @param {object} [oContext] A context to resolve a relative path
	 *
	 * @returns {any} The object matching path and context
	 */
	ODataModel.prototype._getObject = function(sPath, oContext) {
		var oNode = this.isLegacySyntax() ? this.oData : null,
			sResolvedPath = this.resolve(sPath, oContext),
			iSeparator, sDataPath, sMetaPath, oMetaContext, sKey, oMetaModel;

		//check for metadata path
		if (this.oMetadata && sResolvedPath && sResolvedPath.indexOf('/#') > -1)  {
			if (this.isMetaModelPath(sResolvedPath)) {
				// Metadata binding resolved by ODataMetaModel
				iSeparator = sResolvedPath.indexOf('/##');
				oMetaModel = this.getMetaModel();
				if (!this.bMetaModelLoaded) {
					return null;
				}
				sDataPath = sResolvedPath.substr(0, iSeparator);
				sMetaPath = sResolvedPath.substr(iSeparator + 3);
				oMetaContext = oMetaModel.getMetaContext(sDataPath);
				oNode = oMetaModel.getProperty(sMetaPath, oMetaContext);
			} else {
				// Metadata binding resolved by ODataMetadata
				oNode = this.oMetadata._getAnnotation(sResolvedPath);
			}
		} else {
			if (oContext) {
				sKey = oContext.getPath();
				// remove starting slash
				sKey = sKey.substr(1);
				oNode = this.oData[sKey];
			}
			if (!sPath) {
				return oNode;
			}
			var aParts = sPath.split("/"),
				iIndex = 0;
			if (!aParts[0]) {
				// absolute path starting with slash
				oNode = this.oData;
				iIndex++;
			}
			while (oNode && aParts[iIndex]) {
				oNode = oNode[aParts[iIndex]];
				if (oNode) {
					if (oNode.__ref) {
						oNode = this.oData[oNode.__ref];
					} else if (oNode.__list) {
						oNode = oNode.__list;
					} else if (oNode.__deferred) {
					// set to undefined and not to null because navigation properties can have a null value
						oNode = undefined;
					}
				}
				iIndex++;
			}
		}
		return oNode;
	};

	/**
	 * Update the security token, if token handling is enabled and token is not available yet
	 */
	ODataModel.prototype.updateSecurityToken = function() {
		if (this.bTokenHandling) {
			if (!this.oServiceData.securityToken) {
				this.refreshSecurityToken();
			}
			// Update header every time, in case security token was changed by other model
			// Check bTokenHandling again, as updateSecurityToken() might disable token handling
			if (this.bTokenHandling) {
				this.oHeaders["x-csrf-token"] = this.oServiceData.securityToken;
			}
		}
	};

	/**
	 * Clears the security token, as well from the service data as from the headers object
	 */
	ODataModel.prototype.resetSecurityToken = function() {
		delete this.oServiceData.securityToken;
		delete this.oHeaders["x-csrf-token"];
	};

	/**
	 * Returns the current security token. If the token has not been requested from the server it will be requested first.
	 *
	 * @returns {string} the CSRF security token
	 *
	 * @public
	 */
	ODataModel.prototype.getSecurityToken = function() {
		var sToken = this.oServiceData.securityToken;
		if (!sToken) {
			this.refreshSecurityToken();
			sToken = this.oServiceData.securityToken;
		}
		return sToken;
	};

	/**
	 * refresh XSRF token by performing a GET request against the service root URL.
	 *
	 * @param {function} [fnSuccess] a callback function which is called when the data has
	 *            					 been successfully retrieved.
	 * @param {function} [fnError] a callback function which is called when the request failed. The handler can have the parameter: oError which contains
	 *  additional error information.
	 *
	 * @param {boolean} [bAsync=false] true for asynchronous requests.
	 *
	 * @return {object} an object which has an <code>abort</code> function to abort the current request.
	 *
	 * @public
	 */
	ODataModel.prototype.refreshSecurityToken = function(fnSuccess, fnError, bAsync) {
		var that = this, sUrl, sToken;

		// bAsync default is false ?!
		bAsync = bAsync === true;

		// trigger a read to the service url to fetch the token
		sUrl = this._createRequestUrl("/");
		var oRequest = this._createRequest(sUrl, "GET", bAsync);
		oRequest.headers["x-csrf-token"] = "Fetch";

		function _handleSuccess(oData, oResponse) {
			if (oResponse) {
				sToken = that._getHeader("x-csrf-token", oResponse.headers);
				if (sToken) {
					that.oServiceData.securityToken = sToken;
					// For compatibility with applications, that are using getHeaders() to retrieve the current
					// CSRF token additionally keep it in the oHeaders object
					that.oHeaders["x-csrf-token"] = sToken;
				} else {
					// Disable token handling, if service does not return tokens
					that.resetSecurityToken();
					that.bTokenHandling = false;
				}
			}

			if (fnSuccess) {
				fnSuccess(oData, oResponse);
			}
		}

		function _handleError(oError) {
			// Disable token handling, if token request returns an error
			that.resetSecurityToken();
			that.bTokenHandling = false;
			that._handleError(oError);

			if (fnError) {
				fnError(oError);
			}
		}

		return this._request(oRequest, _handleSuccess, _handleError, undefined, undefined, this.getServiceMetadata());
	};

	/**
	 * Submits changes from the requestQueue (queue can currently have only one request).
	 *
	 * @param {object} oRequest
	 *   The request
	 * @param {boolean} bBatch
	 *   Whether the request should be sent as batch
	 * @param {function} [fnSuccess]
	 *   A function to call on success
	 * @param {function} [fnError]
	 *   A function to call on failure
	 * @param {boolean} [bHandleBatchErrors]
	 *   Whether errors in batch requests should be handled
	 * @param {boolean} [bImportData]
	 *   Whether to import the data into the model's data cache on success
	 * @returns {object}
	 *   The request handle
	 *
	 * @private
	 */
	ODataModel.prototype._submitRequest = function(oRequest, bBatch, fnSuccess, fnError, bHandleBatchErrors, bImportData){
		var that = this, oResponseData, mChangedEntities = {};

		function _handleSuccess(oData, oResponse) {
			// check if embedded errors occurred in success request. We don't do that for manual batch requests
			// so we have to check for bHandleBatchErrors
			if (bBatch && bHandleBatchErrors) {
				// check if errors occurred in the batch
				var aErrorResponses = that._getBatchErrors(oData);
				if (aErrorResponses.length > 0) {
					// call handle error with the first error.
					_handleError(aErrorResponses[0]);
					return false;
				}
				// if response contains data
				if (oData.__batchResponses && oData.__batchResponses.length > 0) {

					oResponseData = oData.__batchResponses[0].data;
					if (!oResponseData && oData.__batchResponses[0].__changeResponses) {
						oResponseData = oData.__batchResponses[0].__changeResponses[0].data;
					}
				}
				oData = oResponseData;
			}

			if (bImportData) {
				if (oData && oData.__batchResponses) {
					each(oData.__batchResponses, function(iIndex, oResponse) {
						if (oResponse && oResponse.data) {
							that._importData(oResponse.data, mChangedEntities);
						}
					});
				}
			}

			that._handleETag(oRequest, oResponse, bBatch);

			that._updateRequestQueue(oRequest, bBatch);

			if (that._isRefreshNeeded(oRequest, oResponse)) {
				that._refresh(false, oRequest.keys, oRequest.entityTypes );
			}

			if (fnSuccess) {
				fnSuccess(oData, oResponse);
			}

			return undefined;
		}

		function _handleError(oError) {

			// If error is a 403 with XSRF token "Required" reset the token and retry sending request
			if (that.bTokenHandling && oError.response) {
				var sToken = that._getHeader("x-csrf-token", oError.response.headers);
				if (!oRequest.bTokenReset && oError.response.statusCode == '403' && sToken && sToken.toLowerCase() == "required") {
					that.resetSecurityToken();
					oRequest.bTokenReset = true;
					_submit();
					return;
				}
			}

			that._handleError(oError);

			if (fnError) {
				fnError(oError);
			}
		}

		function _submit() {
			// Make sure requests not requiring a CSRF token don't send one.
			if (that.bTokenHandling) {
				delete oRequest.headers["x-csrf-token"];
			}
			// request token only if we have change operations or batch requests
			// token needs to be set directly on request headers, as request is already created
			if (that.bTokenHandling && oRequest.method !== "GET") {
				that.updateSecurityToken();
				// Check bTokenHandling again, as updateSecurityToken() might disable token handling
				if (that.bTokenHandling) {
					oRequest.headers["x-csrf-token"] = that.oServiceData.securityToken;
				}
			}

			if (bBatch) {
				return that._request(oRequest, _handleSuccess, _handleError, OData.batchHandler, undefined, that.getServiceMetadata());
			} else {
				return that._request(oRequest, _handleSuccess, _handleError, that.oHandler, undefined, that.getServiceMetadata());
			}
		}

		return _submit();
	};

	/*
	 * Create a Batch request
	 * @private
	 */
	ODataModel.prototype._createBatchRequest = function(aBatchRequests, bAsync) {
		var sUrl, oRequest,
			oChangeHeader = {},
			oPayload = {},
			mKeys = {}, mEntityTypes = {};

		oPayload.__batchRequests = aBatchRequests;

		sUrl = this.sServiceUrl	+ "/$batch";

		if (this.aUrlParams.length > 0) {
			sUrl += "?" + this.aUrlParams.join("&");
		}

		extend(oChangeHeader, this.mCustomHeaders, this.oHeaders);

		// reset
		delete oChangeHeader["Content-Type"];

		oRequest = {
				headers : oChangeHeader,
				requestUri : sUrl,
				method : "POST",
				data : oPayload,
				user: this.sUser,
				password: this.sPassword,
				async: bAsync
		};

		if (bAsync) {
			oRequest.withCredentials = this.bWithCredentials;
		}
		//collect keys
		each(aBatchRequests, function(i, oBatchOperation) {
			if (oBatchOperation["__changeRequests"]) {
				//this is a changeset
				each(oBatchOperation["__changeRequests"],function(j, oChangeRequest){
					if (oChangeRequest.keys && oChangeRequest.method != "POST") {
						each(oChangeRequest.keys, function(k,sKey){
							mKeys[k] = sKey;
						});
					} else if (oChangeRequest.entityTypes && oChangeRequest.method == "POST") {
						each(oChangeRequest.entityTypes, function(l, sEntityType){
							mEntityTypes[l] = sEntityType;
						});
					}
				});
			}
		});

		oRequest.keys = mKeys;
		oRequest.entityTypes = mEntityTypes;

		return oRequest;
	};

	/*
	 * handle ETag
	 * @private
	 */
	ODataModel.prototype._handleETag = function(oRequest, oResponse, bBatch) {
		var sUrl,
			oEntry,
			aChangeRequests,
			aChangeResponses,
			aBatchRequests,
			aBatchResponses;

		if (bBatch) {
			aBatchRequests = oRequest.data.__batchRequests;
			aBatchResponses = oResponse.data.__batchResponses;
			if (aBatchResponses && aBatchRequests) {
				for (var i = 0; i < aBatchRequests.length; i++) {
					// get change requests and corresponding responses - the latter are in the same order as the requests according to odata spec
					aChangeRequests = aBatchRequests[i].__changeRequests;
					if (aBatchResponses[i]) {
						aChangeResponses = aBatchResponses[i].__changeResponses;
						if (aChangeRequests && aChangeResponses) {
							for (var j = 0; j < aChangeRequests.length; j++) {
								if (aChangeRequests[j].method == "MERGE" || aChangeRequests[j].method == "PUT") {
									//try to get the object to the uri from the model
									sUrl = aChangeRequests[j].requestUri.replace(this.sServiceUrl + '/','');
									if (!sUrl.startsWith("/")) {
										sUrl = "/" + sUrl;
									}
									oEntry = this._getObject(sUrl);
									// if there is an object, try to update its eTag from the response.
									if (oEntry && oEntry.__metadata && aChangeResponses[j].headers && aChangeResponses[j].headers.ETag) {
										oEntry.__metadata.etag = aChangeResponses[j].headers.ETag;
									}
								}
							}
						}
					} else {
						Log.warning("could not update ETags for batch request: corresponding response for request missing");
					}
				}
			} else {
				Log.warning("could not update ETags for batch request: no batch responses/requests available");
			}
		} else {
			// refresh ETag from response directly. We can not wait for the refresh.
			sUrl = oRequest.requestUri.replace(this.sServiceUrl + '/','');
			if (!sUrl.startsWith("/")) {
				sUrl = "/" + sUrl;
			}
			oEntry = this._getObject(sUrl);
			if (oEntry && oEntry.__metadata && oResponse.headers.ETag) {
				oEntry.__metadata.etag = oResponse.headers.ETag;
			}
		}
	};

	/*
	 * handle batch errors
	 * @private
	 */
	ODataModel.prototype._handleBatchErrors = function(oResponse, oData) {
		this._getBatchErrors(oData);
		this._handleETag();

	};

	/*
	 * returns array of batch errors
	 */
	ODataModel.prototype._getBatchErrors = function(oData) {
		var aErrorResponses = [], sErrorMsg;
		// check if errors occurred in the batch
		each(oData.__batchResponses, function(iIndex, oOperationResponse) {
			if (oOperationResponse.message) {
				sErrorMsg = "The following problem occurred: " + oOperationResponse.message;
				if (oOperationResponse.response) {
					sErrorMsg += oOperationResponse.response.statusCode + "," +
					oOperationResponse.response.statusText + "," +
					oOperationResponse.response.body;
				}
				aErrorResponses.push(oOperationResponse);
				Log.fatal(sErrorMsg);
			}
			if (oOperationResponse.__changeResponses) {
				each(oOperationResponse.__changeResponses, function(iIndex, oChangeOperationResponse) {
					if (oChangeOperationResponse.message) {
						sErrorMsg = "The following problem occurred: " + oChangeOperationResponse.message;
						if (oChangeOperationResponse.response) {
							sErrorMsg += oChangeOperationResponse.response.statusCode + "," +
							oChangeOperationResponse.response.statusText + "," +
							oChangeOperationResponse.response.body;
						}
						aErrorResponses.push(oChangeOperationResponse);
						Log.fatal(sErrorMsg);
					}
				});
			}
		});
		return aErrorResponses;
	};

	/**
	 * Error handling for requests.
	 *
	 * @param {object} oError
	 *   The error object
	 *
	 * @returns {object}
	 *   A map containing information about the error, such as <code>message</code>,
	 *   <code>statusCode</code>, <code>statusText</code> and <code>responseText</code>
	 *
	 * @private
	 */
	ODataModel.prototype._handleError = function(oError) {
		var mParameters = {}, sToken;
		var sErrorMsg = "The following problem occurred: " + oError.message;

		mParameters.message = oError.message;
		if (oError.response) {
			if (this.bTokenHandling) {
				// if XSRFToken is not valid we get 403 with the x-csrf-token header : Required.
				// a new token will be fetched in the refresh afterwards.
				sToken = this._getHeader("x-csrf-token", oError.response.headers);
				if (oError.response.statusCode == '403' && sToken && sToken.toLowerCase() == "required") {
					this.resetSecurityToken();
				}
			}
			sErrorMsg += oError.response.statusCode + "," +
			oError.response.statusText + "," +
			oError.response.body;
			mParameters.statusCode = oError.response.statusCode;
			mParameters.statusText = oError.response.statusText;
			mParameters.responseText = oError.response.body;
		}
		Log.fatal(sErrorMsg);

		return mParameters;
	};

	/**
	 * Return requested data as object if the data has already been loaded and stored in the model.
	 *
	 * @param {string} sPath
	 *   A string containing the path to the data object that should be returned
	 * @param {object} [oContext]
	 *   The optional context which is used with the sPath to retrieve the requested data
	 * @param {boolean} [bIncludeExpandEntries]
	 *   This parameter should be set when a URI or custom parameter with a <code>$expand</code>
	 *   System Query Option was used to retrieve associated entries embedded/inline; if true then
	 *   the <code>getProperty</code> function returns a desired property value/entry and includes
	 *   the associated expand entries (if any); if false the associated/expanded entry properties
	 *   are removed and not included in the desired entry as properties at all; this is useful for
	 *   performing updates on the base entry only; note: A copy and not a reference of the entry
	 *   will be returned.
	 *
	 * @return {object} oData Object containing the requested data if the path is valid.
	 *
	 * @public
	 * @deprecated As of version 1.6.0, please use {@link #getProperty} instead
	 */
	ODataModel.prototype.getData = function(sPath, oContext, bIncludeExpandEntries) {
		return this.getProperty(sPath, oContext, bIncludeExpandEntries);
	};

	/**
	 * Returns an ETag: either the passed sETag or tries to retrieve the ETag from the metadata of
	 * <code>oPayload</code> or <code>sPath</code>.
	 *
	 * @param {string} sPath The path to metadata which could contain an ETag
	 * @param {object} oPayload The payload which metadata could contain an ETag
	 * @param {string} sETag ETag to return of no other ETag is found
	 *
	 * @returns {string} The ETag
	 *
	 * @private
	 */
	ODataModel.prototype._getETag = function(sPath, oPayload, sETag) {
		var sETagHeader, sEntry, iIndex;
		if (sETag) {
			sETagHeader = sETag;
		} else if (oPayload && oPayload.__metadata) {
			sETagHeader = oPayload.__metadata.etag;
		} else if (sPath) {
			sEntry = sPath.replace(this.sServiceUrl + '/','');
			iIndex = sEntry.indexOf("?");
			if (iIndex > -1) {
				sEntry = sEntry.substr(0, iIndex);
			}
			if (this.oData.hasOwnProperty(sEntry)) {
				sETagHeader = this.getProperty('/' + sEntry + '/__metadata/etag');
			}
		}

		return sETagHeader;
	};

	/**
	 * Create a request object for changes.
	 *
	 * @param {string} sUrl The request URL
	 * @param {string} sMethod The request method
	 * @param {boolean} bAsync Whether the request should be sent asynchronously
	 * @param {object} oPayload The payload to send with the request
	 * @param {string} sETag The ETag for the request
	 *
	 * @return {object} The request object
	 *
	 * @private
	 */
	ODataModel.prototype._createRequest = function(sUrl, sMethod, bAsync, oPayload, sETag) {
		var oChangeHeader = {}, sETagHeader;
		extend(oChangeHeader, this.mCustomHeaders, this.oHeaders);

		sETagHeader = this._getETag(sUrl, oPayload, sETag);

		if (sETagHeader && sMethod != "GET") {
			oChangeHeader["If-Match"] = sETagHeader;
		}

		// make sure to set content type header for POST/PUT requests when using JSON format to prevent datajs to add "odata=verbose" to the content-type header
		// may be removed as later gateway versions support this
		if (this.bJSON && sMethod != "DELETE" && this.sMaxDataServiceVersion === "2.0") {
			oChangeHeader["Content-Type"] = "application/json";
		}

		if (sMethod == "MERGE" && !this.bUseBatch) {
			oChangeHeader["x-http-method"] = "MERGE";
			sMethod = "POST";
		}

		var oRequest = {
				headers : oChangeHeader,
				requestUri : sUrl,
				method : sMethod,
				//data : oPayload,
				user: this.sUser,
				password: this.sPassword,
				async: bAsync
		};

		if (oPayload) {
			oRequest.data = oPayload;
		}

		if (bAsync) {
			oRequest.withCredentials = this.bWithCredentials;
		}

		return oRequest;
	};

	/**
	 * Checks if a model refresh is needed, either because the data provided by the
	 * <code>oRequest</code> and <code>oResponse</code> is stored in the model or new data is added
	 * (POST). For batch requests all embedded requests are checked separately.
	 *
	 * @param {object} oRequest The request which may contain change requests
	 * @param {object} oResponse The response which may contain new data
	 *
	 * @return {boolean} Whether a refresh is needed
	 *
	 * @private
	 */
	ODataModel.prototype._isRefreshNeeded = function(oRequest, oResponse) {
		var bRefreshNeeded = false,
		sErrorCode,
		aErrorResponses = [],
			that = this;

		if (!this.bRefreshAfterChange) {
			return bRefreshNeeded;
		}
		// if this is a batch request, loop through the batch operations, find change requests
		// and check every change request individually
		if (oRequest.data && Array.isArray(oRequest.data.__batchRequests)) {
			if (oResponse) {
				aErrorResponses = that._getBatchErrors(oResponse.data);
				each(aErrorResponses, function(iIndex, oErrorResponse){
					if (oErrorResponse.response && oErrorResponse.response.statusCode == "412") {
						sErrorCode = oErrorResponse.response.statusCode;

						return false;
					}

					return true;
				});
				if (sErrorCode) {
					return false;
				}
			}
			each(oRequest.data.__batchRequests, function(iIndex, oBatchRequest) {
				if (Array.isArray(oBatchRequest.__changeRequests)) {
					each(oBatchRequest.__changeRequests, function(iIndex, oChangeRequest) {
						bRefreshNeeded = bRefreshNeeded || that._isRefreshNeeded(oChangeRequest);
						return !bRefreshNeeded; //break
					});
				}
				return !bRefreshNeeded; //break
			});
		} else if (oRequest.method === "GET" ) {
			return false;
		} else if (oResponse && oResponse.statusCode == "412") {
			bRefreshNeeded = false;
		} else {
			bRefreshNeeded = true;
		}

		return bRefreshNeeded;
	};

	/**
	 * Trigger a PUT/MERGE request to the odata service that was specified in the model constructor. Please note that deep updates are not supported
	 * and may not work. These should be done seperate on the entry directly.
	 *
	 * @param {string} sPath A string containing the path to the data that should be updated.
	 * 		The path is concatenated to the sServiceUrl which was specified
	 * 		in the model constructor.
	 * @param {object} oData data of the entry that should be updated.
	 * @param {object} [mParameters] Optional, can contain the following attributes:
	 * @param {object} [mParameters.context] If specified the sPath has to be is relative to the path given with the context.
	 * @param {function} [mParameters.success] a callback function which is called when the data has been successfully updated.
	 * @param {function} [mParameters.error] a callback function which is called when the request failed.
	 * 		The handler can have the parameter <code>oError</code> which contains additional error information.
	 * @param {boolean} [mParameters.merge=false] trigger a MERGE request instead of a PUT request to perform a differential update
	 * @param {string} [mParameters.eTag] If specified, the If-Match-Header will be set to this Etag.
	 * @param {boolean} [mParameters.async=false] Whether the request should be done asynchronously.
	 * 		Please be advised that this feature is officially unsupported as using asynchronous
	 * 		requests can lead to data inconsistencies if the application does not make sure that
	 * 		the request was completed before continuing to work with the data.
	 * @param {Object<string,string>} [mParameters.urlParameters] A map containing the parameters that will be passed as query strings
	 *
	 * @return {object} an object which has an <code>abort</code> function to abort the current request.
	 *
	 * @public
	 */

	ODataModel.prototype.update = function(sPath, oData, mParameters) {
		var fnSuccess, fnError, bMerge, oRequest, sUrl, oContext, sETag, oRequestHandle,
			oBatchRequest, oStoredEntry, sKey,
			mUrlParams,
			bAsync = false;

	//ensure compatibility, check for old or new declaration of parameters
		if (mParameters instanceof Context || arguments.length > 3) {
			oContext  = mParameters;
			fnSuccess = arguments[3];
			fnError = arguments[4];
			bMerge = arguments[5];
		} else {
			//we are using the new parameters
			// For API compatibility, we also allow the "old" hungarian syntax
			oContext  = mParameters.context || mParameters.oContext;
			fnSuccess = mParameters.success || mParameters.fnSuccess;
			fnError   = mParameters.error   || mParameters.fnError;
			sETag     = mParameters.eTag    || mParameters.sETag;
			bMerge    = typeof (mParameters.merge) == "undefined"
				? mParameters.bMerge === true
				: mParameters.merge  === true;
			bAsync    = typeof (mParameters.async) == "undefined"
				? mParameters.bAsync === true
				: mParameters.async  === true;
			mUrlParams = mParameters.urlParameters;
		}

		sUrl = this._createRequestUrl(sPath, oContext, mUrlParams, this.bUseBatch);
		if (bMerge) {
			oRequest = this._createRequest(sUrl, "MERGE", bAsync, oData, sETag);
		} else {
			oRequest = this._createRequest(sUrl, "PUT", bAsync, oData, sETag);
		}

		sPath = this._normalizePath(sPath, oContext);
		oStoredEntry = this._getObject(sPath);
		oRequest.keys = {};
		if (oStoredEntry) {
			sKey = this._getKey(oStoredEntry);
			oRequest.keys[sKey] = true;
		}

		if (this.bUseBatch) {
			oBatchRequest = this._createBatchRequest([{__changeRequests:[oRequest]}], bAsync);
			oRequestHandle = this._submitRequest(oBatchRequest, this.bUseBatch, fnSuccess, fnError, true);
		} else {
			oRequestHandle = this._submitRequest(oRequest, this.bUseBatch, fnSuccess, fnError);
		}
		return oRequestHandle;
	};

	/**
	 * Trigger a POST request to the odata service that was specified in the model constructor. Please note that deep creates are not supported
	 * and may not work.
	 *
	 * @param {string} sPath A string containing the path to the collection where an entry
	 *		should be created. The path is concatenated to the sServiceUrl
	 *		which was specified in the model constructor.
	 * @param {object} oData data of the entry that should be created.
	 * @param {object} [mParameters] Optional parameter map containing any of the following properties:
	 * @param {object} [mParameters.context] If specified the sPath has to be relative to the path given with the context.
	 * @param {function} [mParameters.success] a callback function which is called when the data has
	 *		been successfully retrieved. The handler can have the
	 *		following parameters: oData and response.
	 * @param {function} [mParameters.error] a callback function which is called when the request failed.
	 *		The handler can have the parameter <code>oError</code> which contains additional error information.
	 * @param {boolean} [mParameters.async=false] Whether the request should be done asynchronously. Default: false
	 * 		Please be advised that this feature is officially unsupported as using asynchronous
	 * 		requests can lead to data inconsistencies if the application does not make sure that
	 * 		the request was completed before continuing to work with the data.
	 * @param {Object<string,string>} [mParameters.urlParameters] A map containing the parameters that will be passed as query strings
	 *
	 * @return {object} an object which has an <code>abort</code> function to abort the current request.
	 *
	 * @public
	 */
	ODataModel.prototype.create = function(sPath, oData, mParameters) {
		var oRequest, oBatchRequest, sUrl, oRequestHandle, oEntityMetadata,
			oContext, fnSuccess, fnError, bAsync = false, mUrlParams;

		if (mParameters && typeof (mParameters) == "object" && !(mParameters instanceof Context)) {
			// The object parameter syntax has been used.
			oContext	= mParameters.context;
			fnSuccess	= mParameters.success;
			mUrlParams	= mParameters.urlParameters;
			fnError		= mParameters.error;
			bAsync		= mParameters.async === true;
		} else {
			// Legacy parameter syntax is used
			oContext	= mParameters;
			fnSuccess	= arguments[3];
			fnError		= arguments[4];
		}

		sUrl = this._createRequestUrl(sPath, oContext, mUrlParams, this.bUseBatch);
		oRequest = this._createRequest(sUrl, "POST", bAsync, oData);

		sPath = this._normalizePath(sPath, oContext);
		oEntityMetadata = this.oMetadata._getEntityTypeByPath(sPath);
		oRequest.entityTypes = {};
		if (oEntityMetadata) {
			oRequest.entityTypes[oEntityMetadata.entityType] = true;
		}

		if (this.bUseBatch) {
			oBatchRequest = this._createBatchRequest([{__changeRequests:[oRequest]}], bAsync);
			oRequestHandle = this._submitRequest(oBatchRequest, this.bUseBatch, fnSuccess, fnError, true);
		} else {
			oRequestHandle = this._submitRequest(oRequest, this.bUseBatch, fnSuccess, fnError);
		}
		return oRequestHandle;
	};

	/**
	 * Trigger a DELETE request to the odata service that was specified in the model constructor.
	 *
	 * @param {string} sPath A string containing the path to the data that should be removed.
	 *		The path is concatenated to the sServiceUrl which was specified in the model constructor.
	 * @param {object} [mParameters] Optional, can contain the following attributes: oContext, fnSuccess, fnError, sETag:
	 * @param {object} [mParameters.context] If specified the sPath has to be relative to the path given with the context.
	 * @param {function} [mParameters.success]  a callback function which is called when the data has been successfully retrieved.
	 *		The handler can have the following parameters: <code>oData</code> and <code>response</code>.
	 * @param {function} [mParameters.error] a callback function which is called when the request failed.
	 *		The handler can have the parameter: <code>oError</code> which contains additional error information.
	 * @param {string} [mParameters.eTag] If specified, the If-Match-Header will be set to this Etag.
	 * @param {object} [mParameters.payload] if specified, this optional variable can be used to pass a payload into the delete function,
	 *		e.g. if the entry which should be deleted has not been bound to any control, but has been retrieved via read, only.
	 * @param {boolean} [mParameters.async=false] Whether the request should be done asynchronously.
	 * 		Please be advised that this feature is officially unsupported as using asynchronous
	 * 		requests can lead to data inconsistencies if the application does not make sure that
	 * 		the request was completed before continuing to work with the data.
	 * @param {Object<string,string>} [mParameters.urlParameters] A map containing the parameters that will be passed as query strings
	 *
	 * @return {object} an object which has an <code>abort</code> function to abort the current request.
	 *
	 * @public
	 */
	ODataModel.prototype.remove = function(sPath, mParameters) {
		var oContext, sEntry, oStoredEntry, fnSuccess, fnError, oRequest, sUrl,
			sETag, sKey, oPayload, _fnSuccess, oBatchRequest, oRequestHandle,
			mUrlParams,
			bAsync = false,
			that = this;

		// maintain compatibility, check if the old or new function parameters are used and set values accordingly:
		if ((mParameters instanceof Context) || arguments[2]) {
			oContext  = mParameters;
			fnSuccess = arguments[2];
			fnError   = arguments[3];
		} else if (mParameters) {
			oContext  = mParameters.context || mParameters.oContext;
			fnSuccess = mParameters.success || mParameters.fnSuccess;
			fnError   = mParameters.error   || mParameters.fnError;
			sETag     = mParameters.eTag    || mParameters.sETag;
			oPayload  = mParameters.payload || mParameters.oPayload;
			bAsync    = typeof (mParameters.async) == "undefined"
				? mParameters.bAsync === true
				: mParameters.async === true;
			mUrlParams = mParameters.urlParameters;
		}

		_fnSuccess = function(oData, oResponse) {
			sEntry = sUrl.substr(sUrl.lastIndexOf('/') + 1);
			//remove query params if any
			if (sEntry.indexOf('?') != -1) {
				sEntry = sEntry.substr(0, sEntry.indexOf('?'));
			}
			delete that.oData[sEntry];
			delete that.mContexts["/" + sEntry]; // contexts are stored starting with /

			if (fnSuccess) {
				fnSuccess(oData, oResponse);
			}
		};

		sUrl = this._createRequestUrl(sPath, oContext, mUrlParams, this.bUseBatch);
		oRequest = this._createRequest(sUrl, "DELETE", bAsync, oPayload, sETag);

		sPath = this._normalizePath(sPath, oContext);
		oStoredEntry = this._getObject(sPath);
		oRequest.keys = {};
		if (oStoredEntry) {
			sKey = this._getKey(oStoredEntry);
			oRequest.keys[sKey] = true;
		}

		if (this.bUseBatch) {
			oBatchRequest = this._createBatchRequest([{__changeRequests:[oRequest]}], bAsync);
			oRequestHandle = this._submitRequest(oBatchRequest, this.bUseBatch, _fnSuccess, fnError, true);
		} else {
			oRequestHandle = this._submitRequest(oRequest, this.bUseBatch, _fnSuccess, fnError);
		}
		return oRequestHandle;

	};

	/**
	 * Trigger a request to the function import odata service that was specified in the model constructor.
	 *
	 * @param {string} sFunctionName A string containing the name of the function to call.
	 *		The name is concatenated to the sServiceUrl which was specified in the model constructor.
	 * @param {object} [mParameters] Optional parameter map containing any of the following properties:
	 * @param {string} [mParameters.method] A string containing the type of method to call this function with
	 * @param {Object<string,string>} [mParameters.urlParameters] A map containing the parameters that will be passed as query strings
	 * @param {object} [mParameters.context] If specified the sPath has to be relative to the path given with the context.
	 * @param {function} [mParameters.success] a callback function which is called when the data has been successfully retrieved.
	 *		The handler can have the following parameters: <code>oData</code> and <code>response</code>.
	 * @param {function} [mParameters.error] a callback function which is called when the request failed.
	 *		The handler can have the parameter: <code>oError</code> which contains additional error information.
	 * @param {boolean} [mParameters.async=false] Whether or not to send the request asynchronously. Default: false
	 * 		In case sMethod is "GET", the request is always asynchronous.
	 * 		Please be advised that this feature is officially unsupported as using asynchronous
	 * 		requests can lead to data inconsistencies if the application does not make sure that
	 * 		the request was completed before continuing to work with the data.
	* @return {object} an object which has an <code>abort</code> function to abort the current request.
	 *
	 * @public
	 */
	ODataModel.prototype.callFunction = function (sFunctionName, mParameters) {
		var oRequest, oBatchRequest, sUrl, oRequestHandle,
			oFunctionMetadata,
			oParameters, oContext, fnSuccess, fnError, bAsync,
			sMethod = "GET",
			oUrlParams = {},
			that = this;

		if (mParameters && typeof (mParameters) == "object") {
			// The object parameter syntax has been used.
			sMethod     = mParameters.method ? mParameters.method : sMethod;
			oParameters = mParameters.urlParameters;
			oContext    = mParameters.context;
			fnSuccess   = mParameters.success;
			fnError     = mParameters.error;
			bAsync      = mParameters.async === true;
		} else {
			// Legacy parameter syntax is used
			sMethod     = mParameters;
			oParameters = arguments[2];
			oContext    = arguments[3];
			fnSuccess   = arguments[4];
			fnError     = arguments[5];
			bAsync      = arguments[6] === true;
		}


		oFunctionMetadata = this.oMetadata._getFunctionImportMetadata(sFunctionName, sMethod);
		assert(oFunctionMetadata, "Function " + sFunctionName + " not found in the metadata !");

		if (oFunctionMetadata) {
			sUrl = this._createRequestUrl(sFunctionName, oContext, null,  this.bUseBatch);
			var sUrlURI = new URI(sUrl);
			if (oFunctionMetadata.parameter != null) {
				each(oParameters, function (sParameterName, oParameterValue) {
					var matchingParameters = oFunctionMetadata.parameter.filter(function (oParameter) {
						return oParameter.name == sParameterName && oParameter.mode == "In";
					});
					if (matchingParameters.length > 0) {
						var matchingParameter = matchingParameters[0];
						oUrlParams[sParameterName] = ODataUtils.formatValue(oParameterValue, matchingParameter.type);
					} else {
						Log.warning("Parameter " + sParameterName + " is not defined for function call " + sFunctionName + "!");
					}
				});
			}
			if (sMethod === "GET") {
	//			parameters are encoded in read function
				return that.read(sFunctionName, oContext, oUrlParams, true, fnSuccess, fnError);
			} else {
				each(oUrlParams, function (sParameterName, oParameterValue) {
					// addQuery also encodes the url
					sUrlURI.addQuery(sParameterName, oParameterValue);
				});
				oRequest = this._createRequest(sUrlURI.toString(), sMethod, bAsync);

				if (this.bUseBatch) {
					oBatchRequest = this._createBatchRequest([{__changeRequests:[oRequest]}], bAsync);
					oRequestHandle = this._submitRequest(oBatchRequest, this.bUseBatch, fnSuccess, fnError, true);
				} else {
					oRequestHandle = this._submitRequest(oRequest, this.bUseBatch, fnSuccess, fnError);
				}
				return oRequestHandle;
			}
		}

		return undefined;
	};

	/**
	 * Trigger a GET request to the odata service that was specified in the model constructor.
	 * The data will not be stored in the model. The requested data is returned with the response.
	 *
	 * @param {string} sPath A string containing the path to the data which should
	 *		be retrieved. The path is concatenated to the sServiceUrl
	 *		which was specified in the model constructor.
	 * @param {object} [mParameters] Optional parameter map containing any of the following properties:
	 * @param {object} [mParameters.context] If specified the sPath has to be is relative to the path
	 * 		given with the context.
	 * @param {Object<string,string>} [mParameters.urlParameters] A map containing the parameters that will be passed as query strings
	 * @param {boolean} [mParameters.async=true] true for asynchronous requests.
	 * @param {array} [mParameters.filters] an array of sap.ui.model.Filter to be included in the request URL
	 * @param {array} [mParameters.sorters] an array of sap.ui.model.Sorter to be included in the request URL
	 * @param {function} [mParameters.success] a callback function which is called when the data has
	 *		been successfully retrieved. The handler can have the
	 *		following parameters: oData and response.
	 * @param {function} [mParameters.error] a callback function which is called when the request
	 * 		failed. The handler can have the parameter: oError which contains
	 * additional error information.
	 *
	 * @return {object} an object which has an <code>abort</code> function to abort the current request.
	 *
	 * @public
	 */
	ODataModel.prototype.read = function(sPath, mParameters) {
		var oRequest, sUrl, oRequestHandle, oBatchRequest,
			oContext, mUrlParams, bAsync, fnSuccess, fnError,
			aFilters, aSorters, sFilterParams, sSorterParams,
			oFilter, oEntityType, sResolvedPath,
			aUrlParams;

		if (mParameters && typeof (mParameters) == "object" && !(mParameters instanceof Context)) {
			// The object parameter syntax has been used.
			oContext   = mParameters.context;
			mUrlParams = mParameters.urlParameters;
			bAsync     = mParameters.async !== false; // Defaults to true...
			fnSuccess  = mParameters.success;
			fnError    = mParameters.error;
			aFilters   = mParameters.filters;
			aSorters   = mParameters.sorters;
		} else {
			// Legacy parameter syntax is used
			oContext   = mParameters;
			mUrlParams = arguments[2];
			bAsync     = arguments[3] !== false; // Defaults to true...
			fnSuccess  = arguments[4];
			fnError    = arguments[5];
		}

		// bAsync default is true ?!
		bAsync = bAsync !== false;

		aUrlParams = ODataUtils._createUrlParamsArray(mUrlParams);

		// Add filter/sorter to URL parameters
		sSorterParams = ODataUtils.createSortParams(aSorters);
		if (sSorterParams) { aUrlParams.push(sSorterParams); }

		if (aFilters && !this.oMetadata) {
			Log.fatal("Tried to use filters in read method before metadata is available.");
		} else {
			sResolvedPath = this._normalizePath(sPath, oContext);
			oEntityType = this.oMetadata && this.oMetadata._getEntityTypeByPath(sResolvedPath);
			oFilter = FilterProcessor.groupFilters(aFilters);
			sFilterParams = ODataUtils.createFilterParams(oFilter, this.oMetadata, oEntityType);
			if (sFilterParams) { aUrlParams.push(sFilterParams); }
		}

		sUrl = this._createRequestUrl(sPath, oContext, aUrlParams, this.bUseBatch);
		oRequest = this._createRequest(sUrl, "GET", bAsync);

		if (this.bUseBatch) {
			oBatchRequest = this._createBatchRequest([oRequest], bAsync);
			oRequestHandle = this._submitRequest(oBatchRequest, this.bUseBatch, fnSuccess, fnError, true);
		} else {
			oRequestHandle = this._submitRequest(oRequest, this.bUseBatch, fnSuccess, fnError);
		}
		return oRequestHandle;
	};

	/**
	 * Creates a single batch operation (read or change operation) which can be used in a batch
	 * request.
	 *
	 * @param {string} sPath
	 *   A string containing the path to the collection or entry where the batch operation should
	 *   be performed; the path is concatenated to the <code>sServiceUrl</code> which was specified
	 *   in the model constructor
	 * @param {string} sMethod
	 *   For the batch operation; possible values are <code>GET</code>, <code>PUT</code>,
	 *   <code>MERGE</code>, <code>POST</code> or <code>DELETE</code>
	 * @param {object} [oData]
	 *   Optional data payload which should be created, updated, deleted in a change batch operation
	 * @param {object} [oParameters]
	 *   Optional parameter for additional information introduced in SAPUI5 1.9.1
	 * @param {string} [oParameters.sETag]
	 *   An ETag which can be used for concurrency control. If it is specified, it will be used in
	 *   an If-Match-Header in the request to the server for this entry.
	 *
	 * @returns {object} The created batch operation
	 * @public
	 */
	ODataModel.prototype.createBatchOperation = function(sPath, sMethod, oData, oParameters) {
		var oChangeHeader = {}, sETag, oStoredEntry, sKey, oEntityType;

		extend(oChangeHeader, this.mCustomHeaders, this.oHeaders);

		// for batch remove starting / if any
		if (sPath.startsWith("/")) {
			sPath = sPath.substr(1);
		}

		if (oParameters) {
			sETag = oParameters.sETag;
		}

		if (sMethod != "GET") {
			sETag = this._getETag(sPath, oData, sETag);
			if (sETag) {
				oChangeHeader["If-Match"] = sETag;
			}
		}
		// make sure to set content type header for POST/PUT requests when using JSON format to prevent datajs to add "odata=verbose" to the content-type header
		// may be removed as later gateway versions support this
		if (this.bJSON) {
			if (sMethod != "DELETE" && sMethod != "GET" && this.sMaxDataServiceVersion === "2.0") {
				oChangeHeader["Content-Type"] = "application/json";
			}
		} else {
			// for XML case set the content-type accordingly so that the data is transformed to XML in the batch part
			oChangeHeader["Content-Type"] = "application/atom+xml";
		}

		var oRequest = {
			requestUri: sPath,
			method: sMethod.toUpperCase(),
			headers: oChangeHeader
		};

		if (oData) {
			oRequest.data = oData;
		}

		if (sMethod != "GET" && sMethod != "POST") {
			if (sPath && sPath.indexOf("/") != 0) {
				sPath = '/' + sPath;
			}
			oStoredEntry = this._getObject(sPath);
			if (oStoredEntry) {
				sKey = this._getKey(oStoredEntry);
				oRequest.keys = {};
				oRequest.keys[sKey] = true;
			}
		} else if (sMethod == "POST") {
			// remove URL params
			var sNormalizedPath = sPath;
			if (sPath.indexOf('?') != -1 ) {
				sNormalizedPath = sPath.substr(0, sPath.indexOf('?'));
			}
			oEntityType = this.oMetadata._getEntityTypeByPath(sNormalizedPath);
			if (oEntityType) {
				oRequest.entityTypes = {};
				oRequest.entityTypes[oEntityType.entityType] = true;
			}
		}
		return oRequest;
	};

	/**
	 * Appends the read batch operations to the end of the batch stack. Only GET batch operations
	 * should be included in the specified array. If an illegal batch operation is added to the
	 * batch nothing will be performed and false will be returned.
	 *
	 * @param {any[]} aReadOperations
	 *   An array of read batch operations created via <code>createBatchOperation</code> with
	 *   <code>sMethod = "GET"</code>
	 *
	 * @returns {false|undefined} <code>false</code>, if an illegal batch operation is added
	 * @public
	 */
	ODataModel.prototype.addBatchReadOperations = function(aReadOperations) {
		if (!Array.isArray(aReadOperations) || aReadOperations.length <= 0) {
			Log.warning("No array with batch operations provided!");
			return false;
		}
		var that = this;
		each(aReadOperations, function(iIndex, oReadOperation) {
			if (oReadOperation.method != "GET") {
				Log.warning("Batch operation should be a GET operation!");
				return false;
			}
			that.aBatchOperations.push(oReadOperation);

			return true;
		});

		return undefined;
	};

	/**
	 * Appends the change batch operations to the end of the batch stack. Only <code>PUT</code,
	 * <code>POST</code> or <code>DELETE</code> batch operations should be included in the specified
	 * array. The operations in the array will be included in a single changeset. To embed change
	 * operations in different change sets call this method with the corresponding change operations
	 * again. If an illegal batch operation is added to the change set nothing will be performed and
	 * false will be returned.
	 *
	 * @param {any[]} aChangeOperations
	 *   An array of change batch operations created via <code>createBatchOperation</code> with
	 *   parameter <code>sMethod = "POST"/"PUT"/"MERGE"/"DELETE"</code>
	 *
	 * @returns {false|undefined} <code>false</code>, if an illegal batch operation is added
	 *
	 * @public
	 */
	ODataModel.prototype.addBatchChangeOperations = function(aChangeOperations) {
		if (!Array.isArray(aChangeOperations) || aChangeOperations.length <= 0) {
			return false;
		}
		each(aChangeOperations, function(iIndex, oChangeOperation) {
			if (oChangeOperation.method != "POST" && oChangeOperation.method != "PUT" && oChangeOperation.method != "MERGE" && oChangeOperation.method != "DELETE") {
				Log.warning("Batch operation should be a POST/PUT/MERGE/DELETE operation!");
				return false;
			}

			return true;
		});
		this.aBatchOperations.push({ __changeRequests : aChangeOperations });

		return undefined;
	};

	/**
	 * Removes all operations in the current batch.
	 * @public
	 */
	ODataModel.prototype.clearBatch = function() {
		this.aBatchOperations = [];
	};

	/**
	 * Submits the collected changes in the batch which were collected via <code>addBatchReadOperations</code> or
	 * <code>addBatchChangeOperations</code>. The batch will be cleared afterwards. If the batch is empty, no request
	 * will be sent and false will be returned.
	 *
	 * <b>Note:</b> No data will be stored in the model as long as <code>bImportData</code> is not set.
	 *
	 * @param {function} [fnSuccess]
	 *            A callback function which is called when the batch request has been successfully sent.
	 *            Note: Errors that may have come up in the single batch operations can be accessed
	 *            in the <code>aErrorResponses</code> parameter in the callback handler.
	 *            The handler can have the following parameters: <code>oData</code>, <code>oResponse</code> and
	 *            <code>aErrorResponses</code>.
	 * @param {function} [fnError]
	 *            A callback function which is called when the batch request failed.
	 *            The handler can have the parameter <code>oError</code> which contains additional error information.
	 * @param {boolean} [bAsync=true]
	 *            true for asynchronous request
	 * @param {boolean} [bImportData=false]
	 *            Whether response data should be imported into the model
	 * @return {object} An object which has an <code>abort</code> function to abort the current request.
	 *            Returns false if no request will be performed because the batch is empty.
	 *
	 * @public
	 */
	ODataModel.prototype.submitBatch = function(fnSuccess, fnError, bAsync, bImportData) {
		var oRequest, oRequestHandle, that = this;

		function _handleSuccess(oData, oResponse) {
			if (fnSuccess) {
				fnSuccess(oData, oResponse, that._getBatchErrors(oData));
			}
		}

		// ensure compatibility with old declaration: // bAsync, fnSuccess, fnError
		if (!(typeof (fnSuccess) == "function")) {
			var fnOldError = bAsync;
			var fnOldSuccess = fnError;
			bAsync = fnSuccess;
			fnSuccess = fnOldSuccess;
			fnError = fnOldError;
		}

		// bAsync default is true ?!
		bAsync = bAsync !== false;

		if (this.aBatchOperations.length <= 0) {
			Log.warning("No batch operations in batch. No request will be triggered!");
			return false;
		}
		oRequest = this._createBatchRequest(this.aBatchOperations, bAsync);
		oRequestHandle = this._submitRequest(oRequest, true, _handleSuccess, fnError, false, bImportData);
		this.clearBatch();
		return oRequestHandle;
	};

	/**
	 * Return the metadata object. Please note that when using the model with
	 * <code>bLoadMetadataAsync = true</code> then this function might return <code>undefined</code
	 * because the metadata has not been loaded yet. In this case attach to the
	 * <code>metadataLoaded</code> event to get notified when the metadata is available and then
	 * call this function.
	 *
	 * @return {Object|undefined} Metadata object
	 * @public
	 */
	ODataModel.prototype.getServiceMetadata = function() {
		if (this.oMetadata && this.oMetadata.isLoaded()) {
			return this.oMetadata.getServiceMetadata();
		}

		return undefined;
	};

	/**
	 * Return the annotation object. Please note that when using the model with
	 * <code>bLoadMetadataAsync = true</code> then this function might return undefined because the
	 * metadata has not been loaded yet. In this case attach to the <code>annotationsLoaded</code>
	 * event to get notified when the annotations are available and then call this function.
	 *
	 * @return {Object|undefined} Metadata object
	 * @public
	 */
	ODataModel.prototype.getServiceAnnotations = function() {
		if (this.oAnnotations && this.oAnnotations.getAnnotationsData) {
			return this.oAnnotations.getAnnotationsData();
		}

		return undefined;
	};

	/**
	 * Submits the collected changes which were collected by the setProperty method. A MERGE request will be triggered to only update the changed properties.
	 * If a URI with a $expand System Query Option was used then the expand entries will be removed from the collected changes.
	 * Changes to this entries should be done on the entry itself. So no deep updates are supported.
	 *
	 * @param {function} [fnSuccess] a callback function which is called when the data has
	 *            					 been successfully updated. The handler can have the
	 *            	                 following parameters: oData and response.
	 * @param {function} [fnError] a callback function which is called when the request failed. The handler can have the parameter: oError which contains
	 * additional error information
	 * @param {object} [oParameters] optional parameter for additional information introduced in SAPUI5 1.9.1
	 * @param {string} [oParameters.sETag] an ETag which can be used for concurrency control. If it is specified, it will be used in an If-Match-Header in the request to the server for this entry.
	 * @return {object} an object which has an <code>abort</code> function to abort the current request.
	 *
	 * @public
	 */
	ODataModel.prototype.submitChanges = function(fnSuccess, fnError, oParameters) {

		var oRequest, oPayload, that = this, sPath, sETag, sType, sMetadataETag, oStoredEntry, sKey;

		if (this.sChangeKey) {
			sPath = this.sChangeKey.replace(this.sServiceUrl,'');
			oStoredEntry = this._getObject(sPath);
			oPayload = oStoredEntry;

			if (isPlainObject(oStoredEntry)) {
				// do a copy of the payload or the changes will be deleted in the model as well (reference)
				oPayload = merge({}, oStoredEntry);
				// remove metadata, navigation properties to reduce payload
				if (oPayload.__metadata) {
					sType = oPayload.__metadata.type;
					sMetadataETag = oPayload.__metadata.etag;
					delete oPayload.__metadata;
					if (sType || sMetadataETag) {
						oPayload.__metadata = {};
					}
					// type information may be needed by an odata service!!!
					if (sType) {
						oPayload.__metadata.type = sType;
					}
					// etag information may be needed by an odata service, too!!!
					if (sMetadataETag) {
						oPayload.__metadata.etag = sMetadataETag;
					}
				}
				each(oPayload, function(sPropName, oPropValue) {
					if (oPropValue && oPropValue.__deferred) {
						delete oPayload[sPropName];
					}
				});

				// delete expand properties = navigation properties
				var oEntityType = this.oMetadata._getEntityTypeByPath(sPath);
				if (oEntityType) {
					var aNavProps = this.oMetadata._getNavigationPropertyNames(oEntityType);
					each(aNavProps, function(iIndex, sNavPropName) {
						delete oPayload[sNavPropName];
					});
				}
				// remove any yet existing references which should already have been deleted
				oPayload = this._removeReferences(oPayload);
			}
			if (oParameters && oParameters.sETag) {
				sETag = oParameters.sETag;
			}

			oRequest = this._createRequest(this.sChangeKey, "MERGE", true, oPayload, sETag);
			if (this.sUrlParams) {
				oRequest.requestUri += "?" + this.sUrlParams;
			}

			//get entry from model. If entry exists get key for update bindings
			oRequest.keys = {};
			if (oStoredEntry) {
				sKey = this._getKey(oStoredEntry);
				oRequest.keys[sKey] = true;
			}

			this.oRequestQueue[this.sChangeKey] = oRequest;
		}

		if (isEmptyObject(this.oRequestQueue)) {
			return undefined;
		}

		if (this.bUseBatch) {
			var aChangeRequests = [];
			each(this.oRequestQueue, function(sKey, oCurrentRequest){
				delete oCurrentRequest._oRef;
				var oReqClone = merge({}, oCurrentRequest);
				oCurrentRequest._oRef = oReqClone;

				oReqClone.requestUri = oReqClone.requestUri.replace(that.sServiceUrl + '/','');
				if (oReqClone.data._bCreate) {
					delete oReqClone.data._bCreate;
				}
				aChangeRequests.push(oReqClone);
			});

			oRequest = this._createBatchRequest([{__changeRequests:aChangeRequests}], true);
			this._submitRequest(oRequest, this.bUseBatch, fnSuccess, fnError, true);
		} else {
			//loop request queue
			each(this.oRequestQueue, function(sKey, oCurrentRequest){
				// clone request and store the clone as reference to compare it in updateRequestQueue.
				// We send the cloned request which will be modified by datajs but we want to keep the original request stored
				// because it may fail and we need to send the request again.
				delete oCurrentRequest._oRef;
				var oReqClone = merge({}, oCurrentRequest);
				oCurrentRequest._oRef = oReqClone;
				//remove create flag
				 if (oReqClone.data && oReqClone.data._bCreate) {
					 delete oReqClone.data._bCreate;
				 }

				that._submitRequest(oReqClone, this.bUseBatch, fnSuccess, fnError, true);
			});
		}
		return undefined;
	};

	/*
	 * updateRequestQueue
	 * @private
	 */
	ODataModel.prototype._updateRequestQueue = function(oRequest, bBatch) {
		var aBatchRequests,
			aChangeRequests,
			oChangeRequest,
			that = this;

		if (bBatch) {
			aBatchRequests = oRequest.data.__batchRequests;
			if (aBatchRequests) {
				for (var i = 0; i < aBatchRequests.length; i++) {
					// get change requests and corresponding responses - the latter are in the same order as the requests according to odata spec
					aChangeRequests = aBatchRequests[i].__changeRequests;
					if (aChangeRequests) {
						for (var j = 0; j < aChangeRequests.length; j++) {
							oChangeRequest = aChangeRequests[j];
							/*eslint-disable no-loop-func */
							each(this.oRequestQueue, function(sKey,oCurrentRequest) {
								if (oCurrentRequest._oRef === oChangeRequest && sKey !== that.sChangeKey) {
									delete that.oRequestQueue[sKey];
									delete that.oData[sKey];
									delete that.mContexts["/" + sKey];
								} else if (that.sChangeKey && sKey === that.sChangeKey) {
									delete that.oRequestQueue[sKey];
									that.sChangeKey = null;
								}
							});
							/*eslint-enable no-loop-func */
						}
					}
				}
			}
		} else {
			each(this.oRequestQueue, function(sKey,oCurrentRequest) {
				if (oCurrentRequest._oRef === oRequest && sKey !== that.sChangeKey) {
					delete that.oRequestQueue[sKey];
					delete that.oData[sKey];
					delete that.mContexts["/" + sKey];
				} else if (that.sChangeKey && sKey === that.sChangeKey) {
					delete that.oRequestQueue[sKey];
					that.sChangeKey = null;
				}
			});
		}
	};

	/**
	 *
	 * Resets the collected changes by the setProperty method and reloads the data from the server.
	 *
	 * @param {function} [fnSuccess] a callback function which is called when the data has
	 *            					 been successfully resetted. The handler can have the
	 *            	                 following parameters: oData and response.
	 * @param {function} [fnError] a callback function which is called when the request failed
	 *
	 * @public
	 */
	ODataModel.prototype.resetChanges = function(fnSuccess, fnError) {

		var sPath;
		if (this.sChangeKey) {
			sPath = this.sChangeKey.replace(this.sServiceUrl,'');
			this._loadData(sPath, null, fnSuccess, fnError);
		}
	};

	/**
	 * Sets a new value for the given property <code>sPropertyName</code> in the model without
	 * triggering a server request. This can be done by the <code>submitChanges</code> method.
	 *
	 * Note: Only one entry of one collection can be updated at once. Otherwise a
	 * <code>fireRejectChange</code> event is fired.
	 *
	 * Before updating a different entry the existing changes of the current entry have to be
	 * submitted or reset by the corresponding methods: <code>submitChanges</code>,
	 * <code>resetChanges</code>.
	 *
	 * IMPORTANT: All pending changes are reset in the model if the application triggers any kind of
	 * refresh on that entry. Make sure to submit the pending changes first. To determine if there
	 * are any pending changes call the <code>hasPendingChanges</code> method.
	 *
	 * @param {string} sPath
	 *   Path of the property to set
	 * @param {any} oValue
	 *   Value to set the property to
	 * @param {object} [oContext]
	 *   The context which will be used to set the property
	 * @param {boolean} [bAsyncUpdate]
	 *   Whether to update other bindings dependent on this property asynchronously
	 *
	 * @return {boolean} Whether the value was set correctly
	 *
	 * @public
	 */
	ODataModel.prototype.setProperty = function(sPath, oValue, oContext, bAsyncUpdate) {

		var sProperty, oEntry = { }, oData = { },
			sChangeKey = this._createRequestUrl(sPath, oContext),
			sObjectPath = sPath.substring(0, sPath.lastIndexOf("/")),
			sKey, aPathSegments,
			mChangedEntities = {},
			success = false;

		// check if path / context is valid
		if (!this.resolve(sPath, oContext) ) {
			return false;
		}

		// extract the Url that points to the 'entry'. We need to do this if a complex type will be updated.
		sChangeKey = sChangeKey.replace(this.sServiceUrl + '/','');
		sChangeKey = sChangeKey.substring(0, sChangeKey.indexOf("/"));
		sChangeKey = this.sServiceUrl + '/' + sChangeKey;

		sProperty = sPath.substr(sPath.lastIndexOf("/") + 1);

		oData = this._getObject(sObjectPath, oContext);
		if (!oData) {
			return false;
		}

		//check all path segments to find the entity; The last segment can also point to a complex type
		aPathSegments = sObjectPath.split("/");
		for (var i = aPathSegments.length - 1; i >= 0; i--) {
			oEntry = this._getObject(aPathSegments.join("/"), oContext);
			if (oEntry) {
				sKey = this._getKey(oEntry);
				if (sKey) {
					break;
				}
			}
			aPathSegments.splice(i - 1,1);
		}

		if (!sKey) {
			sKey = this._getKey(oContext);
		}

		if (sKey) {
			mChangedEntities[sKey] = true;
		}

		if (oData._bCreate) {
			oData[sProperty] = oValue;
			success = true;
			this.checkUpdate(false, bAsyncUpdate, mChangedEntities);
		} else {
			if (!this.sChangeKey) {
				this.sChangeKey = sChangeKey;
			}

			if (this.sChangeKey == sChangeKey) {
				oData[sProperty] = oValue;
				success = true;
				this.checkUpdate(false, bAsyncUpdate, mChangedEntities);
			} else {
				this.fireRejectChange(
						{rejectedValue : oValue,
							oldValue: oData[sProperty]}
				);
			}
		}
		return success;

	};


	ODataModel.prototype._isHeaderPrivate = function(sHeaderName) {
		// case sensitive check needed to make sure private headers cannot be overridden by difference in the upper/lower case (e.g. accept and Accept).
		switch (sHeaderName.toLowerCase()) {
			case "accept":
			case "accept-language":
			case "maxdataserviceversion":
			case "dataserviceversion":
				return true;
			case "x-csrf-token":
				return this.bTokenHandling;
			default:
				return false;
		}
	};

	/**
	 * Set custom headers which are provided in a key/value map. These headers are used for requests against the OData backend.
	 * Private headers which are set in the ODataModel cannot be modified.
	 * These private headers are: accept, accept-language, x-csrf-token, MaxDataServiceVersion, DataServiceVersion.
	 *
	 * To remove these headers simply set the mCustomHeaders parameter to null. Please also note that when calling this method again all previous custom headers
	 * are removed unless they are specified again in the mCustomHeaders parameter.
	 *
	 * @param {object} mHeaders the header name/value map.
	 * @public
	 */
	ODataModel.prototype.setHeaders = function(mHeaders) {
		var mCheckedHeaders = {},
			that = this;
		if (mHeaders) {
			each(mHeaders, function(sHeaderName, sHeaderValue){
				// case sensitive check needed to make sure private headers cannot be overridden by difference in the upper/lower case (e.g. accept and Accept).
				if (that._isHeaderPrivate(sHeaderName)) {
					Log.warning("Not allowed to modify private header: " + sHeaderName);
				} else {
					mCheckedHeaders[sHeaderName] = sHeaderValue;
				}
			});
			this.mCustomHeaders = mCheckedHeaders;

		} else {
			this.mCustomHeaders = {};
		}

		// Custom set headers should also be used when requesting annotations, but do not instantiate annotations just for this
		if (this.oAnnotations) {
			this.oAnnotations.setHeaders(this.mCustomHeaders);
		}
	};

	/**
	 * Returns all headers and custom headers which are stored in the OData model.
	 * @return {object} the header map
	 * @public
	 */
	ODataModel.prototype.getHeaders = function() {
		return extend({}, this.mCustomHeaders, this.oHeaders);
	};

	/**
	 * Searches the specified headers map for the specified header name and returns the found header
	 * value.
	 *
	 * @param {string} sFindHeader The name of the header to find
	 * @param {object} mHeaders The headers to search in
	 *
	 * @returns {any} The found header
	 */
	ODataModel.prototype._getHeader = function(sFindHeader, mHeaders) {
		var sHeaderName;
		for (sHeaderName in mHeaders) {
			if (sHeaderName.toLowerCase() === sFindHeader.toLowerCase()) {
				return mHeaders[sHeaderName];
			}
		}
		return null;
	};

	/**
	 * Checks if there exist pending changes in the model created by the setProperty method.
	 * @return {boolean} true/false
	 * @public
	 */
	ODataModel.prototype.hasPendingChanges = function() {
		return this.sChangeKey != null;
	};

	/**
	 * update all bindings
	 * @param {boolean} [bForceUpdate=false] If set to false an update  will only be done when the value of a binding changed.
	 * @public
	 */
	ODataModel.prototype.updateBindings = function(bForceUpdate) {
		this.checkUpdate(bForceUpdate);
	};

	/**
	 * Force no caching
	 * @param {boolean} [bForceNoCache=false] whether to force no caching
	 * @public
	 * @deprecated As of version 1.13, the caching should be controlled by the backend by setting the correct cache control header
	 */
	ODataModel.prototype.forceNoCache = function(bForceNoCache) {
		this.bCache = !bForceNoCache;
	};

	/**
	 * Enable/Disable XCSRF-Token handling
	 * @param {boolean} [bTokenHandling=true] whether to use token handling or not
	 * @public
	 */
	ODataModel.prototype.setTokenHandlingEnabled  = function(bTokenHandling) {
		this.bTokenHandling = bTokenHandling;
	};

	/**
	 * Enable/Disable batch for all requests
	 * @param {boolean} [bUseBatch=false] whether the requests should be encapsulated in a batch request
	 * @public
	 */
	ODataModel.prototype.setUseBatch  = function(bUseBatch) {
		this.bUseBatch = bUseBatch;
	};

	/**
	 * Formats a JavaScript value according to the given
	 * <a href="http://www.odata.org/documentation/odata-version-2-0/overview#AbstractTypeSystem">
	 * EDM type</a>.
	 *
	 * @param {any} vValue the value to format
	 * @param {string} sType the EDM type (e.g. Edm.Decimal)
	 * @return {string} the formatted value
	 */
	ODataModel.prototype.formatValue = function(vValue, sType) {
		return ODataUtils.formatValue(vValue, sType);
	};

	/**
	 * Deletes a created entry from the request queue and the model.
	 * @param {sap.ui.model.Context} oContext The context object pointing to the created entry
	 * @public
	 */
	ODataModel.prototype.deleteCreatedEntry = function(oContext) {
		if (oContext) {
			var sPath = oContext.getPath();
			delete this.mContexts[sPath]; // contexts are stored starting with /
			// remove starting / if any
			if (sPath.startsWith("/")) {
				sPath = sPath.substr(1);
			}
			delete this.oRequestQueue[sPath];
			delete this.oData[sPath];

		}
	};

	/**
	 * Creates a new entry object which is described by the metadata of the entity type of the
	 * specified sPath Name. A context object is returned which can be used to bind
	 * against the newly created object.
	 *
	 * For each created entry a request is created and stored in a request queue.
	 * The request queue can be submitted by calling submitChanges. To delete a created
	 * entry from the request queue call deleteCreateEntry.
	 *
	 * The optional vProperties parameter can be used as follows:
	 *
	 *   - vProperties could be an array containing the property names which should be included
	 *     in the new entry. Other properties defined in the entity type are not included.
	 *   - vProperties could be an object which includes the desired properties and the values
	 *     which should be used for the created entry.
	 *
	 * If vProperties is not specified, all properties in the entity type will be included in the
	 * created entry.
	 *
	 * If there are no values specified the properties will have undefined values.
	 *
	 * Please note that deep creates (including data defined by navigationproperties) are not supported
	 *
	 * @param {string} sPath Name of the path to the collection
	 * @param {array|object} vProperties An array that specifies a set of properties or the entry
	 * @return {sap.ui.model.Context} oContext A Context object that point to the new created entry.
	 * @public
	 */
	ODataModel.prototype.createEntry = function(sPath, vProperties) {
		var oEntity = {},
			sKey,
			sUrl,
			oRequest;

		if (!sPath.startsWith("/")) {
			sPath = "/" + sPath;
		}
		var oEntityMetadata = this.oMetadata._getEntityTypeByPath(sPath);
		if (!oEntityMetadata) {
			assert(oEntityMetadata, "No Metadata for collection " + sPath + " found");
			return undefined;
		}
		if (typeof vProperties === "object" && !Array.isArray(vProperties)) {
			oEntity = vProperties;
		} else {
			for (var i = 0; i < oEntityMetadata.property.length; i++) {
				var oPropertyMetadata = oEntityMetadata.property[i];

				var bPropertyInArray = vProperties && vProperties.indexOf(oPropertyMetadata.name)  > -1;
				if (!vProperties || bPropertyInArray)  {
					oEntity[oPropertyMetadata.name] = this._createPropertyValue(oPropertyMetadata.type);
					if (bPropertyInArray) {
						vProperties.splice(vProperties.indexOf(oPropertyMetadata.name),1);
					}
				}
			}
			if (vProperties) {
				assert(vProperties.length === 0, "No metadata for the following properties found: " + vProperties.join(","));
			}
		}
		//mark as entity for create; we need this for setProperty
		oEntity._bCreate = true;

		// remove starting / for key only
		sKey = sPath.substring(1) + "('" + uid() + "')";

		this.oData[sKey] = oEntity;

		oEntity.__metadata = {type: "" + oEntityMetadata.entityType};

		sUrl = this._createRequestUrl(sPath);

		oRequest = this._createRequest(sUrl, "POST", true, oEntity);

		oRequest.entityTypes = {};
		oRequest.entityTypes[oEntityMetadata.entityType] = true;

		this.oRequestQueue[sKey] = oRequest;

		return this.getContext("/" + sKey); // context wants a path
	};

	/**
	 * Return value for a property. This can also be a ComplexType property.
	 *
	 * @param {string} sType The full qualified Type name
	 *
	 * @returns {any} The property value
	 * @private
	 */
	ODataModel.prototype._createPropertyValue = function(sType) {
		var oTypeInfo = this.oMetadata._splitName(sType); // name, namespace
		var sNamespace = oTypeInfo.namespace;
		var sTypeName = oTypeInfo.name;
		if (sNamespace.toUpperCase() !== 'EDM') {
			var oComplexType = {};
			var oComplexTypeMetadata = this.oMetadata._getObjectMetadata("complexType",sTypeName,sNamespace);
			assert(oComplexTypeMetadata, "Complex type " + sType + " not found in the metadata !");
			for (var i = 0; i < oComplexTypeMetadata.property.length; i++) {
				var oPropertyMetadata = oComplexTypeMetadata.property[i];
				oComplexType[oPropertyMetadata.name] = this._createPropertyValue(oPropertyMetadata.type);
			}
			return oComplexType;
		} else {
			return this._getDefaultPropertyValue(sTypeName,sNamespace);
		}
	};

	/**
	 * Returns <code>undefined</code>.
	 *
	 * @param {string} sType Unused
	 * @param {string} sNamespace Unused
	 *
	 * @returns {undefined}
	 * @private
	 */
	ODataModel.prototype._getDefaultPropertyValue = function(sType, sNamespace) {
		return undefined;
	};

	/**
	 * Removes url params from path and make path absolute if not already.
	 *
	 * @param {string} sPath The path to normalize
	 * @param {sap.ui.model.Context} oContext The context to resolve the path with
	 *
	 * @returns {string} The normalized path
	 */
	ODataModel.prototype._normalizePath = function(sPath, oContext) {

		// remove query params from path if any
		if (sPath && sPath.indexOf('?') != -1 ) {
			sPath = sPath.substr(0, sPath.indexOf('?'));
		}

		if (!oContext && !sPath.startsWith("/")) {
			// we need to add a / due to compatibility reasons; but only if there is no context
			sPath = '/' + sPath;
			Log.warning(this + " path " + sPath + " should be absolute if no Context is set");
		}
		return this.resolve(sPath, oContext);
	};


	/**
	 * Enable/Disable automatic updates of all Bindings after change operations
	 *
	 * @param {boolean} bRefreshAfterChange Whether automatic updates should be enabled
	 * @public
	 * @since 1.16.3
	 */
	ODataModel.prototype.setRefreshAfterChange = function(bRefreshAfterChange) {
		this.bRefreshAfterChange = bRefreshAfterChange;
	};

	/**
	 * Checks if the binding matching path and context is a list.
	 *
	 * @param {string} sPath The path to the binding
	 * @param {object} [oContext] The context to resolve the path with
	 * @returns {boolean} Whether the binding is a list
	 */
	ODataModel.prototype.isList = function(sPath, oContext) {
		sPath = this.resolve(sPath, oContext);
		return sPath && sPath.substr(sPath.lastIndexOf("/")).indexOf("(") === -1;
	};

	/**
	 * Checks if path points to a metamodel property.
	 *
	 * @param {string} sPath The binding path
	 * @returns {boolean} Whether the path points to a metamodel property
	 * @private
	 */
	ODataModel.prototype.isMetaModelPath = function(sPath) {
		return sPath.indexOf("##") == 0 || sPath.indexOf("/##") > -1;
	};

	/**
	 * Wraps the {@link sap.ui.thirdparty.datajs#request} method and keeps track of pending requests
	 *
	 * @param {object} oRequest The request to send
	 * @param {function} fnSuccess The success handler
	 * @param {function} fnError The error handler
	 * @param {object} oHandler Handler for data serialization
	 * @param {object} oHttpClient HTTP client layer
	 * @param {object} oMetadata Metadata for this request
	 *
	 * @returns {object} The request handle
	 * @private
	 */
	ODataModel.prototype._request = function(oRequest, fnSuccess, fnError, oHandler, oHttpClient,
			oMetadata) {
		var oRequestHandle;

		if (this.bDestroyed) {
			return {
				abort: function() {}
			};
		}

		var that = this;

		function wrapHandler(fn) {
			return function() {
				// request finished, remove request handle from pending request array
				if (that.aPendingRequestHandles){
					var iIndex = that.aPendingRequestHandles.indexOf(oRequestHandle);
					if (iIndex > -1) {
						that.aPendingRequestHandles.splice(iIndex, 1);
					}
				}

				// call original handler method
				if (!(oRequestHandle && oRequestHandle.bSuppressErrorHandlerCall)) {
					fn.apply(this, arguments);
				}
			};
		}

		// create request with wrapped handlers
		oRequestHandle = OData.request(
				oRequest,
				wrapHandler(fnSuccess || OData.defaultSuccess),
				wrapHandler(fnError || OData.defaultError),
				oHandler,
				oHttpClient,
				oMetadata
		);

		// add request handle to array and return it (only for async requests)
		if (oRequest.async !== false) {
			this.aPendingRequestHandles.push(oRequestHandle);
		}

		return oRequestHandle;
	};

	/**
	 * @see sap.ui.model.Model.prototype.destroy
	 * @public
	 */
	ODataModel.prototype.destroy = function() {
		this.bDestroyed = true;

		// Abort pending requests
		if (this.aPendingRequestHandles) {
			for (var i = this.aPendingRequestHandles.length - 1; i >= 0; i--) {
				var oRequestHandle = this.aPendingRequestHandles[i];
				if (oRequestHandle && oRequestHandle.abort) {
					oRequestHandle.bSuppressErrorHandlerCall = true;
					oRequestHandle.abort();
				}
			}
			delete this.aPendingRequestHandles;
		}
		if (this.oMetadataLoadEvent) {
			clearTimeout(this.oMetadataLoadEvent);
		}
		if (this.oMetadataFailedEvent) {
			clearTimeout(this.oMetadataFailedEvent);
		}

		if (this.oMetadata) {
			this.oMetadata.detachLoaded(this.onMetadataLoaded);
			this.oMetadata.detachFailed(this.onMetadataFailed);
			// Only destroy metadata, if request is still running and no other models
			// are registered to it
			if (!this.oMetadata.isLoaded() && !this.oMetadata.hasListeners("loaded")) {
				this.oMetadata.destroy();
				delete this.oServiceData.oMetadata;
			}
			delete this.oMetadata;
		}


		if (this.oAnnotations) {
			this.oAnnotations.detachFailed(this.onAnnotationsFailed);
			this.oAnnotations.detachLoaded(this.onAnnotationsLoaded);
			this.oAnnotations.destroy();
			delete this.oAnnotations;
			delete this.pAnnotationsLoaded;
		}



		Model.prototype.destroy.apply(this, arguments);
	};

	/**
	 * Get or create the annotation parser instance.
	 * @param {object} mAnnotationData The annotation data
	 *
	 * @return {sap.ui.model.odata.ODataAnnotations} The annotation parser instance
	 */
	ODataModel.prototype._getAnnotationParser = function(mAnnotationData) {
		if (!this.oAnnotations) {
			this.oAnnotations = new ODataAnnotations({
				annotationData: mAnnotationData,
				url: null,
				metadata: this.oMetadata,
				async: this.bLoadMetadataAsync,
				headers: this.mCustomHeaders
			});
			this.oAnnotations.attachFailed(this.onAnnotationsFailed, this);
			this.oAnnotations.attachLoaded(this.onAnnotationsLoaded, this);
		}

		return this.oAnnotations;
	};

	ODataModel.prototype.onAnnotationsFailed = function(oEvent) {
		this.fireAnnotationsFailed(oEvent.getParameters());
	};

	ODataModel.prototype.onAnnotationsLoaded = function(oEvent) {
		this.fireAnnotationsLoaded(oEvent.getParameters());
	};

	/**
	 * Adds (a) new URL(s) to the be parsed for OData annotations, which are then merged into the annotations object
	 * which can be retrieved by calling the getServiceAnnotations()-method. If a $metadata url is passed the data will
	 * also be merged into the metadata object, which can be reached by calling the getServiceMetadata() method.
	 *
	 * @param {string|string[]} vUrl - Either one URL as string or an array or URL strings
	 * @return {Promise} The Promise to load the given URL(s), resolved if all URLs have been loaded, rejected if at least one fails to load.
	 * 					 If this promise resolves it returns the following parameters:
	 * 					 annotations: The annotation object
	 * 					 entitySets: An array of EntitySet objects containing the newly merged EntitySets from a $metadata requests.
	 * 								 the structure is the same as in the metadata object reached by the getServiceMetadata() method.
	 * 								 For non $metadata requests the array will be empty.
	 *
	 * @protected
	 */
	ODataModel.prototype.addAnnotationUrl = function(vUrl) {
		var aUrls = [].concat(vUrl),
			aMetadataUrls = [],
			aAnnotationUrls = [],
			aEntitySets = [],
			that = this;

		each(aUrls, function(i, sUrl) {
			var iIndex = sUrl.indexOf("$metadata");
			if (iIndex >= 0) {
				//add serviceUrl for relative metadata urls
				if (iIndex == 0) {
					sUrl = that.sServiceUrl + '/' + sUrl;
				}
				aMetadataUrls.push(sUrl);
			} else {
				aAnnotationUrls.push(sUrl);
			}
		});

		return this.oMetadata._addUrl(aMetadataUrls).then(function(aParams) {
			return Promise.all(aParams.map(function(oParam) {
				aEntitySets = aEntitySets.concat(oParam.entitySets);
				return that.addAnnotationXML(oParam["metadataString"]);
			}));
		}).then(function() {
			return that._getAnnotationParser().addUrl(aAnnotationUrls);
		}).then(function(oParam) {
			return {
				annotations: oParam.annotations,
				entitySets: aEntitySets
			};
		});
	};

	/**
	 * Adds new xml content to be parsed for OData annotations, which are then merged into the annotations object which
	 * can be retrieved by calling the getServiceAnnotations()-method.
	 *
	 * @param {string} sXMLContent - The string that should be parsed as annotation XML
	 * @param {boolean} [bSuppressEvents=false] - Whether not to fire annotationsLoaded event on the annotationParser
	 * @return {Promise} The Promise to parse the given XML-String, resolved if parsed without errors, rejected if errors occur
	 * @protected
	 */
	ODataModel.prototype.addAnnotationXML = function(sXMLContent, bSuppressEvents) {
		return new Promise(function(resolve, reject) {
			this._getAnnotationParser().setXML(null, sXMLContent, {
				success:    resolve,
				error:      reject,
				fireEvents: !bSuppressEvents
			});
		}.bind(this));
	};


	/**
	 * Returns an instance of an OData meta model which offers a unified access to both OData V2
	 * metadata and V4 annotations. It uses the existing {@link sap.ui.model.odata.ODataMetadata}
	 * as a foundation and merges V4 annotations from the existing
	 * {@link sap.ui.model.odata.ODataAnnotations} directly into the corresponding model element.
	 *
	 * <b>BEWARE:</b> Access to this OData meta model will fail before the promise returned by
	 * {@link sap.ui.model.odata.ODataMetaModel#loaded loaded} has been resolved!
	 *
	 * @public
	 * @returns {sap.ui.model.odata.ODataMetaModel} The meta model for this ODataModel
	 */
	ODataModel.prototype.getMetaModel = function() {
		var that = this;
		if (!this.oMetaModel) {
			this.oMetaModel = new ODataMetaModel(this.oMetadata, this.oAnnotations, this);
			// Call checkUpdate when metamodel has been loaded to update metamodel bindings
			this.oMetaModel.loaded().then(function() {
				that.bMetaModelLoaded = true;
				that.checkUpdate(false, false, null, true);
			});
		}
		return this.oMetaModel;
	};

	/**
	 * Returns a promise that resolves when the annotations given in the constructor are loaded.
	 *
	 * @returns {Promise}
	 *   A promise that resolves when the annotations are loaded, or <code>null</code> if the
	 *   annotations are already loaded.
	 *
	 * @private
	 */
	ODataModel.prototype.annotationsLoaded = function () {
		return this.oMetadata.isLoaded() && (!this.oAnnotations || this.oAnnotations.isLoaded())
			? null // stay synchronous
			: this.pAnnotationsLoaded;
	};

	return ODataModel;

});