You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ko...@apache.org on 2014/04/07 15:37:05 UTC

[01/11] [OLINGO-238] Build infrastructure for datajs I

Repository: olingo-odata4-js
Updated Branches:
  refs/heads/OLINGO-238 a84a42d51 -> e29bbd8f0


http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/odata-qunit-tests.html
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-qunit-tests.html b/datajs/tests/odata-qunit-tests.html
new file mode 100644
index 0000000..a045427
--- /dev/null
+++ b/datajs/tests/odata-qunit-tests.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
+<html>
+<!--
+Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+    <meta http-equiv="cache-control" content="no-cache" />
+    <meta http-equiv="pragma" content="no-cache" />
+    <meta http-equiv="expires" content="-1" />
+
+    <title>OData unit tests</title>
+    <!--<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.10.0.css" type="text/css" />-->
+    <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css" type="text/css" />
+
+    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.min.js"></script>
+    <!--<script type="text/javascript" src="lib/jquery-1.4.4.min.js"></script>-->
+
+    <!--
+    <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.js"></script>
+    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.2.js"></script>
+    -->
+
+    <!--<script type="text/javascript" src="http://code.jquery.com/qunit/qunit-1.10.0.js"></script>-->
+    <script type="text/javascript" src="../node_modules/qunitjs/qunit/qunit.js"></script>
+
+    <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js"></script>
+    <!--<script type="text/javascript" src="lib/json2.js"></script>-->
+
+
+    <script type="text/javascript" src="common/ODataReadOracle.js"></script>
+    <script type="text/javascript" src="common/TestSynchronizerClient.js"></script>
+
+
+    <script type="text/javascript">
+        window.TestSynchronizer.init(QUnit);
+    </script>
+
+    <script src="tests_sample.js"></script>
+   
+    
+    <script type="text/javascript" src="../src/datajs.js"></script>
+    <script type="text/javascript" src="../src/utils.js"></script>
+    <script type="text/javascript" src="../src/xml.js"></script>
+
+    <script type="text/javascript" src="../src/odata-utils.js"></script>
+    <script type="text/javascript" src="../src/odata-handler.js"></script>
+    <script type="text/javascript" src="../src/odata-gml.js"></script>
+    <script type="text/javascript" src="../src/odata-xml.js"></script>
+    <script type="text/javascript" src="../src/odata-net.js"></script>
+    <script type="text/javascript" src="../src/odata-json-light.js"></script>
+    <script type="text/javascript" src="../src/odata-json.js"></script>
+    <script type="text/javascript" src="../src/odata-atom.js"></script>
+    <script type="text/javascript" src="../src/odata-metadata.js"></script>
+    <script type="text/javascript" src="../src/odata-batch.js"></script>
+    <script type="text/javascript" src="../src/odata.js"></script>
+
+    <script type="text/javascript" src="../src/store-dom.js"></script>
+    <script type="text/javascript" src="../src/store-indexeddb.js"></script>
+    <script type="text/javascript" src="../src/store-memory.js"></script>
+    <script type="text/javascript" src="../src/store.js"></script>
+  
+    <script type="text/javascript" src="../src/deferred.js"></script>
+    <script type="text/javascript" src="../src/cache-source.js"></script>
+    <script type="text/javascript" src="../src/cache.js"></script>
+    
+    <script type="text/javascript" src="common/djstest.js"></script>
+
+<!-- include the tests-->
+    <script type="text/javascript" src="odata-atom-tests.js"></script>
+
+
+  </head>
+  <body>
+    <h1 id="qunit-header">OData Unit Tests</h1>
+    <h2 id="qunit-banner"></h2>
+    <h2 id="qunit-userAgent"></h2>
+    <ol id="qunit-tests">
+    </ol>
+  </body>
+</html>
\ No newline at end of file


[09/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-batch.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-batch.js b/datajs/src/odata-batch.js
new file mode 100644
index 0000000..770d875
--- /dev/null
+++ b/datajs/src/odata-batch.js
@@ -0,0 +1,393 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-batch.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+
+    var extend = datajs.extend;
+    var isArray = datajs.isArray;
+    var trimString = datajs.trimString;
+
+    var contentType = odata.contentType;
+    var handler = odata.handler;
+    var isBatch = odata.isBatch;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var normalizeHeaders = odata.normalizeHeaders;
+    var payloadTypeOf = odata.payloadTypeOf;
+    var prepareRequest = odata.prepareRequest;
+
+    // CONTENT START
+    var batchMediaType = "multipart/mixed";
+    var responseStatusRegex = /^HTTP\/1\.\d (\d{3}) (.*)$/i;
+    var responseHeaderRegex = /^([^()<>@,;:\\"\/[\]?={} \t]+)\s?:\s?(.*)/;
+
+    var hex16 = function () {
+        /// <summary>
+        /// Calculates a random 16 bit number and returns it in hexadecimal format.
+        /// </summary>
+        /// <returns type="String">A 16-bit number in hex format.</returns>
+
+        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substr(1);
+    };
+
+    var createBoundary = function (prefix) {
+        /// <summary>
+        /// Creates a string that can be used as a multipart request boundary.
+        /// </summary>
+        /// <param name="prefix" type="String" optional="true">String to use as the start of the boundary string</param>
+        /// <returns type="String">Boundary string of the format: <prefix><hex16>-<hex16>-<hex16></returns>
+
+        return prefix + hex16() + "-" + hex16() + "-" + hex16();
+    };
+
+    var partHandler = function (context) {
+        /// <summary>
+        /// Gets the handler for data serialization of individual requests / responses in a batch.
+        /// </summary>
+        /// <param name="context">Context used for data serialization.</param>
+        /// <returns>Handler object.</returns>
+
+        return context.handler.partHandler;
+    };
+
+    var currentBoundary = function (context) {
+        /// <summary>
+        /// Gets the current boundary used for parsing the body of a multipart response.
+        /// </summary>
+        /// <param name="context">Context used for parsing a multipart response.</param>
+        /// <returns type="String">Boundary string.</returns>
+
+        var boundaries = context.boundaries;
+        return boundaries[boundaries.length - 1];
+    };
+
+    var batchParser = function (handler, text, context) {
+        /// <summary>Parses a batch response.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Batch text.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An object representation of the batch.</returns>
+
+        var boundary = context.contentType.properties["boundary"];
+        return { __batchResponses: readBatch(text, { boundaries: [boundary], handlerContext: context }) };
+    };
+
+    var batchSerializer = function (handler, data, context) {
+        /// <summary>Serializes a batch object representation into text.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data" type="Object">Representation of a batch.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An text representation of the batch object; undefined if not applicable.</returns>
+
+        var cType = context.contentType = context.contentType || contentType(batchMediaType);
+        if (cType.mediaType === batchMediaType) {
+            return writeBatch(data, context);
+        }
+    };
+
+    var readBatch = function (text, context) {
+        /// <summary>
+        /// Parses a multipart/mixed response body from from the position defined by the context.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Body of the multipart/mixed response.</param>
+        /// <param name="context">Context used for parsing.</param>
+        /// <returns>Array of objects representing the individual responses.</returns>
+
+        var delimiter = "--" + currentBoundary(context);
+
+        // Move beyond the delimiter and read the complete batch
+        readTo(text, context, delimiter);
+
+        // Ignore the incoming line
+        readLine(text, context);
+
+        // Read the batch parts
+        var responses = [];
+        var partEnd;
+
+        while (partEnd !== "--" && context.position < text.length) {
+            var partHeaders = readHeaders(text, context);
+            var partContentType = contentType(partHeaders["Content-Type"]);
+
+            var changeResponses;
+            if (partContentType && partContentType.mediaType === batchMediaType) {
+                context.boundaries.push(partContentType.properties["boundary"]);
+                try {
+                    changeResponses = readBatch(text, context);
+                } catch (e) {
+                    e.response = readResponse(text, context, delimiter);
+                    changeResponses = [e];
+                }
+                responses.push({ __changeResponses: changeResponses });
+                context.boundaries.pop();
+                readTo(text, context, "--" + currentBoundary(context));
+            } else {
+                if (!partContentType || partContentType.mediaType !== "application/http") {
+                    throw { message: "invalid MIME part type " };
+                }
+                // Skip empty line
+                readLine(text, context);
+                // Read the response
+                var response = readResponse(text, context, delimiter);
+                try {
+                    if (response.statusCode >= 200 && response.statusCode <= 299) {
+                        partHandler(context.handlerContext).read(response, context.handlerContext);
+                    } else {
+                        // Keep track of failed responses and continue processing the batch.
+                        response = { message: "HTTP request failed", response: response };
+                    }
+                } catch (e) {
+                    response = e;
+                }
+
+                responses.push(response);
+            }
+
+            partEnd = text.substr(context.position, 2);
+
+            // Ignore the incoming line.
+            readLine(text, context);
+        }
+        return responses;
+    };
+
+    var readHeaders = function (text, context) {
+        /// <summary>
+        /// Parses the http headers in the text from the position defined by the context.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Text containing an http response's headers</param>
+        /// <param name="context">Context used for parsing.</param>
+        /// <returns>Object containing the headers as key value pairs.</returns>
+        /// <remarks>
+        /// This function doesn't support split headers and it will stop reading when it hits two consecutive line breaks.
+        /// </remarks>
+
+        var headers = {};
+        var parts;
+        var line;
+        var pos;
+
+        do {
+            pos = context.position;
+            line = readLine(text, context);
+            parts = responseHeaderRegex.exec(line);
+            if (parts !== null) {
+                headers[parts[1]] = parts[2];
+            } else {
+                // Whatever was found is not a header, so reset the context position.
+                context.position = pos;
+            }
+        } while (line && parts);
+
+        normalizeHeaders(headers);
+
+        return headers;
+    };
+
+    var readResponse = function (text, context, delimiter) {
+        /// <summary>
+        /// Parses an HTTP response.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Text representing the http response.</param>
+        /// <param name="context" optional="false">Context used for parsing.</param>
+        /// <param name="delimiter" type="String" optional="false">String used as delimiter of the multipart response parts.</param>
+        /// <returns>Object representing the http response.</returns>
+
+        // Read the status line.
+        var pos = context.position;
+        var match = responseStatusRegex.exec(readLine(text, context));
+
+        var statusCode;
+        var statusText;
+        var headers;
+
+        if (match) {
+            statusCode = match[1];
+            statusText = match[2];
+            headers = readHeaders(text, context);
+            readLine(text, context);
+        } else {
+            context.position = pos;
+        }
+
+        return {
+            statusCode: statusCode,
+            statusText: statusText,
+            headers: headers,
+            body: readTo(text, context, "\r\n" + delimiter)
+        };
+    };
+
+    var readLine = function (text, context) {
+        /// <summary>
+        /// Returns a substring from the position defined by the context up to the next line break (CRLF).
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Input string.</param>
+        /// <param name="context" optional="false">Context used for reading the input string.</param>
+        /// <returns type="String">Substring to the first ocurrence of a line break or null if none can be found. </returns>
+
+        return readTo(text, context, "\r\n");
+    };
+
+    var readTo = function (text, context, str) {
+        /// <summary>
+        /// Returns a substring from the position given by the context up to value defined by the str parameter and increments the position in the context.
+        /// </summary>
+        /// <param name="text" type="String" optional="false">Input string.</param>
+        /// <param name="context" type="Object" optional="false">Context used for reading the input string.</param>
+        /// <param name="str" type="String" optional="true">Substring to read up to.</param>
+        /// <returns type="String">Substring to the first ocurrence of str or the end of the input string if str is not specified. Null if the marker is not found.</returns>
+
+        var start = context.position || 0;
+        var end = text.length;
+        if (str) {
+            end = text.indexOf(str, start);
+            if (end === -1) {
+                return null;
+            }
+            context.position = end + str.length;
+        } else {
+            context.position = end;
+        }
+
+        return text.substring(start, end);
+    };
+
+    var writeBatch = function (data, context) {
+        /// <summary>
+        /// Serializes a batch request object to a string.
+        /// </summary>
+        /// <param name="data" optional="false">Batch request object in payload representation format</param>
+        /// <param name="context" optional="false">Context used for the serialization</param>
+        /// <returns type="String">String representing the batch request</returns>
+
+        if (!isBatch(data)) {
+            throw { message: "Data is not a batch object." };
+        }
+
+        var batchBoundary = createBoundary("batch_");
+        var batchParts = data.__batchRequests;
+        var batch = "";
+        var i, len;
+        for (i = 0, len = batchParts.length; i < len; i++) {
+            batch += writeBatchPartDelimiter(batchBoundary, false) +
+                     writeBatchPart(batchParts[i], context);
+        }
+        batch += writeBatchPartDelimiter(batchBoundary, true);
+
+        // Register the boundary with the request content type.
+        var contentTypeProperties = context.contentType.properties;
+        contentTypeProperties.boundary = batchBoundary;
+
+        return batch;
+    };
+
+    var writeBatchPartDelimiter = function (boundary, close) {
+        /// <summary>
+        /// Creates the delimiter that indicates that start or end of an individual request.
+        /// </summary>
+        /// <param name="boundary" type="String" optional="false">Boundary string used to indicate the start of the request</param>
+        /// <param name="close" type="Boolean">Flag indicating that a close delimiter string should be generated</param>
+        /// <returns type="String">Delimiter string</returns>
+
+        var result = "\r\n--" + boundary;
+        if (close) {
+            result += "--";
+        }
+
+        return result + "\r\n";
+    };
+
+    var writeBatchPart = function (part, context, nested) {
+        /// <summary>
+        /// Serializes a part of a batch request to a string. A part can be either a GET request or
+        /// a change set grouping several CUD (create, update, delete) requests.
+        /// </summary>
+        /// <param name="part" optional="false">Request or change set object in payload representation format</param>
+        /// <param name="context" optional="false">Object containing context information used for the serialization</param>
+        /// <param name="nested" type="boolean" optional="true">Flag indicating that the part is nested inside a change set</param>
+        /// <returns type="String">String representing the serialized part</returns>
+        /// <remarks>
+        /// A change set is an array of request objects and they cannot be nested inside other change sets.
+        /// </remarks>
+
+        var changeSet = part.__changeRequests;
+        var result;
+        if (isArray(changeSet)) {
+            if (nested) {
+                throw { message: "Not Supported: change set nested in other change set" };
+            }
+
+            var changeSetBoundary = createBoundary("changeset_");
+            result = "Content-Type: " + batchMediaType + "; boundary=" + changeSetBoundary + "\r\n";
+            var i, len;
+            for (i = 0, len = changeSet.length; i < len; i++) {
+                result += writeBatchPartDelimiter(changeSetBoundary, false) +
+                     writeBatchPart(changeSet[i], context, true);
+            }
+
+            result += writeBatchPartDelimiter(changeSetBoundary, true);
+        } else {
+            result = "Content-Type: application/http\r\nContent-Transfer-Encoding: binary\r\n\r\n";
+            var partContext = extend({}, context);
+            partContext.handler = handler;
+            partContext.request = part;
+            partContext.contentType = null;
+
+            prepareRequest(part, partHandler(context), partContext);
+            result += writeRequest(part);
+        }
+
+        return result;
+    };
+
+    var writeRequest = function (request) {
+        /// <summary>
+        /// Serializes a request object to a string.
+        /// </summary>
+        /// <param name="request" optional="false">Request object to serialize</param>
+        /// <returns type="String">String representing the serialized request</returns>
+
+        var result = (request.method ? request.method : "GET") + " " + request.requestUri + " HTTP/1.1\r\n";
+        for (var name in request.headers) {
+            if (request.headers[name]) {
+                result = result + name + ": " + request.headers[name] + "\r\n";
+            }
+        }
+
+        result += "\r\n";
+
+        if (request.body) {
+            result += request.body;
+        }
+
+        return result;
+    };
+
+    odata.batchHandler = handler(batchParser, batchSerializer, batchMediaType, MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.batchSerializer = batchSerializer;
+    odata.writeRequest = writeRequest;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-gml.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-gml.js b/datajs/src/odata-gml.js
new file mode 100644
index 0000000..2bcf5f5
--- /dev/null
+++ b/datajs/src/odata-gml.js
@@ -0,0 +1,831 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-gml.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+
+    var contains = datajs.contains;
+    var djsassert = datajs.djsassert;
+    var http = datajs.http;
+    var isArray = datajs.isArray;
+    var xmlAppendChild = datajs.xmlAppendChild;
+    var xmlAttributeValue = datajs.xmlAttributeValue;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNewElement = datajs.xmlNewElement;
+    var xmlQualifiedName = datajs.xmlQualifiedName;
+    var GEOJSON_POINT = odata.GEOJSON_POINT;
+    var GEOJSON_LINESTRING = odata.GEOJSON_LINESTRING;
+    var GEOJSON_POLYGON = odata.GEOJSON_POLYGON;
+    var GEOJSON_MULTIPOINT = odata.GEOJSON_MULTIPOINT;
+    var GEOJSON_MULTILINESTRING = odata.GEOJSON_MULTILINESTRING;
+    var GEOJSON_MULTIPOLYGON = odata.GEOJSON_MULTIPOLYGON;
+    var GEOJSON_GEOMETRYCOLLECTION = odata.GEOJSON_GEOMETRYCOLLECTION;
+
+    // CONTENT START
+    var gmlOpenGis = http + "www.opengis.net";           // http://www.opengis.net
+    var gmlXmlNs = gmlOpenGis + "/gml";                 // http://www.opengis.net/gml
+    var gmlSrsPrefix = gmlOpenGis + "/def/crs/EPSG/0/"; // http://www.opengis.net/def/crs/EPSG/0/
+
+    var gmlPrefix = "gml";
+
+    var gmlCreateGeoJSONOBject = function (type, member, data) {
+        /// <summary>Creates a GeoJSON object with the specified type, member and value.</summary>
+        /// <param name="type" type="String">GeoJSON object type.</param>
+        /// <param name="member" type="String">Name for the data member in the GeoJSON object.</param>
+        /// <param name="data">Data to be contained by the GeoJSON object.</param>
+        /// <returns type="Object">GeoJSON object.</returns>
+
+        var result = { type: type };
+        result[member] = data;
+        return result;
+    };
+
+    var gmlSwapLatLong = function (coordinates) {
+        /// <summary>Swaps the longitude and latitude in the coordinates array.</summary>
+        /// <param name="coordinates" type="Array">Array of doubles descrbing a set of coordinates.</param>
+        /// <returns type="Array">Array of doubles with the latitude and longitude components swapped.</returns>
+
+        if (isArray(coordinates) && coordinates.length >= 2) {
+            var tmp = coordinates[0];
+            coordinates[0] = coordinates[1];
+            coordinates[1] = tmp;
+        }
+        return coordinates;
+    };
+
+    var gmlReadODataMultiItem = function (domElement, type, member, members, valueReader, isGeography) {
+        /// <summary>
+        ///    Reads a GML DOM element that represents a composite structure like a multi-point or a
+        ///    multi-geometry returnig its GeoJSON representation.
+        /// </summary>
+        /// <param name="domElement">GML DOM element.</param>
+        /// <param name="type" type="String">GeoJSON object type.</param>
+        /// <param name="member" type="String">Name for the child element representing a single item in the composite structure.</param>
+        /// <param name="members" type="String">Name for the child element representing a collection of items in the composite structure.</param>
+        /// <param name="valueReader" type="Function">Callback function invoked to get the coordinates of each item in the comoposite structure.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">GeoJSON object.</returns>
+
+        var coordinates = gmlReadODataMultiItemValue(domElement, member, members, valueReader, isGeography);
+        return gmlCreateGeoJSONOBject(type, "coordinates", coordinates);
+    };
+
+    var gmlReadODataMultiItemValue = function (domElement, member, members, valueReader, isGeography) {
+        /// <summary>
+        ///    Reads the value of a GML DOM element that represents a composite structure like a multi-point or a
+        ///    multi-geometry returnig its items.
+        /// </summary>
+        /// <param name="domElement">GML DOM element.</param>
+        /// <param name="type" type="String">GeoJSON object type.</param>
+        /// <param name="member" type="String">Name for the child element representing a single item in the composite structure.</param>
+        /// <param name="members" type="String">Name for the child element representing a collection of items in the composite structure.</param>
+        /// <param name="valueReader" type="Function">Callback function invoked to get the transformed value of each item in the comoposite structure.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing the transformed value of each item in the multi-item.</returns>
+
+        var items = [];
+
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) !== gmlXmlNs) {
+                return;
+            }
+
+            var localName = xmlLocalName(child);
+
+            if (localName === member) {
+                var valueElement = xmlFirstChildElement(child, gmlXmlNs);
+                if (valueElement) {
+                    var value = valueReader(valueElement, isGeography);
+                    if (value) {
+                        items.push(value);
+                    }
+                }
+                return;
+            }
+
+            if (localName === members) {
+                xmlChildElements(child, function (valueElement) {
+                    if (xmlNamespaceURI(valueElement) !== gmlXmlNs) {
+                        return;
+                    }
+
+                    var value = valueReader(valueElement, isGeography);
+                    if (value) {
+                        items.push(value);
+                    }
+                });
+            }
+        });
+        return items;
+    };
+
+    var gmlReadODataCollection = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-geometry returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiGeometry object in GeoJSON format.</returns>
+
+        var geometries = gmlReadODataMultiItemValue(domElement, "geometryMember", "geometryMembers", gmlReadODataSpatialValue, isGeography);
+        return gmlCreateGeoJSONOBject(GEOJSON_GEOMETRYCOLLECTION, "geometries", geometries);
+    };
+
+    var gmlReadODataLineString = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a line string returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">LineString object in GeoJSON format.</returns>
+
+        return gmlCreateGeoJSONOBject(GEOJSON_LINESTRING, "coordinates", gmlReadODataLineValue(domElement, isGeography));
+    };
+
+    var gmlReadODataMultiLineString = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-line string returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiLineString object in GeoJSON format.</returns>
+
+        return gmlReadODataMultiItem(domElement, GEOJSON_MULTILINESTRING, "curveMember", "curveMembers", gmlReadODataLineValue, isGeography);
+    };
+
+    var gmlReadODataMultiPoint = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-point returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiPoint object in GeoJSON format.</returns>
+
+        return gmlReadODataMultiItem(domElement, GEOJSON_MULTIPOINT, "pointMember", "pointMembers", gmlReadODataPointValue, isGeography);
+    };
+
+    var gmlReadODataMultiPolygon = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a multi-polygon returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">MultiPolygon object in GeoJSON format.</returns>
+
+        return gmlReadODataMultiItem(domElement, GEOJSON_MULTIPOLYGON, "surfaceMember", "surfaceMembers", gmlReadODataPolygonValue, isGeography);
+    };
+
+    var gmlReadODataPoint = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a point returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">Point object in GeoJSON format.</returns>
+
+        return gmlCreateGeoJSONOBject(GEOJSON_POINT, "coordinates", gmlReadODataPointValue(domElement, isGeography));
+    };
+
+    var gmlReadODataPolygon = function (domElement, isGeography) {
+        /// <summary>Reads a GML DOM element representing a polygon returning its GeoJSON representation.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Object">Polygon object in GeoJSON format.</returns>
+
+        return gmlCreateGeoJSONOBject(GEOJSON_POLYGON, "coordinates", gmlReadODataPolygonValue(domElement, isGeography));
+    };
+
+    var gmlReadODataLineValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a line returning its set of coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate of the line.</returns>
+
+        var coordinates = [];
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+
+            if (nsURI !== gmlXmlNs) {
+                return;
+            }
+
+            var localName = xmlLocalName(child);
+
+            if (localName === "posList") {
+                coordinates = gmlReadODataPosListValue(child, isGeography);
+                return;
+            }
+            if (localName === "pointProperty") {
+                coordinates.push(gmlReadODataPointWrapperValue(child, isGeography));
+                return;
+            }
+            if (localName === "pos") {
+                coordinates.push(gmlReadODataPosValue(child, isGeography));
+                return;
+            }
+        });
+
+        return coordinates;
+    };
+
+    var gmlReadODataPointValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a point returning its coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array of doubles containing the point coordinates.</returns>
+
+        var pos = xmlFirstChildElement(domElement, gmlXmlNs, "pos");
+        return pos ? gmlReadODataPosValue(pos, isGeography) : [];
+    };
+
+    var gmlReadODataPointWrapperValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element wrapping an element representing a point returning its coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array of doubles containing the point coordinates.</returns>
+
+        var point = xmlFirstChildElement(domElement, gmlXmlNs, "Point");
+        return point ? gmlReadODataPointValue(point, isGeography) : [];
+    };
+
+    var gmlReadODataPolygonValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a polygon returning its set of coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of array of doubles for each ring of the polygon.</returns>
+
+        var coordinates = [];
+        var exteriorFound = false;
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) !== gmlXmlNs) {
+                return;
+            }
+
+            // Only the exterior and the interior rings are interesting
+            var localName = xmlLocalName(child);
+            if (localName === "exterior") {
+                exteriorFound = true;
+                coordinates.unshift(gmlReadODataPolygonRingValue(child, isGeography));
+                return;
+            }
+            if (localName === "interior") {
+                coordinates.push(gmlReadODataPolygonRingValue(child, isGeography));
+                return;
+            }
+        });
+
+        if (!exteriorFound && coordinates.length > 0) {
+            // Push an empty exterior ring.
+            coordinates.unshift([[]]);
+        }
+
+        return coordinates;
+    };
+
+    var gmlReadODataPolygonRingValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a linear ring in a GML Polygon element.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate of the linear ring.</returns>
+
+        var value = [];
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) !== gmlXmlNs || xmlLocalName(child) !== "LinearRing") {
+                return;
+            }
+            value = gmlReadODataLineValue(child, isGeography);
+        });
+        return value;
+    };
+
+    var gmlReadODataPosListValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element representing a list of positions retruning its set of coordinates.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        ///
+        ///    The positions described by the list are assumed to be 2D, so 
+        ///    an exception will be thrown if the list has an odd number elements.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate in the list.</returns>
+
+        var coordinates = gmlReadODataPosValue(domElement, false);
+        var len = coordinates.length;
+
+        if (len % 2 !== 0) {
+            throw { message: "GML posList element has an uneven number of numeric values" };
+        }
+
+        var value = [];
+        for (var i = 0; i < len; i += 2) {
+            var pos = coordinates.slice(i, i + 2);
+            value.push(isGeography ? gmlSwapLatLong(pos) : pos);
+        }
+        return value;
+    };
+
+    var gmlReadODataPosValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML element describing a position or a set of coordinates in an OData spatial property value.</summary>
+        /// <param name="property">DOM element for the GML element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns type="Array">Array of doubles containing the coordinates.</returns>
+
+        var value = [];
+        var delims = " \t\r\n";
+        var text = xmlInnerText(domElement);
+
+        if (text) {
+            var len = text.length;
+            var start = 0;
+            var end = 0;
+
+            while (end <= len) {
+                if (delims.indexOf(text.charAt(end)) !== -1) {
+                    var coord = text.substring(start, end);
+                    if (coord) {
+                        value.push(parseFloat(coord));
+                    }
+                    start = end + 1;
+                }
+                end++;
+            }
+        }
+
+        return isGeography ? gmlSwapLatLong(value) : value;
+    };
+
+    var gmlReadODataSpatialValue = function (domElement, isGeography) {
+        /// <summary>Reads the value of a GML DOM element a spatial value in an OData XML document.</summary>
+        /// <param name="domElement">DOM element.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each position coordinates in the resulting GeoJSON object.
+        /// </remarks>
+        /// <returns type="Array">Array containing an array of doubles for each coordinate of the polygon.</returns>
+
+        var localName = xmlLocalName(domElement);
+        var reader;
+
+        switch (localName) {
+            case "Point":
+                reader = gmlReadODataPoint;
+                break;
+            case "Polygon":
+                reader = gmlReadODataPolygon;
+                break;
+            case "LineString":
+                reader = gmlReadODataLineString;
+                break;
+            case "MultiPoint":
+                reader = gmlReadODataMultiPoint;
+                break;
+            case "MultiCurve":
+                reader = gmlReadODataMultiLineString;
+                break;
+            case "MultiSurface":
+                reader = gmlReadODataMultiPolygon;
+                break;
+            case "MultiGeometry":
+                reader = gmlReadODataCollection;
+                break;
+            default:
+                throw { message: "Unsupported element: " + localName, element: domElement };
+        }
+
+        var value = reader(domElement, isGeography);
+        // Read the CRS
+        // WCF Data Services qualifies the srsName attribute withing the GML namespace; however
+        // other end points might no do this as per the standard.
+
+        var srsName = xmlAttributeValue(domElement, "srsName", gmlXmlNs) ||
+                      xmlAttributeValue(domElement, "srsName");
+
+        if (srsName) {
+            if (srsName.indexOf(gmlSrsPrefix) !== 0) {
+                throw { message: "Unsupported srs name: " + srsName, element: domElement };
+            }
+
+            var crsId = srsName.substring(gmlSrsPrefix.length);
+            if (crsId) {
+                value.crs = {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:" + crsId
+                    }
+                };
+            }
+        }
+        return value;
+    };
+
+    var gmlNewODataSpatialValue = function (dom, value, type, isGeography) {
+        /// <summary>Creates a new GML DOM element for the value of an OData spatial property or GeoJSON object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">Spatial property value in GeoJSON format.</param>
+        /// <param name="type" type="String">String indicating the GeoJSON type of the value to serialize.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the spatial value. </returns>
+
+        var gmlWriter;
+
+        switch (type) {
+            case GEOJSON_POINT:
+                gmlWriter = gmlNewODataPoint;
+                break;
+            case GEOJSON_LINESTRING:
+                gmlWriter = gmlNewODataLineString;
+                break;
+            case GEOJSON_POLYGON:
+                gmlWriter = gmlNewODataPolygon;
+                break;
+            case GEOJSON_MULTIPOINT:
+                gmlWriter = gmlNewODataMultiPoint;
+                break;
+            case GEOJSON_MULTILINESTRING:
+                gmlWriter = gmlNewODataMultiLineString;
+                break;
+            case GEOJSON_MULTIPOLYGON:
+                gmlWriter = gmlNewODataMultiPolygon;
+                break;
+            case GEOJSON_GEOMETRYCOLLECTION:
+                gmlWriter = gmlNewODataGeometryCollection;
+                break;
+            default:
+                djsassert(false, "gmlNewODataSpatialValue - Unknown GeoJSON type <" + type + ">!!");
+                return null;
+        }
+
+        var gml = gmlWriter(dom, value, isGeography);
+
+        // Set the srsName attribute if applicable.
+        var crs = value.crs;
+        if (crs) {
+            if (crs.type === "name") {
+                var properties = crs.properties;
+                var name = properties && properties.name;
+                if (name && name.indexOf("ESPG:") === 0 && name.length > 5) {
+                    var crsId = name.substring(5);
+                    var srsName = xmlNewAttribute(dom, null, "srsName", gmlPrefix + crsId);
+                    xmlAppendChild(gml, srsName);
+                }
+            }
+        }
+
+        return gml;
+    };
+
+    var gmlNewODataElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the GML namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the GML element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the GML namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, gmlXmlNs, xmlQualifiedName(gmlPrefix, name), children);
+    };
+
+    var gmlNewODataPosElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML pos DOM element.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="coordinates" type="Array">Array of doubles describing the coordinates of the pos element.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first coordinate is the Longitude and
+        ///    will be serialized as the second component of the <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New pos DOM element in the GML namespace.</returns>
+
+        var posValue = isArray(coordinates) ? coordinates : [];
+
+        // If using a geographic reference system, then the first coordinate is the longitude and it has to
+        // swapped with the latitude.
+        posValue = isGeography ? gmlSwapLatLong(posValue) : posValue;
+
+        return gmlNewODataElement(dom, "pos", posValue.join(" "));
+    };
+
+    var gmlNewODataLineElement = function (dom, name, coordinates, isGeography) {
+        /// <summary>Creates a new GML DOM element representing a line.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Name of the element to create.</param>
+        /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the line element.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var element = gmlNewODataElement(dom, name);
+        if (isArray(coordinates)) {
+            var i, len;
+            for (i = 0, len = coordinates.length; i < len; i++) {
+                xmlAppendChild(element, gmlNewODataPosElement(dom, coordinates[i], isGeography));
+            }
+
+            if (len === 0) {
+                xmlAppendChild(element, gmlNewODataElement(dom, "posList"));
+            }
+        }
+        return element;
+    };
+
+    var gmlNewODataPointElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML Point DOM element.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON Point object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON Point.</returns>
+
+        return gmlNewODataElement(dom, "Point", gmlNewODataPosElement(dom, coordinates, isGeography));
+    };
+
+    var gmlNewODataLineStringElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML LineString DOM element.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the line element.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON LineString.</returns>
+
+        return gmlNewODataLineElement(dom, "LineString", coordinates, isGeography);
+    };
+
+    var gmlNewODataPolygonRingElement = function (dom, name, coordinates, isGeography) {
+        /// <summary>Creates a new GML DOM element representing a polygon ring.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Name of the element to create.</param>
+        /// <param name="coordinates" type="Array">Array of array of doubles describing the coordinates of the polygon ring.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the coordinates use a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var ringElement = gmlNewODataElement(dom, name);
+        if (isArray(coordinates) && coordinates.length > 0) {
+            var linearRing = gmlNewODataLineElement(dom, "LinearRing", coordinates, isGeography);
+            xmlAppendChild(ringElement, linearRing);
+        }
+        return ringElement;
+    };
+
+    var gmlNewODataPolygonElement = function (dom, coordinates, isGeography) {
+        /// <summary>Creates a new GML Polygon DOM element for a GeoJSON Polygon object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="coordinates" type="Array">Array of array of array of doubles describing the coordinates of the polygon.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var len = coordinates && coordinates.length;
+        var element = gmlNewODataElement(dom, "Polygon");
+
+        if (isArray(coordinates) && len > 0) {
+            xmlAppendChild(element, gmlNewODataPolygonRingElement(dom, "exterior", coordinates[0], isGeography));
+
+            var i;
+            for (i = 1; i < len; i++) {
+                xmlAppendChild(element, gmlNewODataPolygonRingElement(dom, "interior", coordinates[i], isGeography));
+            }
+        }
+        return element;
+    };
+
+    var gmlNewODataPoint = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML Point DOM element for a GeoJSON Point object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON Point object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON Point.</returns>
+
+        return gmlNewODataPointElement(dom, value.coordinates, isGeography);
+    };
+
+    var gmlNewODataLineString = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML LineString DOM element for a GeoJSON LineString object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON LineString object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON LineString.</returns>
+
+        return gmlNewODataLineStringElement(dom, value.coordinates, isGeography);
+    };
+
+    var gmlNewODataPolygon = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML Polygon DOM element for a GeoJSON Polygon object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON Polygon object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON Polygon.</returns>
+
+        return gmlNewODataPolygonElement(dom, value.coordinates, isGeography);
+    };
+
+    var gmlNewODataMultiItem = function (dom, name, members, items, itemWriter, isGeography) {
+        /// <summary>Creates a new GML DOM element for a composite structure like a multi-point or a multi-geometry.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Name of the element to create.</param>
+        /// <param name="items" type="Array">Array of items in the composite structure.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the multi-item uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each of the items is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        var len = items && items.length;
+        var element = gmlNewODataElement(dom, name);
+
+        if (isArray(items) && len > 0) {
+            var membersElement = gmlNewODataElement(dom, members);
+            var i;
+            for (i = 0; i < len; i++) {
+                xmlAppendChild(membersElement, itemWriter(dom, items[i], isGeography));
+            }
+            xmlAppendChild(element, membersElement);
+        }
+        return element;
+    };
+
+    var gmlNewODataMultiPoint = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiPoint DOM element for a GeoJSON MultiPoint object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON MultiPoint object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON MultiPoint.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiPoint", "pointMembers", value.coordinates, gmlNewODataPointElement, isGeography);
+    };
+
+    var gmlNewODataMultiLineString = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiCurve DOM element for a GeoJSON MultiLineString object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON MultiLineString object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON MultiLineString.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiCurve", "curveMembers", value.coordinates, gmlNewODataLineStringElement, isGeography);
+    };
+
+    var gmlNewODataMultiPolygon = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiSurface DOM element for a GeoJSON MultiPolygon object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON MultiPolygon object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON MultiPolygon.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiSurface", "surfaceMembers", value.coordinates, gmlNewODataPolygonElement, isGeography);
+    };
+
+    var gmlNewODataGeometryCollectionItem = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML element for an item in a geometry collection object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="item" type="Object">GeoJSON object in the geometry collection.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace.</returns>
+
+        return gmlNewODataSpatialValue(dom, value, value.type, isGeography);
+    };
+
+    var gmlNewODataGeometryCollection = function (dom, value, isGeography) {
+        /// <summary>Creates a new GML MultiGeometry DOM element for a GeoJSON GeometryCollection object.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="value" type="Object">GeoJSON GeometryCollection object.</param>
+        /// <param name="isGeography" type="Boolean">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in the GeoJSON value is the Longitude and
+        ///    will be serialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the GeoJSON GeometryCollection.</returns>
+
+        return gmlNewODataMultiItem(dom, "MultiGeometry", "geometryMembers", value.geometries, gmlNewODataGeometryCollectionItem, isGeography);
+    };
+
+    // DATAJS INTERNAL START
+    odata.gmlNewODataSpatialValue = gmlNewODataSpatialValue;
+    odata.gmlReadODataSpatialValue = gmlReadODataSpatialValue;
+    odata.gmlXmlNs = gmlXmlNs;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-handler.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-handler.js b/datajs/src/odata-handler.js
new file mode 100644
index 0000000..5f83429
--- /dev/null
+++ b/datajs/src/odata-handler.js
@@ -0,0 +1,276 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-handler.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+    var assigned = datajs.assigned;
+    var extend = datajs.extend;
+    var trimString = datajs.trimString;
+
+    var maxVersion = odata.maxVersion;
+
+    // CONTENT START
+
+    var MAX_DATA_SERVICE_VERSION = "3.0";
+
+    var contentType = function (str) {
+        /// <summary>Parses a string into an object with media type and properties.</summary>
+        /// <param name="str" type="String">String with media type to parse.</param>
+        /// <returns>null if the string is empty; an object with 'mediaType' and a 'properties' dictionary otherwise.</returns>
+
+        if (!str) {
+            return null;
+        }
+
+        var contentTypeParts = str.split(";");
+        var properties = {};
+
+        var i, len;
+        for (i = 1, len = contentTypeParts.length; i < len; i++) {
+            var contentTypeParams = contentTypeParts[i].split("=");
+            properties[trimString(contentTypeParams[0])] = contentTypeParams[1];
+        }
+
+        return { mediaType: trimString(contentTypeParts[0]), properties: properties };
+    };
+
+    var contentTypeToString = function (contentType) {
+        /// <summary>Serializes an object with media type and properties dictionary into a string.</summary>
+        /// <param name="contentType">Object with media type and properties dictionary to serialize.</param>
+        /// <returns>String representation of the media type object; undefined if contentType is null or undefined.</returns>
+
+        if (!contentType) {
+            return undefined;
+        }
+
+        var result = contentType.mediaType;
+        var property;
+        for (property in contentType.properties) {
+            result += ";" + property + "=" + contentType.properties[property];
+        }
+        return result;
+    };
+
+    var createReadWriteContext = function (contentType, dataServiceVersion, context, handler) {
+        /// <summary>Creates an object that is going to be used as the context for the handler's parser and serializer.</summary>
+        /// <param name="contentType">Object with media type and properties dictionary.</param>
+        /// <param name="dataServiceVersion" type="String">String indicating the version of the protocol to use.</param>
+        /// <param name="context">Operation context.</param>
+        /// <param name="handler">Handler object that is processing a resquest or response.</param>
+        /// <returns>Context object.</returns>
+
+        var rwContext = {};
+        extend(rwContext, context);
+        extend(rwContext, {
+            contentType: contentType,
+            dataServiceVersion: dataServiceVersion,
+            handler: handler
+        });
+
+        return rwContext;
+    };
+
+    var fixRequestHeader = function (request, name, value) {
+        /// <summary>Sets a request header's value. If the header has already a value other than undefined, null or empty string, then this method does nothing.</summary>
+        /// <param name="request">Request object on which the header will be set.</param>
+        /// <param name="name" type="String">Header name.</param>
+        /// <param name="value" type="String">Header value.</param>
+        if (!request) {
+            return;
+        }
+
+        var headers = request.headers;
+        if (!headers[name]) {
+            headers[name] = value;
+        }
+    };
+
+    var fixDataServiceVersionHeader = function (request, version) {
+        /// <summary>Sets the DataServiceVersion header of the request if its value is not yet defined or of a lower version.</summary>
+        /// <param name="request">Request object on which the header will be set.</param>
+        /// <param name="version" type="String">Version value.</param>
+        /// <remarks>
+        /// If the request has already a version value higher than the one supplied the this function does nothing.
+        /// </remarks>
+
+        if (request) {
+            var headers = request.headers;
+            var dsv = headers["DataServiceVersion"];
+            headers["DataServiceVersion"] = dsv ? maxVersion(dsv, version) : version;
+        }
+    };
+
+    var getRequestOrResponseHeader = function (requestOrResponse, name) {
+        /// <summary>Gets the value of a request or response header.</summary>
+        /// <param name="requestOrResponse">Object representing a request or a response.</param>
+        /// <param name="name" type="String">Name of the header to retrieve.</param>
+        /// <returns type="String">String value of the header; undefined if the header cannot be found.</returns>
+
+        var headers = requestOrResponse.headers;
+        return (headers && headers[name]) || undefined;
+    };
+
+    var getContentType = function (requestOrResponse) {
+        /// <summary>Gets the value of the Content-Type header from a request or response.</summary>
+        /// <param name="requestOrResponse">Object representing a request or a response.</param>
+        /// <returns type="Object">Object with 'mediaType' and a 'properties' dictionary; null in case that the header is not found or doesn't have a value.</returns>
+
+        return contentType(getRequestOrResponseHeader(requestOrResponse, "Content-Type"));
+    };
+
+    var versionRE = /^\s?(\d+\.\d+);?.*$/;
+    var getDataServiceVersion = function (requestOrResponse) {
+        /// <summary>Gets the value of the DataServiceVersion header from a request or response.</summary>
+        /// <param name="requestOrResponse">Object representing a request or a response.</param>
+        /// <returns type="String">Data service version; undefined if the header cannot be found.</returns>
+
+        var value = getRequestOrResponseHeader(requestOrResponse, "DataServiceVersion");
+        if (value) {
+            var matches = versionRE.exec(value);
+            if (matches && matches.length) {
+                return matches[1];
+            }
+        }
+
+        // Fall through and return undefined.
+    };
+
+    var handlerAccepts = function (handler, cType) {
+        /// <summary>Checks that a handler can process a particular mime type.</summary>
+        /// <param name="handler">Handler object that is processing a resquest or response.</param>
+        /// <param name="cType">Object with 'mediaType' and a 'properties' dictionary.</param>
+        /// <returns type="Boolean">True if the handler can process the mime type; false otherwise.</returns>
+
+        // The following check isn't as strict because if cType.mediaType = application/; it will match an accept value of "application/xml";
+        // however in practice we don't not expect to see such "suffixed" mimeTypes for the handlers.
+        return handler.accept.indexOf(cType.mediaType) >= 0;
+    };
+
+    var handlerRead = function (handler, parseCallback, response, context) {
+        /// <summary>Invokes the parser associated with a handler for reading the payload of a HTTP response.</summary>
+        /// <param name="handler">Handler object that is processing the response.</param>
+        /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
+        /// <param name="response">HTTP response whose payload is going to be processed.</param>
+        /// <param name="context">Object used as the context for processing the response.</param>
+        /// <returns type="Boolean">True if the handler processed the response payload and the response.data property was set; false otherwise.</returns>
+
+        if (!response || !response.headers) {
+            return false;
+        }
+
+        var cType = getContentType(response);
+        var version = getDataServiceVersion(response) || "";
+        var body = response.body;
+
+        if (!assigned(body)) {
+            return false;
+        }
+
+        if (handlerAccepts(handler, cType)) {
+            var readContext = createReadWriteContext(cType, version, context, handler);
+            readContext.response = response;
+            response.data = parseCallback(handler, body, readContext);
+            return response.data !== undefined;
+        }
+
+        return false;
+    };
+
+    var handlerWrite = function (handler, serializeCallback, request, context) {
+        /// <summary>Invokes the serializer associated with a handler for generating the payload of a HTTP request.</summary>
+        /// <param name="handler">Handler object that is processing the request.</param>
+        /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
+        /// <param name="response">HTTP request whose payload is going to be generated.</param>
+        /// <param name="context">Object used as the context for serializing the request.</param>
+        /// <returns type="Boolean">True if the handler serialized the request payload and the request.body property was set; false otherwise.</returns>
+        if (!request || !request.headers) {
+            return false;
+        }
+
+        var cType = getContentType(request);
+        var version = getDataServiceVersion(request);
+
+        if (!cType || handlerAccepts(handler, cType)) {
+            var writeContext = createReadWriteContext(cType, version, context, handler);
+            writeContext.request = request;
+
+            request.body = serializeCallback(handler, request.data, writeContext);
+
+            if (request.body !== undefined) {
+                fixDataServiceVersionHeader(request, writeContext.dataServiceVersion || "1.0");
+
+                fixRequestHeader(request, "Content-Type", contentTypeToString(writeContext.contentType));
+                fixRequestHeader(request, "MaxDataServiceVersion", handler.maxDataServiceVersion);
+                return true;
+            }
+        }
+
+        return false;
+    };
+
+    var handler = function (parseCallback, serializeCallback, accept, maxDataServiceVersion) {
+        /// <summary>Creates a handler object for processing HTTP requests and responses.</summary>
+        /// <param name="parseCallback" type="Function">Parser function that will process the response payload.</param>
+        /// <param name="serializeCallback" type="Function">Serializer function that will generate the request payload.</param>
+        /// <param name="accept" type="String">String containing a comma separated list of the mime types that this handler can work with.</param>
+        /// <param name="maxDataServiceVersion" type="String">String indicating the highest version of the protocol that this handler can work with.</param>
+        /// <returns type="Object">Handler object.</returns>
+
+        return {
+            accept: accept,
+            maxDataServiceVersion: maxDataServiceVersion,
+
+            read: function (response, context) {
+                return handlerRead(this, parseCallback, response, context);
+            },
+
+            write: function (request, context) {
+                return handlerWrite(this, serializeCallback, request, context);
+            }
+        };
+    };
+
+    var textParse = function (handler, body /*, context */) {
+        return body;
+    };
+
+    var textSerialize = function (handler, data /*, context */) {
+        if (assigned(data)) {
+            return data.toString();
+        } else {
+            return undefined;
+        }
+    };
+
+    odata.textHandler = handler(textParse, textSerialize, "text/plain", MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.contentType = contentType;
+    odata.contentTypeToString = contentTypeToString;
+    odata.handler = handler;
+    odata.createReadWriteContext = createReadWriteContext;
+    odata.fixRequestHeader = fixRequestHeader;
+    odata.getRequestOrResponseHeader = getRequestOrResponseHeader;
+    odata.getContentType = getContentType;
+    odata.getDataServiceVersion = getDataServiceVersion;
+    odata.MAX_DATA_SERVICE_VERSION = MAX_DATA_SERVICE_VERSION;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file


[04/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/store-memory.js
----------------------------------------------------------------------
diff --git a/datajs/src/store-memory.js b/datajs/src/store-memory.js
new file mode 100644
index 0000000..4f35090
--- /dev/null
+++ b/datajs/src/store-memory.js
@@ -0,0 +1,231 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store-memory.js 
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // Imports.
+    var throwErrorCallback = datajs.throwErrorCallback;
+    var delay = datajs.delay;
+
+    // CONTENT START
+
+    var MemoryStore = function (name) {
+        /// <summary>Constructor for store objects that use a sorted array as the underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+
+        var holes = [];
+        var items = [];
+        var keys = {};
+
+        this.name = name;
+
+        var getErrorCallback = function (error) {
+            return error || this.defaultError;
+        };
+
+        var validateKeyInput = function (key, error) {
+            /// <summary>Validates that the specified key is not undefined, not null, and not an array</summary>
+            /// <param name="key">Key value.</param>
+            /// <param name="error" type="Function">Error callback.</param>
+            /// <returns type="Boolean">True if the key is valid. False if the key is invalid and the error callback has been queued for execution.</returns>
+
+            var messageString;
+
+            if (key instanceof Array) {
+                messageString = "Array of keys not supported";
+            }
+
+            if (key === undefined || key === null) {
+                messageString = "Invalid key";
+            }
+
+            if (messageString) {
+                delay(error, { message: messageString });
+                return false;
+            }
+            return true;
+        };
+
+        this.add = function (key, value, success, error) {
+            /// <summary>Adds a new value identified by a key to the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="value">Value that is going to be added to the store.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            /// <remarks>
+            ///    This method errors out if the store already contains the specified key.
+            /// </remarks>
+
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                if (!keys.hasOwnProperty(key)) {
+                    this.addOrUpdate(key, value, success, error);
+                } else {
+                    error({ message: "key already exists", key: key });
+                }
+            }
+        };
+
+        this.addOrUpdate = function (key, value, success, error) {
+            /// <summary>Adds or updates a value identified by a key to the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="value">Value that is going to be added or updated to the store.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            /// <remarks>
+            ///   This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value.
+            /// </remarks>
+
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                var index = keys[key];
+                if (index === undefined) {
+                    if (holes.length > 0) {
+                        index = holes.splice(0, 1);
+                    } else {
+                        index = items.length;
+                    }
+                }
+                items[index] = value;
+                keys[key] = index;
+                delay(success, key, value);
+            }
+        };
+
+        this.clear = function (success) {
+            /// <summary>Removes all the data associated with this store object.</summary>
+            /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
+
+            items = [];
+            keys = {};
+            holes = [];
+
+            delay(success);
+        };
+
+        this.contains = function (key, success) {
+            /// <summary>Checks whether a key exists in the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
+
+            var contained = keys.hasOwnProperty(key);
+            delay(success, contained);
+        };
+
+        this.getAllKeys = function (success) {
+            /// <summary>Gets all the keys that exist in the store.</summary>
+            /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
+
+            var results = [];
+            for (var name in keys) {
+                results.push(name);
+            }
+            delay(success, results);
+        };
+
+        this.read = function (key, success, error) {
+            /// <summary>Reads the value associated to a key in the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                var index = keys[key];
+                delay(success, key, items[index]);
+            }
+        };
+
+        this.remove = function (key, success, error) {
+            /// <summary>Removes a key and its value from the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            error = getErrorCallback(error);
+
+            if (validateKeyInput(key, error)) {
+                var index = keys[key];
+                if (index !== undefined) {
+                    if (index === items.length - 1) {
+                        items.pop();
+                    } else {
+                        items[index] = undefined;
+                        holes.push(index);
+                    }
+                    delete keys[key];
+
+                    // The last item was removed, no need to keep track of any holes in the array.
+                    if (items.length === 0) {
+                        holes = [];
+                    }
+                }
+
+                delay(success);
+            }
+        };
+
+        this.update = function (key, value, success, error) {
+            /// <summary>Updates the value associated to a key in the store.</summary>
+            /// <param name="key" type="String">Key string.</param>
+            /// <param name="value">New value.</param>
+            /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
+            /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+            /// <remarks>
+            ///    This method errors out if the specified key is not found in the store.
+            /// </remarks>
+
+            error = getErrorCallback(error);
+            if (validateKeyInput(key, error)) {
+                if (keys.hasOwnProperty(key)) {
+                    this.addOrUpdate(key, value, success, error);
+                } else {
+                    error({ message: "key not found", key: key });
+                }
+            }
+        };
+    };
+
+    MemoryStore.create = function (name) {
+        /// <summary>Creates a store object that uses memory storage as its underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        /// <returns type="Object">Store object.</returns>
+        return new MemoryStore(name);
+    };
+
+    MemoryStore.isSupported = function () {
+        /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
+        /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</returns>
+        return true;
+    };
+
+    MemoryStore.prototype.close = function () {
+        /// <summary>This function does nothing in MemoryStore as it does not have a connection model.</summary>
+    };
+
+    MemoryStore.prototype.defaultError = throwErrorCallback;
+
+    /// <summary>Identifies the underlying mechanism used by the store.</summary>
+    MemoryStore.prototype.mechanism = "memory";
+
+    // DATAJS INTERNAL START
+    datajs.MemoryStore = MemoryStore;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/store.js
----------------------------------------------------------------------
diff --git a/datajs/src/store.js b/datajs/src/store.js
new file mode 100644
index 0000000..688e88e
--- /dev/null
+++ b/datajs/src/store.js
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store.js 
+
+(function (window, undefined) {
+   
+    var datajs = window.datajs || {};
+
+    var DomStore = datajs.DomStore;
+    var IndexedDBStore = datajs.IndexedDBStore;
+    var MemoryStore = datajs.MemoryStore;
+
+    // CONTENT START
+
+    var mechanisms = {
+        indexeddb: IndexedDBStore,
+        dom: DomStore,
+        memory: MemoryStore
+    };
+
+    datajs.defaultStoreMechanism = "best";
+
+    datajs.createStore = function (name, mechanism) {
+        /// <summary>Creates a new store object.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        /// <param name="mechanism" type="String" optional="true">A specific mechanism to use (defaults to best, can be "best", "dom", "indexeddb", "webdb").</param>
+        /// <returns type="Object">Store object.</returns>
+
+        if (!mechanism) {
+            mechanism = datajs.defaultStoreMechanism;
+        }
+
+        if (mechanism === "best") {
+            mechanism = (DomStore.isSupported()) ? "dom" : "memory";
+        }
+
+        var factory = mechanisms[mechanism];
+        if (factory) {
+            return factory.create(name);
+        }
+
+        throw { message: "Failed to create store", name: name, mechanism: mechanism };
+    };
+
+    // DATAJS INTERNAL START
+    datajs.mechanisms = mechanisms;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/utils.js
----------------------------------------------------------------------
diff --git a/datajs/src/utils.js b/datajs/src/utils.js
new file mode 100644
index 0000000..3dd3a49
--- /dev/null
+++ b/datajs/src/utils.js
@@ -0,0 +1,490 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// utils.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // CONTENT START
+
+    var activeXObject = function (progId) {
+        /// <summary>Creates a new ActiveXObject from the given progId.</summary>
+        /// <param name="progId" type="String" mayBeNull="false" optional="false">
+        ///    ProgId string of the desired ActiveXObject.
+        /// </param>
+        /// <remarks>
+        ///    This function throws whatever exception might occur during the creation
+        ///    of the ActiveXObject.
+        /// </remarks>
+        /// <returns type="Object">
+        ///     The ActiveXObject instance. Null if ActiveX is not supported by the
+        ///     browser.
+        /// </returns>
+        if (window.ActiveXObject) {
+            return new window.ActiveXObject(progId);
+        }
+        return null;
+    };
+
+    var assigned = function (value) {
+        /// <summary>Checks whether the specified value is different from null and undefined.</summary>
+        /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
+        /// <returns type="Boolean">true if the value is assigned; false otherwise.</returns>
+        return value !== null && value !== undefined;
+    };
+
+    var contains = function (arr, item) {
+        /// <summary>Checks whether the specified item is in the array.</summary>
+        /// <param name="arr" type="Array" optional="false" mayBeNull="false">Array to check in.</param>
+        /// <param name="item">Item to look for.</param>
+        /// <returns type="Boolean">true if the item is contained, false otherwise.</returns>
+
+        var i, len;
+        for (i = 0, len = arr.length; i < len; i++) {
+            if (arr[i] === item) {
+                return true;
+            }
+        }
+
+        return false;
+    };
+
+    var defined = function (a, b) {
+        /// <summary>Given two values, picks the first one that is not undefined.</summary>
+        /// <param name="a">First value.</param>
+        /// <param name="b">Second value.</param>
+        /// <returns>a if it's a defined value; else b.</returns>
+        return (a !== undefined) ? a : b;
+    };
+
+    var delay = function (callback) {
+        /// <summary>Delays the invocation of the specified function until execution unwinds.</summary>
+        /// <param name="callback" type="Function">Callback function.</param>
+        if (arguments.length === 1) {
+            window.setTimeout(callback, 0);
+            return;
+        }
+
+        var args = Array.prototype.slice.call(arguments, 1);
+        window.setTimeout(function () {
+            callback.apply(this, args);
+        }, 0);
+    };
+
+    // DATAJS INTERNAL START
+    var djsassert = function (condition, message, data) {
+        /// <summary>Throws an exception in case that a condition evaluates to false.</summary>
+        /// <param name="condition" type="Boolean">Condition to evaluate.</param>
+        /// <param name="message" type="String">Message explaining the assertion.</param>
+        /// <param name="data" type="Object">Additional data to be included in the exception.</param>
+
+        if (!condition) {
+            throw { message: "Assert fired: " + message, data: data };
+        };
+    };
+    // DATAJS INTERNAL END
+
+    var extend = function (target, values) {
+        /// <summary>Extends the target with the specified values.</summary>
+        /// <param name="target" type="Object">Object to add properties to.</param>
+        /// <param name="values" type="Object">Object with properties to add into target.</param>
+        /// <returns type="Object">The target object.</returns>
+
+        for (var name in values) {
+            target[name] = values[name];
+        }
+
+        return target;
+    };
+
+    var find = function (arr, callback) {
+        /// <summary>Returns the first item in the array that makes the callback function true.</summary>
+        /// <param name="arr" type="Array" optional="false" mayBeNull="true">Array to check in.</param>
+        /// <param name="callback" type="Function">Callback function to invoke once per item in the array.</param>
+        /// <returns>The first item that makes the callback return true; null otherwise or if the array is null.</returns>
+
+        if (arr) {
+            var i, len;
+            for (i = 0, len = arr.length; i < len; i++) {
+                if (callback(arr[i])) {
+                    return arr[i];
+                }
+            }
+        }
+        return null;
+    };
+
+    var isArray = function (value) {
+        /// <summary>Checks whether the specified value is an array object.</summary>
+        /// <param name="value">Value to check.</param>
+        /// <returns type="Boolean">true if the value is an array object; false otherwise.</returns>
+
+        return Object.prototype.toString.call(value) === "[object Array]";
+    };
+
+    var isDate = function (value) {
+        /// <summary>Checks whether the specified value is a Date object.</summary>
+        /// <param name="value">Value to check.</param>
+        /// <returns type="Boolean">true if the value is a Date object; false otherwise.</returns>
+
+        return Object.prototype.toString.call(value) === "[object Date]";
+    };
+
+    var isObject = function (value) {
+        /// <summary>Tests whether a value is an object.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <remarks>
+        ///     Per javascript rules, null and array values are objects and will cause this function to return true.
+        /// </remarks>
+        /// <returns type="Boolean">True is the value is an object; false otherwise.</returns>
+
+        return typeof value === "object";
+    };
+
+    var parseInt10 = function (value) {
+        /// <summary>Parses a value in base 10.</summary>
+        /// <param name="value" type="String">String value to parse.</param>
+        /// <returns type="Number">The parsed value, NaN if not a valid value.</returns>
+
+        return parseInt(value, 10);
+    };
+
+    var renameProperty = function (obj, oldName, newName) {
+        /// <summary>Renames a property in an object.</summary>
+        /// <param name="obj" type="Object">Object in which the property will be renamed.</param>
+        /// <param name="oldName" type="String">Name of the property that will be renamed.</param>
+        /// <param name="newName" type="String">New name of the property.</param>
+        /// <remarks>
+        ///    This function will not do anything if the object doesn't own a property with the specified old name.
+        /// </remarks>
+
+        if (obj.hasOwnProperty(oldName)) {
+            obj[newName] = obj[oldName];
+            delete obj[oldName];
+        }
+    };
+
+    var throwErrorCallback = function (error) {
+        /// <summary>Default error handler.</summary>
+        /// <param name="error" type="Object">Error to handle.</param>
+        throw error;
+    };
+
+    var trimString = function (str) {
+        /// <summary>Removes leading and trailing whitespaces from a string.</summary>
+        /// <param name="str" type="String" optional="false" mayBeNull="false">String to trim</param>
+        /// <returns type="String">The string with no leading or trailing whitespace.</returns>
+
+        if (str.trim) {
+            return str.trim();
+        }
+
+        return str.replace(/^\s+|\s+$/g, '');
+    };
+
+    var undefinedDefault = function (value, defaultValue) {
+        /// <summary>Returns a default value in place of undefined.</summary>
+        /// <param name="value" mayBeNull="true" optional="true">Value to check.</param>
+        /// <param name="defaultValue">Value to return if value is undefined.</param>
+        /// <returns>value if it's defined; defaultValue otherwise.</returns>
+        /// <remarks>
+        /// This should only be used for cases where falsy values are valid;
+        /// otherwise the pattern should be 'x = (value) ? value : defaultValue;'.
+        /// </remarks>
+        return (value !== undefined) ? value : defaultValue;
+    };
+
+    // Regular expression that splits a uri into its components:
+    // 0 - is the matched string.
+    // 1 - is the scheme.
+    // 2 - is the authority.
+    // 3 - is the path.
+    // 4 - is the query.
+    // 5 - is the fragment.
+    var uriRegEx = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#:]+)?(\?[^#]*)?(#.*)?/;
+    var uriPartNames = ["scheme", "authority", "path", "query", "fragment"];
+
+    var getURIInfo = function (uri) {
+        /// <summary>Gets information about the components of the specified URI.</summary>
+        /// <param name="uri" type="String">URI to get information from.</param>
+        /// <returns type="Object">
+        /// An object with an isAbsolute flag and part names (scheme, authority, etc.) if available.
+        /// </returns>
+
+        var result = { isAbsolute: false };
+
+        if (uri) {
+            var matches = uriRegEx.exec(uri);
+            if (matches) {
+                var i, len;
+                for (i = 0, len = uriPartNames.length; i < len; i++) {
+                    if (matches[i + 1]) {
+                        result[uriPartNames[i]] = matches[i + 1];
+                    }
+                }
+            }
+            if (result.scheme) {
+                result.isAbsolute = true;
+            }
+        }
+
+        return result;
+    };
+
+    var getURIFromInfo = function (uriInfo) {
+        /// <summary>Builds a URI string from its components.</summary>
+        /// <param name="uriInfo" type="Object"> An object with uri parts (scheme, authority, etc.).</param>
+        /// <returns type="String">URI string.</returns>
+
+        return "".concat(
+            uriInfo.scheme || "",
+            uriInfo.authority || "",
+            uriInfo.path || "",
+            uriInfo.query || "",
+            uriInfo.fragment || "");
+    };
+
+    // Regular expression that splits a uri authority into its subcomponents:
+    // 0 - is the matched string.
+    // 1 - is the userinfo subcomponent.
+    // 2 - is the host subcomponent.
+    // 3 - is the port component.
+    var uriAuthorityRegEx = /^\/{0,2}(?:([^@]*)@)?([^:]+)(?::{1}(\d+))?/;
+
+    // Regular expression that matches percentage enconded octects (i.e %20 or %3A);
+    var pctEncodingRegEx = /%[0-9A-F]{2}/ig;
+
+    var normalizeURICase = function (uri) {
+        /// <summary>Normalizes the casing of a URI.</summary>
+        /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
+        /// <returns type="String">The URI normalized to lower case.</returns>
+
+        var uriInfo = getURIInfo(uri);
+        var scheme = uriInfo.scheme;
+        var authority = uriInfo.authority;
+
+        if (scheme) {
+            uriInfo.scheme = scheme.toLowerCase();
+            if (authority) {
+                var matches = uriAuthorityRegEx.exec(authority);
+                if (matches) {
+                    uriInfo.authority = "//" +
+                    (matches[1] ? matches[1] + "@" : "") +
+                    (matches[2].toLowerCase()) +
+                    (matches[3] ? ":" + matches[3] : "");
+                }
+            }
+        }
+
+        uri = getURIFromInfo(uriInfo);
+
+        return uri.replace(pctEncodingRegEx, function (str) {
+            return str.toLowerCase();
+        });
+    };
+
+    var normalizeURI = function (uri, base) {
+        /// <summary>Normalizes a possibly relative URI with a base URI.</summary>
+        /// <param name="uri" type="String">URI to normalize, absolute or relative.</param>
+        /// <param name="base" type="String" mayBeNull="true">Base URI to compose with.</param>
+        /// <returns type="String">The composed URI if relative; the original one if absolute.</returns>
+
+        if (!base) {
+            return uri;
+        }
+
+        var uriInfo = getURIInfo(uri);
+        if (uriInfo.isAbsolute) {
+            return uri;
+        }
+
+        var baseInfo = getURIInfo(base);
+        var normInfo = {};
+        var path;
+
+        if (uriInfo.authority) {
+            normInfo.authority = uriInfo.authority;
+            path = uriInfo.path;
+            normInfo.query = uriInfo.query;
+        } else {
+            if (!uriInfo.path) {
+                path = baseInfo.path;
+                normInfo.query = uriInfo.query || baseInfo.query;
+            } else {
+                if (uriInfo.path.charAt(0) === '/') {
+                    path = uriInfo.path;
+                } else {
+                    path = mergeUriPathWithBase(uriInfo.path, baseInfo.path);
+                }
+                normInfo.query = uriInfo.query;
+            }
+            normInfo.authority = baseInfo.authority;
+        }
+
+        normInfo.path = removeDotsFromPath(path);
+
+        normInfo.scheme = baseInfo.scheme;
+        normInfo.fragment = uriInfo.fragment;
+
+        return getURIFromInfo(normInfo);
+    };
+
+    var mergeUriPathWithBase = function (uriPath, basePath) {
+        /// <summary>Merges the path of a relative URI and a base URI.</summary>
+        /// <param name="uriPath" type="String>Relative URI path.</param>
+        /// <param name="basePath" type="String">Base URI path.</param>
+        /// <returns type="String">A string with the merged path.</returns>
+
+        var path = "/";
+        var end;
+
+        if (basePath) {
+            end = basePath.lastIndexOf("/");
+            path = basePath.substring(0, end);
+
+            if (path.charAt(path.length - 1) !== "/") {
+                path = path + "/";
+            }
+        }
+
+        return path + uriPath;
+    };
+
+    var removeDotsFromPath = function (path) {
+        /// <summary>Removes the special folders . and .. from a URI's path.</summary>
+        /// <param name="path" type="string">URI path component.</param>
+        /// <returns type="String">Path without any . and .. folders.</returns>
+
+        var result = "";
+        var segment = "";
+        var end;
+
+        while (path) {
+            if (path.indexOf("..") === 0 || path.indexOf(".") === 0) {
+                path = path.replace(/^\.\.?\/?/g, "");
+            } else if (path.indexOf("/..") === 0) {
+                path = path.replace(/^\/\..\/?/g, "/");
+                end = result.lastIndexOf("/");
+                if (end === -1) {
+                    result = "";
+                } else {
+                    result = result.substring(0, end);
+                }
+            } else if (path.indexOf("/.") === 0) {
+                path = path.replace(/^\/\.\/?/g, "/");
+            } else {
+                segment = path;
+                end = path.indexOf("/", 1);
+                if (end !== -1) {
+                    segment = path.substring(0, end);
+                }
+                result = result + segment;
+                path = path.replace(segment, "");
+            }
+        }
+        return result;
+    };
+
+    var convertByteArrayToHexString = function (str) {
+        var arr = [];
+        if (window.atob === undefined) {
+            arr = decodeBase64(str);
+        } else {
+            var binaryStr = window.atob(str);
+            for (var i = 0; i < binaryStr.length; i++) {
+                arr.push(binaryStr.charCodeAt(i));
+            }
+        }
+        var hexValue = "";
+        var hexValues = "0123456789ABCDEF";
+        for (var j = 0; j < arr.length; j++) {
+            var t = arr[j];
+            hexValue += hexValues[t >> 4];
+            hexValue += hexValues[t & 0x0F];
+        }
+        return hexValue;
+    };
+
+    var decodeBase64 = function (str) {
+        var binaryString = "";
+        for (var i = 0; i < str.length; i++) {
+            var base65IndexValue = getBase64IndexValue(str[i]);
+            var binaryValue = "";
+            if (base65IndexValue !== null) {
+                binaryValue = base65IndexValue.toString(2);
+                binaryString += addBase64Padding(binaryValue);
+            }
+        }
+        var byteArray = [];
+        var numberOfBytes = parseInt(binaryString.length / 8, 10);
+        for (i = 0; i < numberOfBytes; i++) {
+            var intValue = parseInt(binaryString.substring(i * 8, (i + 1) * 8), 2);
+            byteArray.push(intValue);
+        }
+        return byteArray;
+    };
+
+    var getBase64IndexValue = function (character) {
+        var asciiCode = character.charCodeAt(0);
+        var asciiOfA = 65;
+        var differenceBetweenZanda = 6;
+        if (asciiCode >= 65 && asciiCode <= 90) {           // between "A" and "Z" inclusive
+            return asciiCode - asciiOfA;
+        } else if (asciiCode >= 97 && asciiCode <= 122) {   // between 'a' and 'z' inclusive
+            return asciiCode - asciiOfA - differenceBetweenZanda;
+        } else if (asciiCode >= 48 && asciiCode <= 57) {    // between '0' and '9' inclusive
+            return asciiCode + 4;
+        } else if (character == "+") {
+            return 62;
+        } else if (character == "/") {
+            return 63;
+        } else {
+            return null;
+        }
+    };
+
+    var addBase64Padding = function (binaryString) {
+        while (binaryString.length < 6) {
+            binaryString = "0" + binaryString;
+        }
+        return binaryString;
+    };
+    // DATAJS INTERNAL START
+
+    datajs.activeXObject = activeXObject;
+    datajs.assigned = assigned;
+    datajs.contains = contains;
+    datajs.defined = defined;
+    datajs.delay = delay;
+    datajs.djsassert = djsassert;
+    datajs.extend = extend;
+    datajs.find = find;
+    datajs.getURIInfo = getURIInfo;
+    datajs.isArray = isArray;
+    datajs.isDate = isDate;
+    datajs.isObject = isObject;
+    datajs.normalizeURI = normalizeURI;
+    datajs.normalizeURICase = normalizeURICase;
+    datajs.parseInt10 = parseInt10;
+    datajs.renameProperty = renameProperty;
+    datajs.throwErrorCallback = throwErrorCallback;
+    datajs.trimString = trimString;
+    datajs.undefinedDefault = undefinedDefault;
+    datajs.decodeBase64 = decodeBase64;
+    datajs.convertByteArrayToHexString = convertByteArrayToHexString;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/xml.js
----------------------------------------------------------------------
diff --git a/datajs/src/xml.js b/datajs/src/xml.js
new file mode 100644
index 0000000..895a4c7
--- /dev/null
+++ b/datajs/src/xml.js
@@ -0,0 +1,824 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// xml.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs;
+
+    var activeXObject = datajs.activeXObject;
+    var djsassert = datajs.djsassert;
+    var extend = datajs.extend;
+    var isArray = datajs.isArray;
+    var isObject = datajs.isObject;
+    var normalizeURI = datajs.normalizeURI;
+
+    // CONTENT START
+
+    // URI prefixes to generate smaller code.
+    var http = "http://";
+    var w3org = http + "www.w3.org/";               // http://www.w3.org/
+
+    var xhtmlNS = w3org + "1999/xhtml";             // http://www.w3.org/1999/xhtml
+    var xmlnsNS = w3org + "2000/xmlns/";            // http://www.w3.org/2000/xmlns/
+    var xmlNS = w3org + "XML/1998/namespace";       // http://www.w3.org/XML/1998/namespace
+
+    var mozillaParserErroNS = http + "www.mozilla.org/newlayout/xml/parsererror.xml";
+
+    var hasLeadingOrTrailingWhitespace = function (text) {
+        /// <summary>Checks whether the specified string has leading or trailing spaces.</summary>
+        /// <param name="text" type="String">String to check.</param>
+        /// <returns type="Boolean">true if text has any leading or trailing whitespace; false otherwise.</returns>
+
+        var re = /(^\s)|(\s$)/;
+        return re.test(text);
+    };
+
+    var isWhitespace = function (text) {
+        /// <summary>Determines whether the specified text is empty or whitespace.</summary>
+        /// <param name="text" type="String">Value to inspect.</param>
+        /// <returns type="Boolean">true if the text value is empty or all whitespace; false otherwise.</returns>
+
+        var ws = /^\s*$/;
+        return text === null || ws.test(text);
+    };
+
+    var isWhitespacePreserveContext = function (domElement) {
+        /// <summary>Determines whether the specified element has xml:space='preserve' applied.</summary>
+        /// <param name="domElement">Element to inspect.</param>
+        /// <returns type="Boolean">Whether xml:space='preserve' is in effect.</returns>
+
+        while (domElement !== null && domElement.nodeType === 1) {
+            var val = xmlAttributeValue(domElement, "space", xmlNS);
+            if (val === "preserve") {
+                return true;
+            } else if (val === "default") {
+                break;
+            } else {
+                domElement = domElement.parentNode;
+            }
+        }
+
+        return false;
+    };
+
+    var isXmlNSDeclaration = function (domAttribute) {
+        /// <summary>Determines whether the attribute is a XML namespace declaration.</summary>
+        /// <param name="domAttribute">Element to inspect.</param>
+        /// <returns type="Boolean">
+        ///    True if the attribute is a namespace declaration (its name is 'xmlns' or starts with 'xmlns:'; false otherwise.
+        /// </returns>
+
+        var nodeName = domAttribute.nodeName;
+        return nodeName == "xmlns" || nodeName.indexOf("xmlns:") === 0;
+    };
+
+    var safeSetProperty = function (obj, name, value) {
+        /// <summary>Safely set as property in an object by invoking obj.setProperty.</summary>
+        /// <param name="obj">Object that exposes a setProperty method.</param>
+        /// <param name="name" type="String" mayBeNull="false">Property name.</param>
+        /// <param name="value">Property value.</param>
+
+        try {
+            obj.setProperty(name, value);
+        } catch (_) { }
+    };
+
+    var msXmlDom3 = function () {
+        /// <summary>Creates an configures new MSXML 3.0 ActiveX object.</summary>
+        /// <remakrs>
+        ///    This function throws any exception that occurs during the creation
+        ///    of the MSXML 3.0 ActiveX object.
+        /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns>
+
+        var msxml3 = activeXObject("Msxml2.DOMDocument.3.0");
+        if (msxml3) {
+            safeSetProperty(msxml3, "ProhibitDTD", true);
+            safeSetProperty(msxml3, "MaxElementDepth", 256);
+            safeSetProperty(msxml3, "AllowDocumentFunction", false);
+            safeSetProperty(msxml3, "AllowXsltScript", false);
+        }
+        return msxml3;
+    };
+
+    var msXmlDom = function () {
+        /// <summary>Creates an configures new MSXML 6.0 or MSXML 3.0 ActiveX object.</summary>
+        /// <remakrs>
+        ///    This function will try to create a new MSXML 6.0 ActiveX object. If it fails then
+        ///    it will fallback to create a new MSXML 3.0 ActiveX object. Any exception that
+        ///    happens during the creation of the MSXML 6.0 will be handled by the function while
+        ///    the ones that happend during the creation of the MSXML 3.0 will be thrown.
+        /// <returns type="Object">New MSXML 3.0 ActiveX object.</returns>
+
+        try {
+            var msxml = activeXObject("Msxml2.DOMDocument.6.0");
+            if (msxml) {
+                msxml.async = true;
+            }
+            return msxml;
+        } catch (_) {
+            return msXmlDom3();
+        }
+    };
+
+    var msXmlParse = function (text) {
+        /// <summary>Parses an XML string using the MSXML DOM.</summary>
+        /// <remakrs>
+        ///    This function throws any exception that occurs during the creation
+        ///    of the MSXML ActiveX object.  It also will throw an exception
+        ///    in case of a parsing error.
+        /// <returns type="Object">New MSXML DOMDocument node representing the parsed XML string.</returns>
+
+        var dom = msXmlDom();
+        if (!dom) {
+            return null;
+        }
+
+        dom.loadXML(text);
+        var parseError = dom.parseError;
+        if (parseError.errorCode !== 0) {
+            xmlThrowParserError(parseError.reason, parseError.srcText, text);
+        }
+        return dom;
+    };
+
+    var xmlThrowParserError = function (exceptionOrReason, srcText, errorXmlText) {
+        /// <summary>Throws a new exception containing XML parsing error information.</summary>
+        /// <param name="exceptionOrReason">
+        ///    String indicatin the reason of the parsing failure or
+        ///    Object detailing the parsing error.
+        /// </param>
+        /// <param name="srcText" type="String">
+        ///    String indicating the part of the XML string that caused the parsing error.
+        /// </param>
+        /// <param name="errorXmlText" type="String">XML string for wich the parsing failed.</param>
+
+        if (typeof exceptionOrReason === "string") {
+            exceptionOrReason = { message: exceptionOrReason };
+        }
+        throw extend(exceptionOrReason, { srcText: srcText || "", errorXmlText: errorXmlText || "" });
+    };
+
+    var xmlParse = function (text) {
+        /// <summary>Returns an XML DOM document from the specified text.</summary>
+        /// <param name="text" type="String">Document text.</param>
+        /// <returns>XML DOM document.</returns>
+        /// <remarks>This function will throw an exception in case of a parse error.</remarks>
+
+        var domParser = window.DOMParser && new window.DOMParser();
+        var dom;
+
+        if (!domParser) {
+            dom = msXmlParse(text);
+            if (!dom) {
+                xmlThrowParserError("XML DOM parser not supported");
+            }
+            return dom;
+        }
+
+        try {
+            dom = domParser.parseFromString(text, "text/xml");
+        } catch (e) {
+            xmlThrowParserError(e, "", text);
+        }
+
+        var element = dom.documentElement;
+        var nsURI = element.namespaceURI;
+        var localName = xmlLocalName(element);
+
+        // Firefox reports errors by returing the DOM for an xml document describing the problem.
+        if (localName === "parsererror" && nsURI === mozillaParserErroNS) {
+            var srcTextElement = xmlFirstChildElement(element, mozillaParserErroNS, "sourcetext");
+            var srcText = srcTextElement ? xmlNodeValue(srcTextElement) : "";
+            xmlThrowParserError(xmlInnerText(element) || "", srcText, text);
+        }
+
+        // Chrome (and maybe other webkit based browsers) report errors by injecting a header with an error message.
+        // The error may be localized, so instead we simply check for a header as the
+        // top element or descendant child of the document.
+        if (localName === "h3" && nsURI === xhtmlNS || xmlFirstDescendantElement(element, xhtmlNS, "h3")) {
+            var reason = "";
+            var siblings = [];
+            var cursor = element.firstChild;
+            while (cursor) {
+                if (cursor.nodeType === 1) {
+                    reason += xmlInnerText(cursor) || "";
+                }
+                siblings.push(cursor.nextSibling);
+                cursor = cursor.firstChild || siblings.shift();
+            }
+            reason += xmlInnerText(element) || "";
+            xmlThrowParserError(reason, "", text);
+        }
+
+        return dom;
+    };
+
+    var xmlQualifiedName = function (prefix, name) {
+        /// <summary>Builds a XML qualified name string in the form of "prefix:name".</summary>
+        /// <param name="prefix" type="String" maybeNull="true">Prefix string.</param>
+        /// <param name="name" type="String">Name string to qualify with the prefix.</param>
+        /// <returns type="String">Qualified name.</returns>
+
+        return prefix ? prefix + ":" + name : name;
+    };
+
+    var xmlAppendText = function (domNode, textNode) {
+        /// <summary>Appends a text node into the specified DOM element node.</summary>
+        /// <param name="domNode">DOM node for the element.</param>
+        /// <param name="text" type="String" mayBeNull="false">Text to append as a child of element.</param>
+        if (hasLeadingOrTrailingWhitespace(textNode.data)) {
+            var attr = xmlAttributeNode(domNode, xmlNS, "space");
+            if (!attr) {
+                attr = xmlNewAttribute(domNode.ownerDocument, xmlNS, xmlQualifiedName("xml", "space"));
+                xmlAppendChild(domNode, attr);
+            }
+            attr.value = "preserve";
+        }
+        domNode.appendChild(textNode);
+        return domNode;
+    };
+
+    var xmlAttributes = function (element, onAttributeCallback) {
+        /// <summary>Iterates through the XML element's attributes and invokes the callback function for each one.</summary>
+        /// <param name="element">Wrapped element to iterate over.</param>
+        /// <param name="onAttributeCallback" type="Function">Callback function to invoke with wrapped attribute nodes.</param>
+
+        var attributes = element.attributes;
+        var i, len;
+        for (i = 0, len = attributes.length; i < len; i++) {
+            onAttributeCallback(attributes.item(i));
+        }
+    };
+
+    var xmlAttributeValue = function (domNode, localName, nsURI) {
+        /// <summary>Returns the value of a DOM element's attribute.</summary>
+        /// <param name="domNode">DOM node for the owning element.</param>
+        /// <param name="localName" type="String">Local name of the attribute.</param>
+        /// <param name="nsURI" type="String">Namespace URI of the attribute.</param>
+        /// <returns type="String" maybeNull="true">The attribute value, null if not found.</returns>
+
+        var attribute = xmlAttributeNode(domNode, localName, nsURI);
+        return attribute ? xmlNodeValue(attribute) : null;
+    };
+
+    var xmlAttributeNode = function (domNode, localName, nsURI) {
+        /// <summary>Gets an attribute node from a DOM element.</summary>
+        /// <param name="domNode">DOM node for the owning element.</param>
+        /// <param name="localName" type="String">Local name of the attribute.</param>
+        /// <param name="nsURI" type="String">Namespace URI of the attribute.</param>
+        /// <returns>The attribute node, null if not found.</returns>
+
+        var attributes = domNode.attributes;
+        if (attributes.getNamedItemNS) {
+            return attributes.getNamedItemNS(nsURI || null, localName);
+        }
+
+        return attributes.getQualifiedItem(localName, nsURI) || null;
+    };
+
+    var xmlBaseURI = function (domNode, baseURI) {
+        /// <summary>Gets the value of the xml:base attribute on the specified element.</summary>
+        /// <param name="domNode">Element to get xml:base attribute value from.</param>
+        /// <param name="baseURI" mayBeNull="true" optional="true">Base URI used to normalize the value of the xml:base attribute.</param>
+        /// <returns type="String">Value of the xml:base attribute if found; the baseURI or null otherwise.</returns>
+
+        var base = xmlAttributeNode(domNode, "base", xmlNS);
+        return (base ? normalizeURI(base.value, baseURI) : baseURI) || null;
+    };
+
+
+    var xmlChildElements = function (domNode, onElementCallback) {
+        /// <summary>Iterates through the XML element's child DOM elements and invokes the callback function for each one.</summary>
+        /// <param name="element">DOM Node containing the DOM elements to iterate over.</param>
+        /// <param name="onElementCallback" type="Function">Callback function to invoke for each child DOM element.</param>
+
+        xmlTraverse(domNode, /*recursive*/false, function (child) {
+            if (child.nodeType === 1) {
+                onElementCallback(child);
+            }
+            // continue traversing.
+            return true;
+        });
+    };
+
+    var xmlFindElementByPath = function (root, namespaceURI, path) {
+        /// <summary>Gets the descendant element under root that corresponds to the specified path and namespace URI.</summary>
+        /// <param name="root">DOM element node from which to get the descendant element.</param>
+        /// <param name="namespaceURI" type="String">The namespace URI of the element to match.</param>
+        /// <param name="path" type="String">Path to the desired descendant element.</param>
+        /// <returns>The element specified by path and namespace URI.</returns>
+        /// <remarks>
+        ///     All the elements in the path are matched against namespaceURI.
+        ///     The function will stop searching on the first element that doesn't match the namespace and the path.
+        /// </remarks>
+
+        var parts = path.split("/");
+        var i, len;
+        for (i = 0, len = parts.length; i < len; i++) {
+            root = root && xmlFirstChildElement(root, namespaceURI, parts[i]);
+        }
+        return root || null;
+    };
+
+    var xmlFindNodeByPath = function (root, namespaceURI, path) {
+        /// <summary>Gets the DOM element or DOM attribute node under root that corresponds to the specified path and namespace URI.</summary>
+        /// <param name="root">DOM element node from which to get the descendant node.</param>
+        /// <param name="namespaceURI" type="String">The namespace URI of the node to match.</param>
+        /// <param name="path" type="String">Path to the desired descendant node.</param>
+        /// <returns>The node specified by path and namespace URI.</returns>
+        /// <remarks>
+        ///     This function will traverse the path and match each node associated to a path segement against the namespace URI.
+        ///     The traversal stops when the whole path has been exahusted or a node that doesn't belogong the specified namespace is encountered.
+        ///
+        ///     The last segment of the path may be decorated with a starting @ character to indicate that the desired node is a DOM attribute.
+        /// </remarks>
+
+        var lastSegmentStart = path.lastIndexOf("/");
+        var nodePath = path.substring(lastSegmentStart + 1);
+        var parentPath = path.substring(0, lastSegmentStart);
+
+        var node = parentPath ? xmlFindElementByPath(root, namespaceURI, parentPath) : root;
+        if (node) {
+            if (nodePath.charAt(0) === "@") {
+                return xmlAttributeNode(node, nodePath.substring(1), namespaceURI);
+            }
+            return xmlFirstChildElement(node, namespaceURI, nodePath);
+        }
+        return null;
+    };
+
+    var xmlFirstChildElement = function (domNode, namespaceURI, localName) {
+        /// <summary>Returns the first child DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
+        /// <param name="domNode">DOM node from which the child DOM element is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <returns>The node's first child DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
+
+        return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/false);
+    };
+
+    var xmlFirstDescendantElement = function (domNode, namespaceURI, localName) {
+        /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
+        /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
+
+        if (domNode.getElementsByTagNameNS) {
+            var result = domNode.getElementsByTagNameNS(namespaceURI, localName);
+            return result.length > 0 ? result[0] : null;
+        }
+        return xmlFirstElementMaybeRecursive(domNode, namespaceURI, localName, /*recursive*/true);
+    };
+
+    var xmlFirstElementMaybeRecursive = function (domNode, namespaceURI, localName, recursive) {
+        /// <summary>Returns the first descendant DOM element under the specified DOM node that matches the specified namespace URI and local name.</summary>
+        /// <param name="domNode">DOM node from which the descendant DOM element is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <param name="recursive" type="Boolean">
+        ///     True if the search should include all the descendants of the DOM node.
+        ///     False if the search should be scoped only to the direct children of the DOM node.
+        /// </param>
+        /// <returns>The node's first descendant DOM element that matches the specified namespace URI and local name; null otherwise.</returns>
+
+        var firstElement = null;
+        xmlTraverse(domNode, recursive, function (child) {
+            if (child.nodeType === 1) {
+                var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(child) === namespaceURI;
+                var isExpectedNodeName = !localName || xmlLocalName(child) === localName;
+
+                if (isExpectedNamespace && isExpectedNodeName) {
+                    firstElement = child;
+                }
+            }
+            return firstElement === null;
+        });
+        return firstElement;
+    };
+
+    var xmlInnerText = function (xmlElement) {
+        /// <summary>Gets the concatenated value of all immediate child text and CDATA nodes for the specified element.</summary>
+        /// <param name="domElement">Element to get values for.</param>
+        /// <returns type="String">Text for all direct children.</returns>
+
+        var result = null;
+        var root = (xmlElement.nodeType === 9 && xmlElement.documentElement) ? xmlElement.documentElement : xmlElement;
+        var whitespaceAlreadyRemoved = root.ownerDocument.preserveWhiteSpace === false;
+        var whitespacePreserveContext;
+
+        xmlTraverse(root, false, function (child) {
+            if (child.nodeType === 3 || child.nodeType === 4) {
+                // isElementContentWhitespace indicates that this is 'ignorable whitespace',
+                // but it's not defined by all browsers, and does not honor xml:space='preserve'
+                // in some implementations.
+                //
+                // If we can't tell either way, we walk up the tree to figure out whether
+                // xml:space is set to preserve; otherwise we discard pure-whitespace.
+                //
+                // For example <a>  <b>1</b></a>. The space between <a> and <b> is usually 'ignorable'.
+                var text = xmlNodeValue(child);
+                var shouldInclude = whitespaceAlreadyRemoved || !isWhitespace(text);
+                if (!shouldInclude) {
+                    // Walk up the tree to figure out whether we are in xml:space='preserve' context
+                    // for the cursor (needs to happen only once).
+                    if (whitespacePreserveContext === undefined) {
+                        whitespacePreserveContext = isWhitespacePreserveContext(root);
+                    }
+
+                    shouldInclude = whitespacePreserveContext;
+                }
+
+                if (shouldInclude) {
+                    if (!result) {
+                        result = text;
+                    } else {
+                        result += text;
+                    }
+                }
+            }
+            // Continue traversing?
+            return true;
+        });
+        return result;
+    };
+
+    var xmlLocalName = function (domNode) {
+        /// <summary>Returns the localName of a XML node.</summary>
+        /// <param name="domNode">DOM node to get the value from.</param>
+        /// <returns type="String">localName of domNode.</returns>
+
+        return domNode.localName || domNode.baseName;
+    };
+
+    var xmlNamespaceURI = function (domNode) {
+        /// <summary>Returns the namespace URI of a XML node.</summary>
+        /// <param name="node">DOM node to get the value from.</param>
+        /// <returns type="String">Namespace URI of domNode.</returns>
+
+        return domNode.namespaceURI || null;
+    };
+
+    var xmlNodeValue = function (domNode) {
+        /// <summary>Returns the value or the inner text of a XML node.</summary>
+        /// <param name="node">DOM node to get the value from.</param>
+        /// <returns>Value of the domNode or the inner text if domNode represents a DOM element node.</returns>
+        
+        if (domNode.nodeType === 1) {
+            return xmlInnerText(domNode);
+        }
+        return domNode.nodeValue;
+    };
+
+    var xmlTraverse = function (domNode, recursive, onChildCallback) {
+        /// <summary>Walks through the descendants of the domNode and invokes a callback for each node.</summary>
+        /// <param name="domNode">DOM node whose descendants are going to be traversed.</param>
+        /// <param name="recursive" type="Boolean">
+        ///    True if the traversal should include all the descenants of the DOM node.
+        ///    False if the traversal should be scoped only to the direct children of the DOM node.
+        /// </param>
+        /// <returns type="String">Namespace URI of node.</returns>
+
+        var subtrees = [];
+        var child = domNode.firstChild;
+        var proceed = true;
+        while (child && proceed) {
+            proceed = onChildCallback(child);
+            if (proceed) {
+                if (recursive && child.firstChild) {
+                    subtrees.push(child.firstChild);
+                }
+                child = child.nextSibling || subtrees.shift();
+            }
+        }
+    };
+
+    var xmlSiblingElement = function (domNode, namespaceURI, localName) {
+        /// <summary>Returns the next sibling DOM element of the specified DOM node.</summary>
+        /// <param name="domNode">DOM node from which the next sibling is going to be retrieved.</param>
+        /// <param name="namespaceURI" type="String" optional="true">The namespace URI of the element to match.</param>
+        /// <param name="localName" type="String" optional="true">Name of the element to match.</param>
+        /// <returns>The node's next sibling DOM element, null if there is none.</returns>
+
+        var sibling = domNode.nextSibling;
+        while (sibling) {
+            if (sibling.nodeType === 1) {
+                var isExpectedNamespace = !namespaceURI || xmlNamespaceURI(sibling) === namespaceURI;
+                var isExpectedNodeName = !localName || xmlLocalName(sibling) === localName;
+
+                if (isExpectedNamespace && isExpectedNodeName) {
+                    return sibling;
+                }
+            }
+            sibling = sibling.nextSibling;
+        }
+        return null;
+    };
+
+    var xmlDom = function () {
+        /// <summary>Creates a new empty DOM document node.</summary>
+        /// <returns>New DOM document node.</returns>
+        /// <remarks>
+        ///    This function will first try to create a native DOM document using
+        ///    the browsers createDocument function.  If the browser doesn't
+        ///    support this but supports ActiveXObject, then an attempt to create
+        ///    an MSXML 6.0 DOM will be made. If this attempt fails too, then an attempt
+        ///    for creating an MXSML 3.0 DOM will be made.  If this last attemp fails or
+        ///    the browser doesn't support ActiveXObject then an exception will be thrown.
+        /// </remarks>
+
+        var implementation = window.document.implementation;
+        return (implementation && implementation.createDocument) ?
+           implementation.createDocument(null, null, null) :
+           msXmlDom();
+    };
+
+    var xmlAppendChildren = function (parent, children) {
+        /// <summary>Appends a collection of child nodes or string values to a parent DOM node.</summary>
+        /// <param name="parent">DOM node to which the children will be appended.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be appended to the parent.</param>
+        /// <returns>The parent with the appended children or string values.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended to the parent.
+        /// </remarks>
+
+        if (!isArray(children)) {
+            return xmlAppendChild(parent, children);
+        }
+
+        var i, len;
+        for (i = 0, len = children.length; i < len; i++) {
+            children[i] && xmlAppendChild(parent, children[i]);
+        }
+        return parent;
+    };
+
+    var xmlAppendChild = function (parent, child) {
+        /// <summary>Appends a child node or a string value to a parent DOM node.</summary>
+        /// <param name="parent">DOM node to which the child will be appended.</param>
+        /// <param name="child">Child DOM node or string value to append to the parent.</param>
+        /// <returns>The parent with the appended child or string value.</returns>
+        /// <remarks>
+        ///    If child is a string value, then a new DOM text node is going to be created
+        ///    for it and then appended to the parent.
+        /// </remarks>
+
+        djsassert(parent !== child, "xmlAppendChild() - parent and child are one and the same!");
+        if (child) {
+            if (typeof child === "string") {
+                return xmlAppendText(parent, xmlNewText(parent.ownerDocument, child));
+            }
+            if (child.nodeType === 2) {
+                parent.setAttributeNodeNS ? parent.setAttributeNodeNS(child) : parent.setAttributeNode(child);
+            } else {
+                parent.appendChild(child);
+            }
+        }
+        return parent;
+    };
+
+    var xmlNewAttribute = function (dom, namespaceURI, qualifiedName, value) {
+        /// <summary>Creates a new DOM attribute node.</summary>
+        /// <param name="dom">DOM document used to create the attribute.</param>
+        /// <param name="prefix" type="String">Namespace prefix.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI.</param>
+        /// <returns>DOM attribute node for the namespace declaration.</returns>
+
+        var attribute =
+            dom.createAttributeNS && dom.createAttributeNS(namespaceURI, qualifiedName) ||
+            dom.createNode(2, qualifiedName, namespaceURI || undefined);
+
+        attribute.value = value || "";
+        return attribute;
+    };
+
+    var xmlNewElement = function (dom, nampespaceURI, qualifiedName, children) {
+        /// <summary>Creates a new DOM element node.</summary>
+        /// <param name="dom">DOM document used to create the DOM element.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element.</param>
+        /// <param name="qualifiedName" type="String">Qualified name in the form of "prefix:name" of the new DOM element.</param>
+        /// <param name="children" type="Array" optional="true">
+        ///     Collection of child DOM nodes or string values that are going to be appended to the new DOM element.
+        /// </param>
+        /// <returns>New DOM element.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended to the new DOM element.
+        /// </remarks>
+
+        var element =
+            dom.createElementNS && dom.createElementNS(nampespaceURI, qualifiedName) ||
+            dom.createNode(1, qualifiedName, nampespaceURI || undefined);
+
+        return xmlAppendChildren(element, children || []);
+    };
+
+    var xmlNewNSDeclaration = function (dom, namespaceURI, prefix) {
+        /// <summary>Creates a namespace declaration attribute.</summary>
+        /// <param name="dom">DOM document used to create the attribute.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI.</param>
+        /// <param name="prefix" type="String">Namespace prefix.</param>
+        /// <returns>DOM attribute node for the namespace declaration.</returns>
+
+        return xmlNewAttribute(dom, xmlnsNS, xmlQualifiedName("xmlns", prefix), namespaceURI);
+    };
+
+    var xmlNewFragment = function (dom, text) {
+        /// <summary>Creates a new DOM document fragment node for the specified xml text.</summary>
+        /// <param name="dom">DOM document from which the fragment node is going to be created.</param>
+        /// <param name="text" type="String" mayBeNull="false">XML text to be represented by the XmlFragment.</param>
+        /// <returns>New DOM document fragment object.</returns>
+
+        var value = "<c>" + text + "</c>";
+        var tempDom = xmlParse(value);
+        var tempRoot = tempDom.documentElement;
+        var imported = ("importNode" in dom) ? dom.importNode(tempRoot, true) : tempRoot;
+        var fragment = dom.createDocumentFragment();
+
+        var importedChild = imported.firstChild;
+        while (importedChild) {
+            fragment.appendChild(importedChild);
+            importedChild = importedChild.nextSibling;
+        }
+        return fragment;
+    };
+
+    var xmlNewText = function (dom, text) {
+        /// <summary>Creates new DOM text node.</summary>
+        /// <param name="dom">DOM document used to create the text node.</param>
+        /// <param name="text" type="String">Text value for the DOM text node.</param>
+        /// <returns>DOM text node.</returns>
+
+        return dom.createTextNode(text);
+    };
+
+    var xmlNewNodeByPath = function (dom, root, namespaceURI, prefix, path) {
+        /// <summary>Creates a new DOM element or DOM attribute node as specified by path and appends it to the DOM tree pointed by root.</summary>
+        /// <param name="dom">DOM document used to create the new node.</param>
+        /// <param name="root">DOM element node used as root of the subtree on which the new nodes are going to be created.</param>
+        /// <param name="namespaceURI" type="String">Namespace URI of the new DOM element or attribute.</param>
+        /// <param name="namespacePrefix" type="String">Prefix used to qualify the name of the new DOM element or attribute.</param>
+        /// <param name="Path" type="String">Path string describing the location of the new DOM element or attribute from the root element.</param>
+        /// <returns>DOM element or attribute node for the last segment of the path.</returns>
+        /// <remarks>
+        ///     This function will traverse the path and will create a new DOM element with the specified namespace URI and prefix
+        ///     for each segment that doesn't have a matching element under root.
+        ///
+        ///     The last segment of the path may be decorated with a starting @ character. In this case a new DOM attribute node
+        ///     will be created.
+        /// </remarks>
+
+        var name = "";
+        var parts = path.split("/");
+        var xmlFindNode = xmlFirstChildElement;
+        var xmlNewNode = xmlNewElement;
+        var xmlNode = root;
+
+        var i, len;
+        for (i = 0, len = parts.length; i < len; i++) {
+            name = parts[i];
+            if (name.charAt(0) === "@") {
+                name = name.substring(1);
+                xmlFindNode = xmlAttributeNode;
+                xmlNewNode = xmlNewAttribute;
+            }
+
+            var childNode = xmlFindNode(xmlNode, namespaceURI, name);
+            if (!childNode) {
+                childNode = xmlNewNode(dom, namespaceURI, xmlQualifiedName(prefix, name));
+                xmlAppendChild(xmlNode, childNode);
+            }
+            xmlNode = childNode;
+        }
+        return xmlNode;
+    };
+
+    var xmlSerialize = function (domNode) {
+        /// <summary>
+        /// Returns the text representation of the document to which the specified node belongs.
+        /// </summary>
+        /// <param name="root">Wrapped element in the document to serialize.</param>
+        /// <returns type="String">Serialized document.</returns>
+
+        var xmlSerializer = window.XMLSerializer;
+        if (xmlSerializer) {
+            var serializer = new xmlSerializer();
+            return serializer.serializeToString(domNode);
+        }
+
+        if (domNode.xml) {
+            return domNode.xml;
+        }
+
+        throw { message: "XML serialization unsupported" };
+    };
+
+    var xmlSerializeDescendants = function (domNode) {
+        /// <summary>Returns the XML representation of the all the descendants of the node.</summary>
+        /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
+        /// <returns type="String">The XML representation of all the descendants of the node.</returns>
+
+        var children = domNode.childNodes;
+        var i, len = children.length;
+        if (len === 0) {
+            return "";
+        }
+
+        // Some implementations of the XMLSerializer don't deal very well with fragments that
+        // don't have a DOMElement as their first child. The work around is to wrap all the
+        // nodes in a dummy root node named "c", serialize it and then just extract the text between
+        // the <c> and the </c> substrings.
+
+        var dom = domNode.ownerDocument;
+        var fragment = dom.createDocumentFragment();
+        var fragmentRoot = dom.createElement("c");
+
+        fragment.appendChild(fragmentRoot);
+        // Move the children to the fragment tree.
+        for (i = 0; i < len; i++) {
+            fragmentRoot.appendChild(children[i]);
+        }
+
+        var xml = xmlSerialize(fragment);
+        xml = xml.substr(3, xml.length - 7);
+
+        // Move the children back to the original dom tree.
+        for (i = 0; i < len; i++) {
+            domNode.appendChild(fragmentRoot.childNodes[i]);
+        }
+
+        return xml;
+    };
+
+    var xmlSerializeNode = function (domNode) {
+        /// <summary>Returns the XML representation of the node and all its descendants.</summary>
+        /// <param name="domNode" optional="false" mayBeNull="false">Node to serialize.</param>
+        /// <returns type="String">The XML representation of the node and all its descendants.</returns>
+
+        var xml = domNode.xml;
+        if (xml !== undefined) {
+            return xml;
+        }
+
+        if (window.XMLSerializer) {
+            var serializer = new window.XMLSerializer();
+            return serializer.serializeToString(domNode);
+        }
+
+        throw { message: "XML serialization unsupported" };
+    };
+
+    // DATAJS INTERNAL START
+
+    datajs.http = http;
+    datajs.w3org = w3org;
+    datajs.xmlNS = xmlNS;
+    datajs.xmlnsNS = xmlnsNS;
+
+    datajs.hasLeadingOrTrailingWhitespace = hasLeadingOrTrailingWhitespace;
+    datajs.isXmlNSDeclaration = isXmlNSDeclaration;
+    datajs.xmlAppendChild = xmlAppendChild;
+    datajs.xmlAppendChildren = xmlAppendChildren;
+    datajs.xmlAttributeNode = xmlAttributeNode;
+    datajs.xmlAttributes = xmlAttributes;
+    datajs.xmlAttributeValue = xmlAttributeValue;
+    datajs.xmlBaseURI = xmlBaseURI;
+    datajs.xmlChildElements = xmlChildElements;
+    datajs.xmlFindElementByPath = xmlFindElementByPath;
+    datajs.xmlFindNodeByPath = xmlFindNodeByPath;
+    datajs.xmlFirstChildElement = xmlFirstChildElement;
+    datajs.xmlFirstDescendantElement = xmlFirstDescendantElement;
+    datajs.xmlInnerText = xmlInnerText;
+    datajs.xmlLocalName = xmlLocalName;
+    datajs.xmlNamespaceURI = xmlNamespaceURI;
+    datajs.xmlNodeValue = xmlNodeValue;
+    datajs.xmlDom = xmlDom;
+    datajs.xmlNewAttribute = xmlNewAttribute;
+    datajs.xmlNewElement = xmlNewElement;
+    datajs.xmlNewFragment = xmlNewFragment;
+    datajs.xmlNewNodeByPath = xmlNewNodeByPath;
+    datajs.xmlNewNSDeclaration = xmlNewNSDeclaration;
+    datajs.xmlNewText = xmlNewText;
+    datajs.xmlParse = xmlParse;
+    datajs.xmlQualifiedName = xmlQualifiedName;
+    datajs.xmlSerialize = xmlSerialize;
+    datajs.xmlSerializeDescendants = xmlSerializeDescendants;
+    datajs.xmlSiblingElement = xmlSiblingElement;
+
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/common/ODataReadOracle.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/ODataReadOracle.js b/datajs/tests/common/ODataReadOracle.js
new file mode 100644
index 0000000..a0c0bbc
--- /dev/null
+++ b/datajs/tests/common/ODataReadOracle.js
@@ -0,0 +1,275 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Client for the odata.read oracle service
+
+(function (window, undefined) {
+    var jsonMime = "application/json;odata=verbose";
+    var universalMime = "*/*";
+    var atomMime = "application/atom+xml";
+
+    var readFeed = function (url, success, mimeType, recognizeDates) {
+        /// <summary>Calls the ReadFeed endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the feed from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME media type in the Accept header</param>
+        var readMethod = getReadMethod(mimeType, "ReadFeed");
+        oracleRequest("GET", readMethod, typeof url === "string" ? { url: url} : url, mimeType, recognizeDates, function (data) {
+            if (!data.results) {
+                data = { results: data };
+            }
+            success(data);
+        });
+    };
+
+    var readEntry = function (url, success, mimeType, recognizeDates) {
+        /// <summary>Calls the ReadEntry endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the entry from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME media type in the Accept header</param>
+        var readMethod = getReadMethod(mimeType, "ReadEntry");
+        oracleRequest("GET", readMethod, typeof url === "string" ? { url: url} : url, mimeType, recognizeDates, success);
+    };
+
+    var readFeedLoopback = function (atomFeedXml, success, recognizeDates) {
+        /// <summary>Calls the ReadFeedLoopback endpoint with the specified atom feed xml</summary>
+        /// <param name="atomFeedXml" type="String">The atom feed xml</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        oracleRequest("POST", "ReadFeedLoopback", atomFeedXml, atomMime, recognizeDates, success);
+    };
+
+    var readEntryLoopback = function (atomEntryXml, success, recognizeDates) {
+        /// <summary>Calls the ReadEntryLoopback endpoint with the specified atom entry xml</summary>
+        /// <param name="atomEntryXml" type="String">The atom entry xml</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        oracleRequest("POST", "ReadEntryLoopback", atomEntryXml, atomMime, recognizeDates, success);
+    };
+
+    var readLinksEntry = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        readJson(
+            url,
+            success
+        );
+    };
+
+    var readLinksFeed = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        readJson(
+            url,
+            function (data) {
+                if (!data.results) {
+                    data = { results: data };
+                }
+                success(data);
+            }
+        );
+    };
+
+    var readMetadata = function (url, success) {
+        /// <summary>Calls the ReadMetadata endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to read the metadata from</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        $.getJSON(
+            "./common/ODataReadOracle.svc/ReadMetadata?url=" + escape(url),
+            function (data) {
+                removeProperty(data.d, "__type");
+                success(data.d);
+            }
+        );
+    };
+
+    var readServiceDocument = function (url, success, mimeType) {
+        /// <summary>Calls the ReadServiceDocument endpoint with the specified URL</summary>
+        /// <param name="url" type="String">The URL to the service</param>
+        /// <param name="success" type="Function">The success callback function</param>
+        /// <param name="mimeType" type="String">The MIME type being tested</param>
+
+        $.getJSON(
+            "./common/ODataReadOracle.svc/ReadServiceDocument?url=" + escape(url) + "&mimeType=" + mimeType,
+            function (data) {
+                removeProperty(data.d, "__type");
+                if (mimeType == jsonMime) {
+                    removeProperty(data.d, "extensions");
+                    $.each(data.d["workspaces"], function (_, workspace) {
+                        delete workspace["title"];
+                    });
+                }
+                success(data.d);
+            }
+        );
+    };
+
+    var readJson = function (url, success) {
+        $.ajax({
+            url: url,
+            accepts: null,
+            dataType: "json",
+            beforeSend: function (xhr) {
+                xhr.setRequestHeader("Accept", jsonMime);
+                xhr.setRequestHeader("MaxDataServiceVersion", "3.0");
+            },
+            success: function (data) {
+                success(data.d);
+            }
+        });
+    };
+
+    var readJsonAcrossServerPages = function (url, success) {
+        var data = [];
+        var readPage = function (url) {
+            readJson(url, function (feedData) {
+                var results = feedData.results || feedData;
+                var next = feedData.__next;
+
+                data = data.concat(results);
+                if (next) {
+                    readPage(next);
+                } else {
+                    success(data);
+                }
+            });
+        };
+
+        readPage(url);
+    }
+
+    var getReadMethod = function (mimeType, defaultEndpoint) {
+        switch (mimeType) {
+            case universalMime:
+            case atomMime:
+                return defaultEndpoint;
+            case jsonMime:
+            default:
+                return "ReadJson";
+        }
+    }
+
+    var oracleRequest = function (method, endpoint, data, mimeType, recognizeDates, success) {
+        /// <summary>Requests a JSON object from the oracle service, removing WCF-specific artifacts</summary>
+        /// <param name="method" type="String">The HTTP method (GET or POST)</param>
+        /// <param name="endpoint" type="String">The oracle endpoint</param>
+        /// <param name="data" type="Object">The data to send with the request</param>
+        /// <param name="reviver" type="Function">The reviver function to run on each deserialized object</param>
+        /// <param name="success" type="Function">Success callback</param>
+        var reviver = mimeType === jsonMime || mimeType === undefined ? (recognizeDates ? odataDateReviver : undefined) : oracleDateReviver;
+        var url = "./common/ODataReadOracle.svc/" + endpoint;
+        $.ajax({
+            type: method,
+            url: url,
+            data: data,
+            dataType: "text",
+            success: function (data) {
+                var json = JSON.parse(data, reviver);
+                removeProperty(json.d, "__type");
+                success(json.d);
+            }
+        });
+    };
+
+    var removeProperty = function (data, property) {
+        /// <summary>Removes the specified property recursively from the given object</summary>
+        /// <param name="data" type="Object">The object to operate on</param>
+        /// <param name="property" type="String">The name of the property to remove</param>
+        if (typeof data === "object" && data !== null) {
+            if (data[property]) {
+                delete data[property];
+            }
+
+            for (prop in data) {
+                removeProperty(data[prop], property);
+            }
+        }
+    }
+
+    var oracleDateReviver = function (key, value) {
+        /// <summary>Revives date objects received from the oracle service</summary>
+        if (value && value["__type"] && value["__type"].search("JsDate") > -1) {
+            var data = new Date(value.milliseconds);
+            if (value["__edmType"]) {
+                data["__edmType"] = value["__edmType"];
+            }
+
+            if (value["__offset"]) {
+                data["__offset"] = value["__offset"];
+            }
+
+            return data;
+        }
+
+        return value;
+    }
+
+    var odataDateReviver = function (key, value) {
+        /// <summary>Revives date objects received from OData JSON payloads</summary>
+        var regexp = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
+        var matches = regexp.exec(value);
+        if (matches) {
+            var milliseconds = parseInt(matches[1], 10);
+            if (!isNaN(milliseconds)) {
+                var result = new Date(milliseconds);
+                if (matches[2]) {
+                    var sign = matches[2];
+                    var offsetMinutes = parseInt(matches[3], 10);
+                    if (sign === "-") {
+                        offsetMinutes = -offsetMinutes;
+                    }
+
+                    result.setUTCMinutes(result.getUTCMinutes() - offsetMinutes);
+                    result["__edmType"] = "Edm.DateTimeOffset";
+                    result["__offset"] = minutesToOffset(offsetMinutes);
+                }
+                return result;
+            }
+        }
+
+        return value;
+    }
+
+    var minutesToOffset = function (minutes) {
+        var padIfNeeded = function (value) {
+            var result = value.toString(10);
+            return result.length < 2 ? "0" + result : result;
+        };
+
+        var sign;
+        if (minutes < 0) {
+            sign = "-";
+            minutes = -minutes;
+        } else {
+            sign = "+";
+        }
+
+        var hours = Math.floor(minutes / 60);
+        minutes = minutes - (60 * hours);
+
+        return sign + padIfNeeded(hours) + ":" + padIfNeeded(minutes);
+    };
+
+    window.ODataReadOracle = {
+        readFeed: readFeed,
+        readEntry: readEntry,
+        readFeedLoopback: readFeedLoopback,
+        readEntryLoopback: readEntryLoopback,
+        readLinksEntry: readLinksEntry,
+        readLinksFeed: readLinksFeed,
+        readJson: readJson,
+        readJsonAcrossServerPages: readJsonAcrossServerPages,
+        readMetadata: readMetadata,
+        readServiceDocument: readServiceDocument
+    };
+})(window);
\ No newline at end of file


[10/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/deferred.js
----------------------------------------------------------------------
diff --git a/datajs/src/deferred.js b/datajs/src/deferred.js
new file mode 100644
index 0000000..7f6f6a8
--- /dev/null
+++ b/datajs/src/deferred.js
@@ -0,0 +1,179 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// deferred.js
+
+(function (window, undefined) {
+
+    // CONTENT START
+
+    var forwardCall = function (thisValue, name, returnValue) {
+        /// <summary>Creates a new function to forward a call.</summary>
+        /// <param name="thisValue" type="Object">Value to use as the 'this' object.</param>
+        /// <param name="name" type="String">Name of function to forward to.</param>
+        /// <param name="returnValue" type="Object">Return value for the forward call (helps keep identity when chaining calls).</param>
+        /// <returns type="Function">A new function that will forward a call.</returns>
+
+        return function () {
+            thisValue[name].apply(thisValue, arguments);
+            return returnValue;
+        };
+    };
+
+    var DjsDeferred = function () {
+        /// <summary>Initializes a new DjsDeferred object.</summary>
+        /// <remarks>
+        /// Compability Note A - Ordering of callbacks through chained 'then' invocations
+        ///
+        /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
+        /// implies that .then() returns a distinct object.
+        ////
+        /// For compatibility with http://api.jquery.com/category/deferred-object/
+        /// we return this same object. This affects ordering, as
+        /// the jQuery version will fire callbacks in registration
+        /// order regardless of whether they occur on the result
+        /// or the original object.
+        ///
+        /// Compability Note B - Fulfillment value
+        ///
+        /// The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
+        /// implies that the result of a success callback is the
+        /// fulfillment value of the object and is received by
+        /// other success callbacks that are chained.
+        ///
+        /// For compatibility with http://api.jquery.com/category/deferred-object/
+        /// we disregard this value instead.
+        /// </remarks>
+
+        this._arguments = undefined;
+        this._done = undefined;
+        this._fail = undefined;
+        this._resolved = false;
+        this._rejected = false;
+    };
+
+    DjsDeferred.prototype = {
+        then: function (fulfilledHandler, errorHandler /*, progressHandler */) {
+            /// <summary>Adds success and error callbacks for this deferred object.</summary>
+            /// <param name="fulfilledHandler" type="Function" mayBeNull="true" optional="true">Success callback.</param>
+            /// <param name="errorHandler" type="Function" mayBeNull="true" optional="true">Error callback.</param>
+            /// <remarks>See Compatibility Note A.</remarks>
+
+            if (fulfilledHandler) {
+                if (!this._done) {
+                    this._done = [fulfilledHandler];
+                } else {
+                    this._done.push(fulfilledHandler);
+                }
+            }
+
+            if (errorHandler) {
+                if (!this._fail) {
+                    this._fail = [errorHandler];
+                } else {
+                    this._fail.push(errorHandler);
+                }
+            }
+
+            //// See Compatibility Note A in the DjsDeferred constructor.
+            //// if (!this._next) {
+            ////    this._next = createDeferred();
+            //// }
+            //// return this._next.promise();
+
+            if (this._resolved) {
+                this.resolve.apply(this, this._arguments);
+            } else if (this._rejected) {
+                this.reject.apply(this, this._arguments);
+            }
+
+            return this;
+        },
+
+        resolve: function (/* args */) {
+            /// <summary>Invokes success callbacks for this deferred object.</summary>
+            /// <remarks>All arguments are forwarded to success callbacks.</remarks>
+
+
+            if (this._done) {
+                var i, len;
+                for (i = 0, len = this._done.length; i < len; i++) {
+                    //// See Compability Note B - Fulfillment value.
+                    //// var nextValue =
+                    this._done[i].apply(null, arguments);
+                }
+
+                //// See Compatibility Note A in the DjsDeferred constructor.
+                //// this._next.resolve(nextValue);
+                //// delete this._next;
+
+                this._done = undefined;
+                this._resolved = false;
+                this._arguments = undefined;
+            } else {
+                this._resolved = true;
+                this._arguments = arguments;
+            }
+        },
+
+        reject: function (/* args */) {
+            /// <summary>Invokes error callbacks for this deferred object.</summary>
+            /// <remarks>All arguments are forwarded to error callbacks.</remarks>
+            if (this._fail) {
+                var i, len;
+                for (i = 0, len = this._fail.length; i < len; i++) {
+                    this._fail[i].apply(null, arguments);
+                }
+
+                this._fail = undefined;
+                this._rejected = false;
+                this._arguments = undefined;
+            } else {
+                this._rejected = true;
+                this._arguments = arguments;
+            }
+        },
+
+        promise: function () {
+            /// <summary>Returns a version of this object that has only the read-only methods available.</summary>
+            /// <returns>An object with only the promise object.</returns>
+
+            var result = {};
+            result.then = forwardCall(this, "then", result);
+            return result;
+        }
+    };
+
+    var createDeferred = function () {
+        /// <summary>Creates a deferred object.</summary>
+        /// <returns type="DjsDeferred">
+        /// A new deferred object. If jQuery is installed, then a jQuery
+        /// Deferred object is returned, which provides a superset of features.
+        /// </returns>
+
+        if (window.jQuery && window.jQuery.Deferred) {
+            return new window.jQuery.Deferred();
+        } else {
+            return new DjsDeferred();
+        }
+    };
+
+    // DATAJS INTERNAL START
+    window.datajs.createDeferred = createDeferred;
+    window.datajs.DjsDeferred = DjsDeferred;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-atom.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-atom.js b/datajs/src/odata-atom.js
new file mode 100644
index 0000000..d92c2a6
--- /dev/null
+++ b/datajs/src/odata-atom.js
@@ -0,0 +1,1411 @@
+/// <reference path="odata-utils.js" />
+/// <reference path="odata-handler.js" />
+/// <reference path="odata-xml.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-atom.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // imports
+    var contains = datajs.contains;
+    var djsassert = datajs.djsassert;
+    var isArray = datajs.isArray;
+    var isObject = datajs.isObject;
+    var isXmlNSDeclaration = datajs.isXmlNSDeclaration;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+    var xmlAppendChild = datajs.xmlAppendChild;
+    var xmlAppendChildren = datajs.xmlAppendChildren
+    var xmlAttributes = datajs.xmlAttributes;
+    var xmlAttributeNode = datajs.xmlAttributeNode;
+    var xmlAttributeValue = datajs.xmlAttributeValue;
+    var xmlBaseURI = datajs.xmlBaseURI;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlDom = datajs.xmlDom;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlFindElementByPath = datajs.xmlFindElementByPath;
+    var xmlFindNodeByPath = datajs.xmlFindNodeByPath;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNewAttribute = datajs.xmlNewAttribute;
+    var xmlNewElement = datajs.xmlNewElement;
+    var xmlNewFragment = datajs.xmlNewFragment;
+    var xmlNewNodeByPath = datajs.xmlNewNodeByPath;
+    var xmlNewNSDeclaration = datajs.xmlNewNSDeclaration;
+    var xmlNewText = datajs.xmlNewText;
+    var xmlNodeValue = datajs.xmlNodeValue;
+    var xmlNS = datajs.xmlNS;
+    var xmlnsNS = datajs.xmlnsNS;
+    var xmlQualifiedName = datajs.xmlQualifiedName;
+    var xmlParse = datajs.xmlParse;
+    var xmlSerialize = datajs.xmlSerialize;
+    var xmlSerializeDescendants = datajs.xmlSerializeDescendants;
+    var xmlSibling = datajs.xmlSibling;
+    var w3org = datajs.w3org;
+
+    var adoDs = odata.adoDs;
+    var contentType = odata.contentType;
+    var createAttributeExtension = odata.createAttributeExtension;
+    var createElementExtension = odata.createElementExtension;
+    var handler = odata.handler;
+    var isPrimitiveEdmType = odata.isPrimitiveEdmType;
+    var isFeed = odata.isFeed;
+    var isNamedStream = odata.isNamedStream;
+    var lookupEntityType = odata.lookupEntityType;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupProperty = odata.lookupProperty;
+    var navigationPropertyKind = odata.navigationPropertyKind;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var maxVersion = odata.maxVersion;
+    var odataXmlNs = odata.odataXmlNs;
+    var odataMetaXmlNs = odata.odataMetaXmlNs;
+    var odataMetaPrefix = odata.odataMetaPrefix;
+    var odataPrefix = odata.odataPrefix;
+    var odataRelatedPrefix = odata.odataRelatedPrefix;
+    var odataScheme = odata.odataScheme;
+    var parseBool = odata.parseBool;
+    var parseDateTime = odata.parseDateTime;
+    var parseDateTimeOffset = odata.parseDateTimeOffset;
+    var parseDuration = odata.parseDuration;
+    var parseTimezone = odata.parseTimezone;
+    var xmlNewODataElement = odata.xmlNewODataElement;
+    var xmlNewODataElementInfo = odata.xmlNewODataElementInfo;
+    var xmlNewODataMetaAttribute = odata.xmlNewODataMetaAttribute;
+    var xmlNewODataMetaElement = odata.xmlNewODataMetaElement;
+    var xmlNewODataDataElement = odata.xmlNewODataDataElement;
+    var xmlReadODataEdmPropertyValue = odata.xmlReadODataEdmPropertyValue;
+    var xmlReadODataProperty = odata.xmlReadODataProperty;
+
+    // CONTENT START
+
+    var atomPrefix = "a";
+
+    var atomXmlNs = w3org + "2005/Atom";                    // http://www.w3.org/2005/Atom
+    var appXmlNs = w3org + "2007/app";                      // http://www.w3.org/2007/app
+
+    var odataEditMediaPrefix = adoDs + "/edit-media/";        // http://schemas.microsoft.com/ado/2007/08/dataservices/edit-media
+    var odataMediaResourcePrefix = adoDs + "/mediaresource/"; // http://schemas.microsoft.com/ado/2007/08/dataservices/mediaresource
+    var odataRelatedLinksPrefix = adoDs + "/relatedlinks/";   // http://schemas.microsoft.com/ado/2007/08/dataservices/relatedlinks
+
+    var atomAcceptTypes = ["application/atom+xml", "application/atomsvc+xml", "application/xml"];
+    var atomMediaType = atomAcceptTypes[0];
+
+    // These are the namespaces that are not considered ATOM extension namespaces.
+    var nonExtensionNamepaces = [atomXmlNs, appXmlNs, xmlNS, xmlnsNS];
+
+    // These are entity property mapping paths that have well-known paths.
+    var knownCustomizationPaths = {
+        SyndicationAuthorEmail: "author/email",
+        SyndicationAuthorName: "author/name",
+        SyndicationAuthorUri: "author/uri",
+        SyndicationContributorEmail: "contributor/email",
+        SyndicationContributorName: "contributor/name",
+        SyndicationContributorUri: "contributor/uri",
+        SyndicationPublished: "published",
+        SyndicationRights: "rights",
+        SyndicationSummary: "summary",
+        SyndicationTitle: "title",
+        SyndicationUpdated: "updated"
+    };
+
+    var expandedFeedCustomizationPath = function (path) {
+        /// <summary>Returns an expanded customization path if it's well-known.</summary>
+        /// <param name="path" type="String">Path to expand.</param>
+        /// <returns type="String">Expanded path or just 'path' otherwise.</returns>
+
+        return knownCustomizationPaths[path] || path;
+    };
+
+    var isExtensionNs = function (nsURI) {
+        /// <summary>Checks whether the specified namespace is an extension namespace to ATOM.</summary>
+        /// <param type="String" name="nsURI">Namespace to check.</param>
+        /// <returns type="Boolean">true if nsURI is an extension namespace to ATOM; false otherwise.</returns>
+
+        return !(contains(nonExtensionNamepaces, nsURI));
+    };
+
+    var atomFeedCustomization = function (customizationModel, entityType, model, propertyName, suffix) {
+        /// <summary>Creates an object describing a feed customization that was delcared in an OData conceptual schema.</summary>
+        /// <param name="customizationModel" type="Object">Object describing the customization delcared in the conceptual schema.</param>
+        /// <param name="entityType" type="Object">Object describing the entity type that owns the customization in an OData conceputal schema.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="propertyName" type="String" optional="true">Name of the property to which this customization applies.</param>
+        /// <param name="suffix" type="String" optional="true">Suffix to feed customization properties in the conceptual schema.</param>
+        /// <returns type="Object">Object that describes an applicable feed customization.</returns>
+
+        suffix = suffix || "";
+        var targetPath = customizationModel["FC_TargetPath" + suffix];
+        if (!targetPath) {
+            return null;
+        }
+
+        var sourcePath = customizationModel["FC_SourcePath" + suffix];
+        var targetXmlPath = expandedFeedCustomizationPath(targetPath);
+
+        var propertyPath = propertyName ? propertyName + (sourcePath ? "/" + sourcePath : "") : sourcePath;
+        var propertyType = propertyPath && lookupPropertyType(model, entityType, propertyPath);
+        var nsURI = customizationModel["FC_NsUri" + suffix] || null;
+        var nsPrefix = customizationModel["FC_NsPrefix" + suffix] || null;
+        var keepinContent = customizationModel["FC_KeepInContent" + suffix] || "";
+
+        if (targetPath !== targetXmlPath) {
+            nsURI = atomXmlNs;
+            nsPrefix = atomPrefix;
+        }
+
+        return {
+            contentKind: customizationModel["FC_ContentKind" + suffix],
+            keepInContent: keepinContent.toLowerCase() === "true",
+            nsPrefix: nsPrefix,
+            nsURI: nsURI,
+            propertyPath: propertyPath,
+            propertyType: propertyType,
+            entryPath: targetXmlPath
+        };
+    };
+
+    var atomApplyAllFeedCustomizations = function (entityType, model, callback) {
+        /// <summary>Gets all the feed customizations that have to be applied to an entry as per the enity type declared in an OData conceptual schema.</summary>
+        /// <param name="entityType" type="Object">Object describing an entity type in a conceptual schema.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="callback" type="Function">Callback function to be invoked for each feed customization that needs to be applied.</param>
+
+        var customizations = [];
+        while (entityType) {
+            var sourcePath = entityType.FC_SourcePath;
+            var customization = atomFeedCustomization(entityType, entityType, model);
+            if (customization) {
+                callback(customization);
+            }
+
+            var properties = entityType.property || [];
+            var i, len;
+            for (i = 0, len = properties.length; i < len; i++) {
+                var property = properties[i];
+                var suffixCounter = 0;
+                var suffix = "";
+
+                while (customization = atomFeedCustomization(property, entityType, model, property.name, suffix)) {
+                    callback(customization);
+                    suffixCounter++;
+                    suffix = "_" + suffixCounter;
+                }
+            }
+            entityType = lookupEntityType(entityType.baseType, model);
+        }
+        return customizations;
+    };
+
+    var atomReadExtensionAttributes = function (domElement) {
+        /// <summary>Reads ATOM extension attributes (any attribute not in the Atom namespace) from a DOM element.</summary>
+        /// <param name="domElement">DOM element with zero or more extension attributes.</param>
+        /// <returns type="Array">An array of extension attribute representations.</returns>
+
+        var extensions = [];
+        xmlAttributes(domElement, function (attribute) {
+            var nsURI = xmlNamespaceURI(attribute);
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createAttributeExtension(attribute, true));
+            }
+        });
+        return extensions;
+    };
+
+    var atomReadExtensionElement = function (domElement) {
+        /// <summary>Reads an ATOM extension element (an element not in the ATOM namespaces).</summary>
+        /// <param name="domElement">DOM element not part of the atom namespace.</param>
+        /// <returns type="Object">Object representing the extension element.</returns>
+
+        return createElementExtension(domElement, /*addNamespaceURI*/true);
+    };
+
+    var atomReadDocument = function (domElement, baseURI, model) {
+        /// <summary>Reads an ATOM entry, feed or service document, producing an object model in return.</summary>
+        /// <param name="domElement">Top-level ATOM DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM document.</param>
+        /// <param name="model" type="Object">Object that describes the conceptual schema.</param>
+        /// <returns type="Object">The object model representing the specified element, undefined if the top-level element is not part of the ATOM specification.</returns>
+
+        var nsURI = xmlNamespaceURI(domElement);
+        var localName = xmlLocalName(domElement);
+
+        // Handle service documents.
+        if (nsURI === appXmlNs && localName === "service") {
+            return atomReadServiceDocument(domElement, baseURI);
+        }
+
+        // Handle feed and entry elements.
+        if (nsURI === atomXmlNs) {
+            if (localName === "feed") {
+                return atomReadFeed(domElement, baseURI, model);
+            }
+            if (localName === "entry") {
+                return atomReadEntry(domElement, baseURI, model);
+            }
+        }
+
+        // Allow undefined to be returned.
+    };
+
+    var atomReadAdvertisedActionOrFunction = function (domElement, baseURI) {
+        /// <summary>Reads the DOM element for an action or a function in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action or function target url.</param>
+        /// <returns type="Object">Object with title, target, and metadata fields.</returns>
+
+        var extensions = [];
+        var result = { extensions: extensions };
+        xmlAttributes(domElement, function (attribute) {
+            var localName = xmlLocalName(attribute);
+            var nsURI = xmlNamespaceURI(attribute);
+            var value = xmlNodeValue(attribute);
+
+            if (nsURI === null) {
+                if (localName === "title" || localName === "metadata") {
+                    result[localName] = value;
+                    return;
+                }
+                if (localName === "target") {
+                    result.target = normalizeURI(value, xmlBaseURI(domElement, baseURI));
+                    return;
+                }
+            }
+
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createAttributeExtension(attribute, true));
+            }
+        });
+        return result;
+    };
+
+    var atomReadAdvertisedAction = function (domElement, baseURI, parentMetadata) {
+        /// <summary>Reads the DOM element for an action in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action or target url.</param>
+        /// <param name="parentMetadata" type="Object">Object to update with the action metadata.</param>
+
+        var actions = parentMetadata.actions = parentMetadata.actions || [];
+        actions.push(atomReadAdvertisedActionOrFunction(domElement, baseURI));
+    };
+
+    var atomReadAdvertisedFunction = function (domElement, baseURI, parentMetadata) {
+        /// <summary>Reads the DOM element for an action in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action or target url.</param>
+        /// <param name="parentMetadata" type="Object">Object to update with the action metadata.</param>
+
+        var functions = parentMetadata.functions = parentMetadata.functions || [];
+        functions.push(atomReadAdvertisedActionOrFunction(domElement, baseURI));
+    };
+
+    var atomReadFeed = function (domElement, baseURI, model) {
+        /// <summary>Reads a DOM element for an ATOM feed, producing an object model in return.</summary>
+        /// <param name="domElement">ATOM feed DOM element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM feed.</param>
+        /// <param name="model">Metadata that describes the conceptual schema.</param>
+        /// <returns type="Object">A new object representing the feed.</returns>
+
+        var extensions = atomReadExtensionAttributes(domElement);
+        var feedMetadata = { feed_extensions: extensions };
+        var results = [];
+
+        var feed = { __metadata: feedMetadata, results: results };
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === odataMetaXmlNs) {
+                if (localName === "count") {
+                    feed.__count = parseInt(xmlInnerText(child), 10);
+                    return;
+                }
+                if (localName === "action") {
+                    atomReadAdvertisedAction(child, baseURI, feedMetadata);
+                    return;
+                }
+                if (localName === "function") {
+                    atomReadAdvertisedFunction(child, baseURI, feedMetadata);
+                    return;
+                }
+            }
+
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createElementExtension(child));
+                return;
+            }
+
+            // The element should belong to the ATOM namespace.
+            djsassert(nsURI === atomXmlNs, "atomReadFeed - child feed element is not in the atom namespace!!");
+
+            if (localName === "entry") {
+                results.push(atomReadEntry(child, baseURI, model));
+                return;
+            }
+            if (localName === "link") {
+                atomReadFeedLink(child, feed, baseURI);
+                return;
+            }
+            if (localName === "id") {
+                feedMetadata.uri = normalizeURI(xmlInnerText(child), baseURI);
+                feedMetadata.uri_extensions = atomReadExtensionAttributes(child);
+                return;
+            }
+            if (localName === "title") {
+                feedMetadata.title = xmlInnerText(child) || "";
+                feedMetadata.title_extensions = atomReadExtensionAttributes(child);
+                return;
+            }
+        });
+
+        return feed;
+    };
+
+    var atomReadFeedLink = function (domElement, feed, baseURI) {
+        /// <summary>Reads an ATOM link DOM element for a feed.</summary>
+        /// <param name="domElement">ATOM link DOM element.</param>
+        /// <param name="feed">Feed object to be annotated with the link data.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var link = atomReadLink(domElement, baseURI);
+        var href = link.href;
+        var rel = link.rel;
+        var extensions = link.extensions;
+        var metadata = feed.__metadata;
+
+        if (rel === "next") {
+            feed.__next = href;
+            metadata.next_extensions = extensions;
+            return;
+        }
+        if (rel === "self") {
+            metadata.self = href;
+            metadata.self_extensions = extensions;
+            return;
+        }
+    };
+
+    var atomReadLink = function (domElement, baseURI) {
+        /// <summary>Reads an ATOM link DOM element.</summary>
+        /// <param name="linkElement">DOM element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the link href.</param>
+        /// <returns type="Object">A link element representation.</returns>
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        var extensions = [];
+        var link = { extensions: extensions, baseURI: baseURI };
+
+        xmlAttributes(domElement, function (attribute) {
+            var nsURI = xmlNamespaceURI(attribute);
+            var localName = xmlLocalName(attribute);
+            var value = attribute.value;
+
+            if (localName === "href") {
+                link.href = normalizeURI(value, baseURI);
+                return;
+            }
+            if (localName === "type" || localName === "rel") {
+                link[localName] = value;
+                return;
+            }
+
+            if (isExtensionNs(nsURI)) {
+                extensions.push(createAttributeExtension(attribute, true));
+            }
+        });
+
+        if (!link.href) {
+            throw { error: "href attribute missing on link element", element: domElement };
+        }
+
+        return link;
+    };
+
+    var atomGetObjectValueByPath = function (path, item) {
+        /// <summary>Gets a slashed path value from the specified item.</summary>
+        /// <param name="path" type="String">Property path to read ('/'-separated).</param>
+        /// <param name="item" type="Object">Object to get value from.</param>
+        /// <returns>The property value, possibly undefined if any path segment is missing.</returns>
+
+        // Fast path.
+        if (path.indexOf('/') === -1) {
+            return item[path];
+        } else {
+            var parts = path.split('/');
+            var i, len;
+            for (i = 0, len = parts.length; i < len; i++) {
+                // Avoid traversing a null object.
+                if (item === null) {
+                    return undefined;
+                }
+
+                item = item[parts[i]];
+                if (item === undefined) {
+                    return item;
+                }
+            }
+
+            return item;
+        }
+    };
+
+    var atomSetEntryValueByPath = function (path, target, value, propertyType) {
+        /// <summary>Sets a slashed path value on the specified target.</summary>
+        /// <param name="path" type="String">Property path to set ('/'-separated).</param>
+        /// <param name="target" type="Object">Object to set value on.</param>
+        /// <param name="value">Value to set.</param>
+        /// <param name="propertyType" type="String" optional="true">Property type to set in metadata.</param>
+
+        var propertyName;
+        if (path.indexOf('/') === -1) {
+            target[path] = value;
+            propertyName = path;
+        } else {
+            var parts = path.split('/');
+            var i, len;
+            for (i = 0, len = (parts.length - 1); i < len; i++) {
+                // We construct each step of the way if the property is missing;
+                // if it's already initialized to null, we stop further processing.
+                var next = target[parts[i]];
+                if (next === undefined) {
+                    next = {};
+                    target[parts[i]] = next;
+                } else if (next === null) {
+                    return;
+                }
+                target = next;
+            }
+            propertyName = parts[i];
+            target[propertyName] = value;
+        }
+
+        if (propertyType) {
+            var metadata = target.__metadata = target.__metadata || {};
+            var properties = metadata.properties = metadata.properties || {};
+            var property = properties[propertyName] = properties[propertyName] || {};
+            property.type = propertyType;
+        }
+    };
+
+    var atomApplyCustomizationToEntryObject = function (customization, domElement, entry) {
+        /// <summary>Applies a specific feed customization item to an object.</summary>
+        /// <param name="customization">Object with customization description.</param>
+        /// <param name="sourcePath">Property path to set ('source' in the description).</param>
+        /// <param name="entryElement">XML element for the entry that corresponds to the object being read.</param>
+        /// <param name="entryObject">Object being read.</param>
+        /// <param name="propertyType" type="String">Name of property type to set.</param>
+        /// <param name="suffix" type="String">Suffix to feed customization properties.</param>
+
+        var propertyPath = customization.propertyPath;
+        // If keepInConent equals true or the property value is null we do nothing as this overrides any other customization.
+        if (customization.keepInContent || atomGetObjectValueByPath(propertyPath, entry) === null) {
+            return;
+        }
+
+        var xmlNode = xmlFindNodeByPath(domElement, customization.nsURI, customization.entryPath);
+
+        // If the XML tree does not contain the necessary elements to read the value,
+        // then it shouldn't be considered null, but rather ignored at all. This prevents
+        // the customization from generating the object path down to the property.
+        if (!xmlNode) {
+            return;
+        }
+
+        var propertyType = customization.propertyType;
+        var propertyValue;
+
+        if (customization.contentKind === "xhtml") {
+            // Treat per XHTML in http://tools.ietf.org/html/rfc4287#section-3.1.1, including the DIV
+            // in the content.
+            propertyValue = xmlSerializeDescendants(xmlNode);
+        } else {
+            propertyValue = xmlReadODataEdmPropertyValue(xmlNode, propertyType || "Edm.String");
+        }
+        // Set the value on the entry.
+        atomSetEntryValueByPath(propertyPath, entry, propertyValue, propertyType);
+    };
+
+    var lookupPropertyType = function (metadata, owningType, path) {
+        /// <summary>Looks up the type of a property given its path in an entity type.</summary>
+        /// <param name="metadata">Metadata in which to search for base and complex types.</param>
+        /// <param name="owningType">Type to which property belongs.</param>
+        /// <param name="path" type="String" mayBeNull="false">Property path to look at.</param>
+        /// <returns type="String">The name of the property type; possibly null.</returns>
+
+        var parts = path.split("/");
+        var i, len;
+        while (owningType) {
+            // Keep track of the type being traversed, necessary for complex types.
+            var traversedType = owningType;
+
+            for (i = 0, len = parts.length; i < len; i++) {
+                // Traverse down the structure as necessary.
+                var properties = traversedType.property;
+                if (!properties) {
+                    break;
+                }
+
+                // Find the property by scanning the property list (might be worth pre-processing).
+                var propertyFound = lookupProperty(properties, parts[i]);
+                if (!propertyFound) {
+                    break;
+                }
+
+                var propertyType = propertyFound.type;
+
+                // We could in theory still be missing types, but that would
+                // be caused by a malformed path.
+                if (!propertyType || isPrimitiveEdmType(propertyType)) {
+                    return propertyType || null;
+                }
+
+                traversedType = lookupComplexType(propertyType, metadata);
+                if (!traversedType) {
+                    return null;
+                }
+            }
+
+            // Traverse up the inheritance chain.
+            owningType = lookupEntityType(owningType.baseType, metadata);
+        }
+
+        return null;
+    };
+
+    var atomReadEntry = function (domElement, baseURI, model) {
+        /// <summary>Reads a DOM element for an ATOM entry, producing an object model in return.</summary>
+        /// <param name="domElement">ATOM entry DOM element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the ATOM entry.</param>
+        /// <param name="model">Metadata that describes the conceptual schema.</param>
+        /// <returns type="Object">A new object representing the entry.</returns>
+
+        var entryMetadata = {};
+        var entry = { __metadata: entryMetadata };
+
+        var etag = xmlAttributeValue(domElement, "etag", odataMetaXmlNs);
+        if (etag) {
+            entryMetadata.etag = etag;
+        }
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === atomXmlNs) {
+                if (localName === "id") {
+                    atomReadEntryId(child, entryMetadata, baseURI);
+                    return;
+                }
+                if (localName === "category") {
+                    atomReadEntryType(child, entryMetadata);
+                    return;
+                }
+                if (localName === "content") {
+                    atomReadEntryContent(child, entry, entryMetadata, baseURI);
+                    return;
+                }
+                if (localName === "link") {
+                    atomReadEntryLink(child, entry, entryMetadata, baseURI, model);
+                    return;
+                }
+                return;
+            }
+
+            if (nsURI === odataMetaXmlNs) {
+                if (localName === "properties") {
+                    atomReadEntryStructuralObject(child, entry, entryMetadata);
+                    return;
+                }
+                if (localName === "action") {
+                    atomReadAdvertisedAction(child, baseURI, entryMetadata);
+                    return;
+                }
+                if (localName === "function") {
+                    atomReadAdvertisedFunction(child, baseURI, entryMetadata);
+                    return;
+                }
+            }
+        });
+
+        // Apply feed customizations if applicable
+        var entityType = lookupEntityType(entryMetadata.type, model);
+        atomApplyAllFeedCustomizations(entityType, model, function (customization) {
+            atomApplyCustomizationToEntryObject(customization, domElement, entry);
+        });
+
+        return entry;
+    };
+
+    var atomReadEntryId = function (domElement, entryMetadata, baseURI) {
+        /// <summary>Reads an ATOM entry id DOM element.</summary>
+        /// <param name="domElement">ATOM id DOM element.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the id information.</param>
+
+        entryMetadata.uri = normalizeURI(xmlInnerText(domElement), xmlBaseURI(domElement, baseURI));
+        entryMetadata.uri_extensions = atomReadExtensionAttributes(domElement);
+    };
+
+    var atomReadEntryType = function (domElement, entryMetadata) {
+        /// <summary>Reads type information from an ATOM category DOM element.</summary>
+        /// <param name="domElement">ATOM category DOM element.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the type information.</param>
+
+        if (xmlAttributeValue(domElement, "scheme") === odataScheme) {
+            if (entryMetadata.type) {
+                throw { message: "Invalid AtomPub document: multiple category elements defining the entry type were encounterd withing an entry", element: domElement };
+            }
+
+            var typeExtensions = [];
+            xmlAttributes(domElement, function (attribute) {
+                var nsURI = xmlNamespaceURI(attribute);
+                var localName = xmlLocalName(attribute);
+
+                if (!nsURI) {
+                    if (localName !== "scheme" && localName !== "term") {
+                        typeExtensions.push(createAttributeExtension(attribute, true));
+                    }
+                    return;
+                }
+
+                if (isExtensionNs(nsURI)) {
+                    typeExtensions.push(createAttributeExtension(attribute, true));
+                }
+            });
+
+            entryMetadata.type = xmlAttributeValue(domElement, "term");
+            entryMetadata.type_extensions = typeExtensions;
+        }
+    };
+
+    var atomReadEntryContent = function (domElement, entry, entryMetadata, baseURI) {
+        /// <summary>Reads an ATOM content DOM element.</summary>
+        /// <param name="domElement">ATOM content DOM element.</param>
+        /// <param name="entry">Entry object to update with information.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the content information.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the Atom entry content.</param>
+
+        var src = xmlAttributeValue(domElement, "src");
+        var type = xmlAttributeValue(domElement, "type");
+
+        if (src) {
+            if (!type) {
+                throw {
+                    message: "Invalid AtomPub document: content element must specify the type attribute if the src attribute is also specified",
+                    element: domElement
+                };
+            }
+
+            entryMetadata.media_src = normalizeURI(src, xmlBaseURI(domElement, baseURI));
+            entryMetadata.content_type = type;
+        }
+
+        xmlChildElements(domElement, function (child) {
+            if (src) {
+                throw { message: "Invalid AtomPub document: content element must not have child elements if the src attribute is specified", element: domElement };
+            }
+
+            if (xmlNamespaceURI(child) === odataMetaXmlNs && xmlLocalName(child) === "properties") {
+                atomReadEntryStructuralObject(child, entry, entryMetadata);
+            }
+        });
+    };
+
+    var atomReadEntryLink = function (domElement, entry, entryMetadata, baseURI, model) {
+        /// <summary>Reads a link element on an entry.</summary>
+        /// <param name="atomEntryLink">'link' element on the entry.</param>
+        /// <param name="entry" type="Object">Entry object to update with the link data.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the link metadata.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the link href.</param>
+        /// <param name="model" type="Object">Metadata that describes the conceptual schema.</param>
+
+        var link = atomReadLink(domElement, baseURI);
+
+        var rel = link.rel;
+        var href = link.href;
+        var extensions = link.extensions;
+
+        if (rel === "self") {
+            entryMetadata.self = href;
+            entryMetadata.self_link_extensions = extensions;
+            return;
+        }
+
+        if (rel === "edit") {
+            entryMetadata.edit = href;
+            entryMetadata.edit_link_extensions = extensions;
+            return;
+        }
+
+        if (rel === "edit-media") {
+            entryMetadata.edit_media = link.href;
+            entryMetadata.edit_media_extensions = extensions;
+            atomReadLinkMediaEtag(link, entryMetadata);
+            return;
+        }
+
+        // This might be a named stream edit link
+        if (rel.indexOf(odataEditMediaPrefix) === 0) {
+            atomReadNamedStreamEditLink(link, entry, entryMetadata);
+            return;
+        }
+
+        // This might be a named stram media resource (read) link
+        if (rel.indexOf(odataMediaResourcePrefix) === 0) {
+            atomReadNamedStreamSelfLink(link, entry, entryMetadata);
+            return;
+        }
+
+        // This might be a navigation property
+        if (rel.indexOf(odataRelatedPrefix) === 0) {
+            atomReadNavPropLink(domElement, link, entry, entryMetadata, model);
+            return;
+        }
+
+        if (rel.indexOf(odataRelatedLinksPrefix) === 0) {
+            atomReadNavPropRelatedLink(link, entryMetadata);
+            return;
+        }
+    };
+
+    var atomReadNavPropRelatedLink = function (link, entryMetadata) {
+        /// <summary>Reads a link represnting the links related to a navigation property in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entryMetadata" type="Object">Entry metadata object to update with the related links information.</param>
+
+        var propertyName = link.rel.substring(odataRelatedLinksPrefix.length);
+        djsassert(propertyName, "atomReadNavPropRelatedLink - property name is null, empty or undefined!");
+
+        // Set the extra property information on the entry object metadata.
+        entryMetadata.properties = entryMetadata.properties || {};
+        var propertyMetadata = entryMetadata.properties[propertyName] = entryMetadata.properties[propertyName] || {};
+
+        propertyMetadata.associationuri = link.href;
+        propertyMetadata.associationuri_extensions = link.extensions;
+    };
+
+    var atomReadNavPropLink = function (domElement, link, entry, entryMetadata, model) {
+        /// <summary>Reads a link representing a navigation property in an OData Atom document.</summary>
+        /// <param name="domElement">DOM element for a navigation property in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object to update with the navigation property.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the navigation property metadata.</param>
+        /// <param name="model" type="Object">Metadata that describes the conceptual schema.</param>
+
+        // Get any inline data.
+        var inlineData;
+        var inlineElement = xmlFirstChildElement(domElement, odataMetaXmlNs, "inline");
+        if (inlineElement) {
+            var inlineDocRoot = xmlFirstChildElement(inlineElement);
+            var inlineBaseURI = xmlBaseURI(inlineElement, link.baseURI);
+            inlineData = inlineDocRoot ? atomReadDocument(inlineDocRoot, inlineBaseURI, model) : null;
+        } else {
+            // If the link has no inline content, we consider it deferred.
+            inlineData = { __deferred: { uri: link.href} };
+        }
+
+        var propertyName = link.rel.substring(odataRelatedPrefix.length);
+
+        // Set the property value on the entry object.
+        entry[propertyName] = inlineData;
+
+        // Set the extra property information on the entry object metadata.
+        entryMetadata.properties = entryMetadata.properties || {};
+        var propertyMetadata = entryMetadata.properties[propertyName] = entryMetadata.properties[propertyName] || {};
+
+        propertyMetadata.extensions = link.extensions;
+    };
+
+    var atomReadNamedStreamEditLink = function (link, entry, entryMetadata) {
+        /// <summary>Reads a link representing the edit-media url of a named stream in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object to update with the named stream data.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the named stream metadata.</param>
+
+        var propertyName = link.rel.substring(odataEditMediaPrefix.length);
+        djsassert(propertyName, "atomReadNamedStreamEditLink - property name is null, empty or undefined!");
+
+        var namedStreamMediaResource = atomGetEntryNamedStreamMediaResource(propertyName, entry, entryMetadata);
+        var mediaResource = namedStreamMediaResource.value;
+        var mediaResourceMetadata = namedStreamMediaResource.metadata;
+
+        var editMedia = link.href;
+
+        mediaResource.edit_media = editMedia;
+        mediaResource.content_type = link.type;
+        mediaResourceMetadata.edit_media_extensions = link.extensions;
+
+        // If there is only the edit link, make it the media self link as well.
+        mediaResource.media_src = mediaResource.media_src || editMedia;
+        mediaResourceMetadata.media_src_extensions = mediaResourceMetadata.media_src_extensions || [];
+
+        atomReadLinkMediaEtag(link, mediaResource);
+    };
+
+    var atomReadNamedStreamSelfLink = function (link, entry, entryMetadata) {
+        /// <summary>Reads a link representing the self url of a named stream in an OData Atom document.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object to update with the named stream data.</param>
+        /// <param name="entryMetadata">Entry metadata object to update with the named stream metadata.</param>
+
+        var propertyName = link.rel.substring(odataMediaResourcePrefix.length);
+        djsassert(propertyName, "atomReadNamedStreamEditLink - property name is null, empty or undefined!");
+
+        var namedStreamMediaResource = atomGetEntryNamedStreamMediaResource(propertyName, entry, entryMetadata);
+        var mediaResource = namedStreamMediaResource.value;
+        var mediaResourceMetadata = namedStreamMediaResource.metadata;
+
+        mediaResource.media_src = link.href;
+        mediaResourceMetadata.media_src_extensions = link.extensions;
+        mediaResource.content_type = link.type;
+    };
+
+    var atomGetEntryNamedStreamMediaResource = function (name, entry, entryMetadata) {
+        /// <summary>Gets the media resource object and metadata object for a named stream in an entry object.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="entry" type="Object">Entry object from which the media resource object will be obtained.</param>
+        /// <param name="entryMetadata" type="Object">Entry metadata object from which the media resource metadata object will be obtained.</param>
+        /// <remarks>
+        ///    If the entry doest' have a media resource for the named stream indicated by the name argument, then this function will create a new
+        ///    one along with its metadata object.
+        /// <remarks>
+        /// <returns type="Object"> Object containing the value and metadata of the named stream's media resource. <returns>
+
+        entryMetadata.properties = entryMetadata.properties || {};
+
+        var mediaResourceMetadata = entryMetadata.properties[name];
+        var mediaResource = entry[name] && entry[name].__mediaresource;
+
+        if (!mediaResource) {
+            mediaResource = {};
+            entry[name] = { __mediaresource: mediaResource };
+            entryMetadata.properties[name] = mediaResourceMetadata = {};
+        }
+        return { value: mediaResource, metadata: mediaResourceMetadata };
+    };
+
+    var atomReadLinkMediaEtag = function (link, mediaResource) {
+        /// <summary>Gets the media etag from the link extensions and updates the media resource object with it.</summary>
+        /// <param name="link" type="Object">Object representing the parsed link DOM element.</param>
+        /// <param name="mediaResource" type="Object">Object containing media information for an OData Atom entry.</param>
+        /// <remarks>
+        ///    The function will remove the extension object for the etag if it finds it in the link extensions and will set
+        ///    its value under the media_etag property of the mediaResource object.
+        /// <remarks>
+        /// <returns type="Object"> Object containing the value and metadata of the named stream's media resource. <returns>
+
+        var extensions = link.extensions;
+        var i, len;
+        for (i = 0, len = extensions.length; i < len; i++) {
+            if (extensions[i].namespaceURI === odataMetaXmlNs && extensions[i].name === "etag") {
+                mediaResource.media_etag = extensions[i].value;
+                extensions.splice(i, 1);
+                return;
+            }
+        }
+    };
+
+    var atomReadEntryStructuralObject = function (domElement, parent, parentMetadata) {
+        /// <summary>Reads an atom entry's property as a structural object and sets its value in the parent and the metadata in the parentMetadata objects.</summary>
+        /// <param name="propertiesElement">XML element for the 'properties' node.</param>
+        /// <param name="parent">
+        ///     Object that will contain the property value. It can be either an antom entry or
+        ///     an atom complex property object.
+        /// </param>
+        /// <param name="parentMetadata">Object that will contain the property metadata. It can be either an atom entry metadata or a complex property metadata object</param>
+
+        xmlChildElements(domElement, function (child) {
+            var property = xmlReadODataProperty(child);
+            if (property) {
+                var propertyName = property.name;
+                var propertiesMetadata = parentMetadata.properties = parentMetadata.properties || {};
+                propertiesMetadata[propertyName] = property.metadata;
+                parent[propertyName] = property.value;
+            }
+        });
+    };
+
+    var atomReadServiceDocument = function (domElement, baseURI) {
+        /// <summary>Reads an AtomPub service document</summary>
+        /// <param name="atomServiceDoc">DOM element for the root of an AtomPub service document</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document.</param>
+        /// <returns type="Object">An object that contains the properties of the service document</returns>
+
+        var workspaces = [];
+        var extensions = [];
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+        // Find all the workspace elements.
+        xmlChildElements(domElement, function (child) {
+            if (xmlNamespaceURI(child) === appXmlNs && xmlLocalName(child) === "workspace") {
+                workspaces.push(atomReadServiceDocumentWorkspace(child, baseURI));
+                return;
+            }
+            extensions.push(createElementExtension(child));
+        });
+
+        // AtomPub (RFC 5023 Section 8.3.1) says a service document MUST contain one or
+        // more workspaces. Throw if we don't find any.
+        if (workspaces.length === 0) {
+            throw { message: "Invalid AtomPub service document: No workspace element found.", element: domElement };
+        }
+
+        return { workspaces: workspaces, extensions: extensions };
+    };
+
+    var atomReadServiceDocumentWorkspace = function (domElement, baseURI) {
+        /// <summary>Reads a single workspace element from an AtomPub service document</summary>
+        /// <param name="domElement">DOM element that represents a workspace of an AtomPub service document</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document workspace.</param>
+        /// <returns type="Object">An object that contains the properties of the workspace</returns>
+
+        var collections = [];
+        var extensions = [];
+        var title; // = undefined;
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === atomXmlNs) {
+                if (localName === "title") {
+                    if (title !== undefined) {
+                        throw { message: "Invalid AtomPub service document: workspace has more than one child title element", element: child };
+                    }
+
+                    title = xmlInnerText(child);
+                    return;
+                }
+            }
+
+            if (nsURI === appXmlNs) {
+                if (localName === "collection") {
+                    collections.push(atomReadServiceDocumentCollection(child, baseURI));
+                }
+                return;
+            }
+            extensions.push(atomReadExtensionElement(child));
+        });
+
+        return { title: title || "", collections: collections, extensions: extensions };
+    };
+
+    var atomReadServiceDocumentCollection = function (domElement, baseURI) {
+        /// <summary>Reads a service document collection element into an object.</summary>
+        /// <param name="domElement">DOM element that represents a collection of an AtomPub service document.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the AtomPub service document collection.</param>
+        /// <returns type="Object">An object that contains the properties of the collection.</returns>
+
+
+        var href = xmlAttributeValue(domElement, "href");
+
+        if (!href) {
+            throw { message: "Invalid AtomPub service document: collection has no href attribute", element: domElement };
+        }
+
+        baseURI = xmlBaseURI(domElement, baseURI);
+        href = normalizeURI(href, xmlBaseURI(domElement, baseURI));
+        var extensions = [];
+        var title; // = undefined;
+
+        xmlChildElements(domElement, function (child) {
+            var nsURI = xmlNamespaceURI(child);
+            var localName = xmlLocalName(child);
+
+            if (nsURI === atomXmlNs) {
+                if (localName === "title") {
+                    if (title !== undefined) {
+                        throw { message: "Invalid AtomPub service document: collection has more than one child title element", element: child };
+                    }
+                    title = xmlInnerText(child);
+                }
+                return;
+            }
+
+            if (nsURI !== appXmlNs) {
+                extensions.push(atomReadExtensionElement(domElement));
+            }
+        });
+
+        // AtomPub (RFC 5023 Section 8.3.3) says the collection element MUST contain
+        // a title element. It's likely to be problematic if the service doc doesn't
+        // have one so here we throw.
+        if (!title) {
+            throw { message: "Invalid AtomPub service document: collection has no title element", element: domElement };
+        }
+
+        return { title: title, href: href, extensions: extensions };
+    };
+
+    var atomNewElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the Atom namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the Atom element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the Atom namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, atomXmlNs, xmlQualifiedName(atomPrefix, name), children);
+    };
+
+    var atomNewAttribute = function (dom, name, value) {
+        /// <summary>Creates a new DOM attribute for an Atom element in the default namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData attribute to create.</param>
+        /// <param name="value">Attribute value.</param>
+        /// <returns>New DOM attribute in the default namespace.</returns>
+
+        return xmlNewAttribute(dom, null, name, value);
+    };
+
+    var atomCanRemoveProperty = function (propertyElement) {
+        /// <summary>Checks whether the property represented by domElement can be removed from the atom document DOM tree.</summary>
+        /// <param name="propertyElement">DOM element for the property to test.</param>
+        /// <remarks>
+        ///     The property can only be removed if it doens't have any children and only has namespace or type declaration attributes.
+        /// </remarks>
+        /// <returns type="Boolean">True is the property can be removed; false otherwise.</returns>
+
+        if (propertyElement.childNodes.length > 0) {
+            return false;
+        }
+
+        var isEmpty = true;
+        var attributes = propertyElement.attributes;
+        var i, len;
+        for (i = 0, len = attributes.length; i < len && isEmpty; i++) {
+            var attribute = attributes[i];
+
+            isEmpty = isEmpty && isXmlNSDeclaration(attribute) ||
+                 (xmlNamespaceURI(attribute) == odataMetaXmlNs && xmlLocalName(attribute) === "type");
+        }
+        return isEmpty;
+    };
+
+    var atomNewODataNavigationProperty = function (dom, name, kind, value, model) {
+        /// <summary>Creates a new Atom link DOM element for a navigation property in an OData Atom document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="kind" type="String">Navigation property kind. Expected values are "deferred", "entry", or "feed".</param>
+        /// <param name="value" optional="true" mayBeNull="true">Value of the navigation property, if any.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new Atom link DOM element for the navigation property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var linkType = null;
+        var linkContent = null;
+        var linkContentBodyData = null;
+        var href = "";
+
+        if (kind !== "deferred") {
+            linkType = atomNewAttribute(dom, "type", "application/atom+xml;type=" + kind);
+            linkContent = xmlNewODataMetaElement(dom, "inline");
+
+            if (value) {
+                href = value.__metadata && value.__metadata.uri || "";
+                linkContentBodyData =
+                    atomNewODataFeed(dom, value, model) ||
+                    atomNewODataEntry(dom, value, model);
+                xmlAppendChild(linkContent, linkContentBodyData.element);
+            }
+        } else {
+            href = value.__deferred.uri;
+        }
+
+        var navProp = atomNewElement(dom, "link", [
+            atomNewAttribute(dom, "href", href),
+            atomNewAttribute(dom, "rel", normalizeURI(name, odataRelatedPrefix)),
+            linkType,
+            linkContent
+        ]);
+
+        return xmlNewODataElementInfo(navProp, linkContentBodyData ? linkContentBodyData.dsv : "1.0");
+    };
+
+    var atomNewODataEntryDataItem = function (dom, name, value, dataItemMetadata, dataItemModel, model) {
+        /// <summary>Creates a new DOM element for a data item in an entry, complex property, or collection property.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Data item name.</param>
+        /// <param name="value" optional="true" mayBeNull="true">Value of the data item, if any.</param>
+        /// <param name="dataItemMetadata" type="Object" optional="true">Object containing metadata about the data item.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the appropriate namespace for the data item and the
+        ///     required data service version for it.
+        /// </returns>
+
+        if (isNamedStream(value)) {
+            return null;
+        }
+
+        var dataElement = xmlNewODataDataElement(dom, name, value, dataItemMetadata, dataItemModel, model);
+        if (!dataElement) {
+            // This may be a navigation property.
+            var navPropKind = navigationPropertyKind(value, dataItemModel);
+            djsassert(navPropKind !== null, "atomNewODataEntryDataItem - navigation property kind is null for property " + name);
+
+            dataElement = atomNewODataNavigationProperty(dom, name, navPropKind, value, model);
+        }
+        return dataElement;
+    };
+
+    var atomEntryCustomization = function (dom, entry, entryProperties, customization) {
+        /// <summary>Applies a feed customization by transforming an Atom entry DOM element as needed.</summary>
+        /// <param name="dom">DOM document used for creating any new DOM nodes required by the customization.</param>
+        /// <param name="entry">DOM element for the Atom entry to which the customization is going to be applied.</param>
+        /// <param name="entryProperties">DOM element containing the properties of the Atom entry.</param>
+        /// <param name="customization" type="Object">Object describing an applicable feed customization.</param>
+        /// <remarks>
+        ///     Look into the atomfeedCustomization function for a description of the customization object.
+        /// </remarks>
+        /// <returns type="String">Data service version required by the applied customization</returns>
+
+        var atomProperty = xmlFindElementByPath(entryProperties, odataXmlNs, customization.propertyPath);
+        var atomPropertyNullAttribute = atomProperty && xmlAttributeNode(atomProperty, "null", odataMetaXmlNs);
+        var atomPropertyValue;
+        var dataServiceVersion = "1.0";
+
+        if (atomPropertyNullAttribute && atomPropertyNullAttribute.value === "true") {
+            return dataServiceVersion;
+        }
+
+        if (atomProperty) {
+            atomPropertyValue = xmlInnerText(atomProperty) || "";
+            if (!customization.keepInContent) {
+                dataServiceVersion = "2.0";
+                var parent = atomProperty.parentNode;
+                var candidate = parent;
+
+                parent.removeChild(atomProperty);
+                while (candidate !== entryProperties && atomCanRemoveProperty(candidate)) {
+                    parent = candidate.parentNode;
+                    parent.removeChild(candidate);
+                    candidate = parent;
+                }
+            }
+        }
+
+        var targetNode = xmlNewNodeByPath(dom, entry,
+            customization.nsURI, customization.nsPrefix, customization.entryPath);
+
+        if (targetNode.nodeType === 2) {
+            targetNode.value = atomPropertyValue;
+            return dataServiceVersion;
+        }
+
+        var contentKind = customization.contentKind;
+        xmlAppendChildren(targetNode, [
+                contentKind && xmlNewAttribute(dom, null, "type", contentKind),
+                contentKind === "xhtml" ? xmlNewFragment(dom, atomPropertyValue) : atomPropertyValue
+        ]);
+
+        return dataServiceVersion;
+    };
+
+    var atomNewODataEntry = function (dom, data, model) {
+        /// <summary>Creates a new DOM element for an Atom entry.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="data" type="Object">Entry object in the library's internal representation.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element for the Atom entry and the required data service version for it.
+        /// </returns>
+
+        var payloadMetadata = data.__metadata || {};
+        var propertiesMetadata = payloadMetadata.properties || {};
+
+        var etag = payloadMetadata.etag;
+        var uri = payloadMetadata.uri;
+        var typeName = payloadMetadata.type;
+        var entityType = lookupEntityType(typeName, model);
+
+        var properties = xmlNewODataMetaElement(dom, "properties");
+        var entry = atomNewElement(dom, "entry", [
+            atomNewElement(dom, "author",
+                atomNewElement(dom, "name")
+            ),
+            etag && xmlNewODataMetaAttribute(dom, "etag", etag),
+            uri && atomNewElement(dom, "id", uri),
+            typeName && atomNewElement(dom, "category", [
+                atomNewAttribute(dom, "term", typeName),
+                atomNewAttribute(dom, "scheme", odataScheme)
+            ]),
+        // TODO: MLE support goes here.
+            atomNewElement(dom, "content", [
+                atomNewAttribute(dom, "type", "application/xml"),
+                properties
+            ])
+        ]);
+
+        var dataServiceVersion = "1.0";
+        for (var name in data) {
+            if (name !== "__metadata") {
+                var entryDataItemMetadata = propertiesMetadata[name] || {};
+                var entryDataItemModel = entityType && (
+                    lookupProperty(entityType.property, name) ||
+                    lookupProperty(entityType.navigationProperty, name));
+
+                var entryDataItem = atomNewODataEntryDataItem(dom, name, data[name], entryDataItemMetadata, entryDataItemModel, model);
+                if (entryDataItem) {
+                    var entryElement = entryDataItem.element;
+                    var entryElementParent = (xmlNamespaceURI(entryElement) === atomXmlNs) ? entry : properties;
+
+                    xmlAppendChild(entryElementParent, entryElement);
+                    dataServiceVersion = maxVersion(dataServiceVersion, entryDataItem.dsv);
+                }
+            }
+        }
+
+        atomApplyAllFeedCustomizations(entityType, model, function (customization) {
+            var customizationDsv = atomEntryCustomization(dom, entry, properties, customization);
+            dataServiceVersion = maxVersion(dataServiceVersion, customizationDsv);
+        });
+
+        return xmlNewODataElementInfo(entry, dataServiceVersion);
+    };
+
+    var atomNewODataFeed = function (dom, data, model) {
+        /// <summary>Creates a new DOM element for an Atom feed.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="data" type="Object">Feed object in the library's internal representation.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element for the Atom feed and the required data service version for it.
+        /// </returns>
+
+        var entries = isArray(data) ? data : data.results;
+
+        if (!entries) {
+            return null;
+        }
+
+        var dataServiceVersion = "1.0";
+        var atomFeed = atomNewElement(dom, "feed");
+
+        var i, len;
+        for (i = 0, len = entries.length; i < len; i++) {
+            var atomEntryData = atomNewODataEntry(dom, entries[i], model);
+            xmlAppendChild(atomFeed, atomEntryData.element);
+            dataServiceVersion = maxVersion(dataServiceVersion, atomEntryData.dsv);
+        }
+        return xmlNewODataElementInfo(atomFeed, dataServiceVersion);
+    };
+
+    var atomNewODataDocument = function (data, model) {
+        /// <summary>Creates a new OData Atom document.</summary>
+        /// <param name="data" type="Object">Feed or entry object in the libary's internal representaion.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM document for the Atom document and the required data service version for it.
+        /// </returns>
+
+        if (data) {
+            var atomRootWriter = isFeed(data) && atomNewODataFeed ||
+                isObject(data) && atomNewODataEntry;
+
+            if (atomRootWriter) {
+                var dom = xmlDom();
+                var atomRootData = atomRootWriter(dom, data, model);
+
+                if (atomRootData) {
+                    var atomRootElement = atomRootData.element;
+                    xmlAppendChildren(atomRootElement, [
+                        xmlNewNSDeclaration(dom, odataMetaXmlNs, odataMetaPrefix),
+                        xmlNewNSDeclaration(dom, odataXmlNs, odataPrefix)
+                    ]);
+                    return xmlNewODataElementInfo(xmlAppendChild(dom, atomRootElement), atomRootData.dsv);
+                }
+            }
+        }
+        return null;
+    };
+
+    var atomParser = function (handler, text, context) {
+        /// <summary>Parses an ATOM document (feed, entry or service document).</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Document text.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An object representation of the document; undefined if not applicable.</returns>
+
+        if (text) {
+            var atomDoc = xmlParse(text);
+            var atomRoot = xmlFirstChildElement(atomDoc);
+            if (atomRoot) {
+                return atomReadDocument(atomRoot, null, context.metadata);
+            }
+        }
+    };
+
+    var atomSerializer = function (handler, data, context) {
+        /// <summary>Serializes an ATOM object into a document (feed or entry).</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data" type="Object">Representation of feed or entry.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An text representation of the data object; undefined if not applicable.</returns>
+
+        var cType = context.contentType = context.contentType || contentType(atomMediaType);
+        if (cType && cType.mediaType === atomMediaType) {
+            var atomDoc = atomNewODataDocument(data, context.metadata);
+            if (atomDoc) {
+                context.dataServiceVersion = maxVersion(context.dataServiceVersion || "1.0", atomDoc.dsv);
+                return xmlSerialize(atomDoc.element);
+            }
+        }
+        // Allow undefined to be returned.
+    };
+
+    odata.atomHandler = handler(atomParser, atomSerializer, atomAcceptTypes.join(","), MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.atomParser = atomParser;
+    odata.atomSerializer = atomSerializer;
+    odata.atomReadDocument = atomReadDocument;
+    odata.atomReadFeed = atomReadFeed;
+    odata.atomReadFeedLink = atomReadFeedLink;
+    odata.atomReadLink = atomReadLink;
+    odata.atomReadExtensionElement = atomReadExtensionElement;
+    odata.atomReadExtensionAttributes = atomReadExtensionAttributes;
+    odata.atomReadEntry = atomReadEntry;
+    odata.atomReadEntryType = atomReadEntryType;
+    odata.atomReadEntryContent = atomReadEntryContent;
+    odata.atomReadEntryLink = atomReadEntryLink;
+    odata.atomReadEntryStructuralObject = atomReadEntryStructuralObject;
+    odata.atomReadServiceDocument = atomReadServiceDocument;
+    odata.atomReadServiceDocumentWorkspace = atomReadServiceDocumentWorkspace;
+    odata.atomReadServiceDocumentCollection = atomReadServiceDocumentCollection;
+    odata.expandedFeedCustomizationPath = expandedFeedCustomizationPath;
+    odata.lookupPropertyType = lookupPropertyType;
+    odata.atomSetEntryValueByPath = atomSetEntryValueByPath;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file


[08/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-json-light.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-json-light.js b/datajs/src/odata-json-light.js
new file mode 100644
index 0000000..9a10601
--- /dev/null
+++ b/datajs/src/odata-json-light.js
@@ -0,0 +1,1391 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-json-light.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+
+    var assigned = datajs.assigned;
+    var djsassert = datajs.djsassert;
+    var extend = datajs.extend;
+    var getURIInfo = datajs.getURIInfo;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var normalizeURI = datajs.normalizeURI;
+    var renameProperty = datajs.renameProperty;
+    var undefinedDefault = datajs.undefinedDefault;
+    var convertByteArrayToHexString = datajs.convertByteArrayToHexString;
+
+    var dataItemTypeName = odata.dataItemTypeName;
+    var EDM_DATETIME = odata.EDM_DATETIME;
+    var EDM_DATETIMEOFFSET = odata.EDM_DATETIMEOFFSET;
+    var EDM_TIME = odata.EDM_TIME;
+    var getCollectionType = odata.getCollectionType;
+    var isCollection = odata.isCollection;
+    var isCollectionType = odata.isCollectionType;
+    var isComplex = odata.isComplex;
+    var isDeferred = odata.isDeferred;
+    var isFeed = odata.isFeed;
+    var isEntry = odata.isEntry;
+    var isGeographyEdmType = odata.isGeographyEdmType;
+    var isGeometryEdmType = odata.isGeometryEdmType;
+    var isPrimitiveEdmType = odata.isPrimitiveEdmType;
+    var isPrimitive = odata.isPrimitive;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupDefaultEntityContainer = odata.lookupDefaultEntityContainer;
+    var lookupEntityContainer = odata.lookupEntityContainer;
+    var lookupEntitySet = odata.lookupEntitySet;
+    var lookupEntityType = odata.lookupEntityType;
+    var lookupFunctionImport = odata.lookupFunctionImport;
+    var lookupNavigationPropertyType = odata.lookupNavigationPropertyType;
+    var getEntitySetInfo = odata.getEntitySetInfo;
+    var lookupNavigationPropertyEntitySet = odata.lookupNavigationPropertyEntitySet;
+    var lookupProperty = odata.lookupProperty;
+    var parseBool = odata.parseBool;
+    var parseDateTime = odata.parseDateTime;
+    var parseDateTimeOffset = odata.parseDateTimeOffset;
+    var parseDuration = odata.parseDuration;
+
+    // CONTENT START
+
+    var PAYLOADTYPE_OBJECT = "o";
+    var PAYLOADTYPE_FEED = "f";
+    var PAYLOADTYPE_PRIMITIVE = "p";
+    var PAYLOADTYPE_COLLECTION = "c";
+    var PAYLOADTYPE_SVCDOC = "s";
+    var PAYLOADTYPE_LINKS = "l";
+
+    var odataNs = "odata";
+    var odataAnnotationPrefix = odataNs + ".";
+
+    var bindAnnotation = "@" + odataAnnotationPrefix + "bind";
+    var metadataAnnotation = odataAnnotationPrefix + "metadata";
+    var navUrlAnnotation = odataAnnotationPrefix + "navigationLinkUrl";
+    var typeAnnotation = odataAnnotationPrefix + "type";
+
+    var jsonLightNameMap = {
+        readLink: "self",
+        editLink: "edit",
+        nextLink: "__next",
+        mediaReadLink: "media_src",
+        mediaEditLink: "edit_media",
+        mediaContentType: "content_type",
+        mediaETag: "media_etag",
+        count: "__count",
+        media_src: "mediaReadLink",
+        edit_media: "mediaEditLink",
+        content_type: "mediaContentType",
+        media_etag: "mediaETag",
+        url: "uri"
+    };
+
+    var jsonLightAnnotations = {
+        metadata: "odata.metadata",
+        count: "odata.count",
+        next: "odata.nextLink",
+        id: "odata.id",
+        etag: "odata.etag",
+        read: "odata.readLink",
+        edit: "odata.editLink",
+        mediaRead: "odata.mediaReadLink",
+        mediaEdit: "odata.mediaEditLink",
+        mediaEtag: "odata.mediaETag",
+        mediaContentType: "odata.mediaContentType",
+        actions: "odata.actions",
+        functions: "odata.functions",
+        navigationUrl: "odata.navigationLinkUrl",
+        associationUrl: "odata.associationLinkUrl",
+        type: "odata.type"
+    };
+
+    var jsonLightAnnotationInfo = function (annotation) {
+        /// <summary>Gets the name and target of an annotation in a JSON light payload.</summary>
+        /// <param name="annotation" type="String">JSON light payload annotation.</param>
+        /// <returns type="Object">Object containing the annotation name and the target property name.</param>
+
+        if (annotation.indexOf(".") > 0) {
+            var targetEnd = annotation.indexOf("@");
+            var target = targetEnd > -1 ? annotation.substring(0, targetEnd) : null;
+            var name = annotation.substring(targetEnd + 1);
+
+            return {
+                target: target,
+                name: name,
+                isOData: name.indexOf(odataAnnotationPrefix) === 0
+            };
+        }
+        return null;
+    };
+
+    var jsonLightDataItemType = function (name, value, container, dataItemModel, model) {
+        /// <summary>Gets the type name of a JSON light data item that belongs to a feed, an entry, a complex type property, or a collection property.</summary>
+        /// <param name="name" type="String">Name of the data item for which the type name is going to be retrieved.</param>
+        /// <param name="value">Value of the data item.</param>
+        /// <param name="container" type="Object">JSON light object that owns the data item.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <remarks>
+        ///    This function will first try to get the type name from the data item's value itself if it is a JSON light object; otherwise
+        ///    it will try to get it from the odata.type annotation applied to the data item in the container. Then, it will fallback to the data item model.
+        ///    If all attempts fail, it will return null.
+        /// </remarks>
+        /// <returns type="String">Data item type name; null if the type name cannot be found.</returns>
+
+        return (isComplex(value) && value[typeAnnotation]) ||
+            (container && container[name + "@" + typeAnnotation]) ||
+            (dataItemModel && dataItemModel.type) ||
+            (lookupNavigationPropertyType(dataItemModel, model)) ||
+            null;
+    };
+
+    var jsonLightDataItemModel = function (name, containerModel) {
+        /// <summary>Gets an object describing a data item in an OData conceptual schema.</summary>
+        /// <param name="name" type="String">Name of the data item for which the model is going to be retrieved.</param>
+        /// <param name="containerModel" type="Object">Object describing the owner of the data item in an OData conceptual schema.</param>
+        /// <returns type="Object">Object describing the data item; null if it cannot be found.</returns>
+
+        if (containerModel) {
+            return lookupProperty(containerModel.property, name) ||
+                lookupProperty(containerModel.navigationProperty, name);
+        }
+        return null;
+    };
+
+    var jsonLightIsEntry = function (data) {
+        /// <summary>Determines whether data represents a JSON light entry object.</summary>
+        /// <param name="data" type="Object">JSON light object to test.</param>
+        /// <returns type="Boolean">True if the data is JSON light entry object; false otherwise.</returns>
+
+        return isComplex(data) && ((odataAnnotationPrefix + "id") in data);
+    };
+
+    var jsonLightIsNavigationProperty = function (name, data, dataItemModel) {
+        /// <summary>Determines whether a data item in a JSON light object is a navigation property.</summary>
+        /// <param name="name" type="String">Name of the data item to test.</param>
+        /// <param name="data" type="Object">JSON light object that owns the data item.</param>
+        /// <param name="dataItemModel" type="Object">Object describing the data item in an OData conceptual schema.</param>
+        /// <returns type="Boolean">True if the data item is a navigation property; false otherwise.</returns>
+
+        djsassert(isComplex(data), "jsonLightIsNavProp - data is not an object!!");
+        if (!!data[name + "@" + navUrlAnnotation] || (dataItemModel && dataItemModel.relationship)) {
+            return true;
+        }
+
+        // Sniff the property value.
+        var value = isArray(data[name]) ? data[name][0] : data[name];
+        return jsonLightIsEntry(value);
+    };
+
+    var jsonLightIsPrimitiveType = function (typeName) {
+        /// <summary>Determines whether a type name is a primitive type in a JSON light payload.</summary>
+        /// <param name="typeName" type="String">Type name to test.</param>
+        /// <returns type="Boolean">True if the type name an EDM primitive type or an OData spatial type; false otherwise.</returns>
+
+        return isPrimitiveEdmType(typeName) || isGeographyEdmType(typeName) || isGeometryEdmType(typeName);
+    };
+
+    var jsonLightReadDataAnnotations = function (data, obj, baseURI, dataModel, model) {
+        /// <summary>Converts annotations found in a JSON light payload object to either properties or metadata.</summary>
+        /// <param name="data" type="Object">JSON light payload object containing the annotations to convert.</param>
+        /// <param name="obj" type="Object">Object that will store the converted annotations.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="dataModel" type="Object">Object describing the JSON light payload in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns>JSON light payload object with its annotations converted to either properties or metadata.</param>
+
+        for (var name in data) {
+            if (name.indexOf(".") > 0 && name.charAt(0) !== "#") {
+                var annotationInfo = jsonLightAnnotationInfo(name);
+                if (annotationInfo) {
+                    var annotationName = annotationInfo.name;
+                    var target = annotationInfo.target;
+                    var targetModel = null;
+                    var targetType = null;
+
+                    if (target) {
+                        targetModel = jsonLightDataItemModel(target, dataModel);
+                        targetType = jsonLightDataItemType(target, data[target], data, targetModel, model);
+                    }
+
+                    if (annotationInfo.isOData) {
+                        jsonLightApplyPayloadODataAnnotation(annotationName, target, targetType, data[name], data, obj, baseURI);
+                    } else {
+                        obj[name] = data[name];
+                    }
+                }
+            }
+        }
+        return obj;
+    };
+
+    var jsonLightApplyPayloadODataAnnotation = function (name, target, targetType, value, data, obj, baseURI) {
+        /// <summary>
+        ///   Processes a JSON Light payload OData annotation producing either a property, payload metadata, or property metadata on its owner object.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property that is being targeted by the annotation.</param>
+        /// <param name="targetType" type="String">Type name of the target property.</param>
+        /// <param name="data" type="Object">JSON light object containing the annotation.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var annotation = name.substring(odataAnnotationPrefix.length);
+
+        switch (annotation) {
+            case "navigationLinkUrl":
+                jsonLightApplyNavigationUrlAnnotation(annotation, target, targetType, value, data, obj, baseURI);
+                return;
+            case "nextLink":
+            case "count":
+                jsonLightApplyFeedAnnotation(annotation, target, value, obj, baseURI);
+                return;
+            case "mediaReadLink":
+            case "mediaEditLink":
+            case "mediaContentType":
+            case "mediaETag":
+                jsonLightApplyMediaAnnotation(annotation, target, targetType, value, obj, baseURI);
+                return;
+            default:
+                jsonLightApplyMetadataAnnotation(annotation, target, value, obj, baseURI);
+                return;
+        }
+    };
+
+    var jsonLightApplyMetadataAnnotation = function (name, target, value, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light annotation that applies to entry metadata only (i.e. odata.editLink or odata.readLink) and its value
+        ///    into their library's internal representation and saves it back to data.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var metadata = obj.__metadata = obj.__metadata || {};
+        var mappedName = jsonLightNameMap[name] || name;
+
+        if (name === "editLink") {
+            metadata.uri = normalizeURI(value, baseURI);
+            metadata[mappedName] = metadata.uri;
+            return;
+        }
+
+        if (name === "readLink" || name === "associationLinkUrl") {
+            value = normalizeURI(value, baseURI);
+        }
+
+        if (target) {
+            var propertiesMetadata = metadata.properties = metadata.properties || {};
+            var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
+
+            if (name === "type") {
+                propertyMetadata[mappedName] = propertyMetadata[mappedName] || value;
+                return;
+            }
+            propertyMetadata[mappedName] = value;
+            return;
+        }
+        metadata[mappedName] = value;
+    };
+
+    var jsonLightApplyFeedAnnotation = function (name, target, value, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light annotation that applies to feeds only (i.e. odata.count or odata.nextlink) and its value
+        ///    into their library's internal representation and saves it back to data.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var mappedName = jsonLightNameMap[name];
+        var feed = target ? obj[target] : obj;
+        feed[mappedName] = (name === "nextLink") ? normalizeURI(value, baseURI) : value;
+    };
+
+    var jsonLightApplyMediaAnnotation = function (name, target, targetType, value, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light media annotation in and its value into their library's internal representation
+        ///    and saves it back to data or metadata.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="targetType" type="String">Type name of the target property.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var metadata = obj.__metadata = obj.__metadata || {};
+        var mappedName = jsonLightNameMap[name];
+
+        if (name === "mediaReadLink" || name === "mediaEditLink") {
+            value = normalizeURI(value, baseURI);
+        }
+
+        if (target) {
+            var propertiesMetadata = metadata.properties = metadata.properties || {};
+            var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
+            propertyMetadata.type = propertyMetadata.type || targetType;
+
+            obj.__metadata = metadata;
+            obj[target] = obj[target] || { __mediaresource: {} };
+            obj[target].__mediaresource[mappedName] = value;
+            return;
+        }
+
+        metadata[mappedName] = value;
+    };
+
+    var jsonLightApplyNavigationUrlAnnotation = function (name, target, targetType, value, data, obj, baseURI) {
+        /// <summary>
+        ///    Converts a JSON light navigation property annotation and its value into their library's internal representation
+        ///    and saves it back to data o metadata.
+        /// </summary>
+        /// <param name="name" type="String">Annotation name.</param>
+        /// <param name="target" type="String">Name of the property on which the annotation should be applied.</param>
+        /// <param name="targetType" type="String">Type name of the target property.</param>
+        /// <param name="value" type="Object">Annotation value.</param>
+        /// <param name="data" type="Object">JSON light object containing the annotation.</param>
+        /// <param name="obj" type="Object">Object that will hold properties produced by the annotation.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+
+        var metadata = obj.__metadata = obj.__metadata || {};
+        var propertiesMetadata = metadata.properties = metadata.properties || {};
+        var propertyMetadata = propertiesMetadata[target] = propertiesMetadata[target] || {};
+        var uri = normalizeURI(value, baseURI);
+
+        if (data.hasOwnProperty(target)) {
+            // The navigation property is inlined in the payload,
+            // so the navigation link url should be pushed to the object's
+            // property metadata instead.
+            propertyMetadata.navigationLinkUrl = uri;
+            return;
+        }
+        obj[target] = { __deferred: { uri: uri} };
+        propertyMetadata.type = propertyMetadata.type || targetType;
+    };
+
+
+    var jsonLightReadDataItemValue = function (value, typeName, dataItemMetadata, baseURI, dataItemModel, model, recognizeDates) {
+        /// <summary>Converts the value of a data item in a JSON light object to its library representation.</summary>
+        /// <param name="value">Data item value to convert.</param>
+        /// <param name="typeName" type="String">Type name of the data item.</param>
+        /// <param name="dataItemMetadata" type="Object">Object containing metadata about the data item.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns>Data item value in its library representation.</param>
+
+        if (typeof value === "string") {
+            return jsonLightReadStringPropertyValue(value, typeName, recognizeDates);
+        }
+
+        if (!jsonLightIsPrimitiveType(typeName)) {
+            if (isArray(value)) {
+                return jsonLightReadCollectionPropertyValue(value, typeName, dataItemMetadata, baseURI, model, recognizeDates);
+            }
+
+            if (isComplex(value)) {
+                return jsonLightReadComplexPropertyValue(value, typeName, dataItemMetadata, baseURI, model, recognizeDates);
+            }
+        }
+        return value;
+    };
+
+    var jsonLightReadStringPropertyValue = function (value, propertyType, recognizeDates) {
+        /// <summary>Convertes the value of a string property in a JSON light object to its library representation.</summary>
+        /// <param name="value" type="String">String value to convert.</param>
+        /// <param name="propertyType" type="String">Type name of the property.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns>String property value in its library representation.</returns>
+
+        switch (propertyType) {
+            case EDM_TIME:
+                return parseDuration(value);
+            case EDM_DATETIME:
+                return parseDateTime(value, /*nullOnError*/false);
+            case EDM_DATETIMEOFFSET:
+                return parseDateTimeOffset(value, /*nullOnError*/false);
+        }
+
+        if (recognizeDates) {
+            return parseDateTime(value, /*nullOnError*/true) ||
+                   parseDateTimeOffset(value, /*nullOnError*/true) ||
+                   value;
+        }
+        return value;
+    };
+
+    var jsonLightReadCollectionPropertyValue = function (value, propertyType, propertyMetadata, baseURI, model, recognizeDates) {
+        /// <summary>Converts the value of a collection property in a JSON light object into its library representation.</summary>
+        /// <param name="value" type="Array">Collection property value to convert.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object containing metadata about the collection property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Collection property value in its library representation.</returns>
+
+        var collectionType = getCollectionType(propertyType);
+        var itemsMetadata = [];
+        var items = [];
+
+        var i, len;
+        for (i = 0, len = value.length; i < len; i++) {
+            var itemType = jsonLightDataItemType(null, value[i]) || collectionType;
+            var itemMetadata = { type: itemType };
+            var item = jsonLightReadDataItemValue(value[i], itemType, itemMetadata, baseURI, null, model, recognizeDates);
+
+            if (!jsonLightIsPrimitiveType(itemType) && !isPrimitive(value[i])) {
+                itemsMetadata.push(itemMetadata);
+            }
+            items.push(item);
+        }
+
+        if (itemsMetadata.length > 0) {
+            propertyMetadata.elements = itemsMetadata;
+        }
+
+        return { __metadata: { type: propertyType }, results: items };
+    };
+
+    var jsonLightReadComplexPropertyValue = function (value, propertyType, propertyMetadata, baseURI, model, recognizeDates) {
+        /// <summary>Converts the value of a comples property in a JSON light object into its library representation.</summary>
+        /// <param name="value" type="Object">Complex property value to convert.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object containing metadata about the complx type property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Complex property value in its library representation.</returns>
+
+        var complexValue = jsonLightReadObject(value, { type: propertyType }, baseURI, model, recognizeDates);
+        var complexMetadata = complexValue.__metadata;
+        var complexPropertiesMetadata = complexMetadata.properties;
+
+        if (complexPropertiesMetadata) {
+            propertyMetadata.properties = complexPropertiesMetadata;
+            delete complexMetadata.properties;
+        }
+        return complexValue;
+    };
+
+    var jsonLightReadNavigationPropertyValue = function (value, propertyInfo, baseURI, model, recognizeDates) {
+        /// <summary>Converts the value of a navigation property in a JSON light object into its library representation.</summary>
+        /// <param name="value">Navigation property property value to convert.</param>
+        /// <param name="propertyInfo" type="String">Information about the property whether it's an entry, feed or complex type.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Collection property value in its library representation.</returns>
+
+        if (isArray(value)) {
+            return jsonLightReadFeed(value, propertyInfo, baseURI, model, recognizeDates);
+        }
+
+        if (isComplex(value)) {
+            return jsonLightReadObject(value, propertyInfo, baseURI, model, recognizeDates);
+        }
+        return null;
+    };
+
+    var jsonLightReadObject = function (data, objectInfo, baseURI, model, recognizeDates) {
+        /// <summary>Converts a JSON light entry or complex type object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light entry or complex type object to convert.</param>
+        /// <param name="objectInfo" type="Object">Information about the entry or complex.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Entry or complex type object.</param>
+
+        objectInfo = objectInfo || {};
+        var actualType = data[typeAnnotation] || objectInfo.type || null;
+        var dataModel = lookupEntityType(actualType, model);
+        var isEntry = true;
+        if (!dataModel) {
+            isEntry = false;
+            dataModel = lookupComplexType(actualType, model);
+        }
+
+        var metadata = { type: actualType };
+        var obj = { __metadata: metadata };
+        var propertiesMetadata = {};
+        var baseTypeModel;
+        if (isEntry && dataModel && objectInfo.entitySet && objectInfo.contentTypeOdata == "minimalmetadata") {
+            var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata"));
+            baseTypeModel = null; // check if the key model is in a parent type.
+            if (!dataModel.key) {
+                baseTypeModel = dataModel;
+            }
+            while (!!baseTypeModel && !baseTypeModel.key && baseTypeModel.baseType) {
+                baseTypeModel = lookupEntityType(baseTypeModel.baseType, model);
+            }
+
+            if (dataModel.key || (!!baseTypeModel && baseTypeModel.key)) {
+                var entryKey;
+                if (dataModel.key) {
+                    entryKey = jsonLightGetEntryKey(data, dataModel);
+                } else {
+                    entryKey = jsonLightGetEntryKey(data, baseTypeModel);
+                }
+                if (entryKey) {
+                    var entryInfo = {
+                        key: entryKey,
+                        entitySet: objectInfo.entitySet,
+                        functionImport: objectInfo.functionImport,
+                        containerName: objectInfo.containerName
+                    };
+                    jsonLightComputeUrisIfMissing(data, entryInfo, actualType, serviceURI, dataModel, baseTypeModel);
+                }
+            }
+        }
+
+        for (var name in data) {
+            if (name.indexOf("#") === 0) {
+                // This is an advertised function or action.
+                jsonLightReadAdvertisedFunctionOrAction(name.substring(1), data[name], obj, baseURI, model);
+            } else {
+                // Is name NOT an annotation?
+                if (name.indexOf(".") === -1) {
+                    if (!metadata.properties) {
+                        metadata.properties = propertiesMetadata;
+                    }
+
+                    var propertyValue = data[name];
+                    var propertyModel = propertyModel = jsonLightDataItemModel(name, dataModel);
+                    baseTypeModel = dataModel;
+                    while (!!dataModel && propertyModel === null && baseTypeModel.baseType) {
+                        baseTypeModel = lookupEntityType(baseTypeModel.baseType, model);
+                        propertyModel = propertyModel = jsonLightDataItemModel(name, baseTypeModel);
+                    }
+                    var isNavigationProperty = jsonLightIsNavigationProperty(name, data, propertyModel);
+                    var propertyType = jsonLightDataItemType(name, propertyValue, data, propertyModel, model);
+                    var propertyMetadata = propertiesMetadata[name] = propertiesMetadata[name] || { type: propertyType };
+                    if (isNavigationProperty) {
+                        var propertyInfo = {};
+                        if (objectInfo.entitySet !== undefined) {
+                            var navigationPropertyEntitySetName = lookupNavigationPropertyEntitySet(propertyModel, objectInfo.entitySet.name, model);
+                            propertyInfo = getEntitySetInfo(navigationPropertyEntitySetName, model);
+                        }
+                        propertyInfo.contentTypeOdata = objectInfo.contentTypeOdata;
+                        propertyInfo.kind = objectInfo.kind;
+                        propertyInfo.type = propertyType;
+                        obj[name] = jsonLightReadNavigationPropertyValue(propertyValue, propertyInfo, baseURI, model, recognizeDates);
+                    } else {
+                        obj[name] = jsonLightReadDataItemValue(propertyValue, propertyType, propertyMetadata, baseURI, propertyModel, model, recognizeDates);
+                    }
+                }
+            }
+        }
+
+        return jsonLightReadDataAnnotations(data, obj, baseURI, dataModel, model);
+    };
+
+    var jsonLightReadAdvertisedFunctionOrAction = function (name, value, obj, baseURI, model) {
+        /// <summary>Converts a JSON light advertised action or function object into its library representation.</summary>
+        /// <param name="name" type="String">Advertised action or function name.</param>
+        /// <param name="value">Advertised action or function value.</param>
+        /// <param name="obj" type="Object">Object that will the converted value of the advertised action or function.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing the action's or function's relative URIs.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <remarks>
+        ///     Actions and functions have the same representation in json light, so to disambiguate them the function uses
+        ///     the model object.  If available, the function will look for the functionImport object that describes the
+        ///     the action or the function.  If for whatever reason the functionImport can't be retrieved from the model (like
+        ///     there is no model available or there is no functionImport within the model), then the value is going to be treated
+        ///     as an advertised action and stored under obj.__metadata.actions.
+        /// </remarks>
+
+        if (!name || !isArray(value) && !isComplex(value)) {
+            return;
+        }
+
+        var isFunction = false;
+        var nsEnd = name.lastIndexOf(".");
+        var simpleName = name.substring(nsEnd + 1);
+        var containerName = (nsEnd > -1) ? name.substring(0, nsEnd) : "";
+
+        var container = (simpleName === name || containerName.indexOf(".") === -1) ?
+            lookupDefaultEntityContainer(model) :
+            lookupEntityContainer(containerName, model);
+
+        if (container) {
+            var functionImport = lookupFunctionImport(container.functionImport, simpleName);
+            if (functionImport && !!functionImport.isSideEffecting) {
+                isFunction = !parseBool(functionImport.isSideEffecting);
+            }
+        }
+
+        var metadata = obj.__metadata;
+        var targetName = isFunction ? "functions" : "actions";
+        var metadataURI = normalizeURI(name, baseURI);
+        var items = (isArray(value)) ? value : [value];
+
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            var item = items[i];
+            if (item) {
+                var targetCollection = metadata[targetName] = metadata[targetName] || [];
+                var actionOrFunction = { metadata: metadataURI, title: item.title, target: normalizeURI(item.target, baseURI) };
+                targetCollection.push(actionOrFunction);
+            }
+        }
+    };
+
+    var jsonLightReadFeed = function (data, feedInfo, baseURI, model, recognizeDates) {
+        /// <summary>Converts a JSON light feed or top level collection property object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light feed object to convert.</param>
+        /// <param name="typeName" type="String">Type name of the feed or collection items.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Feed or top level collection object.</param>
+
+        var items = isArray(data) ? data : data.value;
+        var entries = [];
+        var i, len, entry;
+        for (i = 0, len = items.length; i < len; i++) {
+            entry = jsonLightReadObject(items[i], feedInfo, baseURI, model, recognizeDates);
+            entries.push(entry);
+        }
+
+        var feed = { results: entries };
+
+        if (isComplex(data)) {
+            for (var name in data) {
+                if (name.indexOf("#") === 0) {
+                    // This is an advertised function or action.
+                    feed.__metadata = feed.__metadata || {};
+                    jsonLightReadAdvertisedFunctionOrAction(name.substring(1), data[name], feed, baseURI, model);
+                }
+            }
+            feed = jsonLightReadDataAnnotations(data, feed, baseURI);
+        }
+        return feed;
+    };
+
+    var jsonLightGetEntryKey = function (data, entityModel) {
+        /// <summary>Gets the key of an entry.</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entityModel" type="String">Object describing the entry Model</param>
+        /// <returns type="string">Entry instance key.</returns>
+
+        var entityInstanceKey;
+        var entityKeys = entityModel.key.propertyRef;
+        var type;
+        entityInstanceKey = "(";
+        if (entityKeys.length == 1) {
+            type = lookupProperty(entityModel.property, entityKeys[0].name).type;
+            entityInstanceKey += formatLiteral(data[entityKeys[0].name], type);
+        } else {
+            var first = true;
+            for (var i = 0; i < entityKeys.length; i++) {
+                if (!first) {
+                    entityInstanceKey += ",";
+                } else {
+                    first = false;
+                }
+                type = lookupProperty(entityModel.property, entityKeys[i].name).type;
+                entityInstanceKey += entityKeys[i].name + "=" + formatLiteral(data[entityKeys[i].name], type);
+            }
+        }
+        entityInstanceKey += ")";
+        return entityInstanceKey;
+    };
+
+
+    var jsonLightComputeUrisIfMissing = function (data, entryInfo, actualType, serviceURI, entityModel, baseTypeModel) {
+        /// <summary>Compute the URI according to OData conventions if it doesn't exist</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entryInfo" type="Object">Information about the entry includes type, key, entitySet and entityContainerName.</param>
+        /// <param name="actualType" type="String">Type of the entry</param>
+        /// <param name="serviceURI" type="String">Base URI the service.</param>
+        /// <param name="entityModel" type="Object">Object describing an OData conceptual schema of the entry.</param>
+        /// <param name="baseTypeModel" type="Object" optional="true">Object escribing an OData conceptual schema of the baseType if it exists.</param>
+
+        var lastIdSegment = data[jsonLightAnnotations.id] || data[jsonLightAnnotations.read] || data[jsonLightAnnotations.edit] || entryInfo.entitySet.name + entryInfo.key;
+        data[jsonLightAnnotations.id] = serviceURI + lastIdSegment;
+        if (!data[jsonLightAnnotations.edit]) {
+            data[jsonLightAnnotations.edit] = entryInfo.entitySet.name + entryInfo.key;
+            if (entryInfo.entitySet.entityType != actualType) {
+                data[jsonLightAnnotations.edit] += "/" + actualType;
+            }
+        }
+        data[jsonLightAnnotations.read] = data[jsonLightAnnotations.read] || data[jsonLightAnnotations.edit];
+        if (!data[jsonLightAnnotations.etag]) {
+            var etag = jsonLightComputeETag(data, entityModel, baseTypeModel);
+            if (!!etag) {
+                data[jsonLightAnnotations.etag] = etag;
+            }
+        }
+
+        jsonLightComputeStreamLinks(data, entityModel, baseTypeModel);
+        jsonLightComputeNavigationAndAssociationProperties(data, entityModel, baseTypeModel);
+        jsonLightComputeFunctionImports(data, entryInfo);
+    };
+
+    var jsonLightComputeETag = function (data, entityModel, baseTypeModel) {
+        /// <summary>Computes the etag of an entry</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entryInfo" type="Object">Object describing the entry model.</param>
+        /// <param name="baseTypeModel" type="Object"  optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
+        /// <returns type="string">Etag value</returns>
+        var etag = "";
+        var propertyModel;
+        for (var i = 0; entityModel.property && i < entityModel.property.length; i++) {
+            propertyModel = entityModel.property[i];
+            etag = jsonLightAppendValueToEtag(data, etag, propertyModel);
+
+        }
+        if (baseTypeModel) {
+            for (i = 0; baseTypeModel.property && i < baseTypeModel.property.length; i++) {
+                propertyModel = baseTypeModel.property[i];
+                etag = jsonLightAppendValueToEtag(data, etag, propertyModel);
+            }
+        }
+        if (etag.length > 0) {
+            return etag + "\"";
+        }
+        return null;
+    };
+
+    var jsonLightAppendValueToEtag = function (data, etag, propertyModel) {
+        /// <summary>Adds a propery value to the etag after formatting.</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="etag" type="Object">value of the etag.</param>
+        /// <param name="propertyModel" type="Object">Object describing an OData conceptual schema of the property.</param>
+        /// <returns type="string">Etag value</returns>
+
+        if (propertyModel.concurrencyMode == "Fixed") {
+            if (etag.length > 0) {
+                etag += ",";
+            } else {
+                etag += "W/\"";
+            }
+            if (data[propertyModel.name] !== null) {
+                etag += formatLiteral(data[propertyModel.name], propertyModel.type);
+            } else {
+                etag += "null";
+            }
+        }
+        return etag;
+    };
+
+    var jsonLightComputeNavigationAndAssociationProperties = function (data, entityModel, baseTypeModel) {
+        /// <summary>Adds navigation links to the entry metadata</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entityModel" type="Object">Object describing the entry model.</param>
+        /// <param name="baseTypeModel" type="Object"  optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
+
+        var navigationLinkAnnotation = "@odata.navigationLinkUrl";
+        var associationLinkAnnotation = "@odata.associationLinkUrl";
+        var navigationPropertyName, navigationPropertyAnnotation, associationPropertyAnnotation;
+        for (var i = 0; entityModel.navigationProperty && i < entityModel.navigationProperty.length; i++) {
+            navigationPropertyName = entityModel.navigationProperty[i].name;
+            navigationPropertyAnnotation = navigationPropertyName + navigationLinkAnnotation;
+            if (data[navigationPropertyAnnotation] === undefined) {
+                data[navigationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/" + encodeURIComponent(navigationPropertyName);
+            }
+            associationPropertyAnnotation = navigationPropertyName + associationLinkAnnotation;
+            if (data[associationPropertyAnnotation] === undefined) {
+                data[associationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/$links/" + encodeURIComponent(navigationPropertyName);
+            }
+        }
+
+        if (baseTypeModel && baseTypeModel.navigationProperty) {
+            for (i = 0; i < baseTypeModel.navigationProperty.length; i++) {
+                navigationPropertyName = baseTypeModel.navigationProperty[i].name;
+                navigationPropertyAnnotation = navigationPropertyName + navigationLinkAnnotation;
+                if (data[navigationPropertyAnnotation] === undefined) {
+                    data[navigationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/" + encodeURIComponent(navigationPropertyName);
+                }
+                associationPropertyAnnotation = navigationPropertyName + associationLinkAnnotation;
+                if (data[associationPropertyAnnotation] === undefined) {
+                    data[associationPropertyAnnotation] = data[jsonLightAnnotations.edit] + "/$links/" + encodeURIComponent(navigationPropertyName);
+                }
+            }
+        }
+    };
+
+    var formatLiteral = function (value, type) {
+        /// <summary>Formats a value according to Uri literal format</summary>
+        /// <param name="value">Value to be formatted.</param>
+        /// <param name="type">Edm type of the value</param>
+        /// <returns type="string">Value after formatting</returns>
+
+        value = "" + formatRowLiteral(value, type);
+        value = encodeURIComponent(value.replace("'", "''"));
+        switch ((type)) {
+            case "Edm.Binary":
+                return "X'" + value + "'";
+            case "Edm.DateTime":
+                return "datetime" + "'" + value + "'";
+            case "Edm.DateTimeOffset":
+                return "datetimeoffset" + "'" + value + "'";
+            case "Edm.Decimal":
+                return value + "M";
+            case "Edm.Guid":
+                return "guid" + "'" + value + "'";
+            case "Edm.Int64":
+                return value + "L";
+            case "Edm.Float":
+                return value + "f";
+            case "Edm.Double":
+                return value + "D";
+            case "Edm.Geography":
+                return "geography" + "'" + value + "'";
+            case "Edm.Geometry":
+                return "geometry" + "'" + value + "'";
+            case "Edm.Time":
+                return "time" + "'" + value + "'";
+            case "Edm.String":
+                return "'" + value + "'";
+            default:
+                return value;
+        }
+    };
+
+
+    var formatRowLiteral = function (value, type) {
+        switch (type) {
+            case "Edm.Binary":
+                return convertByteArrayToHexString(value);
+            default:
+                return value;
+        }
+    };
+
+    var jsonLightComputeFunctionImports = function (data, entryInfo) {
+        /// <summary>Adds functions and actions links to the entry metadata</summary>
+        /// <param name="entry" type="Object">JSON light entry.</param>
+        /// <param name="entityInfo" type="Object">Object describing the entry</param>
+
+        var functionImport = entryInfo.functionImport || [];
+        for (var i = 0; i < functionImport.length; i++) {
+            if (functionImport[i].isBindable && functionImport[i].parameter[0] && functionImport[i].parameter[0].type == entryInfo.entitySet.entityType) {
+                var functionImportAnnotation = "#" + entryInfo.containerName + "." + functionImport[i].name;
+                if (data[functionImportAnnotation] == undefined) {
+                    data[functionImportAnnotation] = {
+                        title: functionImport[i].name,
+                        target: data[jsonLightAnnotations.edit] + "/" + functionImport[i].name
+                    };
+                }
+            }
+        }
+    };
+
+    var jsonLightComputeStreamLinks = function (data, entityModel, baseTypeModel) {
+        /// <summary>Adds stream links to the entry metadata</summary>
+        /// <param name="data" type="Object">JSON light entry.</param>
+        /// <param name="entityModel" type="Object">Object describing the entry model.</param>
+        /// <param name="baseTypeModel" type="Object"  optional="true">Object describing an OData conceptual schema of the baseType if it exists.</param>
+
+        if (entityModel.hasStream || (baseTypeModel && baseTypeModel.hasStream)) {
+            data[jsonLightAnnotations.mediaEdit] = data[jsonLightAnnotations.mediaEdit] || data[jsonLightAnnotations.mediaEdit] + "/$value";
+            data[jsonLightAnnotations.mediaRead] = data[jsonLightAnnotations.mediaRead] || data[jsonLightAnnotations.mediaEdit];
+        }
+    };
+
+    var jsonLightReadTopPrimitiveProperty = function (data, typeName, baseURI, recognizeDates) {
+        /// <summary>Converts a JSON light top level primitive property object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light feed object to convert.</param>
+        /// <param name="typeName" type="String">Type name of the primitive property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Top level primitive property object.</param>
+
+        var metadata = { type: typeName };
+        var value = jsonLightReadDataItemValue(data.value, typeName, metadata, baseURI, null, null, recognizeDates);
+        return jsonLightReadDataAnnotations(data, { __metadata: metadata, value: value }, baseURI);
+    };
+
+    var jsonLightReadTopCollectionProperty = function (data, typeName, baseURI, model, recognizeDates) {
+        /// <summary>Converts a JSON light top level collection property object into its library representation.</summary>
+        /// <param name="data" type="Object">JSON light feed object to convert.</param>
+        /// <param name="typeName" type="String">Type name of the collection property.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <returns type="Object">Top level collection property object.</param>
+
+        var propertyMetadata = {};
+        var value = jsonLightReadCollectionPropertyValue(data.value, typeName, propertyMetadata, baseURI, model, recognizeDates);
+        extend(value.__metadata, propertyMetadata);
+        return jsonLightReadDataAnnotations(data, value, baseURI);
+    };
+
+    var jsonLightReadLinksDocument = function (data, baseURI) {
+        /// <summary>Converts a JSON light links collection object to its library representation.</summary>
+        /// <param name="data" type="Object">JSON light link object to convert.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <returns type="Object">Links collection object.</param>
+
+        var items = data.value;
+        if (!isArray(items)) {
+            return jsonLightReadLink(data, baseURI);
+        }
+
+        var results = [];
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            results.push(jsonLightReadLink(items[i], baseURI));
+        }
+
+        var links = { results: results };
+        return jsonLightReadDataAnnotations(data, links, baseURI);
+    };
+
+    var jsonLightReadLink = function (data, baseURI) {
+        /// <summary>Converts a JSON light link object to its library representation.</summary>
+        /// <param name="data" type="Object">JSON light link object to convert.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <returns type="Object">Link object.</param>
+
+        var link = { uri: normalizeURI(data.url, baseURI) };
+
+        link = jsonLightReadDataAnnotations(data, link, baseURI);
+        var metadata = link.__metadata || {};
+        var metadataProperties = metadata.properties || {};
+
+        jsonLightRemoveTypePropertyMetadata(metadataProperties.url);
+        renameProperty(metadataProperties, "url", "uri");
+
+        return link;
+    };
+
+    var jsonLightRemoveTypePropertyMetadata = function (propertyMetadata) {
+        /// <summary>Removes the type property from a property metadata object.</summary>
+        /// <param name="propertyMetadata" type="Object">Property metadata object.</param>
+
+        if (propertyMetadata) {
+            delete propertyMetadata.type;
+        }
+    };
+
+    var jsonLightReadSvcDocument = function (data, baseURI) {
+        /// <summary>Converts a JSON light service document object to its library representation.</summary>
+        /// <param name="data" type="Object">JSON light service document object to convert.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the payload.</param>
+        /// <returns type="Object">Link object.</param>
+
+        var items = data.value;
+        var collections = [];
+        var workspace = jsonLightReadDataAnnotations(data, { collections: collections }, baseURI);
+
+        var metadata = workspace.__metadata || {};
+        var metadataProperties = metadata.properties || {};
+
+        jsonLightRemoveTypePropertyMetadata(metadataProperties.value);
+        renameProperty(metadataProperties, "value", "collections");
+
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            var item = items[i];
+            var collection = { title: item.name, href: normalizeURI(item.url, baseURI) };
+
+            collection = jsonLightReadDataAnnotations(item, collection, baseURI);
+            metadata = collection.__metadata || {};
+            metadataProperties = metadata.properties || {};
+
+            jsonLightRemoveTypePropertyMetadata(metadataProperties.name);
+            jsonLightRemoveTypePropertyMetadata(metadataProperties.url);
+
+            renameProperty(metadataProperties, "name", "title");
+            renameProperty(metadataProperties, "url", "href");
+
+            collections.push(collection);
+        }
+
+        return { workspaces: [workspace] };
+    };
+
+    var jsonLightMakePayloadInfo = function (kind, type) {
+        /// <summary>Creates an object containing information for the json light payload.</summary>
+        /// <param name="kind" type="String">JSON light payload kind, one of the PAYLOADTYPE_XXX constant values.</param>
+        /// <param name="typeName" type="String">Type name of the JSON light payload.</param>
+        /// <returns type="Object">Object with kind and type fields.</returns>
+
+        /// <field name="kind" type="String">Kind of the JSON light payload. One of the PAYLOADTYPE_XXX constant values.</field>
+        /// <field name="type" type="String">Data type of the JSON light payload.</field>
+
+        return { kind: kind, type: type || null };
+    };
+
+    var jsonLightPayloadInfo = function (data, model, inferFeedAsComplexType) {
+        /// <summary>Infers the information describing the JSON light payload from its metadata annotation, structure, and data model.</summary>
+        /// <param name="data" type="Object">Json light response payload object.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="inferFeedAsComplexType" type="Boolean">True if a JSON light payload that looks like a feed should be treated as a complex type property instead.</param>
+        /// <remarks>
+        ///     If the arguments passed to the function don't convey enough information about the payload to determine without doubt that the payload is a feed then it
+        ///     will try to use the payload object structure instead.  If the payload looks like a feed (has value property that is an array or non-primitive values) then
+        ///     the function will report its kind as PAYLOADTYPE_FEED unless the inferFeedAsComplexType flag is set to true. This flag comes from the user request
+        ///     and allows the user to control how the library behaves with an ambigous JSON light payload.
+        /// </remarks>
+        /// <returns type="Object">
+        ///     Object with kind and type fields. Null if there is no metadata annotation or the payload info cannot be obtained..
+        /// </returns>
+
+        var metadataUri = data[metadataAnnotation];
+        if (!metadataUri || typeof metadataUri !== "string") {
+            return null;
+        }
+
+        var fragmentStart = metadataUri.lastIndexOf("#");
+        if (fragmentStart === -1) {
+            return jsonLightMakePayloadInfo(PAYLOADTYPE_SVCDOC);
+        }
+
+        var elementStart = metadataUri.indexOf("@Element", fragmentStart);
+        var fragmentEnd = elementStart - 1;
+
+        if (fragmentEnd < 0) {
+            fragmentEnd = metadataUri.indexOf("?", fragmentStart);
+            if (fragmentEnd === -1) {
+                fragmentEnd = metadataUri.length;
+            }
+        }
+
+        var fragment = metadataUri.substring(fragmentStart + 1, fragmentEnd);
+        if (fragment.indexOf("/$links/") > 0) {
+            return jsonLightMakePayloadInfo(PAYLOADTYPE_LINKS);
+        }
+
+        var fragmentParts = fragment.split("/");
+        if (fragmentParts.length >= 0) {
+            var qualifiedName = fragmentParts[0];
+            var typeCast = fragmentParts[1];
+
+            if (jsonLightIsPrimitiveType(qualifiedName)) {
+                return jsonLightMakePayloadInfo(PAYLOADTYPE_PRIMITIVE, qualifiedName);
+            }
+
+            if (isCollectionType(qualifiedName)) {
+                return jsonLightMakePayloadInfo(PAYLOADTYPE_COLLECTION, qualifiedName);
+            }
+
+            var entityType = typeCast;
+            var entitySet, functionImport, containerName;
+            if (!typeCast) {
+                var nsEnd = qualifiedName.lastIndexOf(".");
+                var simpleName = qualifiedName.substring(nsEnd + 1);
+                var container = (simpleName === qualifiedName) ?
+                    lookupDefaultEntityContainer(model) :
+                    lookupEntityContainer(qualifiedName.substring(0, nsEnd), model);
+
+                if (container) {
+                    entitySet = lookupEntitySet(container.entitySet, simpleName);
+                    functionImport = container.functionImport;
+                    containerName = container.name;
+                    entityType = !!entitySet ? entitySet.entityType : null;
+                }
+            }
+
+            var info;
+            if (elementStart > 0) {
+                info = jsonLightMakePayloadInfo(PAYLOADTYPE_OBJECT, entityType);
+                info.entitySet = entitySet;
+                info.functionImport = functionImport;
+                info.containerName = containerName;
+                return info;
+            }
+
+            if (entityType) {
+                info = jsonLightMakePayloadInfo(PAYLOADTYPE_FEED, entityType);
+                info.entitySet = entitySet;
+                info.functionImport = functionImport;
+                info.containerName = containerName;
+                return info;
+            }
+
+            if (isArray(data.value) && !lookupComplexType(qualifiedName, model)) {
+                var item = data.value[0];
+                if (!isPrimitive(item)) {
+                    if (jsonLightIsEntry(item) || !inferFeedAsComplexType) {
+                        return jsonLightMakePayloadInfo(PAYLOADTYPE_FEED, null);
+                    }
+                }
+            }
+
+            return jsonLightMakePayloadInfo(PAYLOADTYPE_OBJECT, qualifiedName);
+        }
+
+        return null;
+    };
+
+    var jsonLightReadPayload = function (data, model, recognizeDates, inferFeedAsComplexType, contentTypeOdata) {
+        /// <summary>Converts a JSON light response payload object into its library's internal representation.</summary>
+        /// <param name="data" type="Object">Json light response payload object.</param>
+        /// <param name="model" type="Object">Object describing an OData conceptual schema.</param>
+        /// <param name="recognizeDates" type="Boolean" optional="true">Flag indicating whether datetime literal strings should be converted to JavaScript Date objects.</param>
+        /// <param name="inferFeedAsComplexType" type="Boolean">True if a JSON light payload that looks like a feed should be reported as a complex type property instead.</param>
+        /// <param name="contentTypeOdata" type="string">Includes the type of json ( minimalmetadata, fullmetadata .. etc )</param>
+        /// <returns type="Object">Object in the library's representation.</returns>
+
+        if (!isComplex(data)) {
+            return data;
+        }
+
+        contentTypeOdata = contentTypeOdata || "minimalmetadata";
+        var baseURI = data[metadataAnnotation];
+        var payloadInfo = jsonLightPayloadInfo(data, model, inferFeedAsComplexType);
+        if (assigned(payloadInfo)) {
+            payloadInfo.contentTypeOdata = contentTypeOdata;
+        }
+        var typeName = null;
+        if (payloadInfo) {
+            delete data[metadataAnnotation];
+
+            typeName = payloadInfo.type;
+            switch (payloadInfo.kind) {
+                case PAYLOADTYPE_FEED:
+                    return jsonLightReadFeed(data, payloadInfo, baseURI, model, recognizeDates);
+                case PAYLOADTYPE_COLLECTION:
+                    return jsonLightReadTopCollectionProperty(data, typeName, baseURI, model, recognizeDates);
+                case PAYLOADTYPE_PRIMITIVE:
+                    return jsonLightReadTopPrimitiveProperty(data, typeName, baseURI, recognizeDates);
+                case PAYLOADTYPE_SVCDOC:
+                    return jsonLightReadSvcDocument(data, baseURI);
+                case PAYLOADTYPE_LINKS:
+                    return jsonLightReadLinksDocument(data, baseURI);
+            }
+        }
+        return jsonLightReadObject(data, payloadInfo, baseURI, model, recognizeDates);
+    };
+
+    var jsonLightSerializableMetadata = ["type", "etag", "media_src", "edit_media", "content_type", "media_etag"];
+
+    var formatJsonLight = function (obj, context) {
+        /// <summary>Converts an object in the library's internal representation to its json light representation.</summary>
+        /// <param name="obj" type="Object">Object the library's internal representation.</param>
+        /// <param name="context" type="Object">Object with the serialization context.</param>
+        /// <returns type="Object">Object in its json light representation.</returns>
+
+        // Regular expression used to test that the uri is for a $links document.
+        var linksUriRE = /\/\$links\//;
+        var data = {};
+        var metadata = obj.__metadata;
+
+        var islinks = context && linksUriRE.test(context.request.requestUri);
+        formatJsonLightData(obj, (metadata && metadata.properties), data, islinks);
+        return data;
+    };
+
+    var formatJsonLightMetadata = function (metadata, data) {
+        /// <summary>Formats an object's metadata into the appropriate json light annotations and saves them to data.</summary>
+        /// <param name="obj" type="Object">Object whose metadata is going to be formatted as annotations.</param>
+        /// <param name="data" type="Object">Object on which the annotations are going to be stored.</param>
+
+        if (metadata) {
+            var i, len;
+            for (i = 0, len = jsonLightSerializableMetadata.length; i < len; i++) {
+                // There is only a subset of metadata values that are interesting during update requests.
+                var name = jsonLightSerializableMetadata[i];
+                var qName = odataAnnotationPrefix + (jsonLightNameMap[name] || name);
+                formatJsonLightAnnotation(qName, null, metadata[name], data);
+            }
+        }
+    };
+
+    var formatJsonLightData = function (obj, pMetadata, data, isLinks) {
+        /// <summary>Formats an object's data into the appropriate json light values and saves them to data.</summary>
+        /// <param name="obj" type="Object">Object whose data is going to be formatted.</param>
+        /// <param name="pMetadata" type="Object">Object that contains metadata for the properties that are being formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted values are going to be stored.</param>
+        /// <param name="isLinks" type="Boolean">True if a links document is being formatted.  False otherwise.</param>
+
+        for (var key in obj) {
+            var value = obj[key];
+            if (key === "__metadata") {
+                // key is the object metadata.
+                formatJsonLightMetadata(value, data);
+            } else if (key.indexOf(".") === -1) {
+                // key is an regular property or array element.
+                if (isLinks && key === "uri") {
+                    formatJsonLightEntityLink(value, data);
+                } else {
+                    formatJsonLightProperty(key, value, pMetadata, data, isLinks);
+                }
+            } else {
+                data[key] = value;
+            }
+        }
+    };
+
+    var formatJsonLightProperty = function (name, value, pMetadata, data) {
+        /// <summary>Formats an object's value identified by name to its json light representation and saves it to data.</summary>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value.</param>
+        /// <param name="pMetadata" type="Object">Object that contains metadata for the property that is being formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        // Get property type from property metadata
+        var propertyMetadata = pMetadata && pMetadata[name] || { properties: undefined, type: undefined };
+        var typeName = dataItemTypeName(value, propertyMetadata);
+
+        if (isPrimitive(value) || !value) {
+            // It is a primitive value then.
+            formatJsonLightAnnotation(typeAnnotation, name, typeName, data);
+            data[name] = value;
+            return;
+        }
+
+        if (isFeed(value, typeName) || isEntry(value)) {
+            formatJsonLightInlineProperty(name, value, data);
+            return;
+        }
+
+        if (!typeName && isDeferred(value)) {
+            // It is really a deferred property.
+            formatJsonLightDeferredProperty(name, value, data);
+            return;
+        }
+
+        if (isCollection(value, typeName)) {
+            // The thing is a collection, format it as one.
+            if (getCollectionType(typeName)) {
+                formatJsonLightAnnotation(typeAnnotation, name, typeName, data);
+            }
+            formatJsonLightCollectionProperty(name, value, data);
+            return;
+        }
+
+        djsassert(isComplex(value), "formatJsonLightProperty - Value is not a complex type value");
+
+        // Format the complex property value in a new object in data[name].
+        data[name] = {};
+        formatJsonLightAnnotation(typeAnnotation, null, typeName, data[name]);
+        formatJsonLightData(value, propertyMetadata.properties, data[name]);
+    };
+
+    var formatJsonLightEntityLink = function (value, data) {
+        /// <summary>Formats an entity link in a $links document and saves it into data.</summary>
+        /// <param name="value" type="String">Entity link value.</summary>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+        data.url = value;
+    };
+
+    var formatJsonLightDeferredProperty = function (name, value, data) {
+        /// <summary>Formats the object value's identified by name as an odata.navigalinkurl annotation and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the deferred property to be formatted.</param>
+        /// <param name="value" type="Object">Deferred property value to be formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        formatJsonLightAnnotation(navUrlAnnotation, name, value.__deferred.uri, data);
+    };
+
+    var formatJsonLightCollectionProperty = function (name, value, data) {
+        /// <summary>Formats a collection property in obj identified by name as a json light collection property and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the collection property to be formatted.</param>
+        /// <param name="value" type="Object">Collection property value to be formatted.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        data[name] = [];
+        var items = isArray(value) ? value : value.results;
+        formatJsonLightData(items, null, data[name]);
+    };
+
+    var formatJsonLightInlineProperty = function (name, value, data) {
+        /// <summary>Formats an inline feed or entry property in obj identified by name as a json light value and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the inline feed or entry property to be formatted.</param>
+        /// <param name="value" type="Object or Array">Value of the inline feed or entry property.</param>
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        if (isFeed(value)) {
+            data[name] = [];
+            // Format each of the inline feed entries
+            var entries = isArray(value) ? value : value.results;
+            var i, len;
+            for (i = 0, len = entries.length; i < len; i++) {
+                formatJsonLightInlineEntry(name, entries[i], true, data);
+            }
+            return;
+        }
+        formatJsonLightInlineEntry(name, value, false, data);
+    };
+
+    var formatJsonLightInlineEntry = function (name, value, inFeed, data) {
+        /// <summary>Formats an inline entry value in the property identified by name as a json light value and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the inline feed or entry property that owns the entry formatted.</param>
+        /// <param name="value" type="Object">Inline entry value to be formatted.</param>
+        /// <param name="inFeed" type="Boolean">True if the entry is in an inline feed; false otherwise.
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        // This might be a bind instead of a deep insert.
+        var uri = value.__metadata && value.__metadata.uri;
+        if (uri) {
+            formatJsonLightBinding(name, uri, inFeed, data);
+            return;
+        }
+
+        var entry = formatJsonLight(value);
+        if (inFeed) {
+            data[name].push(entry);
+            return;
+        }
+        data[name] = entry;
+    };
+
+    var formatJsonLightBinding = function (name, uri, inFeed, data) {
+        /// <summary>Formats an entry binding in the inline property in obj identified by name as an odata.bind annotation and saves it to data.</summary>
+        /// <param name="name" type="String">Name of the inline property that has the binding to be formated.</param>
+        /// <param name="uri" type="String">Uri to the bound entry.</param>
+        /// <param name="inFeed" type="Boolean">True if the binding is in an inline feed; false otherwise.
+        /// <param name="data" type="Object">Object on which the formatted value is going to be stored.</param>
+
+        var bindingName = name + bindAnnotation;
+        if (inFeed) {
+            // The binding is inside an inline feed, so merge it with whatever other bindings already exist in data.
+            data[bindingName] = data[bindingName] || [];
+            data[bindingName].push(uri);
+            return;
+        }
+        // The binding is on an inline entry; it can be safely overwritten.
+        data[bindingName] = uri;
+    };
+
+    var formatJsonLightAnnotation = function (qName, target, value, data) {
+        /// <summary>Formats a value as a json light annotation and stores it in data</summary>
+        /// <param name="qName" type="String">Qualified name of the annotation.</param>
+        /// <param name="target" type="String">Name of the property that the metadata value targets.</param>
+        /// <param name="value">Annotation value.</param>
+        /// <param name="data" type="Object">Object on which the annotation is going to be stored.</param>
+
+        if (value !== undefined) {
+            if(target) {
+                data[target + "@" + qName] = value;
+            }
+            else {
+                data[qName] = value;
+            }
+        }
+    };
+
+    // DATAJS INTERNAL START
+    odata.jsonLightReadPayload = jsonLightReadPayload;
+    odata.formatJsonLight = formatJsonLight;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
+
+
+


[07/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-json.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-json.js b/datajs/src/odata-json.js
new file mode 100644
index 0000000..8f11857
--- /dev/null
+++ b/datajs/src/odata-json.js
@@ -0,0 +1,379 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-json.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports 
+
+    var defined = datajs.defined;
+    var extend = datajs.extend;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+
+    var contentType = odata.contentType;
+    var jsonLightReadPayload = odata.jsonLightReadPayload;
+    var formatDateTimeOffset = odata.formatDateTimeOffset;
+    var formatDuration = odata.formatDuration;
+    var formatJsonLight = odata.formatJsonLight;
+    var formatNumberWidth = odata.formatNumberWidth;
+    var getCanonicalTimezone = odata.getCanonicalTimezone;
+    var handler = odata.handler;
+    var isComplex = odata.isComplex;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupEntityType = odata.lookupEntityType;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var maxVersion = odata.maxVersion;
+    var parseDateTime = odata.parseDateTime;
+    var parseDuration = odata.parseDuration;
+    var parseTimezone = odata.parseTimezone;
+    var payloadTypeOf = odata.payloadTypeOf;
+    var traverse = odata.traverse;
+
+    // CONTENT START
+
+    var jsonMediaType = "application/json";
+    var jsonContentType = contentType(jsonMediaType);
+
+    var jsonReadAdvertisedActionsOrFunctions = function (value) {
+        /// <summary>Reads and object containing action or function metadata and maps them into a single array of objects.</summary>
+        /// <param name="value" type="Object">Object containing action or function metadata.</param>
+        /// <returns type="Array">Array of objects containing metadata for the actions or functions specified in value.</returns>
+
+        var result = [];
+        for (var name in value) {
+            var i, len;
+            for (i = 0, len = value[name].length; i < len; i++) {
+                result.push(extend({ metadata: name }, value[name][i]));
+            }
+        }
+        return result;
+    };
+
+    var jsonApplyMetadata = function (value, metadata, dateParser, recognizeDates) {
+        /// <summary>Applies metadata coming from both the payload and the metadata object to the value.</summary>
+        /// <param name="value" type="Object">Data on which the metada is going to be applied.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <param name="dateParser" type="function">Function used for parsing datetime values.</param>
+        /// <param name="recognizeDates" type="Boolean">
+        ///     True if strings formatted as datetime values should be treated as datetime values. False otherwise.
+        /// </param>
+        /// <returns type="Object">Transformed data.</returns>
+
+        if (value && typeof value === "object") {
+            var dataTypeName;
+            var valueMetadata = value.__metadata;
+
+            if (valueMetadata) {
+                if (valueMetadata.actions) {
+                    valueMetadata.actions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.actions);
+                }
+                if (valueMetadata.functions) {
+                    valueMetadata.functions = jsonReadAdvertisedActionsOrFunctions(valueMetadata.functions);
+                }
+                dataTypeName = valueMetadata && valueMetadata.type;
+            }
+
+            var dataType = lookupEntityType(dataTypeName, metadata) || lookupComplexType(dataTypeName, metadata);
+            var propertyValue;
+            if (dataType) {
+                var properties = dataType.property;
+                if (properties) {
+                    var i, len;
+                    for (i = 0, len = properties.length; i < len; i++) {
+                        var property = properties[i];
+                        var propertyName = property.name;
+                        propertyValue = value[propertyName];
+
+                        if (property.type === "Edm.DateTime" || property.type === "Edm.DateTimeOffset") {
+                            if (propertyValue) {
+                                propertyValue = dateParser(propertyValue);
+                                if (!propertyValue) {
+                                    throw { message: "Invalid date/time value" };
+                                }
+                                value[propertyName] = propertyValue;
+                            }
+                        } else if (property.type === "Edm.Time") {
+                            value[propertyName] = parseDuration(propertyValue);
+                        }
+                    }
+                }
+            } else if (recognizeDates) {
+                for (var name in value) {
+                    propertyValue = value[name];
+                    if (typeof propertyValue === "string") {
+                        value[name] = dateParser(propertyValue) || propertyValue;
+                    }
+                }
+            }
+        }
+        return value;
+    };
+
+    var isJsonLight = function (contentType) {
+        /// <summary>Tests where the content type indicates a json light payload.</summary>
+        /// <param name="contentType">Object with media type and properties dictionary.</param>
+        /// <returns type="Boolean">True is the content type indicates a json light payload. False otherwise.</returns>
+
+        if (contentType) {
+            var odata = contentType.properties.odata;
+            return odata === "nometadata" || odata === "minimalmetadata" || odata === "fullmetadata";
+        }
+        return false;
+    };
+
+    var normalizeServiceDocument = function (data, baseURI) {
+        /// <summary>Normalizes a JSON service document to look like an ATOM service document.</summary>
+        /// <param name="data" type="Object">Object representation of service documents as deserialized.</param>
+        /// <param name="baseURI" type="String">Base URI to resolve relative URIs.</param>
+        /// <returns type="Object">An object representation of the service document.</returns>
+        var workspace = { collections: [] };
+
+        var i, len;
+        for (i = 0, len = data.EntitySets.length; i < len; i++) {
+            var title = data.EntitySets[i];
+            var collection = {
+                title: title,
+                href: normalizeURI(title, baseURI)
+            };
+
+            workspace.collections.push(collection);
+        }
+
+        return { workspaces: [workspace] };
+    };
+
+    // The regular expression corresponds to something like this:
+    // /Date(123+60)/
+    //
+    // This first number is date ticks, the + may be a - and is optional,
+    // with the second number indicating a timezone offset in minutes.
+    //
+    // On the wire, the leading and trailing forward slashes are
+    // escaped without being required to so the chance of collisions is reduced;
+    // however, by the time we see the objects, the characters already
+    // look like regular forward slashes.
+    var jsonDateRE = /^\/Date\((-?\d+)(\+|-)?(\d+)?\)\/$/;
+
+    var minutesToOffset = function (minutes) {
+        /// <summary>Formats the given minutes into (+/-)hh:mm format.</summary>
+        /// <param name="minutes" type="Number">Number of minutes to format.</param>
+        /// <returns type="String">The minutes in (+/-)hh:mm format.</returns>
+
+        var sign;
+        if (minutes < 0) {
+            sign = "-";
+            minutes = -minutes;
+        } else {
+            sign = "+";
+        }
+
+        var hours = Math.floor(minutes / 60);
+        minutes = minutes - (60 * hours);
+
+        return sign + formatNumberWidth(hours, 2) + ":" + formatNumberWidth(minutes, 2);
+    };
+
+    var parseJsonDateString = function (value) {
+        /// <summary>Parses the JSON Date representation into a Date object.</summary>
+        /// <param name="value" type="String">String value.</param>
+        /// <returns type="Date">A Date object if the value matches one; falsy otherwise.</returns>
+
+        var arr = value && jsonDateRE.exec(value);
+        if (arr) {
+            // 0 - complete results; 1 - ticks; 2 - sign; 3 - minutes
+            var result = new Date(parseInt10(arr[1]));
+            if (arr[2]) {
+                var mins = parseInt10(arr[3]);
+                if (arr[2] === "-") {
+                    mins = -mins;
+                }
+
+                // The offset is reversed to get back the UTC date, which is
+                // what the API will eventually have.
+                var current = result.getUTCMinutes();
+                result.setUTCMinutes(current - mins);
+                result.__edmType = "Edm.DateTimeOffset";
+                result.__offset = minutesToOffset(mins);
+            }
+            if (!isNaN(result.valueOf())) {
+                return result;
+            }
+        }
+
+        // Allow undefined to be returned.
+    };
+
+    // Some JSON implementations cannot produce the character sequence \/
+    // which is needed to format DateTime and DateTimeOffset into the
+    // JSON string representation defined by the OData protocol.
+    // See the history of this file for a candidate implementation of
+    // a 'formatJsonDateString' function.
+
+    var jsonParser = function (handler, text, context) {
+        /// <summary>Parses a JSON OData payload.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text">Payload text (this parser also handles pre-parsed objects).</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>An object representation of the OData payload.</returns>
+
+        var recognizeDates = defined(context.recognizeDates, handler.recognizeDates);
+        var inferJsonLightFeedAsObject = defined(context.inferJsonLightFeedAsObject, handler.inferJsonLightFeedAsObject);
+        var model = context.metadata;
+        var dataServiceVersion = context.dataServiceVersion;
+        var dateParser = parseJsonDateString;
+        var json = (typeof text === "string") ? window.JSON.parse(text) : text;
+
+        if ((maxVersion("3.0", dataServiceVersion) === dataServiceVersion)) {
+            if (isJsonLight(context.contentType)) {
+                return jsonLightReadPayload(json, model, recognizeDates, inferJsonLightFeedAsObject, context.contentType.properties.odata);
+            }
+            dateParser = parseDateTime;
+        }
+
+        json = traverse(json.d, function (key, value) {
+            return jsonApplyMetadata(value, model, dateParser, recognizeDates);
+        });
+
+        json = jsonUpdateDataFromVersion(json, context.dataServiceVersion);
+        return jsonNormalizeData(json, context.response.requestUri);
+    };
+
+    var jsonToString = function (data) {
+        /// <summary>Converts the data into a JSON string.</summary>
+        /// <param name="data">Data to serialize.</param>
+        /// <returns type="String">The JSON string representation of data.</returns>
+
+        var result; // = undefined;
+        // Save the current date.toJSON function
+        var dateToJSON = Date.prototype.toJSON;
+        try {
+            // Set our own date.toJSON function
+            Date.prototype.toJSON = function () {
+                return formatDateTimeOffset(this);
+            };
+            result = window.JSON.stringify(data, jsonReplacer);
+        } finally {
+            // Restore the original toJSON function
+            Date.prototype.toJSON = dateToJSON;
+        }
+        return result;
+    };
+
+    var jsonSerializer = function (handler, data, context) {
+        /// <summary>Serializes the data by returning its string representation.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data">Data to serialize.</param>
+        /// <param name="context" type="Object">Object with serialization context.</param>
+        /// <returns type="String">The string representation of data.</returns>
+
+        var dataServiceVersion = context.dataServiceVersion || "1.0";
+        var useJsonLight = defined(context.useJsonLight, handler.useJsonLight);
+        var cType = context.contentType = context.contentType || jsonContentType;
+
+        if (cType && cType.mediaType === jsonContentType.mediaType) {
+            var json = data;
+            if (useJsonLight || isJsonLight(cType)) {
+                context.dataServiceVersion = maxVersion(dataServiceVersion, "3.0");
+                json = formatJsonLight(data, context);
+                return jsonToString(json);
+            }
+            if (maxVersion("3.0", dataServiceVersion) === dataServiceVersion) {
+                cType.properties.odata = "verbose";
+                context.contentType = cType;
+            }
+            return jsonToString(json);
+        }
+        return undefined;
+    };
+
+    var jsonReplacer = function (_, value) {
+        /// <summary>JSON replacer function for converting a value to its JSON representation.</summary>
+        /// <param value type="Object">Value to convert.</param>
+        /// <returns type="String">JSON representation of the input value.</returns>
+        /// <remarks>
+        ///   This method is used during JSON serialization and invoked only by the JSON.stringify function.
+        ///   It should never be called directly.
+        /// </remarks>
+
+        if (value && value.__edmType === "Edm.Time") {
+            return formatDuration(value);
+        } else {
+            return value;
+        }
+    };
+
+    var jsonNormalizeData = function (data, baseURI) {
+        /// <summary>
+        /// Normalizes the specified data into an intermediate representation.
+        /// like the latest supported version.
+        /// </summary>
+        /// <param name="data" optional="false">Data to update.</param>
+        /// <param name="baseURI" optional="false">URI to use as the base for normalizing references.</param>
+
+        var isSvcDoc = isComplex(data) && !data.__metadata && isArray(data.EntitySets);
+        return isSvcDoc ? normalizeServiceDocument(data, baseURI) : data;
+    };
+
+    var jsonUpdateDataFromVersion = function (data, dataVersion) {
+        /// <summary>
+        /// Updates the specified data in the specified version to look
+        /// like the latest supported version.
+        /// </summary>
+        /// <param name="data" optional="false">Data to update.</param>
+        /// <param name="dataVersion" optional="true" type="String">Version the data is in (possibly unknown).</param>
+
+        // Strip the trailing comma if there.
+        if (dataVersion && dataVersion.lastIndexOf(";") === dataVersion.length - 1) {
+            dataVersion = dataVersion.substr(0, dataVersion.length - 1);
+        }
+
+        if (!dataVersion || dataVersion === "1.0") {
+            if (isArray(data)) {
+                data = { results: data };
+            }
+        }
+
+        return data;
+    };
+
+    var jsonHandler = handler(jsonParser, jsonSerializer, jsonMediaType, MAX_DATA_SERVICE_VERSION);
+    jsonHandler.recognizeDates = false;
+    jsonHandler.useJsonLight = false;
+    jsonHandler.inferJsonLightFeedAsObject = false;
+
+    odata.jsonHandler = jsonHandler;
+
+
+
+    // DATAJS INTERNAL START
+    odata.jsonParser = jsonParser;
+    odata.jsonSerializer = jsonSerializer;
+    odata.jsonNormalizeData = jsonNormalizeData;
+    odata.jsonUpdateDataFromVersion = jsonUpdateDataFromVersion;
+    odata.normalizeServiceDocument = normalizeServiceDocument;
+    odata.parseJsonDateString = parseJsonDateString;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
+
+
+
+

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-metadata.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-metadata.js b/datajs/src/odata-metadata.js
new file mode 100644
index 0000000..a7ca00a
--- /dev/null
+++ b/datajs/src/odata-metadata.js
@@ -0,0 +1,500 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-metadata.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // imports 
+
+    var contains = datajs.contains;
+    var normalizeURI = datajs.normalizeURI;
+    var xmlAttributes = datajs.xmlAttributes;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNS = datajs.xmlNS;
+    var xmlnsNS = datajs.xmlnsNS;
+    var xmlParse = datajs.xmlParse;
+
+    var createAttributeExtension = odata.createAttributeExtension;
+    var createElementExtension = odata.createElementExtension;
+    var edmxNs = odata.edmxNs;
+    var edmNs1 = odata.edmNs1;
+    var edmNs1_1 = odata.edmNs1_1;
+    var edmNs1_2 = odata.edmNs1_2;
+    var edmNs2a = odata.edmNs2a;
+    var edmNs2b = odata.edmNs2b;
+    var edmNs3 = odata.edmNs3;
+    var handler = odata.handler;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var odataMetaXmlNs = odata.odataMetaXmlNs;
+
+
+    var xmlMediaType = "application/xml";
+
+    // CONTENT START
+
+    var schemaElement = function (attributes, elements, text, ns) {
+        /// <summary>Creates an object that describes an element in an schema.</summary>
+        /// <param name="attributes" type="Array">List containing the names of the attributes allowed for this element.</param>
+        /// <param name="elements" type="Array">List containing the names of the child elements allowed for this element.</param>
+        /// <param name="text" type="Boolean">Flag indicating if the element's text value is of interest or not.</param>
+        /// <param name="ns" type="String">Namespace to which the element belongs to.</param>
+        /// <remarks>
+        ///    If a child element name ends with * then it is understood by the schema that that child element can appear 0 or more times.
+        /// </remarks>
+        /// <returns type="Object">Object with attributes, elements, text, and ns fields.</returns>
+
+        return {
+            attributes: attributes,
+            elements: elements,
+            text: text || false,
+            ns: ns
+        };
+    };
+
+    // It's assumed that all elements may have Documentation children and Annotation elements.
+    // See http://msdn.microsoft.com/en-us/library/bb399292.aspx for a CSDL reference.
+    var schema = {
+        elements: {
+            Annotations: schemaElement(
+            /*attributes*/["Target", "Qualifier"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Association: schemaElement(
+            /*attributes*/["Name"],
+            /*elements*/["End*", "ReferentialConstraint", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            AssociationSet: schemaElement(
+            /*attributes*/["Name", "Association"],
+            /*elements*/["End*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Binary: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Bool: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Collection: schemaElement(
+            /*attributes*/null,
+            /*elements*/["String*", "Int*", "Float*", "Decimal*", "Bool*", "DateTime*", "DateTimeOffset*", "Guid*", "Binary*", "Time*", "Collection*", "Record*"]
+            ),
+            CollectionType: schemaElement(
+            /*attributes*/["ElementType", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef"]
+            ),
+            ComplexType: schemaElement(
+            /*attributes*/["Name", "BaseType", "Abstract"],
+            /*elements*/["Property*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            DateTime: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            DateTimeOffset: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Decimal: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            DefiningExpression: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Dependent: schemaElement(
+            /*attributes*/["Role"],
+            /*elements*/["PropertyRef*"]
+            ),
+            Documentation: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            End: schemaElement(
+            /*attributes*/["Type", "Role", "Multiplicity", "EntitySet"],
+            /*elements*/["OnDelete"]
+            ),
+            EntityContainer: schemaElement(
+            /*attributes*/["Name", "Extends"],
+            /*elements*/["EntitySet*", "AssociationSet*", "FunctionImport*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            EntitySet: schemaElement(
+            /*attributes*/["Name", "EntityType"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            EntityType: schemaElement(
+            /*attributes*/["Name", "BaseType", "Abstract", "OpenType"],
+            /*elements*/["Key", "Property*", "NavigationProperty*", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            EnumType: schemaElement(
+            /*attributes*/["Name", "UnderlyingType", "IsFlags"],
+            /*elements*/["Member*"]
+            ),
+            Float: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Function: schemaElement(
+            /*attributes*/["Name", "ReturnType"],
+            /*elements*/["Parameter*", "DefiningExpression", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            FunctionImport: schemaElement(
+            /*attributes*/["Name", "ReturnType", "EntitySet", "IsSideEffecting", "IsComposable", "IsBindable", "EntitySetPath"],
+            /*elements*/["Parameter*", "ReturnType", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Guid: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Int: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Key: schemaElement(
+            /*attributes*/null,
+            /*elements*/["PropertyRef*"]
+            ),
+            LabeledElement: schemaElement(
+            /*attributes*/["Name"],
+            /*elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
+            ),
+            Member: schemaElement(
+            /*attributes*/["Name", "Value"]
+            ),
+            NavigationProperty: schemaElement(
+            /*attributes*/["Name", "Relationship", "ToRole", "FromRole", "ContainsTarget"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Null: schemaElement(
+            /*attributes*/null,
+            /*elements*/null
+            ),
+            OnDelete: schemaElement(
+            /*attributes*/["Action"]
+            ),
+            Path: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Parameter: schemaElement(
+            /*attributes*/["Name", "Type", "Mode", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "SRID"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeRef", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            Principal: schemaElement(
+            /*attributes*/["Role"],
+            /*elements*/["PropertyRef*"]
+            ),
+            Property: schemaElement(
+            /*attributes*/["Name", "Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "ConcurrencyMode", "CollectionKind", "SRID"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType", "TypeAnnotation*", "ValueAnnotation*"]
+            ),
+            PropertyRef: schemaElement(
+            /*attributes*/["Name"]
+            ),
+            PropertyValue: schemaElement(
+            /*attributes*/["Property", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
+            /*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
+            ),
+            ReferenceType: schemaElement(
+            /*attributes*/["Type"]
+            ),
+            ReferentialConstraint: schemaElement(
+            /*attributes*/null,
+            /*elements*/["Principal", "Dependent"]
+            ),
+            ReturnType: schemaElement(
+            /*attributes*/["ReturnType", "Type", "EntitySet"],
+            /*elements*/["CollectionType", "ReferenceType", "RowType"]
+            ),
+            RowType: schemaElement(
+            /*elements*/["Property*"]
+            ),
+            String: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            Schema: schemaElement(
+            /*attributes*/["Namespace", "Alias"],
+            /*elements*/["Using*", "EntityContainer*", "EntityType*", "Association*", "ComplexType*", "Function*", "ValueTerm*", "Annotations*"]
+            ),
+            Time: schemaElement(
+            /*attributes*/null,
+            /*elements*/null,
+            /*text*/true
+            ),
+            TypeAnnotation: schemaElement(
+            /*attributes*/["Term", "Qualifier"],
+            /*elements*/["PropertyValue*"]
+            ),
+            TypeRef: schemaElement(
+            /*attributes*/["Type", "Nullable", "DefaultValue", "MaxLength", "FixedLength", "Precision", "Scale", "Unicode", "Collation", "SRID"]
+            ),
+            Using: schemaElement(
+            /*attributes*/["Namespace", "Alias"]
+            ),
+            ValueAnnotation: schemaElement(
+            /*attributes*/["Term", "Qualifier", "Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time"],
+            /*Elements*/["Path", "String", "Int", "Float", "Decimal", "Bool", "DateTime", "DateTimeOffset", "Guid", "Binary", "Time", "Collection", "Record", "LabeledElement", "Null"]
+            ),
+            ValueTerm: schemaElement(
+            /*attributes*/["Name", "Type"],
+            /*elements*/["TypeAnnotation*", "ValueAnnotation*"]
+            ),
+
+            // See http://msdn.microsoft.com/en-us/library/dd541238(v=prot.10) for an EDMX reference.
+            Edmx: schemaElement(
+            /*attributes*/["Version"],
+            /*elements*/["DataServices", "Reference*", "AnnotationsReference*"],
+            /*text*/false,
+            /*ns*/edmxNs
+            ),
+            DataServices: schemaElement(
+            /*attributes*/null,
+            /*elements*/["Schema*"],
+            /*text*/false,
+            /*ns*/edmxNs
+            )
+        }
+    };
+
+    // See http://msdn.microsoft.com/en-us/library/ee373839.aspx for a feed customization reference.
+    var customizationAttributes = ["m:FC_ContentKind", "m:FC_KeepInContent", "m:FC_NsPrefix", "m:FC_NsUri", "m:FC_SourcePath", "m:FC_TargetPath"];
+    schema.elements.Property.attributes = schema.elements.Property.attributes.concat(customizationAttributes);
+    schema.elements.EntityType.attributes = schema.elements.EntityType.attributes.concat(customizationAttributes);
+
+    // See http://msdn.microsoft.com/en-us/library/dd541284(PROT.10).aspx for an EDMX reference.
+    schema.elements.Edmx = { attributes: ["Version"], elements: ["DataServices"], ns: edmxNs };
+    schema.elements.DataServices = { elements: ["Schema*"], ns: edmxNs };
+
+    // See http://msdn.microsoft.com/en-us/library/dd541233(v=PROT.10) for Conceptual Schema Definition Language Document for Data Services.
+    schema.elements.EntityContainer.attributes.push("m:IsDefaultEntityContainer");
+    schema.elements.Property.attributes.push("m:MimeType");
+    schema.elements.FunctionImport.attributes.push("m:HttpMethod");
+    schema.elements.FunctionImport.attributes.push("m:IsAlwaysBindable");
+    schema.elements.EntityType.attributes.push("m:HasStream");
+    schema.elements.DataServices.attributes = ["m:DataServiceVersion", "m:MaxDataServiceVersion"];
+
+    var scriptCase = function (text) {
+        /// <summary>Converts a Pascal-case identifier into a camel-case identifier.</summary>
+        /// <param name="text" type="String">Text to convert.</param>
+        /// <returns type="String">Converted text.</returns>
+        /// <remarks>If the text starts with multiple uppercase characters, it is left as-is.</remarks>
+
+        if (!text) {
+            return text;
+        }
+
+        if (text.length > 1) {
+            var firstTwo = text.substr(0, 2);
+            if (firstTwo === firstTwo.toUpperCase()) {
+                return text;
+            }
+
+            return text.charAt(0).toLowerCase() + text.substr(1);
+        }
+
+        return text.charAt(0).toLowerCase();
+    };
+
+    var getChildSchema = function (parentSchema, candidateName) {
+        /// <summary>Gets the schema node for the specified element.</summary>
+        /// <param name="parentSchema" type="Object">Schema of the parent XML node of 'element'.</param>
+        /// <param name="candidateName">XML element name to consider.</param>
+        /// <returns type="Object">The schema that describes the specified element; null if not found.</returns>
+
+        if (candidateName === "Documentation") {
+            return { isArray: true, propertyName: "documentation" };
+        }
+
+        var elements = parentSchema.elements;
+        if (!elements) {
+            return null;
+        }
+
+        var i, len;
+        for (i = 0, len = elements.length; i < len; i++) {
+            var elementName = elements[i];
+            var multipleElements = false;
+            if (elementName.charAt(elementName.length - 1) === "*") {
+                multipleElements = true;
+                elementName = elementName.substr(0, elementName.length - 1);
+            }
+
+            if (candidateName === elementName) {
+                var propertyName = scriptCase(elementName);
+                return { isArray: multipleElements, propertyName: propertyName };
+            }
+        }
+
+        return null;
+    };
+
+    // This regular expression is used to detect a feed customization element
+    // after we've normalized it into the 'm' prefix. It starts with m:FC_,
+    // followed by other characters, and ends with _ and a number.
+    // The captures are 0 - whole string, 1 - name as it appears in internal table.
+    var isFeedCustomizationNameRE = /^(m:FC_.*)_[0-9]+$/;
+
+    var isEdmNamespace = function (nsURI) {
+        /// <summary>Checks whether the specifies namespace URI is one of the known CSDL namespace URIs.</summary>
+        /// <param name="nsURI" type="String">Namespace URI to check.</param>
+        /// <returns type="Boolean">true if nsURI is a known CSDL namespace; false otherwise.</returns>
+
+        return nsURI === edmNs1 ||
+               nsURI === edmNs1_1 ||
+               nsURI === edmNs1_2 ||
+               nsURI === edmNs2a ||
+               nsURI === edmNs2b ||
+               nsURI === edmNs3;
+    };
+
+    var parseConceptualModelElement = function (element) {
+        /// <summary>Parses a CSDL document.</summary>
+        /// <param name="element">DOM element to parse.</param>
+        /// <returns type="Object">An object describing the parsed element.</returns>
+
+        var localName = xmlLocalName(element);
+        var nsURI = xmlNamespaceURI(element);
+        var elementSchema = schema.elements[localName];
+        if (!elementSchema) {
+            return null;
+        }
+
+        if (elementSchema.ns) {
+            if (nsURI !== elementSchema.ns) {
+                return null;
+            }
+        } else if (!isEdmNamespace(nsURI)) {
+            return null;
+        }
+
+        var item = {};
+        var extensions = [];
+        var attributes = elementSchema.attributes || [];
+        xmlAttributes(element, function (attribute) {
+
+            var localName = xmlLocalName(attribute);
+            var nsURI = xmlNamespaceURI(attribute);
+            var value = attribute.value;
+
+            // Don't do anything with xmlns attributes.
+            if (nsURI === xmlnsNS) {
+                return;
+            }
+
+            // Currently, only m: for metadata is supported as a prefix in the internal schema table,
+            // un-prefixed element names imply one a CSDL element.
+            var schemaName = null;
+            var handled = false;
+            if (isEdmNamespace(nsURI) || nsURI === null) {
+                schemaName = "";
+            } else if (nsURI === odataMetaXmlNs) {
+                schemaName = "m:";
+            }
+
+            if (schemaName !== null) {
+                schemaName += localName;
+
+                // Feed customizations for complex types have additional
+                // attributes with a suffixed counter starting at '1', so
+                // take that into account when doing the lookup.
+                var match = isFeedCustomizationNameRE.exec(schemaName);
+                if (match) {
+                    schemaName = match[1];
+                }
+
+                if (contains(attributes, schemaName)) {
+                    handled = true;
+                    item[scriptCase(localName)] = value;
+                }
+            }
+
+            if (!handled) {
+                extensions.push(createAttributeExtension(attribute));
+            }
+        });
+
+        xmlChildElements(element, function (child) {
+            var localName = xmlLocalName(child);
+            var childSchema = getChildSchema(elementSchema, localName);
+            if (childSchema) {
+                if (childSchema.isArray) {
+                    var arr = item[childSchema.propertyName];
+                    if (!arr) {
+                        arr = [];
+                        item[childSchema.propertyName] = arr;
+                    }
+                    arr.push(parseConceptualModelElement(child));
+                } else {
+                    item[childSchema.propertyName] = parseConceptualModelElement(child);
+                }
+            } else {
+                extensions.push(createElementExtension(child));
+            }
+        });
+
+        if (elementSchema.text) {
+            item.text = xmlInnerText(element);
+        }
+
+        if (extensions.length) {
+            item.extensions = extensions;
+        }
+
+        return item;
+    };
+
+    var metadataParser = function (handler, text) {
+        /// <summary>Parses a metadata document.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Metadata text.</param>
+        /// <returns>An object representation of the conceptual model.</returns>
+
+        var doc = xmlParse(text);
+        var root = xmlFirstChildElement(doc);
+        return parseConceptualModelElement(root) || undefined;
+    };
+
+    odata.metadataHandler = handler(metadataParser, null, xmlMediaType, MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.schema = schema;
+    odata.scriptCase = scriptCase;
+    odata.getChildSchema = getChildSchema;
+    odata.parseConceptualModelElement = parseConceptualModelElement;
+    odata.metadataParser = metadataParser;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-net.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-net.js b/datajs/src/odata-net.js
new file mode 100644
index 0000000..ad9b8dd
--- /dev/null
+++ b/datajs/src/odata-net.js
@@ -0,0 +1,330 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-net.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+
+    var defined = datajs.defined;
+    var delay = datajs.delay;
+
+    // CONTENT START
+    var ticks = 0;
+
+    var canUseJSONP = function (request) {
+        /// <summary>
+        /// Checks whether the specified request can be satisfied with a JSONP request.
+        /// </summary>
+        /// <param name="request">Request object to check.</param>
+        /// <returns type="Boolean">true if the request can be satisfied; false otherwise.</returns>
+
+        // Requests that 'degrade' without changing their meaning by going through JSONP
+        // are considered usable.
+        //
+        // We allow data to come in a different format, as the servers SHOULD honor the Accept
+        // request but may in practice return content with a different MIME type.
+        if (request.method && request.method !== "GET") {
+            return false;
+        }
+
+        return true;
+    };
+
+    var createIFrame = function (url) {
+        /// <summary>Creates an IFRAME tag for loading the JSONP script</summary>
+        /// <param name="url" type="String">The source URL of the script</param>
+        /// <returns type="HTMLElement">The IFRAME tag</returns>
+        var iframe = window.document.createElement("IFRAME");
+        iframe.style.display = "none";
+
+        var attributeEncodedUrl = url.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/\</g, "&lt;");
+        var html = "<html><head><script type=\"text/javascript\" src=\"" + attributeEncodedUrl + "\"><\/script><\/head><body><\/body><\/html>";
+
+        var body = window.document.getElementsByTagName("BODY")[0];
+        body.appendChild(iframe);
+
+        writeHtmlToIFrame(iframe, html);
+        return iframe;
+    };
+
+    var createXmlHttpRequest = function () {
+        /// <summary>Creates a XmlHttpRequest object.</summary>
+        /// <returns type="XmlHttpRequest">XmlHttpRequest object.</returns>
+        if (window.XMLHttpRequest) {
+            return new window.XMLHttpRequest();
+        }
+        var exception;
+        if (window.ActiveXObject) {
+            try {
+                return new window.ActiveXObject("Msxml2.XMLHTTP.6.0");
+            } catch (_) {
+                try {
+                    return new window.ActiveXObject("Msxml2.XMLHTTP.3.0");
+                } catch (e) {
+                    exception = e;
+                }
+            }
+        } else {
+            exception = { message: "XMLHttpRequest not supported" };
+        }
+        throw exception;
+    };
+
+    var isAbsoluteUrl = function (url) {
+        /// <summary>Checks whether the specified URL is an absolute URL.</summary>
+        /// <param name="url" type="String">URL to check.</param>
+        /// <returns type="Boolean">true if the url is an absolute URL; false otherwise.</returns>
+
+        return url.indexOf("http://") === 0 ||
+            url.indexOf("https://") === 0 ||
+            url.indexOf("file://") === 0;
+    };
+
+    var isLocalUrl = function (url) {
+        /// <summary>Checks whether the specified URL is local to the current context.</summary>
+        /// <param name="url" type="String">URL to check.</param>
+        /// <returns type="Boolean">true if the url is a local URL; false otherwise.</returns>
+
+        if (!isAbsoluteUrl(url)) {
+            return true;
+        }
+
+        // URL-embedded username and password will not be recognized as same-origin URLs.
+        var location = window.location;
+        var locationDomain = location.protocol + "//" + location.host + "/";
+        return (url.indexOf(locationDomain) === 0);
+    };
+
+    var removeCallback = function (name, tick) {
+        /// <summary>Removes a callback used for a JSONP request.</summary>
+        /// <param name="name" type="String">Function name to remove.</param>
+        /// <param name="tick" type="Number">Tick count used on the callback.</param>
+        try {
+            delete window[name];
+        } catch (err) {
+            window[name] = undefined;
+            if (tick === ticks - 1) {
+                ticks -= 1;
+            }
+        }
+    };
+
+    var removeIFrame = function (iframe) {
+        /// <summary>Removes an iframe.</summary>
+        /// <param name="iframe" type="Object">The iframe to remove.</param>
+        /// <returns type="Object">Null value to be assigned to iframe reference.</returns>
+        if (iframe) {
+            writeHtmlToIFrame(iframe, "");
+            iframe.parentNode.removeChild(iframe);
+        }
+
+        return null;
+    };
+
+    var readResponseHeaders = function (xhr, headers) {
+        /// <summary>Reads response headers into array.</summary>
+        /// <param name="xhr" type="XMLHttpRequest">HTTP request with response available.</param>
+        /// <param name="headers" type="Array">Target array to fill with name/value pairs.</param>
+
+        var responseHeaders = xhr.getAllResponseHeaders().split(/\r?\n/);
+        var i, len;
+        for (i = 0, len = responseHeaders.length; i < len; i++) {
+            if (responseHeaders[i]) {
+                var header = responseHeaders[i].split(": ");
+                headers[header[0]] = header[1];
+            }
+        }
+    };
+
+    var writeHtmlToIFrame = function (iframe, html) {
+        /// <summary>Writes HTML to an IFRAME document.</summary>
+        /// <param name="iframe" type="HTMLElement">The IFRAME element to write to.</param>
+        /// <param name="html" type="String">The HTML to write.</param>
+        var frameDocument = (iframe.contentWindow) ? iframe.contentWindow.document : iframe.contentDocument.document;
+        frameDocument.open();
+        frameDocument.write(html);
+        frameDocument.close();
+    };
+
+    odata.defaultHttpClient = {
+        callbackParameterName: "$callback",
+
+        formatQueryString: "$format=json",
+
+        enableJsonpCallback: false,
+
+        request: function (request, success, error) {
+            /// <summary>Performs a network request.</summary>
+            /// <param name="request" type="Object">Request description.</request>
+            /// <param name="success" type="Function">Success callback with the response object.</param>
+            /// <param name="error" type="Function">Error callback with an error object.</param>
+            /// <returns type="Object">Object with an 'abort' method for the operation.</returns>
+
+            var result = {};
+            var xhr = null;
+            var done = false;
+            var iframe;
+
+            result.abort = function () {
+                iframe = removeIFrame(iframe);
+                if (done) {
+                    return;
+                }
+
+                done = true;
+                if (xhr) {
+                    xhr.abort();
+                    xhr = null;
+                }
+
+                error({ message: "Request aborted" });
+            };
+
+            var handleTimeout = function () {
+                iframe = removeIFrame(iframe);
+                if (!done) {
+                    done = true;
+                    xhr = null;
+                    error({ message: "Request timed out" });
+                }
+            };
+
+            var name;
+            var url = request.requestUri;
+            var enableJsonpCallback = defined(request.enableJsonpCallback, this.enableJsonpCallback);
+            var callbackParameterName = defined(request.callbackParameterName, this.callbackParameterName);
+            var formatQueryString = defined(request.formatQueryString, this.formatQueryString);
+            if (!enableJsonpCallback || isLocalUrl(url)) {
+
+                xhr = createXmlHttpRequest();
+                xhr.onreadystatechange = function () {
+                    if (done || xhr === null || xhr.readyState !== 4) {
+                        return;
+                    }
+
+                    // Workaround for XHR behavior on IE.
+                    var statusText = xhr.statusText;
+                    var statusCode = xhr.status;
+                    if (statusCode === 1223) {
+                        statusCode = 204;
+                        statusText = "No Content";
+                    }
+
+                    var headers = [];
+                    readResponseHeaders(xhr, headers);
+
+                    var response = { requestUri: url, statusCode: statusCode, statusText: statusText, headers: headers, body: xhr.responseText };
+
+                    done = true;
+                    xhr = null;
+                    if (statusCode >= 200 && statusCode <= 299) {
+                        success(response);
+                    } else {
+                        error({ message: "HTTP request failed", request: request, response: response });
+                    }
+                };
+
+                xhr.open(request.method || "GET", url, true, request.user, request.password);
+
+                // Set the name/value pairs.
+                if (request.headers) {
+                    for (name in request.headers) {
+                        xhr.setRequestHeader(name, request.headers[name]);
+                    }
+                }
+
+                // Set the timeout if available.
+                if (request.timeoutMS) {
+                    xhr.timeout = request.timeoutMS;
+                    xhr.ontimeout = handleTimeout;
+                }
+
+                xhr.send(request.body);
+            } else {
+                if (!canUseJSONP(request)) {
+                    throw { message: "Request is not local and cannot be done through JSONP." };
+                }
+
+                var tick = ticks;
+                ticks += 1;
+                var tickText = tick.toString();
+                var succeeded = false;
+                var timeoutId;
+                name = "handleJSONP_" + tickText;
+                window[name] = function (data) {
+                    iframe = removeIFrame(iframe);
+                    if (!done) {
+                        succeeded = true;
+                        window.clearTimeout(timeoutId);
+                        removeCallback(name, tick);
+
+                        // Workaround for IE8 and IE10 below where trying to access data.constructor after the IFRAME has been removed
+                        // throws an "unknown exception"
+                        if (window.ActiveXObject) {
+                            data = window.JSON.parse(window.JSON.stringify(data));
+                        }
+
+
+                        var headers;
+                        // Adding dataServiceVersion in case of json light ( data.d doesn't exist )
+                        if (data.d === undefined) {
+                            headers = { "Content-Type": "application/json;odata=minimalmetadata", dataServiceVersion: "3.0" };
+                        } else {
+                            headers = { "Content-Type": "application/json" };
+                        }
+                        // Call the success callback in the context of the parent window, instead of the IFRAME
+                        delay(function () {
+                            removeIFrame(iframe);
+                            success({ body: data, statusCode: 200, headers: headers });
+                        });
+                    }
+                };
+
+                // Default to two minutes before timing out, 1000 ms * 60 * 2 = 120000.
+                var timeoutMS = (request.timeoutMS) ? request.timeoutMS : 120000;
+                timeoutId = window.setTimeout(handleTimeout, timeoutMS);
+
+                var queryStringParams = callbackParameterName + "=parent." + name;
+                if (this.formatQueryString) {
+                    queryStringParams += "&" + formatQueryString;
+                }
+
+                var qIndex = url.indexOf("?");
+                if (qIndex === -1) {
+                    url = url + "?" + queryStringParams;
+                } else if (qIndex === url.length - 1) {
+                    url = url + queryStringParams;
+                } else {
+                    url = url + "&" + queryStringParams;
+                }
+
+                iframe = createIFrame(url);
+            }
+
+            return result;
+        }
+    };
+
+    // DATAJS INTERNAL START
+    odata.canUseJSONP = canUseJSONP;
+    odata.isAbsoluteUrl = isAbsoluteUrl;
+    odata.isLocalUrl = isLocalUrl;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file


[03/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/common/ODataReadOracle.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/common/ODataReadOracle.svc b/datajs/tests/common/ODataReadOracle.svc
new file mode 100644
index 0000000..32b4d50
--- /dev/null
+++ b/datajs/tests/common/ODataReadOracle.svc
@@ -0,0 +1,182 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
+    Service="DataJS.Tests.ODataReadOracle" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+//uncomment this line to debug JSON serialization.
+//#define DEBUG_SERIALIZATION
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using System.Net;
+    using System.Runtime.Serialization;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Syndication;
+    using System.ServiceModel.Web;
+    using System.Xml;
+    using System.Xml.Linq;
+    using System.Spatial;
+    using Microsoft.Data.OData;
+
+    /// <summary>
+    /// Oracle for the OData.read library function
+    /// </summary>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class ODataReadOracle
+    {
+        const string jsonVerboseMediaType = "application/json;odata=verbose";
+
+        /// <summary>
+        /// Reads a URI that will return an OData ATOM feed
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadFeed(string url, string user, string password)
+        {
+            WebResponse response = ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password).GetResponse();
+            return AtomReader.ReadFeed(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return an OData ATOM feed entry
+        /// </summary>
+        /// <param name="url">URL of the entry</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>JSON object expected to be returned by OData.read</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadEntry(string url, string user, string password)
+        {
+            WebResponse response = ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password).GetResponse();
+            return AtomReader.ReadEntry(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return a metadata object
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadMetadata(string url)
+        {
+            WebResponse response = WebRequest.Create(ResolveUri(url, UriKind.Absolute)).GetResponse();
+            return CsdlReader.ReadCsdl(new StreamReader(response.GetResponseStream()));
+        }
+
+        /// <summary>
+        /// Reads a URI that will return a metadata object
+        /// </summary>
+        /// <param name="url">The URL to send the request to</param>
+        /// <param name="mimeType">Mime type being tested to determine base URI</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadServiceDocument(string url, string mimeType)
+        {
+            WebResponse response = WebRequest.Create(ResolveUri(url, UriKind.Absolute)).GetResponse();
+            string baseUri = string.Empty;
+
+            // With JSON responses only relative path passed to the library is available
+            if (mimeType.Equals(jsonVerboseMediaType))
+            {
+                baseUri = ResolveUri(url, UriKind.Relative).ToString();
+            }
+            else
+            {
+                baseUri = response.ResponseUri.AbsoluteUri;
+            }
+
+            return AtomReader.ReadServiceDocument(new StreamReader(response.GetResponseStream()), baseUri);
+        }
+
+        /// <summary>
+        /// Reads a URI that will get the Json response and return the stream
+        /// </summary>
+        /// <param name="url">URL of the entry</param>
+        /// <param name="user">The username for basic authentication</param>
+        /// <param name="password">The password for basic authentication</param>
+        /// <returns>Stream of the Json response expected to be returned by OData.read</returns>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public Stream ReadJson(string url, string user, string password)
+        {
+            HttpWebRequest request = (HttpWebRequest)ReaderUtils.CreateRequest(ResolveUri(url, UriKind.Absolute), user, password);
+            request.Accept = jsonVerboseMediaType + "; charset=utf-8";
+            WebResponse response = request.GetResponse();
+
+            return response.GetResponseStream();
+        }
+
+
+        /// <summary>
+        /// Loops back an ATOM feed passed to the webservice in JSON format.
+        /// </summary>
+        /// <param name="content">The ATOM feed xml stream to loopback as JSON</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadFeedLoopback(Stream content)
+        {
+            return AtomReader.ReadFeed(new StreamReader(content));
+        }
+
+        /// <summary>
+        /// Loops back an ATOM entry passed to the webservice in JSON format.
+        /// </summary>
+        /// <param name="content">The ATOM entry xml stream to loopback as JSON</param>
+        /// <returns>JSON object expected to be returned by OData.read (plus type metadata markers that will need to be removed)</returns>
+        [OperationContract]
+        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
+        public JsonObject ReadEntryLoopback(Stream content)
+        {
+            return AtomReader.ReadEntry(new StreamReader(content));
+        }
+
+        /// <summary>
+        /// Resolves the given url string to a URI
+        /// </summary>
+        /// <param name="url">The given URL string</param>
+        /// <param name="urlKind">URI kind to resolve to</param>
+        /// <returns>The resolved URI</returns>
+        private static string ResolveUri(string url, UriKind uriKind)
+        {
+            Uri resolvedUri = new Uri(url, UriKind.RelativeOrAbsolute);
+            if (!resolvedUri.IsAbsoluteUri)
+            {
+                // If the given URI is relative, then base it on the Referer URI
+                Uri baseUri = new Uri(WebOperationContext.Current.IncomingRequest.Headers["Referer"]);
+                resolvedUri = new Uri(baseUri, resolvedUri);
+                if (uriKind == UriKind.Relative)
+                {
+                    resolvedUri = baseUri.MakeRelativeUri(resolvedUri);
+                }
+            }
+
+            return resolvedUri.ToString();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/common/TestLogger.svc
----------------------------------------------------------------------
diff --git a/datajs/tests/common/TestLogger.svc b/datajs/tests/common/TestLogger.svc
new file mode 100644
index 0000000..d236b24
--- /dev/null
+++ b/datajs/tests/common/TestLogger.svc
@@ -0,0 +1,846 @@
+<%@ ServiceHost Language="C#" Debug="true" Factory="DataJS.Tests.TestSynchronizerFactory" Service="DataJS.Tests.TestSynchronizer" %>
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+namespace DataJS.Tests
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Net;
+    using System.ServiceModel;
+    using System.ServiceModel.Activation;
+    using System.ServiceModel.Channels;
+    using System.ServiceModel.Description;
+    using System.ServiceModel.Dispatcher;
+    using System.ServiceModel.Web;
+    using System.Text;
+    using System.Threading;
+    using System.Xml;
+    
+    /// <summary>
+    /// This factory supports reconfiguring the service to allow incoming messages
+    /// to be larger than the default.
+    /// </summary>
+    public class TestSynchronizerFactory : WebScriptServiceHostFactory
+    {
+        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
+        {
+            var result = base.CreateServiceHost(serviceType, baseAddresses);
+            result.Opening += ServiceHostOpening;
+            return result;
+        }
+
+        private static void UpdateService(ServiceDescription description)
+        {
+            const long LargeMaxReceivedMessageSize = 1024 * 1024 * 16;
+            foreach (var endpoint in description.Endpoints)
+            {
+                var basic = endpoint.Binding as BasicHttpBinding;
+                if (basic != null)
+                {
+                    basic.MaxReceivedMessageSize = LargeMaxReceivedMessageSize;
+                }
+
+                var http = endpoint.Binding as WebHttpBinding;
+                if (http != null)
+                {
+                    http.MaxReceivedMessageSize = LargeMaxReceivedMessageSize;
+                }
+            }
+        }
+
+        private void ServiceHostOpening(object sender, EventArgs e)
+        {
+            UpdateService((sender as ServiceHost).Description);
+        }        
+    }
+
+    /// <summary>Use this class to log test activity.</summary>
+    /// <remarks>
+    /// A test run can be created by invoking CreateTestRun. With a test
+    /// run ID, the following operations can be invoked:
+    ///
+    /// - AddTestPages: adds test pages to be made available to future callers (typically to support tests from different files)
+    /// - SetTestNamePrefix: sets a string that will be prefixed to every test name in logs (typically to include a browser name)
+    /// - MarkInProgress: resets the test run to "in-progress" for another variation (typically to run a different browser)
+    /// - IsTestRunInProgress: checks whether it's still in progress
+    /// - GetTestRunResults: returns the test results in TRX format
+    /// - LogAssert: logs a single assertion in the current test for the run
+    /// - LogTestStart: logs a test that has begun execution
+    /// - LogTestDone: logs a test that has ended execution
+    /// - TestCompleted: logs that a test has completed execution; returns the next page with tests or an empty string
+    /// </remarks>
+    [ServiceContract]
+    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
+    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
+    public class TestSynchronizer
+    {
+        private static readonly Dictionary<string, TestRunContext> testRuns = new Dictionary<string, TestRunContext>();
+        private const string Inconclusive = "Inconclusive";
+        private const string InProgress = "InProgress";
+        private const string Failed = "Failed";
+        private const string Passed = "Passed";
+        private const string Completed = "Completed";
+        
+        /// <summary>
+        /// Adds test pages to the specified runs; replaces existing files (helps with reliablity when something
+        /// fails part-way).
+        /// </summary>
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public int AddTestPages(string testRunId, string pages, string filter)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestPages.Clear();
+                context.TestPages.AddRange(pages.Split(',').Select(page => page + "?testRunId=" + testRunId + (filter == null ? string.Empty : "?filter=" + filter)));
+                return context.TestPages.Count;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public string CreateTestRun()
+        {
+            DisableResponseCaching();
+
+            Guid value = Guid.NewGuid();
+            string result = value.ToString();
+            TestRunContext context = CreateTestRunContextWithId(value);
+            
+            lock (testRuns)
+            {
+                testRuns.Add(result, context);
+            }
+
+            return result;
+        }
+
+        /// <summary>Checks whether the test run is in progress.</summary>
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public bool IsTestRunInProgress(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            return context.TestRun.ResultSummary.Outcome == InProgress;
+        }
+        
+        /// <summary>Provides a list of all test runs being tracked.</summary>
+        [OperationContract]
+        [WebGet(ResponseFormat=WebMessageFormat.Json)]
+        public IEnumerable<string> GetActiveTestRuns()
+        {
+            DisableResponseCaching();
+
+            List<string> result;
+            lock (testRuns)
+            {
+                result = new List<string>(testRuns.Keys);
+            }
+            
+            return result;
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Xml)]
+        public Message GetTestRunResults(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                this.CompleteTestRun(run);
+
+                TestRunXmlBodyWriter writer = new TestRunXmlBodyWriter(run);
+                return Message.CreateMessage(
+                    MessageVersion.None,
+                    OperationContext.Current.OutgoingMessageHeaders.Action,
+                    writer);
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogAssert(string testRunId, bool pass, string message, string name, string actual, string expected)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                TestResult result = run.TestResults.LastOrDefault(r => r.TestName == prefixedName);
+                if (result == null)
+                {
+                    throw new InvalidOperationException("Unable to find test " + prefixedName + " in run " + testRunId);
+                }
+                
+                result.DebugTrace.AppendLine(message);
+                if (!pass)
+                {
+                    result.ErrorMessages.AppendLine(message);
+                    result.ErrorMessages.AppendLine("Expected: " + expected);
+                    result.ErrorMessages.AppendLine("Actual: " + actual);
+                }
+            }
+        }
+        
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebInvoke(ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
+        public void LogBatch(string[] urls)
+        {
+            DisableResponseCaching();
+            
+            foreach (var url in urls)
+            {
+                Uri parsed = new Uri(OperationContext.Current.Channel.LocalAddress.Uri, url);
+                string methodName = parsed.Segments[parsed.Segments.Length - 1];
+                System.Reflection.MethodInfo method = this.GetType().GetMethod(methodName);
+                System.Reflection.ParameterInfo[] parameterInfos = method.GetParameters();
+                object[] parameters = new object[parameterInfos.Length];
+                System.Collections.Specialized.NameValueCollection query = System.Web.HttpUtility.ParseQueryString(parsed.Query);
+                for (int i = 0; i < parameters.Length; i++)
+                {
+                    object value = query[parameterInfos[i].Name];
+                    parameters[i] = Convert.ChangeType(value, parameterInfos[i].ParameterType, System.Globalization.CultureInfo.InvariantCulture);
+                }
+                
+                method.Invoke(this, parameters);
+            }            
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogTestStart(string testRunId, string name, DateTime startTime)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                Guid testId = Guid.NewGuid();
+                Guid executionId = Guid.NewGuid();
+                Guid testListId = run.TestLists.Single().Id;
+                run.TestDefinitions.Add(new TestDefinition()
+                {
+                    Id = testId,
+                    Name = prefixedName,
+                    ExecutionId = executionId,
+                });
+                run.TestEntries.Add(new TestEntry()
+                {
+                    TestId = testId,
+                    ExecutionId = executionId,
+                    TestListId = testListId
+                });
+                run.TestResults.Add(new TestResult()
+                {
+                    ExecutionId = executionId,
+                    TestId = testId,
+                    TestListId = testListId,
+                    TestName = prefixedName,
+                    ComputerName = Environment.MachineName,
+                    StartTime = startTime,
+                    EndTime = startTime,
+                    TestType = Guid.Parse("13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b"),
+                    Outcome = InProgress,
+                    // RelativeResultsDirectory?
+                });
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void LogTestDone(string testRunId, string name, int failures, int total, DateTime endTime)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                TestRun run = context.TestRun;
+                string prefixedName = context.TestNamePrefix + name;
+                TestResult result = run.TestResults.LastOrDefault(r => r.TestName == prefixedName);
+                if (failures > 0)
+                {
+                    result.Outcome = Failed;
+                }
+                else
+                {
+                    result.Outcome = Passed;
+                }
+
+                result.EndTime = endTime;
+            }
+        }
+
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void MarkInProgress(string testRunId)
+        {
+            DisableResponseCaching();
+
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestRun.ResultSummary.Outcome = InProgress;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test harness.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public void SetTestNamePrefix(string testRunId, string prefix)
+        {
+            DisableResponseCaching();
+            
+            TestRunContext context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                context.TestNamePrefix = prefix;
+            }
+        }
+
+        /// <remarks>This method is typically called by the test case.</remarks>
+        [OperationContract]
+        [WebGet(ResponseFormat = WebMessageFormat.Json)]
+        public string TestCompleted(string testRunId, int failures, int total)
+        {
+            DisableResponseCaching();
+            
+            var context = GetTestRunContext(testRunId);
+            lock (context)
+            {
+                string result;
+                if (context.TestPages.Count == 0)
+                {
+                    context.TestRun.ResultSummary.Outcome = Completed;
+                    result = "";
+                }
+                else
+                {
+                    result = context.TestPages[0];
+                    context.TestPages.RemoveAt(0);
+                }
+
+                return result;
+            }
+        }
+
+        private static TestRunContext CreateTestRunContextWithId(Guid value)
+        {
+            TestRun run = new TestRun();
+            run.Id = value;
+            run.Name = "Test run";
+            run.TestTimes.Creation = DateTime.Now;
+            run.TestTimes.Queueing = DateTime.Now;
+            run.TestLists.Add(new TestList()
+            {
+                Name = "All Results",
+                Id = Guid.NewGuid()
+            });
+
+            // For the time being, set up a fake test settings.
+            run.TestSettings.Id = Guid.NewGuid();
+
+            run.ResultSummary.Outcome = InProgress;
+
+            TestRunContext context = new TestRunContext();
+            context.TestRun = run;
+
+            return context;
+        }
+
+        private static void DisableResponseCaching()
+        {
+            WebOperationContext.Current.OutgoingResponse.Headers[HttpResponseHeader.CacheControl] = "no-cache";            
+        }
+
+        private static TestRunContext GetTestRunContext(string testRunId)
+        {
+            if (testRunId == null)
+            {
+                throw new ArgumentNullException("testRunId");
+            }
+            
+            lock (testRuns)
+            {
+                // For an 0-filled GUID, allow create-on-demand to simplify ad-hoc testing.
+                // Something like:
+                // http://localhost:8989/tests/odata-qunit-tests.htm?testRunId=00000000-0000-0000-0000-000000000000
+                if (!testRuns.ContainsKey(testRunId))
+                {
+                    Guid value = Guid.Parse(testRunId);
+                    if (value == Guid.Empty)
+                    {
+                        TestRunContext context = CreateTestRunContextWithId(value);
+                        testRuns.Add(testRunId, context);
+                    }
+                }
+                
+                return testRuns[testRunId];
+            }
+        }
+
+        private void CompleteTestRun(TestRun run)
+        {
+            run.TestTimes.Finish = DateTime.Now;
+
+            // Fill counts in result object.
+            var summary = run.ResultSummary;
+            summary.Executed = 0;
+            summary.Error = 0;
+            summary.Failed = 0;
+            summary.Timeout = 0;
+            summary.Aborted = 0;
+            summary.Inconclusive = 0;
+            summary.PassedButRunAborted = 0;
+            summary.NotRunnable = 0;
+            summary.NotExecuted = 0;
+            summary.Disconnected = 0;
+            summary.Warning = 0;
+            summary.Passed = 0;
+            summary.Completed = 0;
+            summary.InProgress = 0;
+            summary.Pending = 0;
+
+            foreach (var testResult in run.TestResults)
+            {
+                string outcome = testResult.Outcome;
+                switch (outcome)
+                {
+                    case InProgress:
+                        summary.Executed++;
+                        summary.InProgress++;
+                        break;
+                    case Failed:
+                        summary.Executed++;
+                        summary.Completed++;
+                        summary.Failed++;
+                        break;
+                    case Passed:
+                        summary.Executed++;
+                        summary.Completed++;
+                        summary.Passed++;
+                        break;
+                    default:
+                        summary.Failed++;
+                        break;
+                }
+            }
+
+            summary.Total = run.TestResults.Count;
+
+            if (summary.Failed != 0)
+            {
+                summary.Outcome = Failed;
+            }
+            else if (summary.Total <= 0 || summary.Passed < summary.Total)
+            {
+                summary.Outcome = Inconclusive;
+            }
+            else
+            {
+                summary.Outcome = Passed;
+            }
+        }
+    }
+
+    public class TestRunContext
+    {
+        public TestRunContext()
+        {
+            this.TestPages = new List<string>();
+        }
+        
+        public TestRun TestRun { get; set; }
+        public string TestNamePrefix { get; set; }
+        public List<string> TestPages { get; set; }
+    }
+
+    public class TestResultWriter
+    {
+        private const string TestNamespace = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010";
+
+        public static void WriteTestRun(TestRun run, string path)
+        {
+            if (run == null)
+            {
+                throw new ArgumentNullException("run");
+            }
+            if (path == null)
+            {
+                throw new ArgumentNullException("path");
+            }
+
+            using (XmlWriter writer = XmlWriter.Create(path))
+            {
+                WriteTestRun(run, path);
+            }
+        }
+
+        public static void WriteTestRun(TestRun run, XmlWriter writer)
+        {
+            if (run == null)
+            {
+                throw new ArgumentNullException("run");
+            }
+            if (writer == null)
+            {
+                throw new ArgumentNullException("writer");
+            }
+
+            writer.WriteStartElement("TestRun", TestNamespace);
+            writer.WriteGuidIfPresent("id", run.Id);
+            writer.WriteAttributeString("name", run.Name);
+            writer.WriteAttributeString("runUser", run.RunUser);
+
+            WriteTestSettings(run.TestSettings, writer);
+            WriteResultSummary(run.ResultSummary, writer);
+
+            // Write test definitions.
+            writer.WriteStartElement("TestDefinitions", TestNamespace);
+            foreach (var definition in run.TestDefinitions)
+            {
+                WriteTestDefinition(definition, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test lists.
+            writer.WriteStartElement("TestLists", TestNamespace);
+            foreach (var list in run.TestLists)
+            {
+                WriteTestList(list, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test entries.
+            writer.WriteStartElement("TestEntries", TestNamespace);
+            foreach (var entry in run.TestEntries)
+            {
+                WriteTestEntry(entry, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Write test results.
+            writer.WriteStartElement("Results", TestNamespace);
+            foreach (var result in run.TestResults)
+            {
+                WriteTestResults(result, writer);
+            }
+
+            writer.WriteEndElement();
+
+            // Close the test run element.
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestResults(TestResult result, XmlWriter writer)
+        {
+            if (result == null)
+            {
+                throw new ArgumentNullException("result");
+            }
+
+            writer.WriteStartElement("UnitTestResult", TestNamespace);
+            writer.WriteGuidIfPresent("testId", result.TestId);
+            writer.WriteGuidIfPresent("testListId", result.TestListId);
+            writer.WriteGuidIfPresent("executionId", result.ExecutionId);
+            writer.WriteGuidIfPresent("RelativeResultsDirectory", result.RelativeResultsDirectory);
+
+            writer.WriteAttributeString("testName", result.TestName);
+            writer.WriteAttributeString("computerName", result.ComputerName);
+            writer.WriteAttributeString("duration", result.Duration.ToString());
+            writer.WriteAttributeString("startTime", XmlConvert.ToString(result.StartTime));
+            writer.WriteAttributeString("endTime", XmlConvert.ToString(result.EndTime));
+            writer.WriteAttributeString("outcome", result.Outcome);
+
+            writer.WriteGuidIfPresent("testType", result.TestType);
+
+            if (result.DebugTrace.Length > 0)
+            {
+                writer.WriteStartElement("Output");
+
+                writer.WriteStartElement("DebugTrace");
+                writer.WriteString(result.DebugTrace.ToString());
+                writer.WriteEndElement();
+
+                writer.WriteStartElement("ErrorInfo");
+                writer.WriteStartElement("Message");
+                writer.WriteString(result.ErrorMessages.ToString());
+                writer.WriteEndElement();
+                writer.WriteEndElement();
+
+                writer.WriteEndElement();
+            }
+            
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestEntry(TestEntry entry, XmlWriter writer)
+        {
+            if (entry == null)
+            {
+                throw new ArgumentNullException("entry");
+            }
+
+            writer.WriteStartElement("TestEntry", TestNamespace);
+            writer.WriteGuidIfPresent("testId", entry.TestId);
+            writer.WriteGuidIfPresent("testListId", entry.TestListId);
+            writer.WriteGuidIfPresent("executionId", entry.ExecutionId);
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestList(TestList list, XmlWriter writer)
+        {
+            if (list == null)
+            {
+                throw new ArgumentNullException("list");
+            }
+
+            writer.WriteStartElement("TestList", TestNamespace);
+            writer.WriteAttributeString("name", list.Name);
+            writer.WriteGuidIfPresent("id", list.Id);
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestDefinition(TestDefinition definition, XmlWriter writer)
+        {
+            if (definition == null)
+            {
+                throw new ArgumentNullException("definition");
+            }
+
+            writer.WriteStartElement("UnitTest", TestNamespace);
+            writer.WriteAttributeString("name", definition.Name);
+            writer.WriteAttributeString("storage", definition.Storage);
+            writer.WriteGuidIfPresent("id", definition.Id);
+            
+            // There are more thing we could write here: DeploymentItems, Execution, TestMethod
+            
+            // This is the minimum needed to load the test in the IDE.
+            writer.WriteStartElement("Execution", TestNamespace);
+            writer.WriteGuidIfPresent("id", definition.ExecutionId);
+            writer.WriteEndElement();
+
+            writer.WriteStartElement("TestMethod", TestNamespace);
+            writer.WriteAttributeString("codeBase", "fake-test-file.js");
+            writer.WriteAttributeString("adapterTypeName", "Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestAdapter, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.Adapter, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
+            writer.WriteAttributeString("className", "FakeClassName, TestLogging, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
+            writer.WriteAttributeString("name", definition.Name);
+            writer.WriteEndElement();
+            
+            writer.WriteEndElement();
+        }
+
+        private static void WriteResultSummary(ResultSummary resultSummary, XmlWriter writer)
+        {
+            if (resultSummary == null)
+            {
+                throw new ArgumentNullException("resultSummary");
+            }
+
+            writer.WriteStartElement("ResultSummary", TestNamespace);
+            writer.WriteAttributeString("outcome", resultSummary.Outcome);
+
+            writer.WriteStartElement("Counters", TestNamespace);
+            
+            foreach (var p in typeof(ResultSummary).GetProperties())
+            {
+                if (p.PropertyType != typeof(int))
+                {
+                    continue;
+                }
+
+                int value = (int)p.GetValue(resultSummary, null);
+                string attributeName = p.Name;
+                attributeName = attributeName.Substring(0, 1).ToLowerInvariant() +  attributeName.Substring(1);
+                writer.WriteAttributeString(attributeName, value.ToString());
+            }
+            
+            writer.WriteEndElement();
+            writer.WriteEndElement();
+        }
+
+        private static void WriteTestSettings(TestSettings testSettings, XmlWriter writer)
+        {
+            if (testSettings == null)
+            {
+                throw new ArgumentNullException("testSettings");
+            }
+            
+            writer.WriteStartElement("TestSettings", TestNamespace);
+            writer.WriteAttributeString("name", testSettings.Name);
+            writer.WriteGuidIfPresent("id", testSettings.Id);
+            // There are more things we could write here.
+            writer.WriteEndElement();
+        }
+    }
+
+    public static class XmlWriterExtensions
+    {
+        public static void WriteGuidIfPresent(this XmlWriter writer, string attributeName, Guid value)
+        {
+            if (value != Guid.Empty)
+            {
+                writer.WriteAttributeString(attributeName, value.ToString());
+            }
+        }
+    }
+
+    public class TestRun
+    {
+        public TestRun()
+        {
+            this.TestDefinitions = new List<TestDefinition>();
+            this.TestLists = new List<TestList>();
+            this.TestEntries = new List<TestEntry>();
+            this.TestResults = new List<TestResult>();
+            this.ResultSummary = new ResultSummary();
+            this.TestTimes = new TestTimes();
+            this.TestSettings = new TestSettings();
+            
+            this.Id = Guid.NewGuid();
+            this.RunUser = Environment.UserDomainName + "\\" + Environment.UserName;
+        }
+
+        public Guid Id { get; set; }
+        public string Name { get; set; }
+        public string RunUser { get; set; }
+        public TestSettings TestSettings { get; set; }
+        public TestTimes TestTimes { get; set; }
+        public ResultSummary ResultSummary { get; set; }
+        public List<TestDefinition> TestDefinitions { get; set; }
+        public List<TestList> TestLists { get; set; }
+        public List<TestEntry> TestEntries { get; set; }
+        public List<TestResult> TestResults { get; set; }
+    }
+
+    public class TestSettings
+    {
+        public Guid Id { get; set; }
+        public string Name { get; set; }
+    }
+
+    public class TestTimes
+    {
+        public DateTime Creation { get; set; }
+        public DateTime Queueing { get; set; }
+        public DateTime Start { get; set; }
+        public DateTime Finish { get; set; }
+    }
+
+    public class ResultSummary
+    {
+        public string Outcome { get; set; }
+        public int Total { get; set; }
+        public int Executed { get; set; }
+        public int Error { get; set; }
+        public int Failed { get; set; }
+        public int Timeout { get; set; }
+        public int Aborted { get; set; }
+        public int Inconclusive { get; set; }
+        public int PassedButRunAborted { get; set; }
+        public int NotRunnable { get; set; }
+        public int NotExecuted { get; set; }
+        public int Disconnected { get; set; }
+        public int Warning { get; set; }
+        public int Passed { get; set; }
+        public int Completed { get; set; }
+        public int InProgress { get; set; }
+        public int Pending { get; set; }
+    }
+
+    public class TestDefinition
+    {
+        public string Name { get; set; }
+        public string Storage { get; set; }
+        public Guid Id { get; set; }
+        public Guid ExecutionId { get; set; }
+    }
+
+    public class TestList
+    {
+        public string Name { get; set; }
+        public Guid Id { get; set; }
+    }
+
+    public class TestEntry
+    {
+        public Guid TestId { get; set; }
+        public Guid ExecutionId { get; set; }
+        public Guid TestListId { get; set; }
+    }
+
+    public class TestResult
+    {
+        public TestResult()
+        {
+            this.DebugTrace = new StringBuilder();
+            this.ErrorMessages = new StringBuilder();
+        }
+        
+        public Guid ExecutionId { get; set; }
+        public Guid TestId { get; set; }
+        public string TestName { get; set; }
+        public string ComputerName { get; set; }
+        public TimeSpan Duration { get { return this.EndTime - this.StartTime; } }
+        public DateTime StartTime { get; set; }
+        public DateTime EndTime { get; set; }
+        public Guid TestType { get; set; }
+        public string Outcome { get; set; }
+        public Guid TestListId { get; set; }
+        public Guid RelativeResultsDirectory { get; set; }
+        public StringBuilder DebugTrace { get; set; }
+        public StringBuilder ErrorMessages { get; set; }
+    }
+
+    class TestRunXmlBodyWriter : BodyWriter
+    {
+        private readonly TestRun run;
+
+        public TestRunXmlBodyWriter(TestRun run)
+            : base(true)
+        {
+            this.run = run;
+        }
+
+        protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
+        {
+            TestResultWriter.WriteTestRun(this.run, writer);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/common/TestSynchronizerClient.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/TestSynchronizerClient.js b/datajs/tests/common/TestSynchronizerClient.js
new file mode 100644
index 0000000..c758413
--- /dev/null
+++ b/datajs/tests/common/TestSynchronizerClient.js
@@ -0,0 +1,218 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// TestSynchronizer Client
+// Use to log assert pass/fails and notify mstest a test has completed execution
+
+(function (window, undefined) {
+    var testRunId = "";
+    var serviceRoot = "./common/TestLogger.svc/";
+    var recording = null;
+    var recordingLength = 0;
+    var maxStringLength = 8192;
+    var maxPostLength = 2097152;
+
+    var callTestSynchronizer = function (methodName, parameterUrl) {
+        /// <summary>Invokes a function on the test synchronizer.</summary>
+        /// <param name="partialUrl" type="String" optional="true">URL to work with.</param>
+        /// <returns type="String">A response from the server, possibly null.</returns>
+        /// <remarks>
+        /// If the recording variable is assigned, then the call is logged
+        /// but nothing is invoked.
+        /// </remarks>
+
+        var partialUrl;
+        if (testRunId) {
+            partialUrl = methodName + "?testRunId=" + testRunId + "&" + parameterUrl;
+        }
+        else {
+            partialUrl = methodName + "?" + parameterUrl;
+        }
+
+        var url = serviceRoot + partialUrl;
+
+        if (recording) {
+            if (url.length > maxStringLength) {
+                url = url.substr(0, maxStringLength);
+            }
+
+            recordingLength += url.length;
+            if (recordingLength > maxPostLength) {
+                submitRecording();
+                recording = [];
+                recordingLength = url.length;
+            }
+
+            recording.push(url);
+            return null;
+        }
+
+        var xhr;
+        if (window.XMLHttpRequest) {
+            xhr = new window.XMLHttpRequest();
+        } else {
+            xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0");
+        }
+
+        xhr.open("GET", url, false);
+        xhr.send();
+        return xhr.responseText;
+    };
+
+    var getLogPrefix = function (result) {
+        /// <summary>Returns the log prefix for a given result</summary>
+        /// <param name="result" type="Boolean">Whether the result is pass or fail. If null, the log line is assumed to be diagnostic</param>
+        return "[" + getShortDate() + "] " + (result === true ? "[PASS] " : (result === false ? "[FAIL] " : ""));
+    };
+
+    var getShortDate = function () {
+        /// <summary>Returns the current date and time formatted as "yyyy-mm-dd hh:mm:ss.nnn".</summary>
+        var padToLength = function (number, length) {
+            var result = number + "";
+            var lengthDiff = length - result.length;
+            for (var i = 0; i < lengthDiff; i++) {
+                result = "0" + result;
+            }
+
+            return result;
+        }
+
+        var date = new Date();
+        var day = padToLength(date.getDate(), 2);
+        var month = padToLength(date.getMonth() + 1, 2);
+        var year = date.getFullYear();
+
+        var hours = padToLength(date.getHours(), 2);
+        var minutes = padToLength(date.getMinutes(), 2);
+        var seconds = padToLength(date.getSeconds(), 2);
+        var milliseconds = padToLength(date.getMilliseconds(), 3);
+
+        return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds + "." + milliseconds;
+    };
+
+    var submitRecording = function () {
+        var body = { urls: recording };
+        postToUrl("LogBatch", body);
+    };
+
+    var postToUrl = function (methodName, body) {
+        /// <summary>POSTs body to the designated methodName.</summary>
+        var xhr;
+        if (window.XMLHttpRequest) {
+            xhr = new window.XMLHttpRequest();
+        } else {
+            xhr = new ActiveXObject("Msxml2.XMLHTTP.6.0");
+        }
+
+        var url = serviceRoot + methodName;
+        xhr.open("POST", url, false);
+        xhr.setRequestHeader("Content-Type", "application/json");
+        xhr.send(window.JSON.stringify(body));
+        if (xhr.status < 200 || xhr.status > 299) {
+            throw { message: "Unable to POST to url.\r\n" + xhr.responseText };
+        }
+
+        return xhr.responseText;
+    }
+
+    function LogAssert(result, message, name, expected, actual) {
+        var parameterUrl = "pass=" + result + "&message=" + encodeURIComponent(message) + "&name=" + encodeURIComponent(name);
+
+        if (!result) {
+            parameterUrl += "&actual=" + encodeURIComponent(actual) + "&expected=" + encodeURIComponent(expected);
+        }
+
+        callTestSynchronizer("LogAssert", parameterUrl);
+    }
+
+    function LogTestStart(name) {
+        callTestSynchronizer("LogTestStart", "name=" + encodeURIComponent(name) + "&startTime=" + encodeURIComponent(getShortDate()));
+    }
+
+    function LogTestDone(name, failures, total) {
+        callTestSynchronizer("LogTestDone", "name=" + encodeURIComponent(name) + "&failures=" + failures + "&total=" + total + "&endTime=" + encodeURIComponent(getShortDate()));
+    }
+
+    function TestCompleted(failures, total) {
+        return callTestSynchronizer("TestCompleted", "failures=" + failures + "&total=" + total);
+    }
+
+    var extractTestRunId = function () {
+        /// <summary>Extracts the testRunId value from the window query string.</summary>
+        /// <returns type="String">testRunId, possibly empty.</returns>
+        var i, len;
+        var uri = window.location.search;
+        if (uri) {
+            var parameters = uri.split("&");
+            for (i = 0, len = parameters.length; i < len; i++) {
+                var index = parameters[i].indexOf("testRunId=");
+                if (index >= 0) {
+                    return parameters[i].substring(index + "testRunId=".length);
+                }
+            }
+        }
+
+        return "";
+    };
+
+    var init = function (qunit) {
+        /// <summary>Initializes the test logger synchronizer.</summary>
+        /// <param name="qunit">Unit testing to hook into.</param>
+        /// <remarks>If there is no testRunId present, the QUnit functions are left as they are.</remarks>
+        var logToConsole = function (context) {
+            if (window.console && window.console.log) {
+                window.console.log(context.result + ' :: ' + context.message);
+            }
+        };
+
+        testRunId = extractTestRunId();
+        if (!testRunId) {
+            qunit.log = logToConsole;
+        } else {
+            recording = [];
+            qunit.log = function (context) {
+                logToConsole(context);
+
+                var name = qunit.config.current.testName;
+                if (!(context.actual && context.expected)) {
+                    context.actual = context.result;
+                    context.expected = true;
+                }
+                LogAssert(context.result, getLogPrefix(context.result) + context.message, name, window.JSON.stringify(context.expected), window.JSON.stringify(context.actual));
+            };
+
+            qunit.testStart = function (context) {
+                LogTestStart(context.name);
+            };
+
+            qunit.testDone = function (context) {
+                LogTestDone(context.name, context.failed, context.total);
+            }
+
+            qunit.done = function (context) {
+                submitRecording();
+                recording = null;
+
+                var nextUrl = TestCompleted(context.failed, context.total);
+                nextUrl = JSON.parse(nextUrl).d;
+                if (nextUrl) {
+                    window.location.href = nextUrl;
+                }
+            }
+        }
+    };
+
+    window.TestSynchronizer = {
+        init: init
+    };
+})(window);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/common/cacheoracle.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/cacheoracle.js b/datajs/tests/common/cacheoracle.js
new file mode 100644
index 0000000..59a695b
--- /dev/null
+++ b/datajs/tests/common/cacheoracle.js
@@ -0,0 +1,119 @@
+// Copyright (c) Microsoft.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// CacheOracle.js
+// This object verifies the operation of the cache.
+// Internally it maintains a simple model of the cache implemented using a lookup array of the expected cached pages.
+
+(function (window, undefined) {
+
+    var CacheOracle = function (baseUri, pageSize, total) {
+        /// <summary>Creates a new CacheOracle</summary>
+        /// <param name="baseUri" type="String">The base URI of the collection</param>
+        /// <param name="pageSize" type="Integer">The page size used in the cache</param>
+        /// <param name="total" type="Integer">The total number of items in the collection</param>
+        this.baseUri = baseUri;
+        this.pageSize = pageSize;
+        this.total = total;
+
+        this.cachedPages = [];
+    };
+
+    CacheOracle.mechanisms = {
+        memory: "memory",
+        indexeddb: "indexeddb",
+        dom: "dom",
+        best: "best"
+    };
+
+    CacheOracle.isMechanismAvailable = function (mechanism) {
+        /// <summary>Determines if the specified local storage mechanism is available</summary>
+        /// <param name="mechanism">The name of the mechanism</param>
+        /// <returns>Whether the mechanism is available</returns>
+        switch (mechanism) {
+            case CacheOracle.mechanisms.indexeddb:
+                if (window.mozIndexedDB) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+                break;
+            case CacheOracle.mechanisms.dom:
+                if (window.localStorage) {
+                    return true;
+                }
+                else {
+                    return false;
+                }
+                break;
+            case CacheOracle.mechanisms.memory:
+            case CacheOracle.mechanisms.best:
+            case undefined:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    CacheOracle.prototype.clear = function () {
+        /// <summary>Clears the cache in the oracle</summary>
+        this.cachedPages = [];
+    }
+
+    CacheOracle.prototype.verifyRequests = function (requests, responses, index, count, description) {
+        /// <summary>Verifies the HTTP requests for a single data request, and updates the oracle with cached pages</summary>
+        /// <param name="requests" type="Array">The sequence of request objects (from OData.defaultHttpClient)</param>
+        /// <param name="responses" type="Array">The sequence of response objects (from OData.defaultHttpClient)</param>
+        /// <param name="index" type="Integer">The starting index of the read</param>
+        /// <param name="count" type="Integer">The count of items in the read</param>
+        /// <param name="description" type="String">The description of the requests being verified</param>
+        var that = this;
+
+        var pageIndex = function (index) {
+            /// <summary>Returns the page index that the given item index belongs to</summary>
+            /// <param name="index" type="Integer">The item index</param>
+            /// <returns>The page index</returns>
+            return Math.floor(index / that.pageSize);
+        }
+
+        var minPage = pageIndex(index);
+        var maxPage = Math.min(pageIndex(index + count - 1), pageIndex(that.total));
+
+        // Workaround for Bug 2055: Calling readRange with count = 0 still fires a single HTTP request
+        maxPage = Math.max(minPage, maxPage);
+
+        var expectedUris = [];
+        var responseIndex = 0;
+        for (var page = minPage; page <= maxPage; page++) {
+            if (!this.cachedPages[page]) {
+                expectedUris.push(that.baseUri + "?$skip=" + page * that.pageSize + "&$top=" + (that.pageSize));
+
+                // Handle server paging skipToken requests
+                while (responses[responseIndex] && responses[responseIndex].data && responses[responseIndex].data.__next) {
+                    expectedUris.push(responses[responseIndex].data.__next);
+                    responseIndex++;
+                }
+
+                responseIndex++;
+                this.cachedPages[page] = true;
+            }
+        }
+
+        var actualUris = $.map(requests, function (r) { return r.requestUri; });
+        djstest.assertAreEqualDeep(actualUris, expectedUris, description);
+    };
+
+    window.CacheOracle = CacheOracle;
+
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/common/djstest.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/djstest.js b/datajs/tests/common/djstest.js
new file mode 100644
index 0000000..c7b2de5
--- /dev/null
+++ b/datajs/tests/common/djstest.js
@@ -0,0 +1,400 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+(function (window, undefined) {
+    var djstest = {};
+
+    window.djstest = djstest;
+
+    djstest.indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB;
+
+    djstest.cleanStoreOnIndexedDb = function (storeObjects, done) {
+        /// <summary>Cleans all the test data saved in the IndexedDb database.</summary>
+        /// <param name="storeNames" type="Array">Array of store objects with a property that is the name of the store</param>
+        /// <param name="done" type="Function">Callback function</param>
+
+        var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {};
+
+        var deleteObjectStores = function (db) {
+            $.each(db.objectStoreNames, function (_, storeName) {
+                db.deleteObjectStore(storeName);
+            });
+        };
+
+        if (djstest.indexedDB) {
+            var job = new djstest.Job();
+            $.each(storeObjects, function (_, storeObject) {
+                job.queue((function (storeObject) {
+                    return function (success, fail) {
+                        var dbname = "_datajs_" + storeObject.name;
+                        var request = djstest.indexedDB.open(dbname);
+                        request.onsuccess = function (event) {
+                            var db = request.result;
+
+                            if ("setVersion" in db) {
+                                var versionRequest = db.setVersion("0.1");
+                                versionRequest.onsuccess = function (event) {
+                                    var transaction = versionRequest.transaction;
+                                    transaction.oncomplete = function () {
+                                        db.close();
+                                        success();
+                                    }
+                                    deleteObjectStores(db);
+                                };
+                                versionRequest.onerror = function (e) {
+                                    djstest.fail("Error on cleanup - code: " + e.code + " name: " + e.name + "message: " + message);
+                                    fail();
+                                };
+                                return;
+                            }
+
+                            // new api cleanup
+                            db.close();
+                            var deleteRequest = djstest.indexedDB.deleteDatabase(dbname);
+                            deleteRequest.onsuccess = function (event) {
+                                djstest.log("djstest indexeddb cleanup - deleted database " + dbname);
+                                success();
+                            };
+                            deleteRequest.onerror = function (e) {
+                                djstest.fail("djstest indexeddb cleanup - error deleting database " + dbname);
+                                fail();
+                            };
+                            djstest.log("djstest indexeddb cleanup - requested deletion of database " + dbname);
+                        };
+
+                        request.onerror = function (e) {
+                            djstest.fail(e.code + ": " + e.message);
+                        };
+                    };
+                })(storeObject));
+            });
+        }
+
+        if (job) {
+            job.run(function (succeeded) {
+                if (!succeeded) {
+                    djstest.fail("cleanup job failed");
+                }
+                done();
+            });
+        }
+        else {
+            done();
+        }
+    };
+
+    djstest.Job = function () {
+        /// <summary>Constructs a Job object that allows for enqueuing and synchronizing the execution of functions.</summary>
+        /// <returns type="Object">Job object</returns>
+        var currentTask = -1;
+        var tasks = [];
+
+        var failedTasks = 0;
+
+        this.queue = function (fn) {
+            /// <summary>Adds a function to the job queue regardless if the queue is already executing or not.</summary>
+            /// <param name="fn" type="Function">Function to execute.</param>
+            tasks.push(fn);
+        };
+
+        this.queueNext = function (fn) {
+            /// <summary>Adds a function to the front of the job queue regardless if the queue is already executing or not.</summary>
+            /// <param name="fn" type="Function">Function to execute.</param>
+            if (currentTask < 0) {
+                tasks.unshift(fn);
+            } else {
+                tasks.splice(currentTask + 1, 0, fn);
+            }
+        };
+
+        this.run = function (done) {
+            /// <summary>Starts the execution of this job.</summary>
+            /// <param name="done" type="Function">Callback invoked when the job has finished executing all of its enqueued tasks.</param>
+            /// <remarks>
+            /// This method does nothing if called on a unit of work that is already executing.
+            /// </remarks>
+
+            if (currentTask >= 0) {
+                return;
+            }
+
+            if (tasks.length === 0) {
+                done(true);
+                return;
+            }
+
+            var makeTaskDoneCallBack = function (failed) {
+                return function () {
+                    // Track the failed task and continue the execution of the job. 
+                    if (failed) {
+                        failedTasks++;
+                    }
+                    currentTask++;
+                    if (currentTask === tasks.length) {
+                        done(failedTasks === 0);
+                    } else {
+                        runNextTask();
+                    }
+                };
+            };
+
+            var runNextTask = function () {
+                /// <summary>Executes the next function in the queue.</summary>
+                defer(function () {
+                    try {
+                        tasks[currentTask](makeTaskDoneCallBack(false), makeTaskDoneCallBack(true));
+                    } catch (e) {
+                        makeTaskDoneCallBack(true)();
+                    }
+                });
+            };
+
+            currentTask = 0;
+            runNextTask();
+        };
+    };
+
+    var defer = function (fn) {
+        /// <summary>Defers the execution of an arbitrary function that takes no parameters.</summary>
+        /// <param name="fn" type="Function">Function to schedule for later execution.</param>
+        setTimeout(fn, 0);
+    }
+
+    var exposeDateValues = function (data) {
+        /// <summary>Exposes date values for Date objects to facilitate debugging</summary>
+        /// <param name="data" type="Object">The object to operate on</param>
+        if (typeof data === "object") {
+            if (data instanceof Date) {
+                data["__date__"] = data.toUTCString();
+            }
+            else {
+                for (var prop in data) {
+                    exposeDateValues(data[prop]);
+                }
+            }
+        }
+
+        return data;
+    }
+
+    var extractFunctionName = function (text) {
+        /// <summary>Determines the name of a function.</summary>
+        /// <param name="text" type="String">Function text.</param>
+        /// <returns type="String">The name of the function from text if found; the original text otherwise.</returns>
+
+        var index = text.indexOf("function ");
+        if (index < 0) {
+            return text;
+        }
+
+        var nameStart = index + "function ".length;
+        var parensIndex = text.indexOf("(", nameStart);
+        if (parensIndex < 0) {
+            return text;
+        }
+
+        var result = text.substr(nameStart, parensIndex - nameStart);
+        if (result.indexOf("test") === 0) {
+            result = result.substr("test".length);
+        }
+
+        return result;
+    };
+
+    var removeMetadata = function (data) {
+        /// <summary>Removes metadata annotations from the specified object.</summary>
+        /// <param name="data">Object to remove metadata from; possibly null.</param>
+
+        if (typeof data === "object" && data !== null) {
+            delete data["__metadata"];
+            for (prop in data) {
+                removeMetadata(data[prop]);
+            }
+        }
+    };
+
+    djstest.addTest = function (fn, name, arg, timeout) {
+        if (!name) {
+            name = extractFunctionName(fn.toString());
+        }
+
+        test(name, function () {
+            if (!timeout) {
+                timeout = 20000;
+            }
+
+            QUnit.config.testTimeout = timeout;
+            QUnit.stop();
+            fn.call(this, arg);
+        });
+    }
+
+    djstest.assert = function (test, message) {
+        /// <summary>Asserts that a condition is true.</summary>
+        /// <param name="test" type="Boolean">Condition to test.</param>
+        /// <param name="message" type="String">Text message for condition being tested.</param>
+        QUnit.ok(test, message);
+    };
+
+    djstest.assertAreEqual = function (actual, expected, message) {
+        /// <summary>Asserts that the values of the expected and actualobjects are equal.</summary>
+        QUnit.equal(actual, expected, message);
+    };
+
+    djstest.assertAreEqualDeep = function (actual, expected, message) {
+        /// <summary>Asserts that the actual and expected objects are the same.</summary>
+        QUnit.deepEqual(exposeDateValues(actual), exposeDateValues(expected), message);
+    };
+
+    djstest.assertWithoutMetadata = function (actual, expected, message) {
+        removeMetadata(actual)
+        removeMetadata(expected);
+        djstest.assertAreEqualDeep(actual, expected, message);
+    };
+
+    djstest.asyncDo = function (asyncActions, done) {
+        /// <summary>Calls each async action in asyncActions, passing each action a function which keeps a count and
+        /// calls the passed done function when all async actions complete.</summary>
+        /// <param name="asyncActions" type="Array">Array of asynchronous actions to be executed, 
+        /// each taking a single parameter - the callback function to call when the action is done.</param>
+        /// <param name="done" type="Function">Function to be executed in the last async action to complete.</param>
+        var count = 0;
+        var doneOne = function () {
+            count++;
+            if (count >= asyncActions.length) {
+                done();
+            }
+        };
+
+        if (asyncActions.length > 0) {
+            $.each(asyncActions, function (_, asyncAction) {
+                asyncAction(doneOne);
+            });
+        } else {
+            done();
+        }
+    }
+
+    djstest.clone = function (object) {
+        /// <summary>Makes a deep copy of an object.</summary>
+        return $.extend(true, {}, object);
+    };
+
+    djstest.destroyCacheAndDone = function (cache) {
+        /// <summary>Destroys the cache and then completes the test</summary>
+        /// <param name="cache">The cache to destroy</param>
+        cache.clear().then(function () {
+            djstest.done();
+        }, function (err) {
+            djstest.fail("Failed to destroy cache: " + djstest.toString(err));
+            djstest.done();
+        });
+    };
+
+    djstest.done = function () {
+        /// <summary>Indicates that the currently running test has finished.</summary>
+        QUnit.start();
+    };
+
+    djstest.expectException = function (testFunction, message) {
+        /// <summary>Test passes if and only if an exception is thrown.</summary>
+        try {
+            testFunction();
+            djstest.fail("Expected exception but function succeeded: " + " " + message);
+        }
+        catch (e) {
+            // Swallow exception.
+            djstest.pass("Thrown exception expected");
+        }
+    };
+
+    djstest.assertsExpected = function (asserts) {
+        /// <summary>Indicates the expected number of asserts, fails test if number is not met.</summary>
+        /// <param name="asserts" type="Number">Number of asserts expected in test.</param>
+        expect(asserts);
+    }
+
+    djstest.fail = function (message) {
+        /// <summary>Marks the current test as failed.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        QUnit.ok(false, message);
+    };
+
+    djstest.failAndDoneCallback = function (message, cleanupCallback) {
+        /// <summary>Returns a function that when invoked will fail this test and be done with it.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        /// <param name="cleanupCallback" type="Function" optional="true">Optional cleanup function in case of failure.</param>
+        /// <returns type="Function">A new function.</returns>
+
+        return function (err) {
+            message = "" + message + (err) ? window.JSON.stringify(err) : "";
+            djstest.fail(message);
+            if (cleanupCallback) {
+                try {
+                    cleanupCallback();
+                } catch (e) {
+                    djstest.fail("error during cleanupCallback: " + window.JSON.stringify(e));
+                }
+            }
+
+            djstest.done();
+        };
+    };
+
+    djstest.log = function (message) {
+        /// <summary>Logs a test message.</summary>
+        /// <param name="message" type="String">Test message.</param>
+        var context = { result: true, actual: true, expected: true, message: message };
+        QUnit.log(context);
+    };
+
+    djstest.pass = function (message) {
+        /// <summary>Marks the current test as failed.</summary>
+        /// <param name="message" type="String">Failure message.</param>
+        QUnit.ok(true, message);
+    };
+
+    djstest.toString = function (obj) {
+        /// <summary>Dumps the object as a string</summary>
+        /// <param name="obj" type="Object">Object to dump</param>
+        return QUnit.jsDump.parse(obj);
+    };
+
+    djstest.wait = function (fn) {
+        /// <summary>Executes the function, pausing test execution until the callback is called</summary>
+        /// <param name="fn" type="Function">Function to execute; takes one parameter which is the callback</param>
+        /// <remarks>This function is typically used in asynchronous setup/teardown methods</remarks>
+        QUnit.stop();
+        fn(function () {
+            QUnit.start();
+        });
+    };
+
+    // Disable caching to ensure that every test-related AJAX request is actually being sent,
+    // and set up a default error handler
+    $.ajaxSetup({
+        cache: false,
+        error: function (jqXHR, textStatus, errorThrown) {
+            // Work around bug in IE-Mobile on Windows Phone 7
+            if (jqXHR.status !== 1223) {
+                var err = {
+                    status: jqXHR.status,
+                    statusText: jqXHR.statusText,
+                    responseText: jqXHR.responseText
+                };
+                djstest.fail("AJAX request failed with: " + djstest.toString(err));
+            }
+            djstest.done();
+        }
+    });
+})(window);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/common/observablehttpclient.js
----------------------------------------------------------------------
diff --git a/datajs/tests/common/observablehttpclient.js b/datajs/tests/common/observablehttpclient.js
new file mode 100644
index 0000000..c624814
--- /dev/null
+++ b/datajs/tests/common/observablehttpclient.js
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// ObservableHttpClient.js
+// This object extends OData's default httpClient by supporting request and response recording sessions, and firing a custom
+// JQuery event for each request/response.
+//
+// The events fired by this object are:
+//      request: Before a request is made
+//      success: Before the primary success handler is called
+//
+// To bind to an event, JQuery event attachers can be used on the object, e.g.
+//      $(observableHttpClient).bind("request", function (request) { ... });
+//
+// To begin a new recording session, use:
+//      var session = observableHttpClient.newSession();
+//
+// Requests and responses are then recorded in session.requests and session.responses. Session can be ended by session.end().
+// Multiple simultaneous sessions are supported.
+
+(function (window, undefined) {
+
+    var ObservableHttpClient = function (provider) {
+        this.provider = provider ? provider : OData.defaultHttpClient;
+    };
+
+    ObservableHttpClient.prototype.newSession = function () {
+        return new Session(this);
+    };
+
+    ObservableHttpClient.prototype.request = function (request, success, error) {
+        var that = this;
+
+        $(this).triggerHandler("request", request);
+        return this.provider.request(request, function (response) {
+            $(that).triggerHandler("success", response);
+            success(response);
+        }, error);
+    };
+
+
+    var Session = function (client) {
+        var that = this;
+
+        this.client = client;
+        this.clear();
+
+        this.requestHandler = function (event, request) { that.requests.push(request); };
+        $(client).bind("request", this.requestHandler);
+
+        this.successHandler = function (event, response) { that.responses.push(response); };
+        $(client).bind("success", this.successHandler);
+    };
+
+    Session.prototype.clear = function () {
+        this.requests = [];
+        this.responses = [];
+    }
+
+    Session.prototype.end = function () {
+        $(this.client).unbind("request", this.requestHandler);
+        $(this.client).unbind("success", this.successHandler);
+    };
+
+    window.ObservableHttpClient = ObservableHttpClient;
+
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/lib/Formidable Giants.jpg
----------------------------------------------------------------------
diff --git a/datajs/tests/lib/Formidable Giants.jpg b/datajs/tests/lib/Formidable Giants.jpg
new file mode 100644
index 0000000..b9d60e5
Binary files /dev/null and b/datajs/tests/lib/Formidable Giants.jpg differ

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/lib/Peaceful Glen.jpg
----------------------------------------------------------------------
diff --git a/datajs/tests/lib/Peaceful Glen.jpg b/datajs/tests/lib/Peaceful Glen.jpg
new file mode 100644
index 0000000..c81d955
Binary files /dev/null and b/datajs/tests/lib/Peaceful Glen.jpg differ

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/lib/Stratified Whispers.jpg
----------------------------------------------------------------------
diff --git a/datajs/tests/lib/Stratified Whispers.jpg b/datajs/tests/lib/Stratified Whispers.jpg
new file mode 100644
index 0000000..09a72a2
Binary files /dev/null and b/datajs/tests/lib/Stratified Whispers.jpg differ

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/lib/Sultry Terminus.jpg
----------------------------------------------------------------------
diff --git a/datajs/tests/lib/Sultry Terminus.jpg b/datajs/tests/lib/Sultry Terminus.jpg
new file mode 100644
index 0000000..f5c87bc
Binary files /dev/null and b/datajs/tests/lib/Sultry Terminus.jpg differ


[11/11] git commit: [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
[OLINGO-238] Build infrastructure for datajs I


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/commit/e29bbd8f
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/tree/e29bbd8f
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/diff/e29bbd8f

Branch: refs/heads/OLINGO-238
Commit: e29bbd8f017de603d681ef4258af0ea5a1d66aea
Parents: a84a42d
Author: Sven Kobler <sv...@sap.com>
Authored: Mon Apr 7 15:34:02 2014 +0200
Committer: Sven Kobler <sv...@sap.com>
Committed: Mon Apr 7 15:34:02 2014 +0200

----------------------------------------------------------------------
 datajs/.gitattributes                         |    2 +
 datajs/.gitignore                             |    2 +
 datajs/Gruntfile.js                           |   77 +
 datajs/package.json                           |   17 +
 datajs/src/banner.txt                         |   12 +
 datajs/src/cache-source.js                    |  183 +
 datajs/src/cache.js                           | 1349 ++++++
 datajs/src/datajs.js                          |   61 +
 datajs/src/deferred.js                        |  179 +
 datajs/src/odata-atom.js                      | 1411 ++++++
 datajs/src/odata-batch.js                     |  393 ++
 datajs/src/odata-gml.js                       |  831 ++++
 datajs/src/odata-handler.js                   |  276 ++
 datajs/src/odata-json-light.js                | 1391 ++++++
 datajs/src/odata-json.js                      |  379 ++
 datajs/src/odata-metadata.js                  |  500 ++
 datajs/src/odata-net.js                       |  330 ++
 datajs/src/odata-utils.js                     | 1117 +++++
 datajs/src/odata-xml.js                       |  850 ++++
 datajs/src/odata.js                           |  158 +
 datajs/src/store-dom.js                       |  320 ++
 datajs/src/store-indexeddb.js                 |  417 ++
 datajs/src/store-memory.js                    |  231 +
 datajs/src/store.js                           |   61 +
 datajs/src/utils.js                           |  490 ++
 datajs/src/xml.js                             |  824 ++++
 datajs/tests/common/ODataReadOracle.js        |  275 ++
 datajs/tests/common/ODataReadOracle.svc       |  182 +
 datajs/tests/common/TestLogger.svc            |  846 ++++
 datajs/tests/common/TestSynchronizerClient.js |  218 +
 datajs/tests/common/cacheoracle.js            |  119 +
 datajs/tests/common/djstest.js                |  400 ++
 datajs/tests/common/observablehttpclient.js   |   77 +
 datajs/tests/lib/Formidable Giants.jpg        |  Bin 0 -> 2205432 bytes
 datajs/tests/lib/Peaceful Glen.jpg            |  Bin 0 -> 1829458 bytes
 datajs/tests/lib/Stratified Whispers.jpg      |  Bin 0 -> 2281332 bytes
 datajs/tests/lib/Sultry Terminus.jpg          |  Bin 0 -> 2496126 bytes
 datajs/tests/odata-atom-tests.js              | 4756 ++++++++++++++++++++
 datajs/tests/odata-qunit-tests.html           |   92 +
 39 files changed, 18826 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/.gitattributes
----------------------------------------------------------------------
diff --git a/datajs/.gitattributes b/datajs/.gitattributes
new file mode 100644
index 0000000..16dec8d
--- /dev/null
+++ b/datajs/.gitattributes
@@ -0,0 +1,2 @@
+# Set default behaviour, in case users don't have core.autocrlf set.
+* text=auto
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/.gitignore
----------------------------------------------------------------------
diff --git a/datajs/.gitignore b/datajs/.gitignore
new file mode 100644
index 0000000..b0a5c34
--- /dev/null
+++ b/datajs/.gitignore
@@ -0,0 +1,2 @@
+/node_modules/
+/dist/

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/Gruntfile.js
----------------------------------------------------------------------
diff --git a/datajs/Gruntfile.js b/datajs/Gruntfile.js
new file mode 100644
index 0000000..621db4a
--- /dev/null
+++ b/datajs/Gruntfile.js
@@ -0,0 +1,77 @@
+/*global module:false*/
+module.exports = function(grunt) {
+
+  // Project configuration.
+  grunt.initConfig({
+    pkg: grunt.file.readJSON('package.json'),
+    banner: grunt.file.read('src/banner.txt'),
+    concat: {
+      options: {
+        banner: '<%= banner %>',
+        // strips comments
+        stripBanners: true
+      },
+      dist: {
+        src:  ['src/datajs.js', //order matters, so no whildcards
+               'src/utils.js',
+               'src/xml.js',
+               'src/deferred.js',
+               'src/odata-utils.js',
+               'src/odata-net.js',
+               'src/odata-handler.js',
+               'src/odata-gml.js',
+               'src/odata-xml.js',
+               'src/odata-atom.js',
+               'src/odata-metadata.js',
+               'src/odata-json-light.js',
+               'src/odata-json.js',
+               'src/odata-batch.js',
+               'src/odata.js',
+               'src/store-dom.js',
+               'src/store-indexeddb.js',
+               'src/store-memory.js',
+               'src/store.js',
+               'src/cache-source.js',
+               'src/cache.js'],
+        dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js'
+      }
+    },
+    uglify: {
+      options: {
+        banner: '<%= banner %>',
+        sourceMap : 'dist/<%= pkg.name %>-<%= pkg.version %>.map',
+      },
+      dist: {
+        src: '<%= concat.dist.dest %>',
+        dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.min.js'
+      }
+    },
+    qunit: {
+      all: ['tests/**/*.html'],
+      options: {
+        timeout: 300000
+      }
+    }
+  });
+
+  // These plugins provide necessary tasks.
+  grunt.loadNpmTasks('grunt-contrib-uglify');
+  grunt.loadNpmTasks('grunt-contrib-qunit');
+  grunt.loadNpmTasks('grunt-contrib-concat');
+
+  // Default task.
+  grunt.registerTask('default', ['concat','uglify']);
+  grunt.registerTask('test', ['qunit']);
+  /*
+  grunt.registerTask('serv', function() {
+                                grunt.task.run([
+                                  "configureProxies:server",
+                                  "connect:server",
+                                  "open"
+                                ]);
+                              }
+    );
+  */
+
+
+};

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/package.json
----------------------------------------------------------------------
diff --git a/datajs/package.json b/datajs/package.json
new file mode 100644
index 0000000..e4aa286
--- /dev/null
+++ b/datajs/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "datajs",
+  "version": "0.0.0",
+  "title": "datajs library",
+  "engines": {
+    "node": ">= 0.10.0"
+  },
+  "devDependencies": {
+    "grunt": "^0.4.4",
+    "grunt-contrib-concat": "~0.3.0",
+    "grunt-contrib-qunit": "^0.4.0",
+    "grunt-contrib-uglify": "~0.2.7",
+    "grunt-contrib-watch": "~0.5.3",
+    "grunt-open": "^0.2.3",
+    "qunitjs": "^1.14.0"
+  }
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/banner.txt
----------------------------------------------------------------------
diff --git a/datajs/src/banner.txt b/datajs/src/banner.txt
new file mode 100644
index 0000000..1aefb0f
--- /dev/null
+++ b/datajs/src/banner.txt
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/cache-source.js
----------------------------------------------------------------------
diff --git a/datajs/src/cache-source.js b/datajs/src/cache-source.js
new file mode 100644
index 0000000..3616b84
--- /dev/null
+++ b/datajs/src/cache-source.js
@@ -0,0 +1,183 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// cache-source.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    var parseInt10 = datajs.parseInt10;
+    var normalizeURICase = datajs.normalizeURICase;
+
+    // CONTENT START
+
+    var appendQueryOption = function (uri, queryOption) {
+        /// <summary>Appends the specified escaped query option to the specified URI.</summary>
+        /// <param name="uri" type="String">URI to append option to.</param>
+        /// <param name="queryOption" type="String">Escaped query option to append.</param>
+        var separator = (uri.indexOf("?") >= 0) ? "&" : "?";
+        return uri + separator + queryOption;
+    };
+
+    var appendSegment = function (uri, segment) {
+        /// <summary>Appends the specified segment to the given URI.</summary>
+        /// <param name="uri" type="String">URI to append a segment to.</param>
+        /// <param name="segment" type="String">Segment to append.</param>
+        /// <returns type="String">The original URI with a new segment appended.</returns>
+
+        var index = uri.indexOf("?");
+        var queryPortion = "";
+        if (index >= 0) {
+            queryPortion = uri.substr(index);
+            uri = uri.substr(0, index);
+        }
+
+        if (uri[uri.length - 1] !== "/") {
+            uri += "/";
+        }
+        return uri + segment + queryPortion;
+    };
+
+    var buildODataRequest = function (uri, options) {
+        /// <summary>Builds a request object to GET the specified URI.</summary>
+        /// <param name="uri" type="String">URI for request.</param>
+        /// <param name="options" type="Object">Additional options.</param>
+
+        return {
+            method: "GET",
+            requestUri: uri,
+            user: options.user,
+            password: options.password,
+            enableJsonpCallback: options.enableJsonpCallback,
+            callbackParameterName: options.callbackParameterName,
+            formatQueryString: options.formatQueryString
+        };
+    };
+
+    var findQueryOptionStart = function (uri, name) {
+        /// <summary>Finds the index where the value of a query option starts.</summary>
+        /// <param name="uri" type="String">URI to search in.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Number">The index where the query option starts.</returns>
+
+        var result = -1;
+        var queryIndex = uri.indexOf("?");
+        if (queryIndex !== -1) {
+            var start = uri.indexOf("?" + name + "=", queryIndex);
+            if (start === -1) {
+                start = uri.indexOf("&" + name + "=", queryIndex);
+            }
+            if (start !== -1) {
+                result = start + name.length + 2;
+            }
+        }
+        return result;
+    };
+
+    var queryForData = function (uri, options, success, error) {
+        /// <summary>Gets data from an OData service.</summary>
+        /// <param name="uri" type="String">URI to the OData service.</param>
+        /// <param name="options" type="Object">Object with additional well-known request options.</param>
+        /// <param name="success" type="Function">Success callback.</param>
+        /// <param name="error" type="Function">Error callback.</param>
+        /// <returns type="Object">Object with an abort method.</returns>
+
+        var request = queryForDataInternal(uri, options, [], success, error);
+        return request;
+    };
+
+    var queryForDataInternal = function (uri, options, data, success, error) {
+        /// <summary>Gets data from an OData service taking into consideration server side paging.</summary>
+        /// <param name="uri" type="String">URI to the OData service.</param>
+        /// <param name="options" type="Object">Object with additional well-known request options.</param>
+        /// <param name="data" type="Array">Array that stores the data provided by the OData service.</param>
+        /// <param name="success" type="Function">Success callback.</param>
+        /// <param name="error" type="Function">Error callback.</param>
+        /// <returns type="Object">Object with an abort method.</returns>
+
+        var request = buildODataRequest(uri, options);
+        var currentRequest = odata.request(request, function (newData) {
+            var next = newData.__next;
+            var results = newData.results;
+
+            data = data.concat(results);
+
+            if (next) {
+                currentRequest = queryForDataInternal(next, options, data, success, error);
+            } else {
+                success(data);
+            }
+        }, error, undefined, options.httpClient, options.metadata);
+
+        return {
+            abort: function () {
+                currentRequest.abort();
+            }
+        };
+    };
+
+    var ODataCacheSource = function (options) {
+        /// <summary>Creates a data cache source object for requesting data from an OData service.</summary>
+        /// <param name="options">Options for the cache data source.</param>
+        /// <returns type="ODataCacheSource">A new data cache source instance.</returns>
+
+        var that = this;
+        var uri = options.source;
+        
+        that.identifier = normalizeURICase(encodeURI(decodeURI(uri)));
+        that.options = options;
+
+        that.count = function (success, error) {
+            /// <summary>Gets the number of items in the collection.</summary>
+            /// <param name="success" type="Function">Success callback with the item count.</param>
+            /// <param name="error" type="Function">Error callback.</param>
+            /// <returns type="Object">Request object with an abort method./<param>
+
+            var options = that.options;
+            return odata.request(
+                buildODataRequest(appendSegment(uri, "$count"), options),
+                function (data) {
+                    var count = parseInt10(data.toString());
+                    if (isNaN(count)) {
+                        error({ message: "Count is NaN", count: count });
+                    } else {
+                        success(count);
+                    }
+                }, error, undefined, options.httpClient, options.metadata);
+        };
+
+        that.read = function (index, count, success, error) {
+            /// <summary>Gets a number of consecutive items from the collection.</summary>
+            /// <param name="index" type="Number">Zero-based index of the items to retrieve.</param>
+            /// <param name="count" type="Number">Number of items to retrieve.</param>
+            /// <param name="success" type="Function">Success callback with the requested items.</param>
+            /// <param name="error" type="Function">Error callback.</param>
+            /// <returns type="Object">Request object with an abort method./<param>
+
+            var queryOptions = "$skip=" + index + "&$top=" + count;
+            return queryForData(appendQueryOption(uri, queryOptions), that.options, success, error);
+        };
+
+        return that;
+    };
+
+    // DATAJS INTERNAL START
+    window.datajs.ODataCacheSource = ODataCacheSource;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/cache.js
----------------------------------------------------------------------
diff --git a/datajs/src/cache.js b/datajs/src/cache.js
new file mode 100644
index 0000000..b3a3a01
--- /dev/null
+++ b/datajs/src/cache.js
@@ -0,0 +1,1349 @@
+/// <reference path="odata-utils.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// cache.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    var assigned = datajs.assigned;
+    var delay = datajs.delay;
+    var extend = datajs.extend;
+    var djsassert = datajs.djsassert;
+    var isArray = datajs.isArray;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+    var undefinedDefault = datajs.undefinedDefault;
+
+    var createDeferred = datajs.createDeferred;
+    var DjsDeferred = datajs.DjsDeferred;
+    var ODataCacheSource = datajs.ODataCacheSource;
+
+    // CONTENT START
+
+    var appendPage = function (operation, page) {
+        /// <summary>Appends a page's data to the operation data.</summary>
+        /// <param name="operation" type="Object">Operation with (i)ndex, (c)ount and (d)ata.</param>
+        /// <param name="page" type="Object">Page with (i)ndex, (c)ount and (d)ata.</param>
+
+        var intersection = intersectRanges(operation, page);
+        if (intersection) {
+            var start = intersection.i - page.i;
+            var end = start + (operation.c - operation.d.length);
+            operation.d = operation.d.concat(page.d.slice(start, end));
+        }
+    };
+
+    var intersectRanges = function (x, y) {
+        /// <summary>Returns the {(i)ndex, (c)ount} range for the intersection of x and y.</summary>
+        /// <param name="x" type="Object">Range with (i)ndex and (c)ount members.</param>
+        /// <param name="y" type="Object">Range with (i)ndex and (c)ount members.</param>
+        /// <returns type="Object">The intersection (i)ndex and (c)ount; undefined if there is no intersection.</returns>
+
+        var xLast = x.i + x.c;
+        var yLast = y.i + y.c;
+        var resultIndex = (x.i > y.i) ? x.i : y.i;
+        var resultLast = (xLast < yLast) ? xLast : yLast;
+        var result;
+        if (resultLast >= resultIndex) {
+            result = { i: resultIndex, c: resultLast - resultIndex };
+        }
+
+        return result;
+    };
+
+    var checkZeroGreater = function (val, name) {
+        /// <summary>Checks whether val is a defined number with value zero or greater.</summary>
+        /// <param name="val" type="Number">Value to check.</param>
+        /// <param name="name" type="String">Parameter name to use in exception.</param>
+
+        if (val === undefined || typeof val !== "number") {
+            throw { message: "'" + name + "' must be a number." };
+        }
+
+        if (isNaN(val) || val < 0 || !isFinite(val)) {
+            throw { message: "'" + name + "' must be greater than or equal to zero." };
+        }
+    };
+
+    var checkUndefinedGreaterThanZero = function (val, name) {
+        /// <summary>Checks whether val is undefined or a number with value greater than zero.</summary>
+        /// <param name="val" type="Number">Value to check.</param>
+        /// <param name="name" type="String">Parameter name to use in exception.</param>
+
+        if (val !== undefined) {
+            if (typeof val !== "number") {
+                throw { message: "'" + name + "' must be a number." };
+            }
+
+            if (isNaN(val) || val <= 0 || !isFinite(val)) {
+                throw { message: "'" + name + "' must be greater than zero." };
+            }
+        }
+    };
+
+    var checkUndefinedOrNumber = function (val, name) {
+        /// <summary>Checks whether val is undefined or a number</summary>
+        /// <param name="val" type="Number">Value to check.</param>
+        /// <param name="name" type="String">Parameter name to use in exception.</param>
+        if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) {
+            throw { message: "'" + name + "' must be a number." };
+        }
+    };
+
+    var removeFromArray = function (arr, item) {
+        /// <summary>Performs a linear search on the specified array and removes the first instance of 'item'.</summary>
+        /// <param name="arr" type="Array">Array to search.</param>
+        /// <param name="item">Item being sought.</param>
+        /// <returns type="Boolean">Whether the item was removed.</returns>
+
+        var i, len;
+        for (i = 0, len = arr.length; i < len; i++) {
+            if (arr[i] === item) {
+                arr.splice(i, 1);
+                return true;
+            }
+        }
+
+        return false;
+    };
+
+    var estimateSize = function (obj) {
+        /// <summary>Estimates the size of an object in bytes.</summary>
+        /// <param name="obj" type="Object">Object to determine the size of.</param>
+        /// <returns type="Integer">Estimated size of the object in bytes.</returns>
+        var size = 0;
+        var type = typeof obj;
+
+        if (type === "object" && obj) {
+            for (var name in obj) {
+                size += name.length * 2 + estimateSize(obj[name]);
+            }
+        } else if (type === "string") {
+            size = obj.length * 2;
+        } else {
+            size = 8;
+        }
+        return size;
+    };
+
+    var snapToPageBoundaries = function (lowIndex, highIndex, pageSize) {
+        /// <summary>Snaps low and high indices into page sizes and returns a range.</summary>
+        /// <param name="lowIndex" type="Number">Low index to snap to a lower value.</param>
+        /// <param name="highIndex" type="Number">High index to snap to a higher value.</param>
+        /// <param name="pageSize" type="Number">Page size to snap to.</param>
+        /// <returns type="Object">A range with (i)ndex and (c)ount of elements.</returns>
+
+        lowIndex = Math.floor(lowIndex / pageSize) * pageSize;
+        highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize;
+        return { i: lowIndex, c: highIndex - lowIndex };
+    };
+
+    // The DataCache is implemented using state machines.  The following constants are used to properly
+    // identify and label the states that these machines transition to.
+
+    // DataCache state constants
+
+    var CACHE_STATE_DESTROY = "destroy";
+    var CACHE_STATE_IDLE = "idle";
+    var CACHE_STATE_INIT = "init";
+    var CACHE_STATE_READ = "read";
+    var CACHE_STATE_PREFETCH = "prefetch";
+    var CACHE_STATE_WRITE = "write";
+
+    // DataCacheOperation state machine states.
+    // Transitions on operations also depend on the cache current of the cache.
+
+    var OPERATION_STATE_CANCEL = "cancel";
+    var OPERATION_STATE_END = "end";
+    var OPERATION_STATE_ERROR = "error";
+    var OPERATION_STATE_START = "start";
+    var OPERATION_STATE_WAIT = "wait";
+
+    // Destroy state machine states
+
+    var DESTROY_STATE_CLEAR = "clear";
+
+    // Read / Prefetch state machine states
+
+    var READ_STATE_DONE = "done";
+    var READ_STATE_LOCAL = "local";
+    var READ_STATE_SAVE = "save";
+    var READ_STATE_SOURCE = "source";
+
+    var DataCacheOperation = function (stateMachine, promise, isCancelable, index, count, data, pending) {
+        /// <summary>Creates a new operation object.</summary>
+        /// <param name="stateMachine" type="Function">State machine that describes the specific behavior of the operation.</param>
+        /// <param name="promise" type ="DjsDeferred">Promise for requested values.</param>
+        /// <param name="isCancelable" type ="Boolean">Whether this operation can be canceled or not.</param>
+        /// <param name="index" type="Number">Index of first item requested.</param>
+        /// <param name="count" type="Number">Count of items requested.</param>
+        /// <param name="data" type="Array">Array with the items requested by the operation.</param>
+        /// <param name="pending" type="Number">Total number of pending prefetch records.</param>
+        /// <returns type="DataCacheOperation">A new data cache operation instance.</returns>
+
+        /// <field name="p" type="DjsDeferred">Promise for requested values.</field>
+        /// <field name="i" type="Number">Index of first item requested.</field>
+        /// <field name="c" type="Number">Count of items requested.</field>
+        /// <field name="d" type="Array">Array with the items requested by the operation.</field>
+        /// <field name="s" type="Array">Current state of the operation.</field>
+        /// <field name="canceled" type="Boolean">Whether the operation has been canceled.</field>
+        /// <field name="pending" type="Number">Total number of pending prefetch records.</field>
+        /// <field name="oncomplete" type="Function">Callback executed when the operation reaches the end state.</field>
+
+        var stateData;
+        var cacheState;
+        var that = this;
+
+        that.p = promise;
+        that.i = index;
+        that.c = count;
+        that.d = data;
+        that.s = OPERATION_STATE_START;
+
+        that.canceled = false;
+        that.pending = pending;
+        that.oncomplete = null;
+
+        that.cancel = function () {
+            /// <summary>Transitions this operation to the cancel state and sets the canceled flag to true.</summary>
+            /// <remarks>The function is a no-op if the operation is non-cancelable.</summary>
+
+            if (!isCancelable) {
+                return;
+            }
+
+            var state = that.s;
+            if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) {
+                that.canceled = true;
+                transition(OPERATION_STATE_CANCEL, stateData);
+            }
+        };
+
+        that.complete = function () {
+            /// <summary>Transitions this operation to the end state.</summary>
+
+            djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.complete() - operation is in the end state", that);
+            transition(OPERATION_STATE_END, stateData);
+        };
+
+        that.error = function (err) {
+            /// <summary>Transitions this operation to the error state.</summary>
+            if (!that.canceled) {
+                djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.error() - operation is in the end state", that);
+                djsassert(that.s !== OPERATION_STATE_ERROR, "DataCacheOperation.error() - operation is in the error state", that);
+                transition(OPERATION_STATE_ERROR, err);
+            }
+        };
+
+        that.run = function (state) {
+            /// <summary>Executes the operation's current state in the context of a new cache state.</summary>
+            /// <param name="state" type="Object">New cache state.</param>
+
+            cacheState = state;
+            that.transition(that.s, stateData);
+        };
+
+        that.wait = function (data) {
+            /// <summary>Transitions this operation to the wait state.</summary>
+
+            djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.wait() - operation is in the end state", that);
+            transition(OPERATION_STATE_WAIT, data);
+        };
+
+        var operationStateMachine = function (opTargetState, cacheState, data) {
+            /// <summary>State machine that describes all operations common behavior.</summary>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+
+            switch (opTargetState) {
+                case OPERATION_STATE_START:
+                    // Initial state of the operation. The operation will remain in this state until the cache has been fully initialized.
+                    if (cacheState !== CACHE_STATE_INIT) {
+                        stateMachine(that, opTargetState, cacheState, data);
+                    }
+                    break;
+
+                case OPERATION_STATE_WAIT:
+                    // Wait state indicating that the operation is active but waiting for an asynchronous operation to complete.
+                    stateMachine(that, opTargetState, cacheState, data);
+                    break;
+
+                case OPERATION_STATE_CANCEL:
+                    // Cancel state.
+                    stateMachine(that, opTargetState, cacheState, data);
+                    that.fireCanceled();
+                    transition(OPERATION_STATE_END);
+                    break;
+
+                case OPERATION_STATE_ERROR:
+                    // Error state. Data is expected to be an object detailing the error condition.
+                    stateMachine(that, opTargetState, cacheState, data);
+                    that.canceled = true;
+                    that.fireRejected(data);
+                    transition(OPERATION_STATE_END);
+                    break;
+
+                case OPERATION_STATE_END:
+                    // Final state of the operation.
+                    if (that.oncomplete) {
+                        that.oncomplete(that);
+                    }
+                    if (!that.canceled) {
+                        that.fireResolved();
+                    }
+                    stateMachine(that, opTargetState, cacheState, data);
+                    break;
+
+                default:
+                    // Any other state is passed down to the state machine describing the operation's specific behavior.
+                    // DATAJS INTERNAL START 
+                    if (true) {
+                        // Check that the state machine actually handled the sate.
+                        var handled = stateMachine(that, opTargetState, cacheState, data);
+                        djsassert(handled, "Bad operation state: " + opTargetState + " cacheState: " + cacheState, this);
+                    } else {
+                        // DATAJS INTERNAL END 
+                        stateMachine(that, opTargetState, cacheState, data);
+                        // DATAJS INTERNAL START
+                    }
+                    // DATAJS INTERNAL END
+                    break;
+            }
+        };
+
+        var transition = function (state, data) {
+            /// <summary>Transitions this operation to a new state.</summary>
+            /// <param name="state" type="Object">State to transition the operation to.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+
+            that.s = state;
+            stateData = data;
+            operationStateMachine(state, cacheState, data);
+        };
+
+        that.transition = transition;
+
+        return that;
+    };
+
+    DataCacheOperation.prototype.fireResolved = function () {
+        /// <summary>Fires a resolved notification as necessary.</summary>
+
+        // Fire the resolve just once.
+        var p = this.p;
+        if (p) {
+            this.p = null;
+            p.resolve(this.d);
+        }
+    };
+
+    DataCacheOperation.prototype.fireRejected = function (reason) {
+        /// <summary>Fires a rejected notification as necessary.</summary>
+
+        // Fire the rejection just once.
+        var p = this.p;
+        if (p) {
+            this.p = null;
+            p.reject(reason);
+        }
+    };
+
+    DataCacheOperation.prototype.fireCanceled = function () {
+        /// <summary>Fires a canceled notification as necessary.</summary>
+
+        this.fireRejected({ canceled: true, message: "Operation canceled" });
+    };
+
+
+    var DataCache = function (options) {
+        /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
+        /// <param name="options">
+        /// Options for the data cache, including name, source, pageSize,
+        /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
+        /// </param>
+        /// <returns type="DataCache">A new data cache instance.</returns>
+
+        var state = CACHE_STATE_INIT;
+        var stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
+
+        var clearOperations = [];
+        var readOperations = [];
+        var prefetchOperations = [];
+
+        var actualCacheSize = 0;                                             // Actual cache size in bytes.
+        var allDataLocal = false;                                            // Whether all data is local.
+        var cacheSize = undefinedDefault(options.cacheSize, 1048576);        // Requested cache size in bytes, default 1 MB.
+        var collectionCount = 0;                                             // Number of elements in the server collection.
+        var highestSavedPage = 0;                                            // Highest index of all the saved pages.
+        var highestSavedPageSize = 0;                                        // Item count of the saved page with the highest index.
+        var overflowed = cacheSize === 0;                                    // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0);
+        var pageSize = undefinedDefault(options.pageSize, 50);               // Number of elements to store per page.
+        var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling.
+        var version = "1.0";
+        var cacheFailure;
+
+        var pendingOperations = 0;
+
+        var source = options.source;
+        if (typeof source === "string") {
+            // Create a new cache source.
+            source = new ODataCacheSource(options);
+        }
+        source.options = options;
+
+        // Create a cache local store.
+        var store = datajs.createStore(options.name, options.mechanism);
+
+        var that = this;
+
+        that.onidle = options.idle;
+        that.stats = stats;
+
+        that.count = function () {
+            /// <summary>Counts the number of items in the collection.</summary>
+            /// <returns type="Object">A promise with the number of items.</returns>
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            var deferred = createDeferred();
+            var canceled = false;
+
+            if (allDataLocal) {
+                delay(function () {
+                    deferred.resolve(collectionCount);
+                });
+
+                return deferred.promise();
+            }
+
+            // TODO: Consider returning the local data count instead once allDataLocal flag is set to true.
+            var request = source.count(function (count) {
+                request = null;
+                stats.counts++;
+                deferred.resolve(count);
+            }, function (err) {
+                request = null;
+                deferred.reject(extend(err, { canceled: canceled }));
+            });
+
+            return extend(deferred.promise(), {
+                cancel: function () {
+                    /// <summary>Aborts the count operation.</summary>
+                    if (request) {
+                        canceled = true;
+                        request.abort();
+                        request = null;
+                    }
+                }
+            });
+        };
+
+        that.clear = function () {
+            /// <summary>Cancels all running operations and clears all local data associated with this cache.</summary>
+            /// <remarks>
+            /// New read requests made while a clear operation is in progress will not be canceled.
+            /// Instead they will be queued for execution once the operation is completed.
+            /// </remarks>
+            /// <returns type="Object">A promise that has no value and can't be canceled.</returns>
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            if (clearOperations.length === 0) {
+                var deferred = createDeferred();
+                var op = new DataCacheOperation(destroyStateMachine, deferred, false);
+                queueAndStart(op, clearOperations);
+                return deferred.promise();
+            }
+            return clearOperations[0].p;
+        };
+
+        that.filterForward = function (index, count, predicate) {
+            /// <summary>Filters the cache data based a predicate.</summary>
+            /// <param name="index" type="Number">The index of the item to start filtering forward from.</param>
+            /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
+            /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
+            /// <remarks>
+            /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+            /// </remarks>
+            /// <returns type="DjsDeferred">A promise for an array of results.</returns>
+            return filter(index, count, predicate, false);
+        };
+
+        that.filterBack = function (index, count, predicate) {
+            /// <summary>Filters the cache data based a predicate.</summary>
+            /// <param name="index" type="Number">The index of the item to start filtering backward from.</param>
+            /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
+            /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
+            /// <remarks>
+            /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+            /// </remarks>
+            /// <returns type="DjsDeferred">A promise for an array of results.</returns>
+            return filter(index, count, predicate, true);
+        };
+
+        that.readRange = function (index, count) {
+            /// <summary>Reads a range of adjacent records.</summary>
+            /// <param name="index" type="Number">Zero-based index of record range to read.</param>
+            /// <param name="count" type="Number">Number of records in the range.</param>
+            /// <remarks>
+            /// New read requests made while a clear operation is in progress will not be canceled.
+            /// Instead they will be queued for execution once the operation is completed.
+            /// </remarks>
+            /// <returns type="DjsDeferred">
+            /// A promise for an array of records; less records may be returned if the
+            /// end of the collection is found.
+            /// </returns>
+
+            checkZeroGreater(index, "index");
+            checkZeroGreater(count, "count");
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            var deferred = createDeferred();
+
+            // Merging read operations would be a nice optimization here.
+            var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, [], 0);
+            queueAndStart(op, readOperations);
+
+            return extend(deferred.promise(), {
+                cancel: function () {
+                    /// <summary>Aborts the readRange operation.</summary>
+                    op.cancel();
+                }
+            });
+        };
+
+        that.ToObservable = that.toObservable = function () {
+            /// <summary>Creates an Observable object that enumerates all the cache contents.</summary>
+            /// <returns>A new Observable object that enumerates all the cache contents.</returns>
+            if (!window.Rx || !window.Rx.Observable) {
+                throw { message: "Rx library not available - include rx.js" };
+            }
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            return window.Rx.Observable.CreateWithDisposable(function (obs) {
+                var disposed = false;
+                var index = 0;
+
+                var errorCallback = function (error) {
+                    if (!disposed) {
+                        obs.OnError(error);
+                    }
+                };
+
+                var successCallback = function (data) {
+                    if (!disposed) {
+                        var i, len;
+                        for (i = 0, len = data.length; i < len; i++) {
+                            // The wrapper automatically checks for Dispose
+                            // on the observer, so we don't need to check it here.
+                            obs.OnNext(data[i]);
+                        }
+
+                        if (data.length < pageSize) {
+                            obs.OnCompleted();
+                        } else {
+                            index += pageSize;
+                            that.readRange(index, pageSize).then(successCallback, errorCallback);
+                        }
+                    }
+                };
+
+                that.readRange(index, pageSize).then(successCallback, errorCallback);
+
+                return { Dispose: function () { disposed = true; } };
+            });
+        };
+
+        var cacheFailureCallback = function (message) {
+            /// <summary>Creates a function that handles a callback by setting the cache into failure mode.</summary>
+            /// <param name="message" type="String">Message text.</param>
+            /// <returns type="Function">Function to use as error callback.</returns>
+            /// <remarks>
+            /// This function will specifically handle problems with critical store resources
+            /// during cache initialization.
+            /// </remarks>
+
+            return function (error) {
+                cacheFailure = { message: message, error: error };
+
+                // Destroy any pending clear or read operations.
+                // At this point there should be no prefetch operations.
+                // Count operations will go through but are benign because they
+                // won't interact with the store.
+                djsassert(prefetchOperations.length === 0, "prefetchOperations.length === 0");
+                var i, len;
+                for (i = 0, len = readOperations.length; i < len; i++) {
+                    readOperations[i].fireRejected(cacheFailure);
+                }
+                for (i = 0, len = clearOperations.length; i < len; i++) {
+                    clearOperations[i].fireRejected(cacheFailure);
+                }
+
+                // Null out the operation arrays.
+                readOperations = clearOperations = null;
+            };
+        };
+
+        var changeState = function (newState) {
+            /// <summary>Updates the cache's state and signals all pending operations of the change.</summary>
+            /// <param name="newState" type="Object">New cache state.</param>
+            /// <remarks>This method is a no-op if the cache's current state and the new state are the same.</remarks>
+
+            if (newState !== state) {
+                state = newState;
+                var operations = clearOperations.concat(readOperations, prefetchOperations);
+                var i, len;
+                for (i = 0, len = operations.length; i < len; i++) {
+                    operations[i].run(state);
+                }
+            }
+        };
+
+        var clearStore = function () {
+            /// <summary>Removes all the data stored in the cache.</summary>
+            /// <returns type="DjsDeferred">A promise with no value.</returns>
+            djsassert(state === CACHE_STATE_DESTROY || state === CACHE_STATE_INIT, "DataCache.clearStore() - cache is not on the destroy or initialize state, current sate = " + state);
+
+            var deferred = new DjsDeferred();
+            store.clear(function () {
+
+                // Reset the cache settings.
+                actualCacheSize = 0;
+                allDataLocal = false;
+                collectionCount = 0;
+                highestSavedPage = 0;
+                highestSavedPageSize = 0;
+                overflowed = cacheSize === 0;
+
+                // version is not reset, in case there is other state in eg V1.1 that is still around.
+
+                // Reset the cache stats.
+                stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
+                that.stats = stats;
+
+                store.close();
+                deferred.resolve();
+            }, function (err) {
+                deferred.reject(err);
+            });
+            return deferred;
+        };
+
+        var dequeueOperation = function (operation) {
+            /// <summary>Removes an operation from the caches queues and changes the cache state to idle.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation to dequeue.</param>
+            /// <remarks>This method is used as a handler for the operation's oncomplete event.</remarks>
+
+            var removed = removeFromArray(clearOperations, operation);
+            if (!removed) {
+                removed = removeFromArray(readOperations, operation);
+                if (!removed) {
+                    removeFromArray(prefetchOperations, operation);
+                }
+            }
+
+            pendingOperations--;
+            changeState(CACHE_STATE_IDLE);
+        };
+
+        var fetchPage = function (start) {
+            /// <summary>Requests data from the cache source.</summary>
+            /// <param name="start" type="Number">Zero-based index of items to request.</param>
+            /// <returns type="DjsDeferred">A promise for a page object with (i)ndex, (c)ount, (d)ata.</returns>
+
+            djsassert(state !== CACHE_STATE_DESTROY, "DataCache.fetchPage() - cache is on the destroy state");
+            djsassert(state !== CACHE_STATE_IDLE, "DataCache.fetchPage() - cache is on the idle state");
+
+            var deferred = new DjsDeferred();
+            var canceled = false;
+
+            var request = source.read(start, pageSize, function (data) {
+                var page = { i: start, c: data.length, d: data };
+                deferred.resolve(page);
+            }, function (err) {
+                deferred.reject(err);
+            });
+
+            return extend(deferred, {
+                cancel: function () {
+                    if (request) {
+                        request.abort();
+                        canceled = true;
+                        request = null;
+                    }
+                }
+            });
+        };
+
+        var filter = function (index, count, predicate, backwards) {
+            /// <summary>Filters the cache data based a predicate.</summary>
+            /// <param name="index" type="Number">The index of the item to start filtering from.</param>
+            /// <param name="count" type="Number">Maximum number of items to include in the result.</param>
+            /// <param name="predicate" type="Function">Callback function returning a boolean that determines whether an item should be included in the result or not.</param>
+            /// <param name="backwards" type="Boolean">True if the filtering should move backward from the specified index, falsey otherwise.</param>
+            /// <remarks>
+            /// Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+            /// </remarks>
+            /// <returns type="DjsDeferred">A promise for an array of results.</returns>
+            index = parseInt10(index);
+            count = parseInt10(count);
+
+            if (isNaN(index)) {
+                throw { message: "'index' must be a valid number.", index: index };
+            }
+            if (isNaN(count)) {
+                throw { message: "'count' must be a valid number.", count: count };
+            }
+
+            if (cacheFailure) {
+                throw cacheFailure;
+            }
+
+            index = Math.max(index, 0);
+
+            var deferred = createDeferred();
+            var arr = [];
+            var canceled = false;
+            var pendingReadRange = null;
+
+            var readMore = function (readIndex, readCount) {
+                if (!canceled) {
+                    if (count >= 0 && arr.length >= count) {
+                        deferred.resolve(arr);
+                    } else {
+                        pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
+                            for (var i = 0, length = data.length; i < length && (count < 0 || arr.length < count); i++) {
+                                var dataIndex = backwards ? length - i - 1 : i;
+                                var item = data[dataIndex];
+                                if (predicate(item)) {
+                                    var element = {
+                                        index: readIndex + dataIndex,
+                                        item: item
+                                    };
+
+                                    backwards ? arr.unshift(element) : arr.push(element);
+                                }
+                            }
+
+                            // Have we reached the end of the collection?
+                            if ((!backwards && data.length < readCount) || (backwards && readIndex <= 0)) {
+                                deferred.resolve(arr);
+                            } else {
+                                var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount;
+                                readMore(nextIndex, pageSize);
+                            }
+                        }, function (err) {
+                            deferred.reject(err);
+                        });
+                    }
+                }
+            };
+
+            // Initially, we read from the given starting index to the next/previous page boundary
+            var initialPage = snapToPageBoundaries(index, index, pageSize);
+            var initialIndex = backwards ? initialPage.i : index;
+            var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index;
+            readMore(initialIndex, initialCount);
+
+            return extend(deferred.promise(), {
+                cancel: function () {
+                    /// <summary>Aborts the filter operation</summary>
+                    if (pendingReadRange) {
+                        pendingReadRange.cancel();
+                    }
+                    canceled = true;
+                }
+            });
+        };
+
+        var fireOnIdle = function () {
+            /// <summary>Fires an onidle event if any functions are assigned.</summary>
+
+            if (that.onidle && pendingOperations === 0) {
+                that.onidle();
+            }
+        };
+
+        var prefetch = function (start) {
+            /// <summary>Creates and starts a new prefetch operation.</summary>
+            /// <param name="start" type="Number">Zero-based index of the items to prefetch.</param>
+            /// <remarks>
+            /// This method is a no-op if any of the following conditions is true:
+            ///     1.- prefetchSize is 0
+            ///     2.- All data has been read and stored locally in the cache.
+            ///     3.- There is already an all data prefetch operation queued.
+            ///     4.- The cache has run out of available space (overflowed).
+            /// <remarks>
+
+            if (allDataLocal || prefetchSize === 0 || overflowed) {
+                return;
+            }
+
+            djsassert(state === CACHE_STATE_READ, "DataCache.prefetch() - cache is not on the read state, current state: " + state);
+
+            if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) {
+                // Merging prefetch operations would be a nice optimization here.
+                var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize);
+                queueAndStart(op, prefetchOperations);
+            }
+        };
+
+        var queueAndStart = function (op, queue) {
+            /// <summary>Queues an operation and runs it.</summary>
+            /// <param name="op" type="DataCacheOperation">Operation to queue.</param>
+            /// <param name="queue" type="Array">Array that will store the operation.</param>
+
+            op.oncomplete = dequeueOperation;
+            queue.push(op);
+            pendingOperations++;
+            op.run(state);
+        };
+
+        var readPage = function (key) {
+            /// <summary>Requests a page from the cache local store.</summary>
+            /// <param name="key" type="Number">Zero-based index of the reuqested page.</param>
+            /// <returns type="DjsDeferred">A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.</returns>
+
+            djsassert(state !== CACHE_STATE_DESTROY, "DataCache.readPage() - cache is on the destroy state");
+
+            var canceled = false;
+            var deferred = extend(new DjsDeferred(), {
+                cancel: function () {
+                    /// <summary>Aborts the readPage operation.</summary>
+                    canceled = true;
+                }
+            });
+
+            var error = storeFailureCallback(deferred, "Read page from store failure");
+
+            store.contains(key, function (contained) {
+                if (canceled) {
+                    return;
+                }
+                if (contained) {
+                    store.read(key, function (_, data) {
+                        if (!canceled) {
+                            deferred.resolve(data !== undefined, data);
+                        }
+                    }, error);
+                    return;
+                }
+                deferred.resolve(false);
+            }, error);
+            return deferred;
+        };
+
+        var savePage = function (key, page) {
+            /// <summary>Saves a page to the cache local store.</summary>
+            /// <param name="key" type="Number">Zero-based index of the requested page.</param>
+            /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata, and (t)icks.</param>
+            /// <returns type="DjsDeferred">A promise with no value.</returns>
+
+            djsassert(state !== CACHE_STATE_DESTROY, "DataCache.savePage() - cache is on the destroy state");
+            djsassert(state !== CACHE_STATE_IDLE, "DataCache.savePage() - cache is on the idle state");
+
+            var canceled = false;
+
+            var deferred = extend(new DjsDeferred(), {
+                cancel: function () {
+                    /// <summary>Aborts the readPage operation.</summary>
+                    canceled = true;
+                }
+            });
+
+            var error = storeFailureCallback(deferred, "Save page to store failure");
+
+            var resolve = function () {
+                deferred.resolve(true);
+            };
+
+            if (page.c > 0) {
+                var pageBytes = estimateSize(page);
+                overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes;
+
+                if (!overflowed) {
+                    store.addOrUpdate(key, page, function () {
+                        updateSettings(page, pageBytes);
+                        saveSettings(resolve, error);
+                    }, error);
+                } else {
+                    resolve();
+                }
+            } else {
+                updateSettings(page, 0);
+                saveSettings(resolve, error);
+            }
+            return deferred;
+        };
+
+        var saveSettings = function (success, error) {
+            /// <summary>Saves the cache's current settings to the local store.</summary>
+            /// <param name="success" type="Function">Success callback.</param>
+            /// <param name="error" type="Function">Errror callback.</param>
+
+            var settings = {
+                actualCacheSize: actualCacheSize,
+                allDataLocal: allDataLocal,
+                cacheSize: cacheSize,
+                collectionCount: collectionCount,
+                highestSavedPage: highestSavedPage,
+                highestSavedPageSize: highestSavedPageSize,
+                pageSize: pageSize,
+                sourceId: source.identifier,
+                version: version
+            };
+
+            store.addOrUpdate("__settings", settings, success, error);
+        };
+
+        var storeFailureCallback = function (deferred/*, message*/) {
+            /// <summary>Creates a function that handles a store error.</summary>
+            /// <param name="deferred" type="DjsDeferred">Deferred object to resolve.</param>
+            /// <param name="message" type="String">Message text.</param>
+            /// <returns type="Function">Function to use as error callback.</returns>
+            /// <remarks>
+            /// This function will specifically handle problems when interacting with the store.
+            /// </remarks>
+
+            return function (/*error*/) {
+                // var console = window.console;
+                // if (console && console.log) {
+                //    console.log(message);
+                //    console.dir(error);
+                // }
+                deferred.resolve(false);
+            };
+        };
+
+        var updateSettings = function (page, pageBytes) {
+            /// <summary>Updates the cache's settings based on a page object.</summary>
+            /// <param name="page" type="Object">Object with (i)ndex, (c)ount, (d)ata.</param>
+            /// <param name="pageBytes" type="Number">Size of the page in bytes.</param>
+
+            var pageCount = page.c;
+            var pageIndex = page.i;
+
+            // Detect the collection size.
+            if (pageCount === 0) {
+                if (highestSavedPage === pageIndex - pageSize) {
+                    collectionCount = highestSavedPage + highestSavedPageSize;
+                }
+            } else {
+                highestSavedPage = Math.max(highestSavedPage, pageIndex);
+                if (highestSavedPage === pageIndex) {
+                    highestSavedPageSize = pageCount;
+                }
+                actualCacheSize += pageBytes;
+                if (pageCount < pageSize && !collectionCount) {
+                    collectionCount = pageIndex + pageCount;
+                }
+            }
+
+            // Detect the end of the collection.
+            if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) {
+                allDataLocal = true;
+            }
+        };
+
+        var cancelStateMachine = function (operation, opTargetState, cacheState, data) {
+            /// <summary>State machine describing the behavior for cancelling a read or prefetch operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <remarks>
+            /// This state machine contains behavior common to read and prefetch operations.
+            /// </remarks>
+
+            var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END;
+            if (canceled) {
+                if (opTargetState === OPERATION_STATE_CANCEL) {
+                    // Cancel state.
+                    // Data is expected to be any pending request made to the cache.
+                    if (data && data.cancel) {
+                        data.cancel();
+                    }
+                }
+            }
+            return canceled;
+        };
+
+        var destroyStateMachine = function (operation, opTargetState, cacheState) {
+            /// <summary>State machine describing the behavior of a clear operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <remarks>
+            /// Clear operations have the highest priority and can't be interrupted by other operations; however,
+            /// they will preempt any other operation currently executing.
+            /// </remarks>
+
+            var transition = operation.transition;
+
+            // Signal the cache that a clear operation is running.
+            if (cacheState !== CACHE_STATE_DESTROY) {
+                changeState(CACHE_STATE_DESTROY);
+                return true;
+            }
+
+            switch (opTargetState) {
+                case OPERATION_STATE_START:
+                    // Initial state of the operation.
+                    transition(DESTROY_STATE_CLEAR);
+                    break;
+
+                case OPERATION_STATE_END:
+                    // State that signals the operation is done.
+                    fireOnIdle();
+                    break;
+
+                case DESTROY_STATE_CLEAR:
+                    // State that clears all the local data of the cache.
+                    clearStore().then(function () {
+                        // Terminate the operation once the local store has been cleared.
+                        operation.complete();
+                    });
+                    // Wait until the clear request completes.
+                    operation.wait();
+                    break;
+
+                default:
+                    return false;
+            }
+            return true;
+        };
+
+        var prefetchStateMachine = function (operation, opTargetState, cacheState, data) {
+            /// <summary>State machine describing the behavior of a prefetch operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <remarks>
+            /// Prefetch operations have the lowest priority and will be interrupted by operations of
+            /// other kinds. A preempted prefetch operation will resume its execution only when the state
+            /// of the cache returns to idle.
+            ///
+            /// If a clear operation starts executing then all the prefetch operations are canceled,
+            /// even if they haven't started executing yet.
+            /// </remarks>
+
+            // Handle cancelation
+            if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
+
+                var transition = operation.transition;
+
+                // Handle preemption
+                if (cacheState !== CACHE_STATE_PREFETCH) {
+                    if (cacheState === CACHE_STATE_DESTROY) {
+                        if (opTargetState !== OPERATION_STATE_CANCEL) {
+                            operation.cancel();
+                        }
+                    } else if (cacheState === CACHE_STATE_IDLE) {
+                        // Signal the cache that a prefetch operation is running.
+                        changeState(CACHE_STATE_PREFETCH);
+                    }
+                    return true;
+                }
+
+                switch (opTargetState) {
+                    case OPERATION_STATE_START:
+                        // Initial state of the operation.
+                        if (prefetchOperations[0] === operation) {
+                            transition(READ_STATE_LOCAL, operation.i);
+                        }
+                        break;
+
+                    case READ_STATE_DONE:
+                        // State that determines if the operation can be resolved or has to
+                        // continue processing.
+                        // Data is expected to be the read page.
+                        var pending = operation.pending;
+
+                        if (pending > 0) {
+                            pending -= Math.min(pending, data.c);
+                        }
+
+                        // Are we done, or has all the data been stored?
+                        if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) {
+                            operation.complete();
+                        } else {
+                            // Continue processing the operation.
+                            operation.pending = pending;
+                            transition(READ_STATE_LOCAL, data.i + pageSize);
+                        }
+                        break;
+
+                    default:
+                        return readSaveStateMachine(operation, opTargetState, cacheState, data, true);
+                }
+            }
+            return true;
+        };
+
+        var readStateMachine = function (operation, opTargetState, cacheState, data) {
+            /// <summary>State machine describing the behavior of a read operation.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <remarks>
+            /// Read operations have a higher priority than prefetch operations, but lower than
+            /// clear operations. They will preempt any prefetch operation currently running
+            /// but will be interrupted by a clear operation.
+            ///
+            /// If a clear operation starts executing then all the currently running
+            /// read operations are canceled. Read operations that haven't started yet will
+            /// wait in the start state until the destory operation finishes.
+            /// </remarks>
+
+            // Handle cancelation
+            if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
+
+                var transition = operation.transition;
+
+                // Handle preemption
+                if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) {
+                    if (cacheState === CACHE_STATE_DESTROY) {
+                        if (opTargetState !== OPERATION_STATE_START) {
+                            operation.cancel();
+                        }
+                    } else if (cacheState !== CACHE_STATE_WRITE) {
+                        // Signal the cache that a read operation is running.
+                        djsassert(state == CACHE_STATE_IDLE || state === CACHE_STATE_PREFETCH, "DataCache.readStateMachine() - cache is not on the read or idle state.");
+                        changeState(CACHE_STATE_READ);
+                    }
+
+                    return true;
+                }
+
+                switch (opTargetState) {
+                    case OPERATION_STATE_START:
+                        // Initial state of the operation.
+                        // Wait until the cache is idle or prefetching.
+                        if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) {
+                            // Signal the cache that a read operation is running.
+                            changeState(CACHE_STATE_READ);
+                            if (operation.c > 0) {
+                                // Snap the requested range to a page boundary.
+                                var range = snapToPageBoundaries(operation.i, operation.c, pageSize);
+                                transition(READ_STATE_LOCAL, range.i);
+                            } else {
+                                transition(READ_STATE_DONE, operation);
+                            }
+                        }
+                        break;
+
+                    case READ_STATE_DONE:
+                        // State that determines if the operation can be resolved or has to
+                        // continue processing.
+                        // Data is expected to be the read page.
+                        appendPage(operation, data);
+                        var len = operation.d.length;
+                        // Are we done?
+                        if (operation.c === len || data.c < pageSize) {
+                            // Update the stats, request for a prefetch operation.
+                            stats.cacheReads++;
+                            prefetch(data.i + data.c);
+                            // Terminate the operation.
+                            operation.complete();
+                        } else {
+                            // Continue processing the operation.
+                            transition(READ_STATE_LOCAL, data.i + pageSize);
+                        }
+                        break;
+
+                    default:
+                        return readSaveStateMachine(operation, opTargetState, cacheState, data, false);
+                }
+            }
+
+            return true;
+        };
+
+        var readSaveStateMachine = function (operation, opTargetState, cacheState, data, isPrefetch) {
+            /// <summary>State machine describing the behavior for reading and saving data into the cache.</summary>
+            /// <param name="operation" type="DataCacheOperation">Operation being run.</param>
+            /// <param name="opTargetState" type="Object">Operation state to transition to.</param>
+            /// <param name="cacheState" type="Object">Current cache state.</param>
+            /// <param name="data" type="Object" optional="true">Additional data passed to the state.</param>
+            /// <param name="isPrefetch" type="Boolean">Flag indicating whether a read (false) or prefetch (true) operation is running.
+            /// <remarks>
+            /// This state machine contains behavior common to read and prefetch operations.
+            /// </remarks>
+
+            var error = operation.error;
+            var transition = operation.transition;
+            var wait = operation.wait;
+            var request;
+
+            switch (opTargetState) {
+                case OPERATION_STATE_END:
+                    // State that signals the operation is done.
+                    fireOnIdle();
+                    break;
+
+                case READ_STATE_LOCAL:
+                    // State that requests for a page from the local store.
+                    // Data is expected to be the index of the page to request.
+                    request = readPage(data).then(function (found, page) {
+                        // Signal the cache that a read operation is running.
+                        if (!operation.canceled) {
+                            if (found) {
+                                // The page is in the local store, check if the operation can be resolved.
+                                transition(READ_STATE_DONE, page);
+                            } else {
+                                // The page is not in the local store, request it from the source.
+                                transition(READ_STATE_SOURCE, data);
+                            }
+                        }
+                    });
+                    break;
+
+                case READ_STATE_SOURCE:
+                    // State that requests for a page from the cache source.
+                    // Data is expected to be the index of the page to request.
+                    request = fetchPage(data).then(function (page) {
+                        // Signal the cache that a read operation is running.
+                        if (!operation.canceled) {
+                            // Update the stats and save the page to the local store.
+                            if (isPrefetch) {
+                                stats.prefetches++;
+                            } else {
+                                stats.netReads++;
+                            }
+                            transition(READ_STATE_SAVE, page);
+                        }
+                    }, error);
+                    break;
+
+                case READ_STATE_SAVE:
+                    // State that saves a  page to the local store.
+                    // Data is expected to be the page to save.
+                    // Write access to the store is exclusive.
+                    if (cacheState !== CACHE_STATE_WRITE) {
+                        changeState(CACHE_STATE_WRITE);
+                        request = savePage(data.i, data).then(function (saved) {
+                            if (!operation.canceled) {
+                                if (!saved && isPrefetch) {
+                                    operation.pending = 0;
+                                }
+                                // Check if the operation can be resolved.
+                                transition(READ_STATE_DONE, data);
+                            }
+                            changeState(CACHE_STATE_IDLE);
+                        });
+                    }
+                    break;
+
+                default:
+                    // Unknown state that can't be handled by this state machine.
+                    return false;
+            }
+
+            if (request) {
+                // The operation might have been canceled between stack frames do to the async calls.
+                if (operation.canceled) {
+                    request.cancel();
+                } else if (operation.s === opTargetState) {
+                    // Wait for the request to complete.
+                    wait(request);
+                }
+            }
+
+            return true;
+        };
+
+        // Initialize the cache.
+        store.read("__settings", function (_, settings) {
+            if (assigned(settings)) {
+                var settingsVersion = settings.version;
+                if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) {
+                    cacheFailureCallback("Unsupported cache store version " + settingsVersion)();
+                    return;
+                }
+
+                if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) {
+                    // The shape or the source of the data was changed so invalidate the store.
+                    clearStore().then(function () {
+                        // Signal the cache is fully initialized.
+                        changeState(CACHE_STATE_IDLE);
+                    }, cacheFailureCallback("Unable to clear store during initialization"));
+                } else {
+                    // Restore the saved settings.
+                    actualCacheSize = settings.actualCacheSize;
+                    allDataLocal = settings.allDataLocal;
+                    cacheSize = settings.cacheSize;
+                    collectionCount = settings.collectionCount;
+                    highestSavedPage = settings.highestSavedPage;
+                    highestSavedPageSize = settings.highestSavedPageSize;
+                    version = settingsVersion;
+
+                    // Signal the cache is fully initialized.
+                    changeState(CACHE_STATE_IDLE);
+                }
+            } else {
+                // This is a brand new cache.
+                saveSettings(function () {
+                    // Signal the cache is fully initialized.
+                    changeState(CACHE_STATE_IDLE);
+                }, cacheFailureCallback("Unable to write settings during initialization."));
+            }
+        }, cacheFailureCallback("Unable to read settings from store."));
+
+        return that;
+    };
+
+    datajs.createDataCache = function (options) {
+        /// <summary>Creates a data cache for a collection that is efficiently loaded on-demand.</summary>
+        /// <param name="options">
+        /// Options for the data cache, including name, source, pageSize,
+        /// prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
+        /// </param>
+        /// <returns type="DataCache">A new data cache instance.</returns>
+        checkUndefinedGreaterThanZero(options.pageSize, "pageSize");
+        checkUndefinedOrNumber(options.cacheSize, "cacheSize");
+        checkUndefinedOrNumber(options.prefetchSize, "prefetchSize");
+
+        if (!assigned(options.name)) {
+            throw { message: "Undefined or null name", options: options };
+        }
+
+        if (!assigned(options.source)) {
+            throw { message: "Undefined source", options: options };
+        }
+
+        return new DataCache(options);
+    };
+
+    // DATAJS INTERNAL START
+    window.datajs.estimateSize = estimateSize;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/datajs.js
----------------------------------------------------------------------
diff --git a/datajs/src/datajs.js b/datajs/src/datajs.js
new file mode 100644
index 0000000..fc5bb62
--- /dev/null
+++ b/datajs/src/datajs.js
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// datajs.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // AMD support
+    if (typeof define === 'function' && define.amd) {
+        define('datajs', datajs);
+        define('OData', odata);
+    } else {
+        window.datajs = datajs;
+        window.OData = odata;
+    }
+
+    datajs.version = {
+        major: 1,
+        minor: 1,
+        build: 1
+    };
+
+    // INCLUDE: utils.js
+    // INCLUDE: xml.js
+
+    // INCLUDE: deferred.js
+
+    // INCLUDE: odata-utils.js
+    // INCLUDE: odata-net.js
+    // INCLUDE: odata-handler.js
+    // INCLUDE: odata-gml.js
+    // INCLUDE: odata-xml.js
+    // INCLUDE: odata-atom.js
+    // INCLUDE: odata-metadata.js
+    // INCLUDE: odata-json-light.js
+    // INCLUDE: odata-json.js
+    // INCLUDE: odata-batch.js
+    // INCLUDE: odata.js
+
+    // INCLUDE: store-dom.js
+    // INCLUDE: store-indexeddb.js
+    // INCLUDE: store-memory.js
+    // INCLUDE: store.js
+
+    // INCLUDE: cache-source.js
+    // INCLUDE: cache.js
+
+})(this);
\ No newline at end of file


[05/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-xml.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-xml.js b/datajs/src/odata-xml.js
new file mode 100644
index 0000000..b2d00f6
--- /dev/null
+++ b/datajs/src/odata-xml.js
@@ -0,0 +1,850 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-xml.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports.
+
+    var djsassert = datajs.djsassert;
+    var http = datajs.http;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var isObject = datajs.isObject;
+    var normalizeURI = datajs.normalizeURI;
+    var parseInt10 = datajs.parseInt10;
+    var xmlAppendChild = datajs.xmlAppendChild;
+    var xmlAppendChildren = datajs.xmlAppendChildren;
+    var xmlAttributes = datajs.xmlAttributes;
+    var xmlBaseURI = datajs.xmlBaseURI;
+    var xmlChildElements = datajs.xmlChildElements;
+    var xmlDom = datajs.xmlDom;
+    var xmlFirstChildElement = datajs.xmlFirstChildElement;
+    var xmlInnerText = datajs.xmlInnerText;
+    var xmlLocalName = datajs.xmlLocalName;
+    var xmlNamespaceURI = datajs.xmlNamespaceURI;
+    var xmlNewAttribute = datajs.xmlNewAttribute;
+    var xmlNewElement = datajs.xmlNewElement;
+    var xmlNodeValue = datajs.xmlNodeValue;
+    var xmlNS = datajs.xmlNS;
+    var xmlnsNS = datajs.xmlnsNS;
+    var xmlParse = datajs.xmlParse;
+    var xmlQualifiedName = datajs.xmlQualifiedName;
+    var xmlSerialize = datajs.xmlSerialize;
+    var xmlSiblingElement = datajs.xmlSiblingElement;
+    var w3org = datajs.w3org;
+
+    var dataItemTypeName = odata.dataItemTypeName;
+    var EDM_BINARY = odata.EDM_BINARY;
+    var EDM_BOOLEAN = odata.EDM_BOOLEAN;
+    var EDM_BYTE = odata.EDM_BYTE;
+    var EDM_DATETIME = odata.EDM_DATETIME;
+    var EDM_DATETIMEOFFSET = odata.EDM_DATETIMEOFFSET;
+    var EDM_DECIMAL = odata.EDM_DECIMAL;
+    var EDM_DOUBLE = odata.EDM_DOUBLE;
+    var EDM_GEOGRAPHY = odata.EDM_GEOGRAPHY;
+    var EDM_GEOGRAPHY_POINT = odata.EDM_GEOGRAPHY_POINT;
+    var EDM_GEOGRAPHY_LINESTRING = odata.EDM_GEOGRAPHY_LINESTRING;
+    var EDM_GEOGRAPHY_POLYGON = odata.EDM_GEOGRAPHY_POLYGON;
+    var EDM_GEOGRAPHY_COLLECTION = odata.EDM_GEOGRAPHY_COLLECTION;
+    var EDM_GEOGRAPHY_MULTIPOLYGON = odata.EDM_GEOGRAPHY_MULTIPOLYGON;
+    var EDM_GEOGRAPHY_MULTILINESTRING = odata.EDM_GEOGRAPHY_MULTILINESTRING;
+    var EDM_GEOGRAPHY_MULTIPOINT = odata.EDM_GEOGRAPHY_MULTIPOINT;
+    var EDM_GEOMETRY = odata.EDM_GEOMETRY;
+    var EDM_GEOMETRY_POINT = odata.EDM_GEOMETRY_POINT;
+    var EDM_GEOMETRY_LINESTRING = odata.EDM_GEOMETRY_LINESTRING;
+    var EDM_GEOMETRY_POLYGON = odata.EDM_GEOMETRY_POLYGON;
+    var EDM_GEOMETRY_COLLECTION = odata.EDM_GEOMETRY_COLLECTION;
+    var EDM_GEOMETRY_MULTIPOLYGON = odata.EDM_GEOMETRY_MULTIPOLYGON;
+    var EDM_GEOMETRY_MULTILINESTRING = odata.EDM_GEOMETRY_MULTILINESTRING;
+    var EDM_GEOMETRY_MULTIPOINT = odata.EDM_GEOMETRY_MULTIPOINT;
+    var EDM_GUID = odata.EDM_GUID;
+    var EDM_INT16 = odata.EDM_INT16;
+    var EDM_INT32 = odata.EDM_INT32;
+    var EDM_INT64 = odata.EDM_INT64;
+    var EDM_SBYTE = odata.EDM_SBYTE;
+    var EDM_SINGLE = odata.EDM_SINGLE;
+    var EDM_STRING = odata.EDM_STRING;
+    var EDM_TIME = odata.EDM_TIME;
+    var GEOJSON_POINT = odata.GEOJSON_POINT;
+    var GEOJSON_LINESTRING = odata.GEOJSON_LINESTRING;
+    var GEOJSON_POLYGON = odata.GEOJSON_POLYGON;
+    var GEOJSON_MULTIPOINT = odata.GEOJSON_MULTIPOINT;
+    var GEOJSON_MULTILINESTRING = odata.GEOJSON_MULTILINESTRING;
+    var GEOJSON_MULTIPOLYGON = odata.GEOJSON_MULTIPOLYGON;
+    var GEOJSON_GEOMETRYCOLLECTION = odata.GEOJSON_GEOMETRYCOLLECTION;
+    var formatDateTimeOffset = odata.formatDateTimeOffset;
+    var formatDuration = odata.formatDuration;
+    var getCollectionType = odata.getCollectionType;
+    var gmlNewODataSpatialValue = odata.gmlNewODataSpatialValue;
+    var gmlReadODataSpatialValue = odata.gmlReadODataSpatialValue;
+    var gmlXmlNs = odata.gmlXmlNs;
+    var handler = odata.handler;
+    var isCollection = odata.isCollection;
+    var isCollectionType = odata.isCollectionType;
+    var isDeferred = odata.isDeferred;
+    var isNamedStream = odata.isNamedStream;
+    var isGeographyEdmType = odata.isGeographyEdmType;
+    var isGeometryEdmType = odata.isGeometryEdmType;
+    var isPrimitive = odata.isPrimitive;
+    var isPrimitiveEdmType = odata.isPrimitiveEdmType;
+    var lookupComplexType = odata.lookupComplexType;
+    var lookupProperty = odata.lookupProperty;
+    var maxVersion = odata.maxVersion;
+    var navigationPropertyKind = odata.navigationPropertyKind;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var parseBool = odata.parseBool;
+    var parseDateTime = odata.parseDateTime;
+    var parseDateTimeOffset = odata.parseDateTimeOffset;
+    var parseDuration = odata.parseDuration;
+    var parseTimezone = odata.parseTimezone;
+
+    // CONTENT START
+
+    var xmlMediaType = "application/xml";
+
+    var ado = http + "schemas.microsoft.com/ado/";      // http://schemas.microsoft.com/ado/
+    var adoDs = ado + "2007/08/dataservices";           // http://schemas.microsoft.com/ado/2007/08/dataservices
+
+    var edmxNs = ado + "2007/06/edmx";                  // http://schemas.microsoft.com/ado/2007/06/edmx
+    var edmNs1 = ado + "2006/04/edm";                   // http://schemas.microsoft.com/ado/2006/04/edm
+    var edmNs1_1 = ado + "2007/05/edm";                 // http://schemas.microsoft.com/ado/2007/05/edm
+    var edmNs1_2 = ado + "2008/01/edm";                 // http://schemas.microsoft.com/ado/2008/01/edm
+
+    // There are two valid namespaces for Edm 2.0
+    var edmNs2a = ado + "2008/09/edm";                  // http://schemas.microsoft.com/ado/2008/09/edm
+    var edmNs2b = ado + "2009/08/edm";                  // http://schemas.microsoft.com/ado/2009/08/edm
+
+    var edmNs3 = ado + "2009/11/edm";                   // http://schemas.microsoft.com/ado/2009/11/edm
+
+    var odataXmlNs = adoDs;                             // http://schemas.microsoft.com/ado/2007/08/dataservices
+    var odataMetaXmlNs = adoDs + "/metadata";           // http://schemas.microsoft.com/ado/2007/08/dataservices/metadata
+    var odataRelatedPrefix = adoDs + "/related/";       // http://schemas.microsoft.com/ado/2007/08/dataservices/related
+    var odataScheme = adoDs + "/scheme";                // http://schemas.microsoft.com/ado/2007/08/dataservices/scheme
+
+    var odataPrefix = "d";
+    var odataMetaPrefix = "m";
+
+    var createAttributeExtension = function (domNode, useNamespaceURI) {
+        /// <summary>Creates an extension object for the specified attribute.</summary>
+        /// <param name="domNode">DOM node for the attribute.</param>
+        /// <param name="useNamespaceURI" type="Boolean">Flag indicating if the namespaceURI property should be added to the extension object instead of the namespace property.</param>
+        /// <remarks>
+        ///    The useNamespaceURI flag is used to prevent a breaking change from older versions of datajs in which extension
+        ///    objects created for Atom extension attributes have the namespaceURI property instead of the namespace one.
+        ///    
+        ///    This flag and the namespaceURI property should be deprecated in future major versions of the library.
+        /// </remarks>
+        /// <returns type="Object">The new extension object.</returns>
+
+        djsassert(domNode.nodeType === 2, "createAttributeExtension - domNode is not an attribute node!!");
+        var extension = { name: xmlLocalName(domNode), value: domNode.value };
+        extension[useNamespaceURI ? "namespaceURI" : "namespace"] = xmlNamespaceURI(domNode);
+
+        return extension;
+    };
+
+    var createElementExtension = function (domNode, useNamespaceURI) {
+        /// <summary>Creates an extension object for the specified element.</summary>
+        /// <param name="domNode">DOM node for the element.</param>
+        /// <param name="useNamespaceURI" type="Boolean">Flag indicating if the namespaceURI property should be added to the extension object instead of the namespace property.</param>
+        /// <remarks>
+        ///    The useNamespaceURI flag is used to prevent a breaking change from older versions of datajs in which extension
+        ///    objects created for Atom extension attributes have the namespaceURI property instead of the namespace one.
+        ///    
+        ///    This flag and the namespaceURI property should be deprecated in future major versions of the library.
+        /// </remarks>
+        /// <returns type="Object">The new extension object.</returns>
+
+        djsassert(domNode.nodeType === 1, "createAttributeExtension - domNode is not an element node!!");
+
+        var attributeExtensions = [];
+        var childrenExtensions = [];
+
+        var i, len;
+        var attributes = domNode.attributes;
+        for (i = 0, len = attributes.length; i < len; i++) {
+            var attr = attributes[i];
+            if (xmlNamespaceURI(attr) !== xmlnsNS) {
+                attributeExtensions.push(createAttributeExtension(attr, useNamespaceURI));
+            }
+        }
+
+        var child = domNode.firstChild;
+        while (child != null) {
+            if (child.nodeType === 1) {
+                childrenExtensions.push(createElementExtension(child, useNamespaceURI));
+            }
+            child = child.nextSibling;
+        }
+
+        var extension = {
+            name: xmlLocalName(domNode),
+            value: xmlInnerText(domNode),
+            attributes: attributeExtensions,
+            children: childrenExtensions
+        };
+
+        extension[useNamespaceURI ? "namespaceURI" : "namespace"] = xmlNamespaceURI(domNode);
+        return extension;
+    };
+
+    var isCollectionItemElement = function (domElement) {
+        /// <summary>Checks whether the domElement is a collection item.</summary>
+        /// <param name="domElement">DOM element possibliy represnting a collection item.</param>
+        /// <returns type="Boolean">True if the domeElement belongs to the OData metadata namespace and its local name is "element"; false otherwise.</returns>
+
+        return xmlNamespaceURI(domElement) === odataXmlNs && xmlLocalName(domElement) === "element";
+    };
+
+    var makePropertyMetadata = function (type, extensions) {
+        /// <summary>Creates an object containing property metadata.</summary>
+        /// <param type="String" name="type">Property type name.</param>
+        /// <param type="Array" name="extensions">Array of attribute extension objects.</param>
+        /// <returns type="Object">Property metadata object cotaining type and extensions fields.</returns>
+
+        return { type: type, extensions: extensions };
+    };
+
+    var odataInferTypeFromPropertyXmlDom = function (domElement) {
+        /// <summary>Infers type of a property based on its xml DOM tree.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <returns type="String">Inferred type name; null if the type cannot be determined.</returns>
+
+        if (xmlFirstChildElement(domElement, gmlXmlNs)) {
+            return EDM_GEOMETRY;
+        }
+
+        var firstChild = xmlFirstChildElement(domElement, odataXmlNs);
+        if (!firstChild) {
+            return EDM_STRING;
+        }
+
+        if (isCollectionItemElement(firstChild)) {
+            var sibling = xmlSiblingElement(firstChild, odataXmlNs);
+            if (sibling && isCollectionItemElement(sibling)) {
+                // More than one <element> tag have been found, it can be safely assumed that this is a collection property.
+                return "Collection()";
+            }
+        }
+
+        return null;
+    };
+
+    var xmlReadODataPropertyAttributes = function (domElement) {
+        /// <summary>Reads the attributes of a property DOM element in an OData XML document.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <returns type="Object">Object containing the property type, if it is null, and its attribute extensions.</returns>
+
+        var type = null;
+        var isNull = false;
+        var extensions = [];
+
+        xmlAttributes(domElement, function (attribute) {
+            var nsURI = xmlNamespaceURI(attribute);
+            var localName = xmlLocalName(attribute);
+            var value = xmlNodeValue(attribute);
+
+            if (nsURI === odataMetaXmlNs) {
+                if (localName === "null") {
+                    isNull = (value.toLowerCase() === "true");
+                    return;
+                }
+
+                if (localName === "type") {
+                    type = value;
+                    return;
+                }
+            }
+
+            if (nsURI !== xmlNS && nsURI !== xmlnsNS) {
+                extensions.push(createAttributeExtension(attribute, true));
+                return;
+            }
+        });
+
+        return { type: (!type && isNull ? EDM_STRING : type), isNull: isNull, extensions: extensions };
+    };
+
+    var xmlReadODataProperty = function (domElement) {
+        /// <summary>Reads a property DOM element in an OData XML document.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <returns type="Object">Object with name, value, and metadata for the property.</returns>
+
+        if (xmlNamespaceURI(domElement) !== odataXmlNs) {
+            // domElement is not a proprety element because it is not in the odata xml namespace.
+            return null;
+        }
+
+        var propertyName = xmlLocalName(domElement);
+        var propertyAttributes = xmlReadODataPropertyAttributes(domElement);
+
+        var propertyIsNull = propertyAttributes.isNull;
+        var propertyType = propertyAttributes.type;
+
+        var propertyMetadata = makePropertyMetadata(propertyType, propertyAttributes.extensions);
+        var propertyValue = propertyIsNull ? null : xmlReadODataPropertyValue(domElement, propertyType, propertyMetadata);
+
+        return { name: propertyName, value: propertyValue, metadata: propertyMetadata };
+    };
+
+    var xmlReadODataPropertyValue = function (domElement, propertyType, propertyMetadata) {
+        /// <summary>Reads the value of a property in an OData XML document.</summary>
+        /// <param name="domElement">DOM element for the property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
+        /// <returns>Property value.</returns>
+
+        if (!propertyType) {
+            propertyType = odataInferTypeFromPropertyXmlDom(domElement);
+            propertyMetadata.type = propertyType;
+        }
+
+        var isGeograhpyType = isGeographyEdmType(propertyType);
+        if (isGeograhpyType || isGeometryEdmType(propertyType)) {
+            return xmlReadODataSpatialPropertyValue(domElement, propertyType, isGeograhpyType);
+        }
+
+        if (isPrimitiveEdmType(propertyType)) {
+            return xmlReadODataEdmPropertyValue(domElement, propertyType);
+        }
+
+        if (isCollectionType(propertyType)) {
+            return xmlReadODataCollectionPropertyValue(domElement, propertyType, propertyMetadata);
+        }
+
+        return xmlReadODataComplexPropertyValue(domElement, propertyType, propertyMetadata);
+    };
+
+    var xmlReadODataSpatialPropertyValue = function (domElement, propertyType, isGeography) {
+        /// <summary>Reads the value of an spatial property in an OData XML document.</summary>
+        /// <param name="property">DOM element for the spatial property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="isGeography" type="Boolean" Optional="True">Flag indicating if the value uses a geographic reference system or not.<param>
+        /// <remarks>
+        ///    When using a geographic reference system, the first component of all the coordinates in each <pos> element in the GML DOM tree is the Latitude and
+        ///    will be deserialized as the second component of each <pos> element in the GML DOM tree.
+        /// </remarks>
+        /// <returns>Spatial property value in GeoJSON format.</returns>
+
+        var gmlRoot = xmlFirstChildElement(domElement, gmlXmlNs);
+        djsassert(gmlRoot, "xmlReadODataSpatialPropertyValue - domElement doesn't have a child element that belongs to the gml namespace!!");
+
+        var value = gmlReadODataSpatialValue(gmlRoot, isGeography);
+        value.__metadata = { type: propertyType };
+        return value;
+    };
+
+    var xmlReadODataEdmPropertyValue = function (domNode, propertyType) {
+        /// <summary>Reads the value of an EDM property in an OData XML document.</summary>
+        /// <param name="donNode">DOM node for the EDM property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <returns>EDM property value.</returns>
+
+        var propertyValue = xmlNodeValue(domNode) || "";
+
+        switch (propertyType) {
+            case EDM_BOOLEAN:
+                return parseBool(propertyValue);
+            case EDM_BINARY:
+            case EDM_DECIMAL:
+            case EDM_GUID:
+            case EDM_INT64:
+            case EDM_STRING:
+                return propertyValue;
+            case EDM_BYTE:
+            case EDM_INT16:
+            case EDM_INT32:
+            case EDM_SBYTE:
+                return parseInt10(propertyValue);
+            case EDM_DOUBLE:
+            case EDM_SINGLE:
+                return parseFloat(propertyValue);
+            case EDM_TIME:
+                return parseDuration(propertyValue);
+            case EDM_DATETIME:
+                return parseDateTime(propertyValue);
+            case EDM_DATETIMEOFFSET:
+                return parseDateTimeOffset(propertyValue);
+        }
+
+        return propertyValue;
+    };
+
+    var xmlReadODataComplexPropertyValue = function(domElement, propertyType, propertyMetadata) {
+        /// <summary>Reads the value of a complex type property in an OData XML document.</summary>
+        /// <param name="property">DOM element for the complex type property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
+        /// <returns type="Object">Complex type property value.</returns>
+
+        var propertyValue = { __metadata: { type: propertyType } };
+        xmlChildElements(domElement, function(child) {
+            var childProperty = xmlReadODataProperty(child);
+            var childPropertyName = childProperty.name;
+
+            propertyMetadata.properties = propertyMetadata.properties || {};
+            propertyMetadata.properties[childPropertyName] = childProperty.metadata;
+            propertyValue[childPropertyName] = childProperty.value;
+        });
+
+        return propertyValue;
+    };
+
+    var xmlReadODataCollectionPropertyValue = function (domElement, propertyType, propertyMetadata) {
+        /// <summary>Reads the value of a collection property in an OData XML document.</summary>
+        /// <param name="property">DOM element for the collection property.</param>
+        /// <param name="propertyType" type="String">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object">Object that will store metadata about the property.</param>
+        /// <returns type="Object">Collection property value.</returns>
+
+        var items = [];
+        var itemsMetadata = propertyMetadata.elements = [];
+        var collectionType = getCollectionType(propertyType);
+
+        xmlChildElements(domElement, function (child) {
+            if (isCollectionItemElement(child)) {
+                var itemAttributes = xmlReadODataPropertyAttributes(child);
+                var itemExtensions = itemAttributes.extensions;
+                var itemType = itemAttributes.type || collectionType;
+                var itemMetadata = makePropertyMetadata(itemType, itemExtensions);
+
+                var item = xmlReadODataPropertyValue(child, itemType, itemMetadata);
+
+                items.push(item);
+                itemsMetadata.push(itemMetadata);
+            }
+        });
+
+        return { __metadata: { type: propertyType === "Collection()" ? null : propertyType }, results: items };
+    };
+
+    var readODataXmlDocument = function (xmlRoot, baseURI) {
+        /// <summary>Reads an OData link(s) producing an object model in return.</summary>
+        /// <param name="xmlRoot">Top-level element to read.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
+        /// <returns type="Object">The object model representing the specified element.</returns>
+
+        if (xmlNamespaceURI(xmlRoot) === odataXmlNs) {
+            baseURI = xmlBaseURI(xmlRoot, baseURI);
+            var localName = xmlLocalName(xmlRoot);
+
+            if (localName === "links") {
+                return readLinks(xmlRoot, baseURI);
+            }
+            if (localName === "uri") {
+                return readUri(xmlRoot, baseURI);
+            }
+        }
+        return undefined;
+    };
+
+    var readLinks = function (linksElement, baseURI) {
+        /// <summary>Deserializes an OData XML links element.</summary>
+        /// <param name="linksElement">XML links element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
+        /// <returns type="Object">A new object representing the links collection.</returns>
+
+        var uris = [];
+
+        xmlChildElements(linksElement, function (child) {
+            if (xmlLocalName(child) === "uri" && xmlNamespaceURI(child) === odataXmlNs) {
+                uris.push(readUri(child, baseURI));
+            }
+        });
+
+        return { results: uris };
+    };
+
+    var readUri = function (uriElement, baseURI) {
+        /// <summary>Deserializes an OData XML uri element.</summary>
+        /// <param name="uriElement">XML uri element.</param>
+        /// <param name="baseURI" type="String">Base URI for normalizing relative URIs found in the XML payload.</param>
+        /// <returns type="Object">A new object representing the uri.</returns>
+
+        var uri = xmlInnerText(uriElement) || "";
+        return { uri: normalizeURI(uri, baseURI) };
+    };
+
+    var xmlODataInferSpatialValueGeoJsonType = function (value, edmType) {
+        /// <summary>Infers the GeoJSON type from the spatial property value and the edm type name.</summary>
+        /// <param name="value" type="Object">Spatial property value in GeoJSON format.</param>
+        /// <param name="edmType" type="String" mayBeNull="true" optional="true">Spatial property edm type.<param>
+        /// <remarks>
+        ///   If the edmType parameter is null, undefined, "Edm.Geometry" or "Edm.Geography", then the function returns
+        ///   the GeoJSON type indicated by the value's type property.
+        ///
+        ///   If the edmType parameter is specified or is not one of the base spatial types, then it is used to
+        ///   determine the GeoJSON type and the value's type property is ignored.
+        /// </remarks>
+        /// <returns>New DOM element in the GML namespace for the spatial value. </returns>
+
+        if (edmType === EDM_GEOMETRY || edmType === EDM_GEOGRAPHY) {
+            return value && value.type;
+        }
+
+        if (edmType === EDM_GEOMETRY_POINT || edmType === EDM_GEOGRAPHY_POINT) {
+            return GEOJSON_POINT;
+        }
+
+        if (edmType === EDM_GEOMETRY_LINESTRING || edmType === EDM_GEOGRAPHY_LINESTRING) {
+            return GEOJSON_LINESTRING;
+        }
+
+        if (edmType === EDM_GEOMETRY_POLYGON || edmType === EDM_GEOGRAPHY_POLYGON) {
+            return GEOJSON_POLYGON;
+        }
+
+        if (edmType === EDM_GEOMETRY_COLLECTION || edmType === EDM_GEOGRAPHY_COLLECTION) {
+            return GEOJSON_GEOMETRYCOLLECTION;
+        }
+
+        if (edmType === EDM_GEOMETRY_MULTIPOLYGON || edmType === EDM_GEOGRAPHY_MULTIPOLYGON) {
+            return GEOJSON_MULTIPOLYGON;
+        }
+
+        if (edmType === EDM_GEOMETRY_MULTILINESTRING || edmType === EDM_GEOGRAPHY_MULTILINESTRING) {
+            return GEOJSON_MULTILINESTRING;
+        }
+
+        if (edmType === EDM_GEOMETRY_MULTIPOINT || edmType === EDM_GEOGRAPHY_MULTIPOINT) {
+            return GEOJSON_MULTIPOINT;
+        }
+
+        djsassert(false, "gmlInferGeoJsonType - edm type <" + edmType + "> was unexpected!!");
+        return null;
+    };
+
+    var xmlNewODataMetaElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the OData metadata namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData metadata element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the OData metadata namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, odataMetaXmlNs, xmlQualifiedName(odataMetaPrefix, name), children);
+    };
+
+    var xmlNewODataMetaAttribute = function (dom, name, value) {
+        /// <summary>Creates a new DOM attribute in the odata namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData attribute to create.</param>
+        /// <param name="value">Attribute value.</param>
+        /// <returns>New DOM attribute in the odata namespace.</returns>
+
+        return xmlNewAttribute(dom, odataMetaXmlNs, xmlQualifiedName(odataMetaPrefix, name), value);
+    };
+
+    var xmlNewODataElement = function (dom, name, children) {
+        /// <summary>Creates a new DOM element in the OData namespace.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Local name of the OData element to create.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <returns>New DOM element in the OData namespace.</returns>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+
+        return xmlNewElement(dom, odataXmlNs, xmlQualifiedName(odataPrefix, name), children);
+    };
+
+    var xmlNewODataPrimitiveValue = function (value, typeName) {
+        /// <summary>Returns the string representation of primitive value for an OData XML document.</summary>
+        /// <param name="value">Primivite value to format.</param>
+        /// <param name="typeName" type="String" optional="true">Type name of the primitive value.</param>
+        /// <returns type="String">Formatted primitive value.</returns>
+
+        if (typeName === EDM_DATETIME || typeName === EDM_DATETIMEOFFSET || isDate(value)) {
+            return formatDateTimeOffset(value);
+        }
+        if (typeName === EDM_TIME) {
+            return formatDuration(value);
+        }
+        return value.toString();
+    };
+
+    var xmlNewODataElementInfo = function (domElement, dataServiceVersion) {
+        /// <summary>Creates an object that represents a new DOM element for an OData XML document and the data service version it requires.</summary>
+        /// <param name="domElement">New DOM element for an OData XML document.</param>
+        /// <param name="dataServiceVersion" type="String">Required data service version by the new DOM element.</param>
+        /// <returns type="Object">Object containing new DOM element and its required data service version.</returns>
+
+        return { element: domElement, dsv: dataServiceVersion };
+    };
+
+    var xmlNewODataProperty = function (dom, name, typeName, children) {
+        /// <summary>Creates a new DOM element for an entry property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="children" type="Array">Array containing DOM nodes or string values that will be added as children of the new DOM element.</param>
+        /// <remarks>
+        ///    If a value in the children collection is a string, then a new DOM text node is going to be created
+        ///    for it and then appended as a child of the new DOM Element.
+        /// </remarks>
+        /// <returns>New DOM element in the OData namespace for the entry property.</returns>
+
+        var typeAttribute = typeName ? xmlNewODataMetaAttribute(dom, "type", typeName) : null;
+        var property = xmlNewODataElement(dom, name, typeAttribute);
+        return xmlAppendChildren(property, children);
+    };
+
+    var xmlNewODataEdmProperty = function (dom, name, value, typeName) {
+        /// <summary>Creates a new DOM element for an EDM property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the EDM property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var propertyValue = xmlNewODataPrimitiveValue(value, typeName);
+        var property = xmlNewODataProperty(dom, name, typeName, propertyValue);
+        return xmlNewODataElementInfo(property, /*dataServiceVersion*/"1.0");
+    };
+
+    var xmlNewODataNullProperty = function (dom, name, typeName, model) {
+        /// <summary>Creates a new DOM element for a null property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <remarks>
+        ///     If no typeName is specified, then it will be assumed that this is a primitive type property.
+        /// </remarks>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the null property and the 
+        ///     required data service version for this property.
+        /// </returns>
+
+        var nullAttribute = xmlNewODataMetaAttribute(dom, "null", "true");
+        var property = xmlNewODataProperty(dom, name, typeName, nullAttribute);
+        var dataServiceVersion = lookupComplexType(typeName, model) ? "2.0" : "1.0";
+
+        return xmlNewODataElementInfo(property, dataServiceVersion);
+    };
+
+    var xmlNewODataCollectionProperty = function (dom, name, value, typeName, collectionMetadata, collectionModel, model) {
+        /// <summary>Creates a new DOM element for a collection property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value either as an array or an object representing a collection in the library's internal representation.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="collectionMetadata" type="Object" optional="true">Object containing metadata about the collection property.</param>
+        /// <param name="collectionModel" type="Object" optional="true">Object describing the collection property in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the collection property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var itemTypeName = getCollectionType(typeName);
+        var items = isArray(value) ? value : value.results;
+        var itemMetadata = typeName ? { type: itemTypeName} : {};
+        itemMetadata.properties = collectionMetadata.properties;
+
+        var xmlProperty = xmlNewODataProperty(dom, name, itemTypeName ? typeName : null);
+
+        var i, len;
+        for (i = 0, len = items.length; i < len; i++) {
+            var itemValue = items[i];
+            var item = xmlNewODataDataElement(dom, "element", itemValue, itemMetadata, collectionModel, model);
+
+            xmlAppendChild(xmlProperty, item.element);
+        }
+        return xmlNewODataElementInfo(xmlProperty, /*dataServiceVersion*/"3.0");
+    };
+
+    var xmlNewODataComplexProperty = function (dom, name, value, typeName, propertyMetadata, propertyModel, model) {
+        /// <summary>Creates a new DOM element for a complex type property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value">Property value as an object in the library's internal representation.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <param name="propertyMetadata" type="Object" optional="true">Object containing metadata about the complex type property.</param>
+        /// <param name="propertyModel" type="Object" optional="true">Object describing the complex type property in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the complex type property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var xmlProperty = xmlNewODataProperty(dom, name, typeName);
+        var complexTypePropertiesMetadata = propertyMetadata.properties || {};
+        var complexTypeModel = lookupComplexType(typeName, model) || {};
+
+        var dataServiceVersion = "1.0";
+
+        for (var key in value) {
+            if (key !== "__metadata") {
+                var memberValue = value[key];
+                var memberModel = lookupProperty(complexTypeModel.property, key);
+                var memberMetadata = complexTypePropertiesMetadata[key] || {};
+                var member = xmlNewODataDataElement(dom, key, memberValue, memberMetadata, memberModel, model);
+
+                dataServiceVersion = maxVersion(dataServiceVersion, member.dsv);
+                xmlAppendChild(xmlProperty, member.element);
+            }
+        }
+        return xmlNewODataElementInfo(xmlProperty, dataServiceVersion);
+    };
+
+    var xmlNewODataSpatialProperty = function (dom, name, value, typeName, isGeography) {
+        /// <summary>Creates a new DOM element for an EDM spatial property in an OData XML document.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Property name.</param>
+        /// <param name="value" type="Object">GeoJSON object containing the property value.</param>
+        /// <param name="typeName" type="String" optional="true">Property type name.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the OData namespace for the EDM property and the
+        ///     required data service version for this property.
+        /// </returns>
+
+        var geoJsonType = xmlODataInferSpatialValueGeoJsonType(value, typeName);
+
+        var gmlRoot = gmlNewODataSpatialValue(dom, value, geoJsonType, isGeography);
+        var xmlProperty = xmlNewODataProperty(dom, name, typeName, gmlRoot);
+
+        return xmlNewODataElementInfo(xmlProperty, "3.0");
+    };
+
+    var xmlNewODataDataElement = function (dom, name, value, dataItemMetadata, dataItemModel, model) {
+        /// <summary>Creates a new DOM element for a data item in an entry, complex property, or collection property.</summary>
+        /// <param name="dom">DOM document used for creating the new DOM Element.</param>
+        /// <param name="name" type="String">Data item name.</param>
+        /// <param name="value" optional="true" mayBeNull="true">Value of the data item, if any.</param>
+        /// <param name="dataItemMetadata" type="Object" optional="true">Object containing metadata about the data item.</param>
+        /// <param name="dataItemModel" type="Object" optional="true">Object describing the data item in an OData conceptual schema.</param>
+        /// <param name="model" type="Object" optional="true">Object describing an OData conceptual schema.</param>
+        /// <returns type="Object">
+        ///     Object containing the new DOM element in the appropriate namespace for the data item and the
+        ///     required data service version for it.
+        /// </returns>
+
+        var typeName = dataItemTypeName(value, dataItemMetadata, dataItemModel);
+        if (isPrimitive(value)) {
+            return xmlNewODataEdmProperty(dom, name, value, typeName || EDM_STRING);
+        }
+
+        var isGeography = isGeographyEdmType(typeName);
+        if (isGeography || isGeometryEdmType(typeName)) {
+            return xmlNewODataSpatialProperty(dom, name, value, typeName, isGeography);
+        }
+
+        if (isCollection(value, typeName)) {
+            return xmlNewODataCollectionProperty(dom, name, value, typeName, dataItemMetadata, dataItemModel, model);
+        }
+
+        if (isNamedStream(value)) {
+            return null;
+        }
+
+        // This may be a navigation property.
+        var navPropKind = navigationPropertyKind(value, dataItemModel);
+        if (navPropKind !== null) {
+            return null;
+        }
+
+        if (value === null) {
+            return xmlNewODataNullProperty(dom, name, typeName);
+        }
+
+        djsassert(isObject(value), "xmlNewODataEntryProperty - property '" + name + "' is not an object");
+        return xmlNewODataComplexProperty(dom, name, value, typeName, dataItemMetadata, dataItemModel, model);
+    };
+
+    var odataNewLinkDocument = function (data) {
+        /// <summary>Writes the specified data into an OData XML document.</summary>
+        /// <param name="data">Data to write.</param>
+        /// <returns>The root of the DOM tree built.</returns>
+
+        if (data && isObject(data)) {
+            var dom = xmlDom();
+            return xmlAppendChild(dom, xmlNewODataElement(dom, "uri", data.uri));
+        }
+        // Allow for undefined to be returned.
+    };
+
+    var xmlParser = function (handler, text) {
+        /// <summary>Parses an OData XML document.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="text" type="String">Document text.</param>
+        /// <returns>An object representation of the document; undefined if not applicable.</returns>
+
+        if (text) {
+            var doc = xmlParse(text);
+            var root = xmlFirstChildElement(doc);
+            if (root) {
+                return readODataXmlDocument(root);
+            }
+        }
+
+        // Allow for undefined to be returned.
+    };
+
+    var xmlSerializer = function (handler, data, context) {
+        /// <summary>Serializes an OData XML object into a document.</summary>
+        /// <param name="handler">This handler.</param>
+        /// <param name="data" type="Object">Representation of feed or entry.</param>
+        /// <param name="context" type="Object">Object with parsing context.</param>
+        /// <returns>A text representation of the data object; undefined if not applicable.</returns>
+
+        var cType = context.contentType = context.contentType || contentType(xmlMediaType);
+        if (cType && cType.mediaType === xmlMediaType) {
+            return xmlSerialize(odataNewLinkDocument(data));
+        }
+        return undefined;
+    };
+
+    odata.xmlHandler = handler(xmlParser, xmlSerializer, xmlMediaType, MAX_DATA_SERVICE_VERSION);
+
+    // DATAJS INTERNAL START
+    odata.adoDs = adoDs;
+    odata.createAttributeExtension = createAttributeExtension;
+    odata.createElementExtension = createElementExtension;
+    odata.edmxNs = edmxNs;
+    odata.edmNs1 = edmNs1;
+    odata.edmNs1_1 = edmNs1_1;
+    odata.edmNs1_2 = edmNs1_2
+    odata.edmNs2a = edmNs2a;
+    odata.edmNs2b = edmNs2b;
+    odata.edmNs3 = edmNs3;
+    odata.odataMetaXmlNs = odataMetaXmlNs;
+    odata.odataMetaPrefix = odataMetaPrefix;
+    odata.odataXmlNs = odataXmlNs;
+    odata.odataPrefix = odataPrefix;
+    odata.odataScheme = odataScheme;
+    odata.odataRelatedPrefix = odataRelatedPrefix;
+    odata.xmlNewODataElement = xmlNewODataElement;
+    odata.xmlNewODataElementInfo = xmlNewODataElementInfo;
+    odata.xmlNewODataMetaAttribute = xmlNewODataMetaAttribute;
+    odata.xmlNewODataMetaElement = xmlNewODataMetaElement;
+    odata.xmlNewODataDataElement = xmlNewODataDataElement;
+    odata.xmlReadODataEdmPropertyValue = xmlReadODataEdmPropertyValue;
+    odata.xmlReadODataProperty = xmlReadODataProperty;
+
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata.js b/datajs/src/odata.js
new file mode 100644
index 0000000..98b80e2
--- /dev/null
+++ b/datajs/src/odata.js
@@ -0,0 +1,158 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+
+    var assigned = datajs.assigned;
+    var defined = datajs.defined;
+    var throwErrorCallback = datajs.throwErrorCallback;
+
+    var invokeRequest = odata.invokeRequest;
+    var MAX_DATA_SERVICE_VERSION = odata.MAX_DATA_SERVICE_VERSION;
+    var prepareRequest = odata.prepareRequest;
+    var metadataParser = odata.metadataParser;
+
+    // CONTENT START
+
+    var handlers = [odata.jsonHandler, odata.atomHandler, odata.xmlHandler, odata.textHandler];
+
+    var dispatchHandler = function (handlerMethod, requestOrResponse, context) {
+        /// <summary>Dispatches an operation to handlers.</summary>
+        /// <param name="handlerMethod" type="String">Name of handler method to invoke.</param>
+        /// <param name="requestOrResponse" type="Object">request/response argument for delegated call.</param>
+        /// <param name="context" type="Object">context argument for delegated call.</param>
+
+        var i, len;
+        for (i = 0, len = handlers.length; i < len && !handlers[i][handlerMethod](requestOrResponse, context); i++) {
+        }
+
+        if (i === len) {
+            throw { message: "no handler for data" };
+        }
+    };
+
+    odata.defaultSuccess = function (data) {
+        /// <summary>Default success handler for OData.</summary>
+        /// <param name="data">Data to process.</param>
+
+        window.alert(window.JSON.stringify(data));
+    };
+
+    odata.defaultError = throwErrorCallback;
+
+    odata.defaultHandler = {
+        read: function (response, context) {
+            /// <summary>Reads the body of the specified response by delegating to JSON and ATOM handlers.</summary>
+            /// <param name="response">Response object.</param>
+            /// <param name="context">Operation context.</param>
+
+            if (response && assigned(response.body) && response.headers["Content-Type"]) {
+                dispatchHandler("read", response, context);
+            }
+        },
+
+        write: function (request, context) {
+            /// <summary>Write the body of the specified request by delegating to JSON and ATOM handlers.</summary>
+            /// <param name="request">Reques tobject.</param>
+            /// <param name="context">Operation context.</param>
+
+            dispatchHandler("write", request, context);
+        },
+
+        maxDataServiceVersion: MAX_DATA_SERVICE_VERSION,
+        accept: "application/atomsvc+xml;q=0.8, application/json;odata=fullmetadata;q=0.7, application/json;q=0.5, */*;q=0.1"
+    };
+
+    odata.defaultMetadata = [];
+
+    odata.read = function (urlOrRequest, success, error, handler, httpClient, metadata) {
+        /// <summary>Reads data from the specified URL.</summary>
+        /// <param name="urlOrRequest">URL to read data from.</param>
+        /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
+        /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
+        /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
+        /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
+        /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
+
+        var request;
+        if (urlOrRequest instanceof String || typeof urlOrRequest === "string") {
+            request = { requestUri: urlOrRequest };
+        } else {
+            request = urlOrRequest;
+        }
+
+        return odata.request(request, success, error, handler, httpClient, metadata);
+    };
+
+    odata.request = function (request, success, error, handler, httpClient, metadata) {
+        /// <summary>Sends a request containing OData payload to a server.</summary>
+        /// <param name="request" type="Object">Object that represents the request to be sent.</param>
+        /// <param name="success" type="Function" optional="true">Callback for a successful read operation.</param>
+        /// <param name="error" type="Function" optional="true">Callback for handling errors.</param>
+        /// <param name="handler" type="Object" optional="true">Handler for data serialization.</param>
+        /// <param name="httpClient" type="Object" optional="true">HTTP client layer.</param>
+        /// <param name="metadata" type="Object" optional="true">Conceptual metadata for this request.</param>
+
+        success = success || odata.defaultSuccess;
+        error = error || odata.defaultError;
+        handler = handler || odata.defaultHandler;
+        httpClient = httpClient || odata.defaultHttpClient;
+        metadata = metadata || odata.defaultMetadata;
+
+        // Augment the request with additional defaults.
+        request.recognizeDates = defined(request.recognizeDates, odata.jsonHandler.recognizeDates);
+        request.callbackParameterName = defined(request.callbackParameterName, odata.defaultHttpClient.callbackParameterName);
+        request.formatQueryString = defined(request.formatQueryString, odata.defaultHttpClient.formatQueryString);
+        request.enableJsonpCallback = defined(request.enableJsonpCallback, odata.defaultHttpClient.enableJsonpCallback);
+        request.useJsonLight = defined(request.useJsonLight, odata.jsonHandler.enableJsonpCallback);
+        request.inferJsonLightFeedAsObject = defined(request.inferJsonLightFeedAsObject, odata.jsonHandler.inferJsonLightFeedAsObject);
+
+        // Create the base context for read/write operations, also specifying complete settings.
+        var context = {
+            metadata: metadata,
+            recognizeDates: request.recognizeDates,
+            callbackParameterName: request.callbackParameterName,
+            formatQueryString: request.formatQueryString,
+            enableJsonpCallback: request.enableJsonpCallback,
+            useJsonLight: request.useJsonLight,
+            inferJsonLightFeedAsObject: request.inferJsonLightFeedAsObject
+        };
+
+        try {
+            prepareRequest(request, handler, context);
+            return invokeRequest(request, success, error, handler, httpClient, context);
+        } catch (err) {
+            error(err);
+        }
+    };
+
+    odata.parseMetadata = function (csdlMetadataDocument) {
+        /// <summary>Parses the csdl metadata to DataJS metatdata format. This method can be used when the metadata is retrieved using something other than DataJS</summary>
+        /// <param name="atomMetadata" type="string">A string that represents the entire csdl metadata.</param>
+        /// <returns type="Object">An object that has the representation of the metadata in Datajs format.</returns>
+
+        return metadataParser(null, csdlMetadataDocument);
+    };
+
+    // Configure the batch handler to use the default handler for the batch parts.
+    odata.batchHandler.partHandler = odata.defaultHandler;
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/store-dom.js
----------------------------------------------------------------------
diff --git a/datajs/src/store-dom.js b/datajs/src/store-dom.js
new file mode 100644
index 0000000..bf8a887
--- /dev/null
+++ b/datajs/src/store-dom.js
@@ -0,0 +1,320 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store-dom.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // Imports.
+    var throwErrorCallback = datajs.throwErrorCallback;
+    var delay = datajs.delay;
+
+    // CONTENT START
+
+    var localStorage = null;
+
+    var domStoreDateToJSON = function () {
+        /// <summary>Converts a Date object into an object representation friendly to JSON serialization.</summary>
+        /// <returns type="Object">Object that represents the Date.</returns>
+        /// <remarks>
+        ///   This method is used to override the Date.toJSON method and is called only by
+        ///   JSON.stringify.  It should never be called directly.
+        /// </remarks>
+
+        var newValue = { v: this.valueOf(), t: "[object Date]" };
+        // Date objects might have extra properties on them so we save them.
+        for (var name in this) {
+            newValue[name] = this[name];
+        }
+        return newValue;
+    };
+
+    var domStoreJSONToDate = function (_, value) {
+        /// <summary>JSON reviver function for converting an object representing a Date in a JSON stream to a Date object</summary>
+        /// <param value="Object">Object to convert.</param>
+        /// <returns type="Date">Date object.</returns>
+        /// <remarks>
+        ///   This method is used during JSON parsing and invoked only by the reviver function.
+        ///   It should never be called directly.
+        /// </remarks>
+
+        if (value && value.t === "[object Date]") {
+            var newValue = new Date(value.v);
+            for (var name in value) {
+                if (name !== "t" && name !== "v") {
+                    newValue[name] = value[name];
+                }
+            }
+            value = newValue;
+        }
+        return value;
+    };
+
+    var qualifyDomStoreKey = function (store, key) {
+        /// <summary>Qualifies the key with the name of the store.</summary>
+        /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
+        /// <param name="key" type="String">Key string.</param>
+        /// <returns type="String">Fully qualified key string.</returns>
+
+        return store.name + "#!#" + key;
+    };
+
+    var unqualifyDomStoreKey = function (store, key) {
+        /// <summary>Gets the key part of a fully qualified key string.</summary>
+        /// <param name="store" type="Object">Store object whose name will be used for qualifying the key.</param>
+        /// <param name="key" type="String">Fully qualified key string.</param>
+        /// <returns type="String">Key part string</returns>
+
+        return key.replace(store.name + "#!#", "");
+    };
+
+    var DomStore = function (name) {
+        /// <summary>Constructor for store objects that use DOM storage as the underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        this.name = name;
+    };
+
+    DomStore.create = function (name) {
+        /// <summary>Creates a store object that uses DOM Storage as its underlying mechanism.</summary>
+        /// <param name="name" type="String">Store name.</param>
+        /// <returns type="Object">Store object.</returns>
+
+        if (DomStore.isSupported()) {
+            localStorage = localStorage || window.localStorage;
+            return new DomStore(name);
+        }
+
+        throw { message: "Web Storage not supported by the browser" };
+    };
+
+    DomStore.isSupported = function () {
+        /// <summary>Checks whether the underlying mechanism for this kind of store objects is supported by the browser.</summary>
+        /// <returns type="Boolean">True if the mechanism is supported by the browser; otherwise false.</summary>
+        return !!window.localStorage;
+    };
+
+    DomStore.prototype.add = function (key, value, success, error) {
+        /// <summary>Adds a new value identified by a key to the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="value">Value that is going to be added to the store.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful add operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///    This method errors out if the store already contains the specified key.
+        /// </remarks>
+
+        error = error || this.defaultError;
+        var store = this;
+        this.contains(key, function (contained) {
+            if (!contained) {
+                store.addOrUpdate(key, value, success, error);
+            } else {
+                delay(error, { message: "key already exists", key: key });
+            }
+        }, error);
+    };
+
+    DomStore.prototype.addOrUpdate = function (key, value, success, error) {
+        /// <summary>Adds or updates a value identified by a key to the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="value">Value that is going to be added or updated to the store.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful add or update operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///   This method will overwrite the key's current value if it already exists in the store; otherwise it simply adds the new key and value.
+        /// </remarks>
+
+        error = error || this.defaultError;
+
+        if (key instanceof Array) {
+            error({ message: "Array of keys not supported" });
+        } else {
+            var fullKey = qualifyDomStoreKey(this, key);
+            var oldDateToJSON = Date.prototype.toJSON;
+            try {
+                var storedValue = value;
+                if (storedValue !== undefined) {
+                    // Dehydrate using json
+                    Date.prototype.toJSON = domStoreDateToJSON;
+                    storedValue = window.JSON.stringify(value);
+                }
+                // Save the json string.
+                localStorage.setItem(fullKey, storedValue);
+                delay(success, key, value);
+            }
+            catch (e) {
+                if (e.code === 22 || e.number === 0x8007000E) {
+                    delay(error, { name: "QUOTA_EXCEEDED_ERR", error: e });
+                } else {
+                    delay(error, e);
+                }
+            }
+            finally {
+                Date.prototype.toJSON = oldDateToJSON;
+            }
+        }
+    };
+
+    DomStore.prototype.clear = function (success, error) {
+        /// <summary>Removes all the data associated with this store object.</summary>
+        /// <param name="success" type="Function" optional="no">Callback for a successful clear operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///    In case of an error, this method will not restore any keys that might have been deleted at that point.
+        /// </remarks>
+
+        error = error || this.defaultError;
+        try {
+            var i = 0, len = localStorage.length;
+            while (len > 0 && i < len) {
+                var fullKey = localStorage.key(i);
+                var key = unqualifyDomStoreKey(this, fullKey);
+                if (fullKey !== key) {
+                    localStorage.removeItem(fullKey);
+                    len = localStorage.length;
+                } else {
+                    i++;
+                }
+            }
+            delay(success);
+        }
+        catch (e) {
+            delay(error, e);
+        }
+    };
+
+    DomStore.prototype.close = function () {
+        /// <summary>This function does nothing in DomStore as it does not have a connection model</summary>
+    };
+
+    DomStore.prototype.contains = function (key, success, error) {
+        /// <summary>Checks whether a key exists in the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="success" type="Function" optional="no">Callback indicating whether the store contains the key or not.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        error = error || this.defaultError;
+        try {
+            var fullKey = qualifyDomStoreKey(this, key);
+            var value = localStorage.getItem(fullKey);
+            delay(success, value !== null);
+        } catch (e) {
+            delay(error, e);
+        }
+    };
+
+    DomStore.prototype.defaultError = throwErrorCallback;
+
+    DomStore.prototype.getAllKeys = function (success, error) {
+        /// <summary>Gets all the keys that exist in the store.</summary>
+        /// <param name="success" type="Function" optional="no">Callback for a successful get operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+
+        error = error || this.defaultError;
+
+        var results = [];
+        var i, len;
+
+        try {
+            for (i = 0, len = localStorage.length; i < len; i++) {
+                var fullKey = localStorage.key(i);
+                var key = unqualifyDomStoreKey(this, fullKey);
+                if (fullKey !== key) {
+                    results.push(key);
+                }
+            }
+            delay(success, results);
+        }
+        catch (e) {
+            delay(error, e);
+        }
+    };
+
+    /// <summary>Identifies the underlying mechanism used by the store.</summary>
+    DomStore.prototype.mechanism = "dom";
+
+    DomStore.prototype.read = function (key, success, error) {
+        /// <summary>Reads the value associated to a key in the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful reads operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        error = error || this.defaultError;
+
+        if (key instanceof Array) {
+            error({ message: "Array of keys not supported" });
+        } else {
+            try {
+                var fullKey = qualifyDomStoreKey(this, key);
+                var value = localStorage.getItem(fullKey);
+                if (value !== null && value !== "undefined") {
+                    // Hydrate using json
+                    value = window.JSON.parse(value, domStoreJSONToDate);
+                }
+                else {
+                    value = undefined;
+                }
+                delay(success, key, value);
+            } catch (e) {
+                delay(error, e);
+            }
+        }
+    };
+
+    DomStore.prototype.remove = function (key, success, error) {
+        /// <summary>Removes a key and its value from the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful remove operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        error = error || this.defaultError;
+
+        if (key instanceof Array) {
+            error({ message: "Batches not supported" });
+        } else {
+            try {
+                var fullKey = qualifyDomStoreKey(this, key);
+                localStorage.removeItem(fullKey);
+                delay(success);
+            } catch (e) {
+                delay(error, e);
+            }
+        }
+    };
+
+    DomStore.prototype.update = function (key, value, success, error) {
+        /// <summary>Updates the value associated to a key in the store.</summary>
+        /// <param name="key" type="String">Key string.</param>
+        /// <param name="value">New value.</param>
+        /// <param name="success" type="Function" optional="no">Callback for a successful update operation.</param>
+        /// <param name="error" type="Function" optional="yes">Callback for handling errors. If not specified then store.defaultError is invoked.</param>
+        /// <remarks>
+        ///    This method errors out if the specified key is not found in the store.
+        /// </remarks>
+
+        error = error || this.defaultError;
+        var store = this;
+        this.contains(key, function (contained) {
+            if (contained) {
+                store.addOrUpdate(key, value, success, error);
+            } else {
+                delay(error, { message: "key not found", key: key });
+            }
+        }, error);
+    };
+
+    // DATAJS INTERNAL START
+    datajs.DomStore = DomStore;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/store-indexeddb.js
----------------------------------------------------------------------
diff --git a/datajs/src/store-indexeddb.js b/datajs/src/store-indexeddb.js
new file mode 100644
index 0000000..b56828f
--- /dev/null
+++ b/datajs/src/store-indexeddb.js
@@ -0,0 +1,417 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// store-indexeddb.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+
+    // Imports.
+    var throwErrorCallback = datajs.throwErrorCallback;
+    var delay = datajs.delay;
+
+    // CONTENT START
+
+    var indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB;
+    var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
+    var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {};
+
+    var IDBT_READ_ONLY = IDBTransaction.READ_ONLY || "readonly";
+    var IDBT_READ_WRITE = IDBTransaction.READ_WRITE || "readwrite";
+
+    var getError = function (error, defaultError) {
+        /// <summary>Returns either a specific error handler or the default error handler</summary>
+        /// <param name="error" type="Function">The specific error handler</param>
+        /// <param name="defaultError" type="Function">The default error handler</param>
+        /// <returns type="Function">The error callback</returns>
+
+        return function (e) {
+            var errorFunc = error || defaultError;
+            if (!errorFunc) {
+                return;
+            }
+
+            // Old api quota exceeded error support.
+            if (Object.prototype.toString.call(e) === "[object IDBDatabaseException]") {
+                if (e.code === 11 /* IndexedDb disk quota exceeded */) {
+                    errorFunc({ name: "QuotaExceededError", error: e });
+                    return;
+                }
+                errorFunc(e);
+                return;
+            }
+
+            var errName;
+            try {
+                var errObj = e.target.error || e;
+                errName = errObj.name;
+            } catch (ex) {
+                errName = (e.type === "blocked") ? "IndexedDBBlocked" : "UnknownError";
+            }
+            errorFunc({ name: errName, error: e });
+        };
+    };
+
+    var openStoreDb = function (store, success, error) {
+        /// <summary>Opens the store object's indexed db database.</summary>
+        /// <param name="store" type="IndexedDBStore">The store object</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+
+        var storeName = store.name;
+        var dbName = "_datajs_" + storeName;
+
+        var request = indexedDB.open(dbName);
+        request.onblocked = error;
+        request.onerror = error;
+
+        request.onupgradeneeded = function () {
+            var db = request.result;
+            if (!db.objectStoreNames.contains(storeName)) {
+                db.createObjectStore(storeName);
+            }
+        };
+
+        request.onsuccess = function (event) {
+            var db = request.result;
+            if (!db.objectStoreNames.contains(storeName)) {
+                // Should we use the old style api to define the database schema?
+                if ("setVersion" in db) {
+                    var versionRequest = db.setVersion("1.0");
+                    versionRequest.onsuccess = function () {
+                        var transaction = versionRequest.transaction;
+                        transaction.oncomplete = function () {
+                            success(db);
+                        };
+                        db.createObjectStore(storeName, null, false);
+                    };
+                    versionRequest.onerror = error;
+                    versionRequest.onblocked = error;
+                    return;
+                }
+
+                // The database doesn't have the expected store.
+                // Fabricate an error object for the event for the schema mismatch
+                // and error out.
+                event.target.error = { name: "DBSchemaMismatch" };
+                error(event);
+                return;
+            }
+
+            db.onversionchange = function(event) {
+                event.target.close();
+            };
+            success(db);
+        };
+    };
+
+    var openTransaction = function (store, mode, success, error) {
+        /// <summary>Opens a new transaction to the store</summary>
+        /// <param name="store" type="IndexedDBStore">The store object</param>
+        /// <param name="mode" type="Short">The read/write mode of the transaction (constants from IDBTransaction)</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+
+        var storeName = store.name;
+        var storeDb = store.db;
+        var errorCallback = getError(error, store.defaultError);
+
+        if (storeDb) {
+            success(storeDb.transaction(storeName, mode));
+            return;
+        }
+
+        openStoreDb(store, function (db) {
+            store.db = db;
+            success(db.transaction(storeName, mode));
+        }, errorCallback);
+    };
+
+    var IndexedDBStore = function (name) {
+        /// <summary>Creates a new IndexedDBStore.</summary>
+        /// <param name="name" type="String">The name of the store.</param>
+        /// <returns type="Object">The new IndexedDBStore.</returns>
+        this.name = name;
+    };
+
+    IndexedDBStore.create = function (name) {
+        /// <summary>Creates a new IndexedDBStore.</summary>
+        /// <param name="name" type="String">The name of the store.</param>
+        /// <returns type="Object">The new IndexedDBStore.</returns>
+        if (IndexedDBStore.isSupported()) {
+            return new IndexedDBStore(name);
+        }
+
+        throw { message: "IndexedDB is not supported on this browser" };
+    };
+
+    IndexedDBStore.isSupported = function () {
+        /// <summary>Returns whether IndexedDB is supported.</summary>
+        /// <returns type="Boolean">True if IndexedDB is supported, false otherwise.</returns>
+        return !!indexedDB;
+    };
+
+    IndexedDBStore.prototype.add = function (key, value, success, error) {
+        /// <summary>Adds a key/value pair to the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="value" type="Object">The value</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = [];
+        var values = [];
+
+        if (key instanceof Array) {
+            keys = key;
+            values = value;
+        } else {
+            keys = [key];
+            values = [value];
+        }
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onabort = getError(error, defaultError, key, "add");
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(key, value);
+                }
+            };
+
+            for (var i = 0; i < keys.length && i < values.length; i++) {
+                transaction.objectStore(name).add({ v: values[i] }, keys[i]);
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.addOrUpdate = function (key, value, success, error) {
+        /// <summary>Adds or updates a key/value pair in the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="value" type="Object">The value</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = [];
+        var values = [];
+
+        if (key instanceof Array) {
+            keys = key;
+            values = value;
+        } else {
+            keys = [key];
+            values = [value];
+        }
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onabort = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(key, value);
+                }
+            };
+
+            for (var i = 0; i < keys.length && i < values.length; i++) {
+                var record = { v: values[i] };
+                transaction.objectStore(name).put(record, keys[i]);
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.clear = function (success, error) {
+        /// <summary>Clears the store</summary>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onerror = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                success();
+            };
+
+            transaction.objectStore(name).clear();
+        }, error);
+    };
+
+    IndexedDBStore.prototype.close = function () {
+        /// <summary>Closes the connection to the database</summary>
+        if (this.db) {
+            this.db.close();
+            this.db = null;
+        }
+    };
+
+    IndexedDBStore.prototype.contains = function (key, success, error) {
+        /// <summary>Returns whether the store contains a key</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        openTransaction(this, IDBT_READ_ONLY, function (transaction) {
+            var objectStore = transaction.objectStore(name);
+            var request = objectStore["get"](key);
+
+            transaction.oncomplete = function () {
+                success(!!request.result);
+            };
+            transaction.onerror = getError(error, defaultError);
+        }, error);
+    };
+
+    IndexedDBStore.prototype.defaultError = throwErrorCallback;
+
+    IndexedDBStore.prototype.getAllKeys = function (success, error) {
+        /// <summary>Gets all the keys from the store</summary>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            var results = [];
+
+            transaction.oncomplete = function () {
+                success(results);
+            };
+
+            var request = transaction.objectStore(name).openCursor();
+
+            request.onerror = getError(error, defaultError);
+            request.onsuccess = function (event) {
+                var cursor = event.target.result;
+                if (cursor) {
+                    results.push(cursor.key);
+                    // Some tools have issues because continue is a javascript reserved word.
+                    cursor["continue"].call(cursor);
+                }
+            };
+        }, error);
+    };
+
+    /// <summary>Identifies the underlying mechanism used by the store.</summary>
+    IndexedDBStore.prototype.mechanism = "indexeddb";
+
+    IndexedDBStore.prototype.read = function (key, success, error) {
+        /// <summary>Reads the value for the specified key</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        /// <remarks>If the key does not exist, the success handler will be called with value = undefined</remarks>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = (key instanceof Array) ? key : [key];
+
+        openTransaction(this, IDBT_READ_ONLY, function (transaction) {
+            var values = [];
+
+            transaction.onerror = getError(error, defaultError, key, "read");
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(keys[0], values[0]);
+                }
+            };
+
+            for (var i = 0; i < keys.length; i++) {
+                // Some tools have issues because get is a javascript reserved word. 
+                var objectStore = transaction.objectStore(name);
+                var request = objectStore["get"].call(objectStore, keys[i]);
+                request.onsuccess = function (event) {
+                    var record = event.target.result;
+                    values.push(record ? record.v : undefined);
+                };
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.remove = function (key, success, error) {
+        /// <summary>Removes the specified key from the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = (key instanceof Array) ? key : [key];
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onerror = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                success();
+            };
+
+            for (var i = 0; i < keys.length; i++) {
+                // Some tools have issues because continue is a javascript reserved word.
+                var objectStore = transaction.objectStore(name);
+                objectStore["delete"].call(objectStore, keys[i]);
+            }
+        }, error);
+    };
+
+    IndexedDBStore.prototype.update = function (key, value, success, error) {
+        /// <summary>Updates a key/value pair in the store</summary>
+        /// <param name="key" type="String">The key</param>
+        /// <param name="value" type="Object">The value</param>
+        /// <param name="success" type="Function">The success callback</param>
+        /// <param name="error" type="Function">The error callback</param>
+        var name = this.name;
+        var defaultError = this.defaultError;
+        var keys = [];
+        var values = [];
+
+        if (key instanceof Array) {
+            keys = key;
+            values = value;
+        } else {
+            keys = [key];
+            values = [value];
+        }
+
+        openTransaction(this, IDBT_READ_WRITE, function (transaction) {
+            transaction.onabort = getError(error, defaultError);
+            transaction.oncomplete = function () {
+                if (key instanceof Array) {
+                    success(keys, values);
+                } else {
+                    success(key, value);
+                }
+            };
+
+            for (var i = 0; i < keys.length && i < values.length; i++) {
+                var request = transaction.objectStore(name).openCursor(IDBKeyRange.only(keys[i]));
+                var record = { v: values[i] };
+                request.pair = { key: keys[i], value: record };
+                request.onsuccess = function (event) {
+                    var cursor = event.target.result;
+                    if (cursor) {
+                        cursor.update(event.target.pair.value);
+                    } else {
+                        transaction.abort();
+                    }
+                };
+            }
+        }, error);
+    };
+
+    // DATAJS INTERNAL START
+    datajs.IndexedDBStore = IndexedDBStore;
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file


[02/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/tests/odata-atom-tests.js
----------------------------------------------------------------------
diff --git a/datajs/tests/odata-atom-tests.js b/datajs/tests/odata-atom-tests.js
new file mode 100644
index 0000000..52c0c94
--- /dev/null
+++ b/datajs/tests/odata-atom-tests.js
@@ -0,0 +1,4756 @@
+/// <reference path="../src/odata-utils.js" />
+/// <reference path="../src/odata-handler.js" />
+/// <reference path="../src/odata-atom.js" />
+/// <reference path="../src/odata-xml.js" />
+/// <reference path="common/djstest.js" />
+
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-atom-tests.js
+
+(function (window, undefined) {
+
+    module("Unit");
+
+    var parseMetadataHelper = function (text) {
+        var response = { statusCode: 200, body: text, headers: { "Content-Type": "application/xml"} };
+        OData.metadataHandler.read(response, {});
+        return response.data;
+    };
+
+    var resetFoodData = function () {
+        $.ajax({ url: "./endpoints/FoodStoreDataServiceV2.svc/ResetData", async: false, type: "POST" });
+    };
+
+    var customerSampleMetadataText = '' +
+    '<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">\r\n' +
+    '<edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0">\r\n' +
+    '<Schema Namespace="Ns" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">\r\n' +
+    '    <EntityType Name="Customer">\r\n' +
+    '    <Key><PropertyRef Name="ID" /></Key>\r\n' +
+    '     <Property Name="ID" Type="Edm.Int32" Nullable="false" />\r\n' +
+    '     <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationSummary" m:FC_ContentKind="xhtml" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="LastName" Type="Edm.String" Nullable="true" m:FC_TargetPath="foo/bar/@baz" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="FavoriteNumber" Type="Edm.Int32" Nullable="true" m:FC_TargetPath="favorite/number" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_KeepInContent="false" />\r\n' +
+    '     <Property Name="Address" Type="Ns.Address" Nullable="false" \r\n' +
+    '       m:FC_TargetPath="foo/bar/@city" m:FC_NsUri="htp://prefix" m:FC_NsPrefix="prefix" m:FC_SourcePath="City" m:FC_KeepInContent="false" \r\n' +
+    '       m:FC_TargetPath_1="foo/bar" m:FC_NsUri_1="htp://prefix" m:FC_NsPrefix_1="prefix" m:FC_SourcePath_1="Street" m:FC_KeepInContent_1="false" />\r\n' +
+    '    </EntityType>\r\n' +
+    '    <ComplexType Name="Address">\r\n' +
+    '     <Property Name="Street" Type="Edm.String" Nullable="true" />\r\n' +
+    '     <Property Name="City" Type="Edm.String" Nullable="true" />\r\n' +
+    '    </ComplexType>\r\n' +
+    '    <EntityContainer Name="SampleContext" m:IsDefaultEntityContainer="true">\r\n' +
+    '     <EntitySet Name="Customers" EntityType="Ns.Customer" />\r\n' +
+    '    </EntityContainer>\r\n' +
+    '</Schema>\r\n' +
+    '</edmx:DataServices></edmx:Edmx>';
+
+    var foodServiceV2FoodsSampleText = '' +
+    '<feed xml:base="http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' +
+    '  <title type="text">Foods</title>' +
+    '  <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods</id>' +
+    '  <updated>2010-12-28T23:09:54Z</updated>' +
+    '  <link rel="self" title="Foods" href="Foods" />' +
+    '  <entry>' +
+    '    <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(0)</id>' +
+    '    <title type="text">flour</title>' +
+    '    <updated>2010-12-28T23:09:54Z</updated>' +
+    '    <author><name /></author>' +
+    '    <link rel="edit" title="Food" href="Foods(0)" />' +
+    '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+    '    <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+    '    <content type="application/xml">' +
+    '      <m:properties>' +
+    '        <d:FoodID m:type="Edm.Int32">0</d:FoodID>' +
+    '        <d:Name>flour</d:Name>' +
+    '        <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+    '        <d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+    '        <d:ProteinGrams m:type="Edm.Byte">3</d:ProteinGrams>' +
+    '        <d:FatGrams m:type="Edm.Int16">1</d:FatGrams>' +
+    '        <d:CarbohydrateGrams m:type="Edm.Int32">20</d:CarbohydrateGrams>' +
+    '        <d:CaloriesPerServing m:type="Edm.Int64">140</d:CaloriesPerServing>' +
+    '        <d:IsAvailable m:type="Edm.Boolean">true</d:IsAvailable>' +
+    '        <d:ExpirationDate m:type="Edm.DateTime">2010-12-25T12:00:00</d:ExpirationDate>' +
+    '        <d:ItemGUID m:type="Edm.Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+    '        <d:Weight m:type="Edm.Single">10</d:Weight>' +
+    '        <d:AvailableUnits m:type="Edm.SByte">1</d:AvailableUnits>' +
+    '        <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+    '          <d:Type m:null="true" />' +
+    '          <d:Color></d:Color>' +
+    '          <d:NumberPerPackage m:type="Edm.Int32">2147483647</d:NumberPerPackage>' +
+    '          <d:RequiresRefridgeration m:type="Edm.Boolean">false</d:RequiresRefridgeration>' +
+    '          <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+    '          <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+    '            <d:Length m:type="Edm.Decimal">79228162514264337593543950335</d:Length>' +
+    '            <d:Height m:type="Edm.Int16">32767</d:Height>' +
+    '            <d:Width m:type="Edm.Int64">9223372036854775807</d:Width>' +
+    '            <d:Volume m:type="Edm.Double">1.7976931348623157E+308</d:Volume>' +
+    '          </d:PackageDimensions>' +
+    '        </d:Packaging>' +
+    '      </m:properties>' +
+    '    </content>' +
+    '    <cooked:cooked cooked:length="2" cooked:height="1" cooked:width="3" xmlns:cooked="http://www.example.org/cooked/">' +
+    '      <cooked:volume>6</cooked:volume>' +
+    '    </cooked:cooked>' +
+    '    <pr:price pr:value="0.19999" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+    '  </entry>' +
+    '  <entry>' +
+    '    <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(1)</id>' +
+    '    <title type="text">sugar</title>' +
+    '    <updated>2010-12-28T23:09:54Z</updated>' +
+    '    <author>' +
+    '      <name />' +
+    '    </author>' +
+    '    <link rel="edit" title="Food" href="Foods(1)" />' +
+    '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(1)/Category" />' +
+    '    <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+    '    <content type="application/xml">' +
+    '      <m:properties>' +
+    '        <d:FoodID m:type="Edm.Int32">1</d:FoodID>' +
+    '        <d:Name>sugar</d:Name>' +
+    '        <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+    '        <d:MeasurementUnit>tsp</d:MeasurementUnit>' +
+    '        <d:ProteinGrams m:type="Edm.Byte">0</d:ProteinGrams>' +
+    '        <d:FatGrams m:type="Edm.Int16">0</d:FatGrams>' +
+    '        <d:CarbohydrateGrams m:type="Edm.Int32">4</d:CarbohydrateGrams>' +
+    '        <d:CaloriesPerServing m:type="Edm.Int64">16</d:CaloriesPerServing>' +
+    '        <d:IsAvailable m:type="Edm.Boolean">false</d:IsAvailable>' +
+    '        <d:ExpirationDate m:type="Edm.DateTime">2011-12-28T00:00:00</d:ExpirationDate>' +
+    '        <d:ItemGUID m:type="Edm.Guid">ffffffff-ffff-ffff-ffff-ffffffffffff</d:ItemGUID>' +
+    '        <d:Weight m:type="Edm.Single">0.1</d:Weight>' +
+    '        <d:AvailableUnits m:type="Edm.SByte">0</d:AvailableUnits>' +
+    '        <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+    '          <d:Type xml:space="preserve"> </d:Type>' +
+    '          <d:Color>BLUE</d:Color>' +
+    '          <d:NumberPerPackage m:type="Edm.Int32">-2147483648</d:NumberPerPackage>' +
+    '          <d:RequiresRefridgeration m:type="Edm.Boolean">true</d:RequiresRefridgeration>' +
+    '          <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+    '          <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+    '            <d:Length m:type="Edm.Decimal">-79228162514264337593543950335</d:Length>' +
+    '            <d:Height m:type="Edm.Int16">-32768</d:Height>' +
+    '            <d:Width m:type="Edm.Int64">-9223372036854775808</d:Width>' +
+    '            <d:Volume m:type="Edm.Double">-1.7976931348623157E+308</d:Volume>' +
+    '          </d:PackageDimensions>' +
+    '        </d:Packaging>' +
+    '        <d:CookedSize m:type="DataJS.Tests.V2.CookedDimensions" m:null="true" />' +
+    '      </m:properties>' +
+    '    </content>' +
+    '    <cooked:cooked cooked:length="" cooked:height="" cooked:width="" xmlns:cooked="http://www.example.org/cooked/">' +
+    '      <cooked:volume></cooked:volume>' +
+    '    </cooked:cooked>' +
+    '    <pr:price pr:value="0.2" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+    '  </entry>' +
+    '</feed>';
+
+    var foodServiceV2MetadataText = '' +
+    '<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx">' +
+    '  <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0">' +
+    '    <Schema Namespace="DataJS.Tests.V2" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns="http://schemas.microsoft.com/ado/2007/05/edm">' +
+    '      <EntityType Name="Category">' +
+    '        <Key>' +
+    '          <PropertyRef Name="CategoryID" />' +
+    '        </Key>' +
+    '        <Property Name="CategoryID" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="true" />' +
+    '        <NavigationProperty Name="Foods" Relationship="DataJS.Tests.V2.Category_Foods" FromRole="Category" ToRole="Foods" />' +
+    '      </EntityType>' +
+    '      <EntityType Name="PreparedFood" BaseType="DataJS.Tests.V2.Food">' +
+    '        <Property Name="Instructions" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="NumberOfIngredients" Type="Edm.Single" Nullable="false" />' +
+    '      </EntityType>' +
+    '      <EntityType Name="Food">' +
+    '        <Key>' +
+    '          <PropertyRef Name="FoodID" />' +
+    '        </Key>' +
+    '        <Property Name="FoodID" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="Name" Type="Edm.String" Nullable="true" m:FC_TargetPath="SyndicationTitle" m:FC_ContentKind="text" m:FC_KeepInContent="true" />' +
+    '        <Property Name="UnitPrice" Type="Edm.Double" Nullable="false" m:FC_TargetPath="price/@value" m:FC_NsUri="http://www.example.org/price/" m:FC_NsPrefix="pr" m:FC_KeepInContent="false" />' +
+    '        <Property Name="ServingSize" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="MeasurementUnit" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="ProteinGrams" Type="Edm.Byte" Nullable="false" />' +
+    '        <Property Name="FatGrams" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="CarbohydrateGrams" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="CaloriesPerServing" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="IsAvailable" Type="Edm.Boolean" Nullable="false" />' +
+    '        <Property Name="ExpirationDate" Type="Edm.DateTime" Nullable="false" />' +
+    '        <Property Name="ItemGUID" Type="Edm.Guid" Nullable="false" />' +
+    '        <Property Name="Weight" Type="Edm.Single" Nullable="false" />' +
+    '        <Property Name="AvailableUnits" Type="Edm.SByte" Nullable="false" />' +
+    '        <Property Name="Packaging" Type="DataJS.Tests.V2.Package" Nullable="false" />' +
+    '        <Property Name="CookedSize" Type="DataJS.Tests.V2.CookedDimensions" Nullable="false" m:FC_TargetPath="cooked/volume" m:FC_NsUri="http://www.example.org/cooked/" m:FC_NsPrefix="cooked" m:FC_SourcePath="Volume" m:FC_KeepInContent="false" m:FC_TargetPath_1="cooked/@width" m:FC_NsUri_1="http://www.example.org/cooked/" m:FC_NsPrefix_1="cooked" m:FC_SourcePath_1="Width" m:FC_KeepInContent_1="false" m:FC_TargetPath_2="cooked/@height" m:FC_NsUri_2="http://www.example.org/cooked/" m:FC_NsPrefix_2="cooked" m:FC_SourcePath_2="Height" m:FC_KeepInContent_2="false" m:FC_TargetPath_3="cooked/@length" m:FC_NsUri_3="http://www.example.org/cooked/" m:FC_NsPrefix_3="cooked" m:FC_SourcePath_3="Length" m:FC_KeepInContent_3="false" />' +
+    '        <NavigationProperty Name="Category" Relationship="DataJS.Tests.V2.Food_Category" FromRole="Food" ToRole="Category" />' +
+    '      </EntityType>' +
+    '      <ComplexType Name="Package">' +
+    '        <Property Name="Type" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="Color" Type="Edm.String" Nullable="true" />' +
+    '        <Property Name="NumberPerPackage" Type="Edm.Int32" Nullable="false" />' +
+    '        <Property Name="RequiresRefridgeration" Type="Edm.Boolean" Nullable="false" />' +
+    '        <Property Name="ShipDate" Type="Edm.DateTime" Nullable="false" />' +
+    '        <Property Name="PackageDimensions" Type="DataJS.Tests.V2.Dimensions" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <ComplexType Name="Dimensions">' +
+    '        <Property Name="Length" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="Height" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="Width" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="Volume" Type="Edm.Double" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <ComplexType Name="CookedDimensions">' +
+    '        <Property Name="Length" Type="Edm.Decimal" Nullable="false" />' +
+    '        <Property Name="Height" Type="Edm.Int16" Nullable="false" />' +
+    '        <Property Name="Width" Type="Edm.Int64" Nullable="false" />' +
+    '        <Property Name="Volume" Type="Edm.Double" Nullable="false" />' +
+    '      </ComplexType>' +
+    '      <Association Name="Category_Foods">' +
+    '        <End Role="Category" Type="DataJS.Tests.V2.Category" Multiplicity="*" />' +
+    '        <End Role="Foods" Type="DataJS.Tests.V2.Food" Multiplicity="*" />' +
+    '      </Association>' +
+    '      <Association Name="Food_Category">' +
+    '        <End Role="Food" Type="DataJS.Tests.V2.Food" Multiplicity="*" />' +
+    '        <End Role="Category" Type="DataJS.Tests.V2.Category" Multiplicity="0..1" />' +
+    '      </Association>' +
+    '      <EntityContainer Name="FoodContainer" m:IsDefaultEntityContainer="true">' +
+    '        <EntitySet Name="Categories" EntityType="DataJS.Tests.V2.Category" />' +
+    '        <EntitySet Name="Foods" EntityType="DataJS.Tests.V2.Food" />' +
+    '        <AssociationSet Name="Category_Foods" Association="DataJS.Tests.Category_Foods">' +
+    '          <End Role="Category" EntitySet="Categories" />' +
+    '          <End Role="Foods" EntitySet="Foods" />' +
+    '        </AssociationSet>' +
+    '        <AssociationSet Name="Food_Category" Association="DataJS.Tests.V2.Food_Category">' +
+    '          <End Role="Food" EntitySet="Foods" />' +
+    '          <End Role="Category" EntitySet="Categories" />' +
+    '        </AssociationSet>' +
+    '        <FunctionImport Name="ResetData" m:HttpMethod="POST" />' +
+    '        <FunctionImport Name="FoodsAvailable" ReturnType="Collection(Edm.String)" m:HttpMethod="GET" />' +
+    '        <FunctionImport Name="PackagingTypes" ReturnType="Collection(DataJS.Tests.V2.Package)" m:HttpMethod="GET" />' +
+    '      </EntityContainer>' +
+    '    </Schema>' +
+    '  </edmx:DataServices>' +
+    '</edmx:Edmx>';
+
+    djstest.addTest(function applyEntryCustomizationToEntryTest() {
+        var metadata = parseMetadataHelper(customerSampleMetadataText);
+        var data = { __metadata: { type: "Ns.Customer" }, Name: "Name", LastName: "Last Name", Address: { Street: "Street Value", City: "City Value" }, FavoriteNumber: 123 };
+        var request = { data: data, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.write(request, { metadata: metadata });
+
+        djstest.assert(request.body !== null, "request.body !== null");
+        djstest.assert(request.body.indexOf("<a:summary type=\"xhtml\">Name</a:summary>") !== -1, 'request.body.indexOf("<a:summary>Name</a:summary>") !== -1');
+        djstest.assert(request.body.indexOf('baz="Last Name"') !== -1, 'request.body.indexOf(baz="Last Name") !== -1');
+        djstest.assert(request.body.indexOf('city="City Value"') !== -1, 'request.body.indexOf(city="City Value") !== -1');
+        djstest.assert(request.body.indexOf('<prefix:foo ') !== -1, "request.body.indexOf('<prefix:foo ') !== -1");
+        djstest.assert(request.body.indexOf('term="Ns.Customer"') !== -1, "request.body.indexOf(term='Ns.Customer') !== -1");
+        djstest.assert(request.body.indexOf('>123</') !== -1, "request.body.indexOf(>123</) !== -1");
+
+        // Try with other mapping types.
+        metadata.dataServices.schema[0].entityType[0].property[1].FC_ContentKind = "html";
+        request.body = undefined;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf("<a:summary type=\"html\">Name</a:summary>") !== -1, 'request.body.indexOf("<a:summary type="html">Name</a:summary>") !== -1');
+
+        // Try with a null value now.
+        request.data.FavoriteNumber = null;
+        request.body = null;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf('>123</') === -1, "request.body.indexOf(>123</) === -1");
+        djstest.assert(request.body.indexOf('m:null="true"') !== -1, "request.body.indexOf(m:null=true) !== -1");
+
+        // Try with a null complex type now.
+        request.data.FavoriteNumber = 123;
+        request.data.Address = null;
+        request.body = null;
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf('Street') === -1, "request.body.indexOf(Street) === -1");
+        djstest.assert(request.body.indexOf('m:null="true"') !== -1, "request.body.indexOf(m:null=true) !== -1");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function normalizeHeadersReadTest() {
+        // Verifies that headers are normalized for reading.
+        // See issue at http://datajs.codeplex.com/workitem/148
+        window.MockHttpClient.clear().addResponse("/foo", {
+            statusCode: 200,
+            body: foodServiceV2FoodsSampleText,
+            headers: { "Accept": "application/json", "unknown": "u", "content-type": "application/atom+xml", "dataserviceversion": "2.0" }
+        });
+
+        OData.read("/foo", function (data, response) {
+            djstest.assertAreEqual(data.results.length, 2, "data.results.length has two entries");
+            djstest.assertAreEqual(response.headers.Accept, "application/json", "Accept available");
+            djstest.assertAreEqual(response.headers.unknown, "u", "u unmodified");
+            djstest.assertAreEqual(response.headers["Content-Type"], "application/atom+xml", "Content-Type available");
+            djstest.assertAreEqual(response.headers["DataServiceVersion"], "2.0", "DataServiceVersion available");
+            djstest.done();
+        }, undefined, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function normalizeHeadersWriteTest() {
+        // Verifies that headers are normalized for writing.
+        // See issue at http://datajs.codeplex.com/workitem/148
+        window.MockHttpClient.clear().addRequestVerifier("/foo", function (request) {
+            djstest.assertAreEqual(request.headers["Content-Type"], "application/atom+xml", "json found");
+            djstest.assert(request.body.indexOf(":bar") !== -1, "format recognized and applied as XML");
+            djstest.done();
+        });
+
+        var request = {
+            method: "POST",
+            requestUri: "/foo",
+            data: { "bar": 123 },
+            headers: { "content-type": "application/atom+xml" }
+        };
+        OData.request(request, function (data) {
+        }, undefined, undefined, MockHttpClient);
+    });
+
+    djstest.addTest(function testParsePrimitivePropertiesBasic() {
+        var feed = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:d=\'http://schemas.microsoft.com/ado/2007/08/dataservices\' \r\n\
+               xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'>\r\n\
+           <id>http://services.odata.org/OData/OData.svc/the id</id> \r\n \
+           <content type='application/xml'>\r\n \
+            <m:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n \
+             <Boolean m:type='Edm.Boolean'>true</Boolean>\r\n \
+             <Binary m:type='Edm.Binary'>01007A8A680D9E14A64EAC1242DD33C9DB05</Binary>\r\n \
+             <Byte m:type='Edm.Byte'>8</Byte>\r\n \
+             <DateTime m:type='Edm.DateTime'>2010-11-01T15:13:25</DateTime>\r\n \
+             <Decimal m:type='Edm.Decimal'>100.10</Decimal>\r\n \
+             <Guid m:type='Edm.Guid'>12345678-aaaa-bbbb-cccc-ddddeeeeffff</Guid>\r\n \
+             <!-- <Time m:type='Edm.Time'>P05DT12H30M05.125S</Time> --> \r\n \
+             <DateTimeOffset m:type='Edm.DateTimeOffset'>2010-11-01T15:13:25+10:00</DateTimeOffset>\r\n \
+             <Double m:type='Edm.Double'>1E+10</Double>\r\n \
+             <Single m:type='Edm.Single'>100.01</Single>\r\n \
+             <Int16 m:type='Edm.Int16'>16</Int16>\r\n \
+             <Int32 m:type='Edm.Int32'>32</Int32>\r\n \
+             <Int64 m:type='Edm.Int64'>64</Int64>\r\n \
+             <SByte m:type='Edm.SByte'>-8</SByte>\r\n \
+            </m:properties>\r\n \
+           </content>\r\n \
+        </entry>\r\n";
+
+        var response = { body: feed, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, {});
+
+        djstest.assertsExpected(1);
+        ODataReadOracle.readEntryLoopback(feed, function (expectedData) {
+            djstest.assertAreEqualDeep(response.data, expectedData, "Verify deserialized data");
+            djstest.done();
+        });
+    });
+
+    djstest.addTest(function deserializeCustomizationsNullAndXhtmlTest() {
+        var payload = "<entry " +
+        ' xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" \r\n' +
+        " xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n" +
+        " xmlns=\"http://www.w3.org/2005/Atom\">\r\n" +
+        " <id>http://localhost/tests/endpoints/FoodDataService.svc/Foods(1)</id> " +
+        " <author><name>Customer #1</name></author>" +
+        " <summary><b>Name</b></summary>" +
+        " <category term='Ns.Customer' scheme='http://schemas.microsoft.com/ado/2007/08/dataservices/scheme' /> " +
+        " <content type='application/xml'><m:properties><d:ID m:type='Edm.Int32'>1</d:ID>" +
+        " <d:LastName m:null='true' /></m:properties></content>" +
+        "</entry>";
+        var metadata = parseMetadataHelper(customerSampleMetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assertAreEqual(response.data.LastName, null, "last name is null");
+        djstest.assertAreEqual(response.data.Name, "<b xmlns=\"http://www.w3.org/2005/Atom\">Name</b>", "name includes tags");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function parseCustomizationSampleTest() {
+        var payload = foodServiceV2FoodsSampleText;
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assert(response.data !== null, "response.data !== null");
+        djstest.assert(response.data.results !== null, "response.data.results !== null");
+
+        var r = response.data.results;
+        djstest.assertAreEqual(r[0].__metadata.type, "DataJS.Tests.V2.Food", "r[0].__metadata.type");
+        djstest.assertAreEqual(r[0].Name, "flour", "r[0].Name");
+        djstest.assertAreEqual(r[0].UnitPrice, 0.19999, "r[0].UnitPrice");
+        djstest.assertAreEqual(r[0].ServingSize, 1, "r[0].ServingSize");
+
+        // CONSIDER: we intended to have complex type have their type annotation out-of-band, but JSON has it in-line; do we want to normalize this out everywhere?
+        // djstest.assertAreEqual(r[0].Packaging.__metadata.type, "DataJS.Tests.PackageV2", "r[0].Packaging.__metadata.type");
+        djstest.assertAreEqual(r[0].Packaging.Type, null, "package type for flour is null");
+        djstest.assertAreEqual(r[0].Packaging.PackageDimensions.Height, 32767, "r[0].Packaging.PackageDimensions.Height");
+
+        djstest.assertAreEqual(r[0].CookedSize.Length, 2, "r[0].CookedSize.Length");
+        djstest.assertAreEqual(r[0].CookedSize.Volume, 6, "r[0].CookedSize.Volume");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function parseIntoPropertiesTest() {
+        var payload = '' +
+            '<entry xml:base="http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' +
+            '  <id>http://localhost/tests/endpoints/FoodStoreDataServiceV2.svc/Foods(0)</id>' +
+            '  <title type="text">flour</title>' +
+            '  <updated>2010-12-28T23:09:54Z</updated>' +
+            '  <author><name /></author>' +
+            '  <link rel="edit" title="Food" href="Foods(0)" />' +
+            '  <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(0)/Category" />' +
+            '  <category term="DataJS.Tests.V2.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+            '  <content type="application/xml">' +
+            '    <m:properties>' +
+            '      <d:FoodID m:type="Edm.Int32">0</d:FoodID>' +
+            '      <d:Name>flour</d:Name>' +
+            '      <d:ServingSize m:type="Edm.Decimal">1</d:ServingSize>' +
+            '      <d:MeasurementUnit>Cup</d:MeasurementUnit>' +
+            '      <d:ProteinGrams m:type="Edm.Byte">3</d:ProteinGrams>' +
+            '      <d:FatGrams m:type="Edm.Int16">1</d:FatGrams>' +
+            '      <d:CarbohydrateGrams m:type="Edm.Int32">20</d:CarbohydrateGrams>' +
+            '      <d:CaloriesPerServing m:type="Edm.Int64">140</d:CaloriesPerServing>' +
+            '      <d:IsAvailable m:type="Edm.Boolean">true</d:IsAvailable>' +
+            '      <d:ExpirationDate m:type="Edm.DateTime">2010-12-25T12:00:00</d:ExpirationDate>' +
+            '      <d:ItemGUID m:type="Edm.Guid">27272727-2727-2727-2727-272727272727</d:ItemGUID>' +
+            '      <d:Weight m:type="Edm.Single">10</d:Weight>' +
+            '      <d:AvailableUnits m:type="Edm.SByte">1</d:AvailableUnits>' +
+            '      <d:Packaging m:type="DataJS.Tests.V2.Package">' +
+            '        <d:Type m:null="true" />' +
+            '        <d:Color></d:Color>' +
+            '        <d:NumberPerPackage m:type="Edm.Int32">2147483647</d:NumberPerPackage>' +
+            '        <d:RequiresRefridgeration m:type="Edm.Boolean">false</d:RequiresRefridgeration>' +
+            '        <d:ShipDate m:type="Edm.DateTime">0001-01-01T00:00:00</d:ShipDate>' +
+            '        <d:PackageDimensions m:type="DataJS.Tests.V2.Dimensions">' +
+            '          <d:Length m:type="Edm.Decimal">79228162514264337593543950335</d:Length>' +
+            '          <d:Height m:type="Edm.Int16">32767</d:Height>' +
+            '          <d:Width m:type="Edm.Int64">9223372036854775807</d:Width>' +
+            '          <d:Volume m:type="Edm.Double">1.7976931348623157E+308</d:Volume>' +
+            '        </d:PackageDimensions>' +
+            '      </d:Packaging>' +
+            '    </m:properties>' +
+            '  </content>' +
+            '  <cooked:cooked cooked:length="2" cooked:height="1" cooked:width="3" xmlns:cooked="http://www.example.org/cooked/">' +
+            '    <cooked:volume>6</cooked:volume>' +
+            '  </cooked:cooked>' +
+            '  <pr:price pr:value="0.19999" xmlns:pr="http://www.example.org/price/"></pr:price>' +
+            '</entry>';
+
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        var response = { body: payload, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, { metadata: metadata });
+
+        djstest.assert(response.data !== null, "response.data !== null");
+        djstest.assertAreEqual(response.data.__metadata.type, "DataJS.Tests.V2.Food", "types match");
+        djstest.assertAreEqual(response.data.UnitPrice, 0.19999, "Price is as expected");
+        djstest.assertAreEqual(response.data.__metadata.properties.UnitPrice.type, "Edm.Double", "Price was marked as a double");
+
+        djstest.assertAreEqual(response.data.CookedSize.__metadata.properties.Length.type, "Edm.Decimal", "CookedSize.Length was marked as a decimal");
+
+        // When properties are marked on complex type metadata, this assertion will be true as well.
+        // djstest.assertAreEqual(response.data.Packaging.__metadata.Type.type, "Edm.String", "Packaging type was marked as a string");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function parseNullInlineTest() {
+        // Shorter version of:
+        // OData.read("/tests/endpoints/FoodStoreDataService.svc/Foods?$expand=Category&$filter=Category eq null", function(data, response) {
+
+        var body = '' +
+        '<feed xml:base="http://localhost/tests/endpoints/FoodStoreDataService.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">' +
+        '  <entry>' +
+        '    <id>http://localhost/tests/endpoints/FoodStoreDataService.svc/Foods(2)</id>' +
+        '    <link rel="edit" title="Food" href="Foods(2)" />' +
+        '    <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Foods(2)/Category">' +
+        '      <m:inline />' +
+        '    </link>' +
+        '    <category term="DataJS.Tests.Food" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />' +
+        '    <content type="application/xml">' +
+        '      <m:properties>' +
+        '        <d:FoodID m:type="Edm.Int32">2</d:FoodID>' +
+        '        <d:Name>1 Chicken Egg</d:Name>' +
+        '        <d:UnitPrice m:type="Edm.Double">0.55</d:UnitPrice>' +
+        '      </m:properties>' +
+        '    </content>' +
+        '  </entry>' +
+        '</feed>';
+        var response = { body: body, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.read(response, {});
+        var data = response.data;
+        var r = data.results[0];
+        djstest.assertAreEqual(r.Category, null, "r.Category is null as an empty inline entry");
+        djstest.assertAreEqual(r.FoodID, 2, "r.FoodID read correctly");
+        djstest.assertAreEqual(r.__metadata.properties.Category.extensions[0].name, "title", "Category title extension parsed");
+        djstest.assertAreEqual(r.__metadata.properties.Category.extensions[0].value, "Category", "Category title value parsed");
+        djstest.done();
+    });
+
+    djstest.addTest(function serializeEpmTest() {
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        var data = { __metadata: { type: "DataJS.Tests.V2.Food" }, FoodID: 123, Name: "name", CookedSize: { Length: 1, Height: 2, Width: 3, Volume: 4} };
+        var request = { data: data, headers: { "Content-Type": "application/atom+xml"} };
+        OData.atomHandler.write(request, { metadata: metadata });
+        djstest.assert(request.body.indexOf("CookedSize") === -1, "CookedSize element is missing from payload");
+        djstest.assert(request.body.indexOf("length=") !== -1, "length is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("height=") !== -1, "height is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("width=") !== -1, "width is available as a mapped attribute");
+        djstest.assert(request.body.indexOf("volume>") !== -1, "volume is available as a mapped element");
+        djstest.done();
+    });
+
+    djstest.addTest(function writeNullComplexTypeTest() {
+
+        // Verify that the server can be updated to set a complex value to null.
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        resetFoodData();
+
+        // Without metadata, this will fail because the server version won't be set to 2.0.
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+            djstest.assert(item.Packaging, "item.Packaging is not null");
+            item.Packaging = null;
+
+            // The server will reject links for PUT operations.
+            delete item.Category;
+
+            OData.request({ method: "PUT", requestUri: item.__metadata.uri, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(item.__metadata.uri, function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(!data.Packaging, "!data.Packaging");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to write food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    djstest.addTest(function writeNonNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+
+            // Turn this into something different.
+            delete item.__metadata.uri;
+            item.FoodID = 1001;
+
+            OData.request({ method: "POST", requestUri: foodSetUrl, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(data.__metadata.uri + "?$expand=Category", function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(data.Category, "data.Category");
+                    djstest.assert(data.Category.Name, "data.Category.Name");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to add modified food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    djstest.addTest(function writeNullLinkTest() {
+        // Verify that the server can be updated to set a link to null.
+        resetFoodData();
+        var foodSetUrl = "./endpoints/FoodStoreDataServiceV2.svc/Foods";
+        var metadata = parseMetadataHelper(foodServiceV2MetadataText);
+        OData.read(foodSetUrl + "?$top=1", function (data) {
+            var item = data.results[0];
+
+            // Turn this into something different.
+            delete item.__metadata.uri;
+            item.FoodID = 1001;
+            item.Category = null;
+
+            OData.request({ method: "POST", requestUri: foodSetUrl, data: item, headers: { "Content-Type": "application/atom+xml"} }, function (data) {
+                // Re-read the item.
+                OData.read(data.__metadata.uri + "?$expand=Category", function (data) {
+                    djstest.assert(data, "data was read successfully again");
+                    djstest.assert(!data.Category, "data.Category");
+                    resetFoodData();
+                    djstest.done();
+                }, djstest.failAndDoneCallback("Failed to read back food.", resetFoodData));
+            }, djstest.failAndDoneCallback("Failed to add modified food"), null, null, metadata);
+        }, djstest.failAndDoneCallback("Failed to read food"), null, null, metadata);
+    });
+
+    // DATAJS INTERNAL START
+    djstest.addTest(function lookupEntityTypeInSchemaTest() {
+        var schemaEmpty = {};
+        var schemaZero = {
+            namespace: "Zero",
+            entityType: [
+                { name: "Genre" },
+                { name: "Language" }
+            ]
+        };
+        var schemaOne = {
+            namespace: "One",
+            entityType: [
+                { name: "Genre1" },
+                { name: "Language1" }
+            ]
+        };
+        var edmx = { dataServices: { schema: [schemaEmpty, schemaZero, schemaOne]} };
+
+        var lookupEntityTypeInSchema = function (name, schema) {
+            return OData.lookupInMetadata(name, schema, "entityType");
+        };
+
+        djstest.assertAreEqual(
+        lookupEntityTypeInSchema("Zero.Genre"),
+        null, "Expected null for missing metadata");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("", schemaEmpty),
+            null, "Expected null for empty type name");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("FooWar", schemaEmpty),
+            null, "Expected null for mismatched name/namespace");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero", schemaZero),
+            null, "Expected null for unqualified type name");
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero.Something", schemaZero),
+            null, "Expected null for mismatched type name");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("Zero.Genre", schemaZero),
+            { name: "Genre" }, "Found type by full name");
+
+        djstest.assertAreEqual(
+            lookupEntityTypeInSchema("Zero.Something", edmx),
+            null, "Expected null for mismatched type name in edmx");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("Zero.Genre", edmx),
+            { name: "Genre" }, "Found type by full name in edmx");
+        djstest.assertAreEqualDeep(
+            lookupEntityTypeInSchema("One.Genre1", edmx),
+            { name: "Genre1" }, "Found type by full name in edmx");
+
+        djstest.assertAreEqual(
+            OData.lookupInMetadata("One.Genre1", edmx, "complexType"),
+            null, "Expected null for a complex type lookup of an entity type.");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testLookupEntityType() {
+        var schemaZero = {
+            namespace: "Zero",
+            entityType: [
+                { name: "Genre" },
+                { name: "Language" }
+            ]
+        };
+        var schemaOne = {
+            namespace: "One",
+            entityType: [
+                { name: "Genre1" },
+                { name: "Language1" }
+            ]
+        };
+        var schemaTwo = {
+            namespace: "Two",
+            entityType: [
+                { name: "Genre2" },
+                { name: "Language2" }
+            ]
+        };
+        var edmx = { dataServices: { schema: [schemaZero, schemaOne]} };
+        var metadata = [edmx, schemaTwo];
+
+        djstest.assertAreEqual(
+            OData.lookupEntityType("Zero.Something", metadata),
+            null, "Expected null for mismatched type name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Zero.Genre", null),
+            null, "Expected null for missing metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType(null, metadata),
+            null, "Expected null for missing name");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Zero.Genre", metadata),
+            { name: "Genre" }, "Found type by full name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("One.Genre1", metadata),
+            { name: "Genre1" }, "Found type by full name in metadata");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("One.Genre1", edmx),
+            { name: "Genre1" }, "Found type by full name in edmx");
+        djstest.assertAreEqualDeep(
+            OData.lookupEntityType("Two.Genre2", metadata),
+            { name: "Genre2" }, "Found type by full name in metadata");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testParseSimpleServiceDocument() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Products", expectedTitle: "Products" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Categories", expectedTitle: "Categories" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Suppliers", expectedTitle: "Suppliers" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testServiceDocMustHaveAtLeastOneWorkspaceElement() {
+        // Construct a service doc with no workspaces and verify that the parser throws.
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with no workspaces");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testServiceDocMayHaveMoreThanOneWorkspaceElement() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+            <workspace>\r\n\
+              <atom:title>Second Workspace</atom:title> \r\n\
+              <collection href=\"Collection1\">\r\n\
+                <atom:title>Collection Number 1</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Collection2\">\r\n\
+                <atom:title>Collection Number 2</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 2, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections;
+        expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Products", expectedTitle: "Products" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Categories", expectedTitle: "Categories" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Suppliers", expectedTitle: "Suppliers" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        workspace = serviceDoc.workspaces[1];
+        djstest.assertAreEqual(workspace.title, "Second Workspace", "Incorrect service doc title");
+
+        expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Collection1", expectedTitle: "Collection Number 1" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/Collection2", expectedTitle: "Collection Number 2" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testCollectionTitlesAndHrefsMayBeDifferent() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"abc\">\r\n\
+                <atom:title>xyz</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"blah\">\r\n\
+                <atom:title>foo</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "Default", "Incorrect service doc title");
+
+        var expectedCollections = [
+            { expectedHref: "http://services.odata.org/OData/OData.svc/abc", expectedTitle: "xyz" },
+            { expectedHref: "http://services.odata.org/OData/OData.svc/blah", expectedTitle: "foo" }
+        ];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        var i, len;
+        for (i = 0, len = expectedCollections.length; i < len; i++) {
+            djstest.assertAreEqual(workspace.collections[i].href, expectedCollections[i].expectedHref, "Incorrect href on collection");
+            djstest.assertAreEqual(workspace.collections[i].title, expectedCollections[i].expectedTitle, "Incorrect title on collection");
+        }
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testParserShouldTreatMissingWorkspaceTitleAsBlank() {
+        // Per RFC 5023 Section 8.3.2.1, the workspace element MUST have a title but
+        // in the interests of being permissive, we should treat this as blank.
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <!-- No workspace title element -->\r\n\
+              <collection href=\"Products\">\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Categories\">\r\n\
+                <atom:title>Categories</atom:title> \r\n\
+              </collection>\r\n\
+              <collection href=\"Suppliers\">\r\n\
+                <atom:title>Suppliers</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        djstest.assertAreEqual(workspace.title, "", "Incorrect service doc title");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testWorkspaceMayHaveNoCollections() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+
+        djstest.assertAreEqual(serviceDoc.workspaces.length, 1, "Incorrect number of workspaces");
+
+        var workspace = serviceDoc.workspaces[0];
+        var expectedCollections = [];
+
+        djstest.assertAreEqual(workspace.collections.length, expectedCollections.length, "Incorrect number of collections in workspace");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testCollectionMustHaveTitleElement() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <collection href=\"Products\">\r\n\
+                <!-- No title element -->\r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with a collection with no title element");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function testCollectionMustHaveHrefAttribute() {
+        var serviceDocString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+           <workspace>\r\n\
+              <atom:title>Default</atom:title> \r\n\
+              <!-- href attribute missing below --> \r\n\
+              <collection>\r\n\
+                <atom:title>Products</atom:title> \r\n\
+              </collection>\r\n\
+            </workspace>\r\n\
+         </service>\r\n";
+
+        djstest.expectException(function () {
+            var serviceDoc = OData.atomParser(OData.atomHandler, serviceDocString, {});
+        }, "Parsing service doc with a collection with no href attribute");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadDocumentTest() {
+        var emptyServiceString = "\
+        <service xml:base=\"http://services.odata.org/OData/OData.svc/\" \r\n\
+                 xmlns:atom=\"http://www.w3.org/2005/Atom\" \r\n\
+                 xmlns:app=\"http://www.w3.org/2007/app\" \r\n\
+                 xmlns=\"http://www.w3.org/2007/app\">\r\n\
+          <workspace>\r\n\
+            <atom:title>empty service</atom:title> \r\n\
+          </workspace>\r\n\
+        </service>\r\n";
+
+        var emptyFeedString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>empty feed</title> \r\n\
+        </feed> \r\n";
+
+        var emptyEntryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>entry id</id> \r\n\
+           <title>empty entry</title> \r\n\
+        </entry> \r\n";
+
+        var nonAtomString = "\
+        <notAtom xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>entry id</id> \r\n\
+           <title>empty entry</title> \r\n\
+        </notAtom> \r\n";
+
+        var service = OData.atomReadDocument(datajs.xmlParse(emptyServiceString).documentElement);
+        var feed = OData.atomReadDocument(datajs.xmlParse(emptyFeedString).documentElement);
+        var entry = OData.atomReadDocument(datajs.xmlParse(emptyEntryString).documentElement);
+        var nonAtom = OData.atomReadDocument(datajs.xmlParse(nonAtomString).documentElement);
+
+        djstest.assert(service && service.workspaces.length === 1, "atomReadDocument deserialized a service document");
+        djstest.assert(feed && feed.results.length === 0, "atomReadDocument deserialized a feed document");
+        djstest.assert(entry && !entry.results && entry.__metadata.uri === "http://services.odata.org/OData/OData.svc/entry id", "atomReadDocument deserialized a entry document");
+        djstest.assertAreEqual(nonAtom, undefined, "atomReadDocument returns undefined with non Atom input");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedV1Test() {
+        var simpleFeedString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <entry> \r\n\
+             <id>entry id</id> \r\n\
+             <title>empty entry</title> \r\n\
+           </entry> \r\n\
+        </feed> \r\n";
+
+        var expectedFeed = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: []
+            },
+            results: [
+                { __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: []
+                }
+                }
+           ]
+        };
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(simpleFeedString).documentElement);
+
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a V1 payload");
+        djstest.assertAreEqualDeep(feed, expectedFeed, "atomReadFeed didn't return the expected feed");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedV2Test() {
+        var simpleFeedString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:m=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <m:count>2</m:count> \r\n\
+           <entry> \r\n\
+             <id>entry id</id> \r\n\
+             <title>empty entry</title> \r\n\
+           </entry> \r\n\
+           <link rel=\'next\' href=\'http://nexturi\'/> \r\n\
+        </feed> \r\n";
+
+        var expectedFeed = {
+            __count: 2,
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: [],
+                next_extensions: []
+            },
+            __next: "http://nexturi",
+            results: [
+                { __metadata: {
+                    uri: "http://services.odata.org/OData/OData.svc/entry id",
+                    uri_extensions: []
+                }
+                }
+           ]
+        };
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(simpleFeedString).documentElement);
+
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a V2 payload");
+        djstest.assertAreEqualDeep(feed, expectedFeed, "atomReadFeed didn't return the expected feed");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedWithActionsAndFunctionsTest() {
+        var feed = "\r\n\
+        <feed xml:base='http://services.odata.org/OData/OData.svc/' \r\n\
+              xmlns:app='http://www.w3.org/2007/app' \r\n\
+              xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata' \r\n\
+              xmlns:me='http://myExtensions' \r\n\
+              xmlns='http://www.w3.org/2005/Atom'> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+           <m:action metadata='#EntityContainer.Action1' title='Action1' target='http://service/entities(0)/action' /> \r\n\
+           <m:action metadata='#EntityContainer.Action2' title='Action2' target='entities(0)/action2'/> \r\n\
+           <m:action metadata='http://someService/$metadata#Container.Action1' title='Action1' target='http://someService/action' /> \r\n\
+           <m:function metadata='#EntityContainer.Function1' title='Function1' target='http://service/entities(0)/function' /> \r\n\
+           <m:function metadata='#EntityContainer.Function2' title='Function2' target='entities(0)/function2' /> \r\n\
+           <m:function metadata='http://someService/$metadata#Container.Function1' title='Function1' target='http://someService/function' /> \r\n\
+        </feed> \r\n";
+
+        var expected = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/feed id",
+                uri_extensions: [],
+                title: "test feed",
+                title_extensions: [],
+                feed_extensions: [],
+                actions: [
+                    {
+                        metadata: "#EntityContainer.Action1",
+                        title: "Action1",
+                        target: "http://service/entities(0)/action",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Action2",
+                        title: "Action2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/action2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Action1",
+                        title: "Action1",
+                        target: "http://someService/action",
+                        extensions: []
+                    }
+                ],
+                functions: [
+                    {
+                        metadata: "#EntityContainer.Function1",
+                        title: "Function1",
+                        target: "http://service/entities(0)/function",
+                        extensions: []
+                    },
+                    {
+                        metadata: "#EntityContainer.Function2",
+                        title: "Function2",
+                        target: "http://services.odata.org/OData/OData.svc/entities(0)/function2",
+                        extensions: []
+                    },
+                    {
+                        metadata: "http://someService/$metadata#Container.Function1",
+                        title: "Function1",
+                        target: "http://someService/function",
+                        extensions: []
+                    }
+                ]
+            },
+            results: []
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml", "DataServiceVersion": "3.0" }, body: feed };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, expected, "atomReadEntry didn't return the expected entry object");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedExtensionsTest() {
+        var feedWithExtensionsString = "\
+         <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:me=\'http://myExtensions' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' attr1=\'a1\' me:attr2=\'a2\'> \r\n\
+           <me:element1>e1</me:element1> \r\n\
+           <me:element2> \r\n\
+               <me:element21 attr3=\'a3\' me:attr4=\'a4\' >e1</me:element21> \r\n\
+           </me:element2> \r\n\
+           <id>feed id</id> \r\n\
+           <title>test feed</title> \r\n\
+        </feed> \r\n"
+
+        var feed = OData.atomReadFeed(datajs.xmlParse(feedWithExtensionsString).documentElement);
+        djstest.assert(feed, "atomReadFeed didn't return a feed object for a payload with feed extensions");
+        djstest.assertAreEqual(feed.__metadata.feed_extensions.length, 4, "atomReadFeed didn't return the expected number of extensions");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadFeedLinksTest() {
+        var feedLinksString = "\
+        <feed xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+              xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+              xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+          <link rel=\'next\' href=\'http://nexturi\' me:attr1=\'a1\' attr2=\'a2\'/> \r\n\
+          <link rel=\'self\' href=\'http://selfuri\' me:attr3=\'a1\' attr4=\'a2\'/> \r\n\
+          <link rel=\'alternate\' href=\'http://otheruri\'/> \r\n\
+        </feed> \r\n";
+
+        var root = datajs.xmlParse(feedLinksString).documentElement;
+        var feed = { __metadata: {} };
+        datajs.xmlChildElements(root, function (child) {
+            OData.atomReadFeedLink(child, feed);
+        });
+
+        djstest.assertAreEqual(feed.__next, "http://nexturi", "atomReadFeedLink didn't read the next link element");
+        djstest.assertAreEqual(feed.__metadata.next_extensions.length, 2, "atomReadFeedLink didn't return the expected number of next link extensions");
+        djstest.assertAreEqual(feed.__metadata.self, "http://selfuri", "atomReadFeedLink didn't read the self link element");
+        djstest.assertAreEqual(feed.__metadata.self_extensions.length, 2, "atomReadFeedLink didn't return the expected number of self link extensions");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadLinkTest() {
+        var linkString = "\
+        <link xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' \r\n\
+              rel=\'next\' \r\n\
+              href=\'http://nexturi\' \r\n\
+              type=\'application/atom+xml;type=feed\' \r\n\
+              me:attr1=\'a1\' \r\n\
+              attr2=\'a2\'/> \r\n";
+
+        var link = OData.atomReadLink(datajs.xmlParse(linkString).documentElement);
+
+        djstest.assert(link, "atomReadLink didn't return a link object");
+        djstest.assertAreEqual(link.href, "http://nexturi", "atomReadLink, link object href field has an unexpected value");
+        djstest.assertAreEqual(link.rel, "next", "atomReadLink, link object rel field has an unexpected value");
+        djstest.assertAreEqual(link.type, "application/atom+xml;type=feed", "atomReadLink, link object type field has an unexpected value");
+        djstest.assertAreEqual(link.extensions.length, 2, "atomReadLink, link object extensions doesn't have the expected number of extensions");
+
+        djstest.done();
+
+    });
+
+    djstest.addTest(function atomReadLinkThrowHrefMissingTest() {
+        var linkString = "\
+        <link xmlns:me=\'http://myExtensions\' \r\n\
+              xmlns=\'http://www.w3.org/2005/Atom\' \r\n\
+              rel=\'next\' \r\n\
+              type=\'application/atom+xml;type=feed\' \r\n\
+              me:attr1=\'a1\' \r\n\
+              attr2=\'a2\'/> \r\n";
+
+
+        var linkRoot = datajs.xmlParse(linkString).documentElement;
+        djstest.expectException(function () {
+            OData.atomReadLink(linkRoot);
+        }, "atomReadLink didn't throw an exception when the link doesn't have the href attribute");
+        djstest.done();
+
+    });
+
+    djstest.addTest(function atomReadExtensionElementTest() {
+        var extensionString = "\
+        <me:ext xmlns:me=\'http://myExtensions\' me:attr1=\'a1\' attr2=\'a2\'> \r\n\
+          <ext>e1</ext> \r\n\
+        </me:ext> \r\n";
+
+        var validateExtension = function (ext, name, namespaceURI, attributeCount, childrenCount, value) {
+            djstest.assertAreEqual(ext.name, name, "atomReadExtensionElement, extension object name field has an unexpected value");
+            djstest.assertAreEqual(ext.namespaceURI, namespaceURI, "atomReadExtensionElement, extension object namespaceURI field has an unexpected value");
+            djstest.assertAreEqual(ext.attributes.length, attributeCount, "atomReadExtensionElement, extension object attributes doesn't have the expected number of attributes");
+            djstest.assertAreEqual(ext.children.length, childrenCount, "atomReadExtensionElement, extension object attributes doesn't have the expected number of children");
+            djstest.assertAreEqual(ext.value, value, "atomReadExtensionElement, extension object value field has an unexpected value");
+        };
+
+        var extension = OData.atomReadExtensionElement(datajs.xmlParse(extensionString).documentElement);
+        validateExtension(extension, "ext", "http://myExtensions", 2, 1);
+
+        extension = extension.children[0];
+        validateExtension(extension, "ext", null, 0, 0, "e1");
+
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadExtensionAttributesTest() {
+        var extensionString = "\
+        <me:ext xmlns:me=\'http://myExtensions\' me:attr1=\'a1\' attr2=\'a2\' /> \r\n";
+
+        var extensionAttributes = OData.atomReadExtensionAttributes(datajs.xmlParse(extensionString).documentElement);
+        djstest.assertAreEqual(extensionAttributes.length, 2, "atomReadExtensionAttribute, returned collection doesn't have the expected number of attributes");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadExtensionAttributeTest() {
+
+        var tests = {
+            "extension with namespace": {
+                i: '<me:ext xmlns:me="http://myExtensions" me:attr1="a1" />',
+                e: { name: "attr1", namespaceURI: "http://myExtensions", value: "a1" }
+            },
+            "extension without namespace": {
+                i: '<me:ext xmlns:me="http://myExtensions" attr2="a2" />',
+                e: { name: "attr2", namespaceURI: null, value: "a2" }
+            }
+        };
+
+        for (var name in tests) {
+            var test = tests[name];
+            var xmlElement = datajs.xmlParse(test.i).documentElement;
+            var extensions = OData.atomReadExtensionAttributes(xmlElement);
+
+            djstest.assertAreEqualDeep(extensions[0], test.e, name + " - extension object is the expected one");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryTest() {
+        var entryString = "\
+        <entry xml:base=\'http://services.odata.org/OData/OData.svc/\' \r\n\
+               xmlns:d2=\'http://schemas.microsoft.com/ado/2007/08/dataservices\' \r\n\
+               xmlns:m2=\'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\' \r\n\
+               xmlns:atom=\'http://www.w3.org/2005/Atom\' \r\n\
+               xmlns:app=\'http://www.w3.org/2007/app\' \r\n\
+               xmlns=\'http://www.w3.org/2005/Atom\'> \r\n\
+           <id>the id</id> \r\n\
+           <category term=\'the type\' \r\n\
+                     scheme=\'http://schemas.microsoft.com/ado/2007/08/dataservices/scheme\' /> \r\n\
+           <content type=\'application/xml\'> \r\n\
+            <m2:properties xmlns=\'http://schemas.microsoft.com/ado/2007/08/dataservices\'>\r\n\
+             <Untyped>untyped value</Untyped> \r\n\
+             <Typed m2:type='Edm.Int32'>100</Typed> \r\n\
+            </m2:properties> \r\n\
+           </content> \r\n\
+           <link rel=\'self\' href=\'http://selfuri\' /> \r\n\
+        </entry>\r\n";
+
+        var expectedEntry = {
+            __metadata: {
+                uri: "http://services.odata.org/OData/OData.svc/the id",
+                uri_extensions: [],
+                type: "the type",
+                type_extensions: [],
+                self: "http://selfuri",
+                self_link_extensions: [],
+                properties: {
+                    Untyped: {
+                        type: "Edm.String",
+                        extensions: []
+                    },
+                    Typed: {
+                        type: "Edm.Int32",
+                        extensions: []
+                    }
+                }
+            },
+            Untyped: "untyped value",
+            Typed: 100
+        };
+
+        var entry = OData.atomReadEntry(datajs.xmlParse(entryString).documentElement);
+
+        djstest.assert(entry, "atomReadEntry didn't return an entry object");
+        djstest.assertAreEqualDeep(entry, expectedEntry);
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlCRSValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <PointQualified>                                                            \r\n\
+                   <gml:Point gml:srsName='http://www.opengis.net/def/crs/EPSG/0/1234'>      \r\n\
+                      <gml:pos>1 2 3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointQualified>                                                           \r\n\
+                 <PointUnQualified>                                                          \r\n\
+                   <gml:Point srsName='http://www.opengis.net/def/crs/EPSG/0/5678'>          \r\n\
+                      <gml:pos>5 6 7 8</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointUnQualified>                                                         \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    PointQualified: { type: "Edm.Geometry", extensions: [] },
+                    PointUnQualified: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            PointQualified: {
+                __metadata: { type: "Edm.Geometry" },
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:1234"
+                    }
+                },
+                type: "Point",
+                coordinates: [1, 2, 3, 4]
+            },
+            PointUnQualified: {
+                __metadata: { type: "Edm.Geometry" },
+                crs: {
+                    type: "name",
+                    properties: {
+                        name: "EPSG:5678"
+                    }
+                },
+                type: "Point",
+                coordinates: [5, 6, 7, 8]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlUnknownCRSValueThrowsTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <Point>                                                                     \r\n\
+                   <gml:Point srsName='http://www.opengis.net/def/crs/EPSG/1/1234'>          \r\n\
+                      <gml:pos>1 2 3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                     \r\n\
+               </m:properties>                                                               \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        try {
+            OData.atomHandler.read(response);
+            djstest.fail("An exception was expected");
+        } catch (e) {
+            djstest.assert(e.message.indexOf("Unsupported srs name:") === 0, "Error is the expected one");
+        }
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlPointValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+               <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'   \r\n\
+                           xmlns:gml='http://www.opengis.net/gml'>                           \r\n\
+                 <Point>                                                                     \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>1 2 -3 4</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </Point>                                                                    \r\n\
+                 <PointWithExtraTags>                                                        \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:name>the point</gml:name>                                         \r\n\
+                      <gml:pos>5 6 7 8</gml:pos>                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithExtraTags>                                                       \r\n\
+                 <EmptyPoint >                                                               \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos/>                                                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </EmptyPoint>                                                               \r\n\
+                 <PointWithSpacesInValue>                                                    \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>  8  9 10   11      12 </gml:pos>                             \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSpacesInValue>                                                   \r\n\
+                 <PointWithSingleValue>                                                      \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos>13</gml:pos>                                                  \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSingleValue>                                                     \r\n\
+                 <PointWithSingleValueAndSpaces>                                             \r\n\
+                   <gml:Point>                                                               \r\n\
+                      <gml:pos> 14 </gml:pos>                                                \r\n\
+                   </gml:Point>                                                              \r\n\
+                 </PointWithSingleValueAndSpaces>                                            \r\n\
+              </m:properties>                                                                \r\n\
+              </content>                                                                     \r\n\
+            </entry>";
+
+        var entry = {
+            __metadata: {
+                properties: {
+                    Point: { type: "Edm.Geometry", extensions: [] },
+                    PointWithExtraTags: { type: "Edm.Geometry", extensions: [] },
+                    EmptyPoint: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSpacesInValue: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSingleValue: { type: "Edm.Geometry", extensions: [] },
+                    PointWithSingleValueAndSpaces: { type: "Edm.Geometry", extensions: [] }
+                }
+            },
+            Point: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [1, 2, -3, 4]
+            },
+            PointWithExtraTags: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [5, 6, 7, 8]
+            },
+            EmptyPoint: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: []
+            },
+            PointWithSpacesInValue: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [8, 9, 10, 11, 12]
+            },
+            PointWithSingleValue: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [13]
+            },
+            PointWithSingleValueAndSpaces: {
+                __metadata: { type: "Edm.Geometry" },
+                type: "Point",
+                coordinates: [14]
+            }
+        };
+
+        var response = { headers: { "Content-Type": "application/atom+xml" }, body: entryXml };
+
+        OData.atomHandler.read(response);
+        djstest.assertAreEqualDeep(response.data, entry, "Entry was read successfully");
+        djstest.done();
+    });
+
+    djstest.addTest(function atomReadEntryGmlLineStringValueTest() {
+        var entryXml =
+            "<entry                                                                          \r\n\
+               xmlns:m='http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'      \r\n\
+               xmlns='http://www.w3.org/2005/Atom'>                                          \r\n\
+               <content type='application/xml'>                                              \r\n\
+                <m:properties xmlns='http://schemas.microsoft.com/ado/2007/08/dataservices'  \r\n\
+                             xmlns:gml='http://www.opengis.net/gml'>                         \r\n\
+                 <LineStringExtraTags>                                                       \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:name>the line</gml:name>                                           \r\n\
+                     <gml:posList>1.0 2.0 3.0 4.0</gml:posList>                              \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringExtraTags>                                                      \r\n\
+                 <LineStringPosList>                                                         \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList>5.0 6.0 7.0 8.0</gml:posList>                              \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringPosList>                                                        \r\n\
+                 <LineStringEmptyPosList>                                                    \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:posList/>                                                          \r\n\
+                   </gml:LineString>                                                         \r\n\
+                 </LineStringEmptyPosList>                                                   \r\n\
+                 <LineStringPosAndPoint>                                                     \r\n\
+                   <gml:LineString>                                                          \r\n\
+                     <gml:pos>7 8</gml:pos>                                                  \r\n\
+                     <gml:pointProperty>                                                     \r\n\
+                        <gml:Point>                                                          \r\n\
+                          <gml:pos>9 10 11 12</gml:pos>                                      \r\n\
+                        </gml:Point>                                                         \r\n\
+                     </gml:pointProperty>                               

<TRUNCATED>

[06/11] [OLINGO-238] Build infrastructure for datajs I

Posted by ko...@apache.org.
http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/e29bbd8f/datajs/src/odata-utils.js
----------------------------------------------------------------------
diff --git a/datajs/src/odata-utils.js b/datajs/src/odata-utils.js
new file mode 100644
index 0000000..ad2cf00
--- /dev/null
+++ b/datajs/src/odata-utils.js
@@ -0,0 +1,1117 @@
+// Copyright (c) Microsoft Open Technologies, Inc.  All rights reserved.
+// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation 
+// files (the "Software"), to deal  in the Software without restriction, including without limitation the rights  to use, copy,
+// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 
+// Software is furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+// WARRANTIES OF MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// odata-utils.js
+
+(function (window, undefined) {
+
+    var datajs = window.datajs || {};
+    var odata = window.OData || {};
+
+    // Imports
+    var assigned = datajs.assigned;
+    var contains = datajs.contains;
+    var find = datajs.find;
+    var isArray = datajs.isArray;
+    var isDate = datajs.isDate;
+    var isObject = datajs.isObject;
+    var parseInt10 = datajs.parseInt10;
+
+    // CONTENT START
+
+    var dataItemTypeName = function (value, metadata) {
+        /// <summary>Gets the type name of a data item value that belongs to a feed, an entry, a complex type property, or a collection property.</summary>
+        /// <param name="value">Value of the data item from which the type name is going to be retrieved.</param>
+        /// <param name="metadata" type="object" optional="true">Object containing metadata about the data tiem.</param>
+        /// <remarks>
+        ///    This function will first try to get the type name from the data item's value itself if it is an object with a __metadata property; otherwise
+        ///    it will try to recover it from the metadata.  If both attempts fail, it will return null.
+        /// </remarks>
+        /// <returns type="String">Data item type name; null if the type name cannot be found within the value or the metadata</returns>
+
+        var valueTypeName = ((value && value.__metadata) || {}).type;
+        return valueTypeName || (metadata ? metadata.type : null);
+    };
+
+    var EDM = "Edm.";
+    var EDM_BINARY = EDM + "Binary";
+    var EDM_BOOLEAN = EDM + "Boolean";
+    var EDM_BYTE = EDM + "Byte";
+    var EDM_DATETIME = EDM + "DateTime";
+    var EDM_DATETIMEOFFSET = EDM + "DateTimeOffset";
+    var EDM_DECIMAL = EDM + "Decimal";
+    var EDM_DOUBLE = EDM + "Double";
+    var EDM_GUID = EDM + "Guid";
+    var EDM_INT16 = EDM + "Int16";
+    var EDM_INT32 = EDM + "Int32";
+    var EDM_INT64 = EDM + "Int64";
+    var EDM_SBYTE = EDM + "SByte";
+    var EDM_SINGLE = EDM + "Single";
+    var EDM_STRING = EDM + "String";
+    var EDM_TIME = EDM + "Time";
+
+    var EDM_GEOGRAPHY = EDM + "Geography";
+    var EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY + "Point";
+    var EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY + "LineString";
+    var EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY + "Polygon";
+    var EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY + "Collection";
+    var EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY + "MultiPolygon";
+    var EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY + "MultiLineString";
+    var EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY + "MultiPoint";
+
+    var EDM_GEOMETRY = EDM + "Geometry";
+    var EDM_GEOMETRY_POINT = EDM_GEOMETRY + "Point";
+    var EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY + "LineString";
+    var EDM_GEOMETRY_POLYGON = EDM_GEOMETRY + "Polygon";
+    var EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY + "Collection";
+    var EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY + "MultiPolygon";
+    var EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY + "MultiLineString";
+    var EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY + "MultiPoint";
+
+    var GEOJSON_POINT = "Point";
+    var GEOJSON_LINESTRING = "LineString";
+    var GEOJSON_POLYGON = "Polygon";
+    var GEOJSON_MULTIPOINT = "MultiPoint";
+    var GEOJSON_MULTILINESTRING = "MultiLineString";
+    var GEOJSON_MULTIPOLYGON = "MultiPolygon";
+    var GEOJSON_GEOMETRYCOLLECTION = "GeometryCollection";
+
+    var primitiveEdmTypes = [
+        EDM_STRING,
+        EDM_INT32,
+        EDM_INT64,
+        EDM_BOOLEAN,
+        EDM_DOUBLE,
+        EDM_SINGLE,
+        EDM_DATETIME,
+        EDM_DATETIMEOFFSET,
+        EDM_TIME,
+        EDM_DECIMAL,
+        EDM_GUID,
+        EDM_BYTE,
+        EDM_INT16,
+        EDM_SBYTE,
+        EDM_BINARY
+    ];
+
+    var geometryEdmTypes = [
+        EDM_GEOMETRY,
+        EDM_GEOMETRY_POINT,
+        EDM_GEOMETRY_LINESTRING,
+        EDM_GEOMETRY_POLYGON,
+        EDM_GEOMETRY_COLLECTION,
+        EDM_GEOMETRY_MULTIPOLYGON,
+        EDM_GEOMETRY_MULTILINESTRING,
+        EDM_GEOMETRY_MULTIPOINT
+    ];
+
+    var geographyEdmTypes = [
+        EDM_GEOGRAPHY,
+        EDM_GEOGRAPHY_POINT,
+        EDM_GEOGRAPHY_LINESTRING,
+        EDM_GEOGRAPHY_POLYGON,
+        EDM_GEOGRAPHY_COLLECTION,
+        EDM_GEOGRAPHY_MULTIPOLYGON,
+        EDM_GEOGRAPHY_MULTILINESTRING,
+        EDM_GEOGRAPHY_MULTIPOINT
+    ];
+
+    var forEachSchema = function (metadata, callback) {
+        /// <summary>Invokes a function once per schema in metadata.</summary>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <param name="callback" type="Function">Callback function to invoke once per schema.</param>
+        /// <returns>
+        /// The first truthy value to be returned from the callback; null or the last falsy value otherwise.
+        /// </returns>
+
+        if (!metadata) {
+            return null;
+        }
+
+        if (isArray(metadata)) {
+            var i, len, result;
+            for (i = 0, len = metadata.length; i < len; i++) {
+                result = forEachSchema(metadata[i], callback);
+                if (result) {
+                    return result;
+                }
+            }
+
+            return null;
+        } else {
+            if (metadata.dataServices) {
+                return forEachSchema(metadata.dataServices.schema, callback);
+            }
+
+            return callback(metadata);
+        }
+    };
+
+    var formatMilliseconds = function (ms, ns) {
+        /// <summary>Formats a millisecond and a nanosecond value into a single string.</summary>
+        /// <param name="ms" type="Number" mayBeNull="false">Number of milliseconds to format.</param>
+        /// <param name="ns" type="Number" mayBeNull="false">Number of nanoseconds to format.</param>
+        /// <returns type="String">Formatted text.</returns>
+        /// <remarks>If the value is already as string it's returned as-is.</remarks>
+
+        // Avoid generating milliseconds if not necessary.
+        if (ms === 0) {
+            ms = "";
+        } else {
+            ms = "." + formatNumberWidth(ms.toString(), 3);
+        }
+        if (ns > 0) {
+            if (ms === "") {
+                ms = ".000";
+            }
+            ms += formatNumberWidth(ns.toString(), 4);
+        }
+        return ms;
+    };
+
+    var formatDateTimeOffset = function (value) {
+        /// <summary>Formats a DateTime or DateTimeOffset value a string.</summary>
+        /// <param name="value" type="Date" mayBeNull="false">Value to format.</param>
+        /// <returns type="String">Formatted text.</returns>
+        /// <remarks>If the value is already as string it's returned as-is.</remarks>
+
+        if (typeof value === "string") {
+            return value;
+        }
+
+        var hasOffset = isDateTimeOffset(value);
+        var offset = getCanonicalTimezone(value.__offset);
+        if (hasOffset && offset !== "Z") {
+            // We're about to change the value, so make a copy.
+            value = new Date(value.valueOf());
+
+            var timezone = parseTimezone(offset);
+            var hours = value.getUTCHours() + (timezone.d * timezone.h);
+            var minutes = value.getUTCMinutes() + (timezone.d * timezone.m);
+
+            value.setUTCHours(hours, minutes);
+        } else if (!hasOffset) {
+            // Don't suffix a 'Z' for Edm.DateTime values.
+            offset = "";
+        }
+
+        var year = value.getUTCFullYear();
+        var month = value.getUTCMonth() + 1;
+        var sign = "";
+        if (year <= 0) {
+            year = -(year - 1);
+            sign = "-";
+        }
+
+        var ms = formatMilliseconds(value.getUTCMilliseconds(), value.__ns);
+
+        return sign +
+            formatNumberWidth(year, 4) + "-" +
+            formatNumberWidth(month, 2) + "-" +
+            formatNumberWidth(value.getUTCDate(), 2) + "T" +
+            formatNumberWidth(value.getUTCHours(), 2) + ":" +
+            formatNumberWidth(value.getUTCMinutes(), 2) + ":" +
+            formatNumberWidth(value.getUTCSeconds(), 2) +
+            ms + offset;
+    };
+
+    var formatDuration = function (value) {
+        /// <summary>Converts a duration to a string in xsd:duration format.</summary>
+        /// <param name="value" type="Object">Object with ms and __edmType properties.</param>
+        /// <returns type="String">String representation of the time object in xsd:duration format.</returns>
+
+        var ms = value.ms;
+
+        var sign = "";
+        if (ms < 0) {
+            sign = "-";
+            ms = -ms;
+        }
+
+        var days = Math.floor(ms / 86400000);
+        ms -= 86400000 * days;
+        var hours = Math.floor(ms / 3600000);
+        ms -= 3600000 * hours;
+        var minutes = Math.floor(ms / 60000);
+        ms -= 60000 * minutes;
+        var seconds = Math.floor(ms / 1000);
+        ms -= seconds * 1000;
+
+        return sign + "P" +
+               formatNumberWidth(days, 2) + "DT" +
+               formatNumberWidth(hours, 2) + "H" +
+               formatNumberWidth(minutes, 2) + "M" +
+               formatNumberWidth(seconds, 2) +
+               formatMilliseconds(ms, value.ns) + "S";
+    };
+
+    var formatNumberWidth = function (value, width, append) {
+        /// <summary>Formats the specified value to the given width.</summary>
+        /// <param name="value" type="Number">Number to format (non-negative).</param>
+        /// <param name="width" type="Number">Minimum width for number.</param>
+        /// <param name="append" type="Boolean">Flag indicating if the value is padded at the beginning (false) or at the end (true).</param>
+        /// <returns type="String">Text representation.</returns>
+        var result = value.toString(10);
+        while (result.length < width) {
+            if (append) {
+                result += "0";
+            } else {
+                result = "0" + result;
+            }
+        }
+
+        return result;
+    };
+
+    var getCanonicalTimezone = function (timezone) {
+        /// <summary>Gets the canonical timezone representation.</summary>
+        /// <param name="timezone" type="String">Timezone representation.</param>
+        /// <returns type="String">An 'Z' string if the timezone is absent or 0; the timezone otherwise.</returns>
+
+        return (!timezone || timezone === "Z" || timezone === "+00:00" || timezone === "-00:00") ? "Z" : timezone;
+    };
+
+    var getCollectionType = function (typeName) {
+        /// <summary>Gets the type of a collection type name.</summary>
+        /// <param name="typeName" type="String">Type name of the collection.</param>
+        /// <returns type="String">Type of the collection; null if the type name is not a collection type.</returns>
+
+        if (typeof typeName === "string") {
+            var end = typeName.indexOf(")", 10);
+            if (typeName.indexOf("Collection(") === 0 && end > 0) {
+                return typeName.substring(11, end);
+            }
+        }
+        return null;
+    };
+
+    var invokeRequest = function (request, success, error, handler, httpClient, context) {
+        /// <summary>Sends a request containing OData payload to a server.</summary>
+        /// <param name="request">Object that represents the request to be sent..</param>
+        /// <param name="success">Callback for a successful read operation.</param>
+        /// <param name="error">Callback for handling errors.</param>
+        /// <param name="handler">Handler for data serialization.</param>
+        /// <param name="httpClient">HTTP client layer.</param>
+        /// <param name="context">Context used for processing the request</param>
+
+        return httpClient.request(request, function (response) {
+            try {
+                if (response.headers) {
+                    normalizeHeaders(response.headers);
+                }
+
+                if (response.data === undefined && response.statusCode !== 204) {
+                    handler.read(response, context);
+                }
+            } catch (err) {
+                if (err.request === undefined) {
+                    err.request = request;
+                }
+                if (err.response === undefined) {
+                    err.response = response;
+                }
+                error(err);
+                return;
+            }
+
+            success(response.data, response);
+        }, error);
+    };
+
+    var isBatch = function (value) {
+        /// <summary>Tests whether a value is a batch object in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a batch object; false otherwise.</returns>
+
+        return isComplex(value) && isArray(value.__batchRequests);
+    };
+
+    // Regular expression used for testing and parsing for a collection type.
+    var collectionTypeRE = /Collection\((.*)\)/;
+
+    var isCollection = function (value, typeName) {
+        /// <summary>Tests whether a value is a collection value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param>
+        /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns>
+
+        var colData = value && value.results || value;
+        return !!colData &&
+            (isCollectionType(typeName)) ||
+            (!typeName && isArray(colData) && !isComplex(colData[0]));
+    };
+
+    var isCollectionType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a collection type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is the name of a collection type; false otherwise.</returns>
+        return collectionTypeRE.test(typeName);
+    };
+
+    var isComplex = function (value) {
+        /// <summary>Tests whether a value is a complex type value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a complex type value; false otherwise.</returns>
+
+        return !!value &&
+            isObject(value) &&
+            !isArray(value) &&
+            !isDate(value);
+    };
+
+    var isDateTimeOffset = function (value) {
+        /// <summary>Checks whether a Date object is DateTimeOffset value</summary>
+        /// <param name="value" type="Date" mayBeNull="false">Value to check.</param>
+        /// <returns type="Boolean">true if the value is a DateTimeOffset, false otherwise.</returns>
+        return (value.__edmType === "Edm.DateTimeOffset" || (!value.__edmType && value.__offset));
+    };
+
+    var isDeferred = function (value) {
+        /// <summary>Tests whether a value is a deferred navigation property in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a deferred navigation property; false otherwise.</returns>
+
+        if (!value && !isComplex(value)) {
+            return false;
+        }
+        var metadata = value.__metadata || {};
+        var deferred = value.__deferred || {};
+        return !metadata.type && !!deferred.uri;
+    };
+
+    var isEntry = function (value) {
+        /// <summary>Tests whether a value is an entry object in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is an entry object; false otherwise.</returns>
+
+        return isComplex(value) && value.__metadata && "uri" in value.__metadata;
+    };
+
+    var isFeed = function (value, typeName) {
+        /// <summary>Tests whether a value is a feed value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <param name="typeName" type="Sting">Type name of the value. This is used to disambiguate from a collection property value.</param>
+        /// <returns type="Boolean">True is the value is a feed value; false otherwise.</returns>
+
+        var feedData = value && value.results || value;
+        return isArray(feedData) && (
+            (!isCollectionType(typeName)) &&
+            (isComplex(feedData[0]))
+        );
+    };
+
+    var isGeographyEdmType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a geography EDM type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is a geography EDM type; false otherwise.</returns>
+
+        return contains(geographyEdmTypes, typeName);
+    };
+
+    var isGeometryEdmType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a geometry EDM type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is a geometry EDM type; false otherwise.</returns>
+
+        return contains(geometryEdmTypes, typeName);
+    };
+
+    var isNamedStream = function (value) {
+        /// <summary>Tests whether a value is a named stream value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <returns type="Boolean">True is the value is a named stream; false otherwise.</returns>
+
+        if (!value && !isComplex(value)) {
+            return false;
+        }
+        var metadata = value.__metadata;
+        var mediaResource = value.__mediaresource;
+        return !metadata && !!mediaResource && !!mediaResource.media_src;
+    };
+
+    var isPrimitive = function (value) {
+        /// <summary>Tests whether a value is a primitive type value in the library's internal representation.</summary>
+        /// <param name="value">Value to test.</param>
+        /// <remarks>
+        ///    Date objects are considered primitive types by the library.
+        /// </remarks>
+        /// <returns type="Boolean">True is the value is a primitive type value.</returns>
+
+        return isDate(value) ||
+            typeof value === "string" ||
+            typeof value === "number" ||
+            typeof value === "boolean";
+    };
+
+    var isPrimitiveEdmType = function (typeName) {
+        /// <summary>Checks whether the specified type name is a primitive EDM type.</summary>
+        /// <param name="typeName" type="String">Name of type to check.</param>
+        /// <returns type="Boolean">True if the type is a primitive EDM type; false otherwise.</returns>
+
+        return contains(primitiveEdmTypes, typeName);
+    };
+
+    var navigationPropertyKind = function (value, propertyModel) {
+        /// <summary>Gets the kind of a navigation property value.</summary>
+        /// <param name="value">Value of the navigation property.</param>
+        /// <param name="propertyModel" type="Object" optional="true">
+        ///     Object that describes the navigation property in an OData conceptual schema.
+        /// </param>
+        /// <remarks>
+        ///     The returned string is as follows
+        /// </remarks>
+        /// <returns type="String">String value describing the kind of the navigation property; null if the kind cannot be determined.</returns>
+
+        if (isDeferred(value)) {
+            return "deferred";
+        }
+        if (isEntry(value)) {
+            return "entry";
+        }
+        if (isFeed(value)) {
+            return "feed";
+        }
+        if (propertyModel && propertyModel.relationship) {
+            if (value === null || value === undefined || !isFeed(value)) {
+                return "entry";
+            }
+            return "feed";
+        }
+        return null;
+    };
+
+    var lookupProperty = function (properties, name) {
+        /// <summary>Looks up a property by name.</summary>
+        /// <param name="properties" type="Array" mayBeNull="true">Array of property objects as per EDM metadata.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Object">The property object; null if not found.</returns>
+
+        return find(properties, function (property) {
+            return property.name === name;
+        });
+    };
+
+    var lookupInMetadata = function (name, metadata, kind) {
+        /// <summary>Looks up a type object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param>
+        /// <returns>An type description if the name is found; null otherwise.</returns>
+
+        return (name) ? forEachSchema(metadata, function (schema) {
+            return lookupInSchema(name, schema, kind);
+        }) : null;
+    };
+
+    var lookupEntitySet = function (entitySets, name) {
+        /// <summary>Looks up a entity set by name.</summary>
+        /// <param name="properties" type="Array" mayBeNull="true">Array of entity set objects as per EDM metadata.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Object">The entity set object; null if not found.</returns>
+
+        return find(entitySets, function (entitySet) {
+            return entitySet.name === name;
+        });
+    };
+
+    var lookupComplexType = function (name, metadata) {
+        /// <summary>Looks up a complex type object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>A complex type description if the name is found; null otherwise.</returns>
+
+        return lookupInMetadata(name, metadata, "complexType");
+    };
+
+    var lookupEntityType = function (name, metadata) {
+        /// <summary>Looks up an entity type object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>An entity type description if the name is found; null otherwise.</returns>
+
+        return lookupInMetadata(name, metadata, "entityType");
+    };
+
+    var lookupDefaultEntityContainer = function (metadata) {
+        /// <summary>Looks up an</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>An entity container description if the name is found; null otherwise.</returns>
+
+        return forEachSchema(metadata, function (schema) {
+            return find(schema.entityContainer, function (container) {
+                return parseBool(container.isDefaultEntityContainer);
+            });
+        });
+    };
+
+    var lookupEntityContainer = function (name, metadata) {
+        /// <summary>Looks up an entity container object by name.</summary>
+        /// <param name="name" type="String">Name, possibly null or empty.</param>
+        /// <param name="metadata">Metadata store; one of edmx, schema, or an array of any of them.</param>
+        /// <returns>An entity container description if the name is found; null otherwise.</returns>
+
+        return lookupInMetadata(name, metadata, "entityContainer");
+    };
+
+    var lookupFunctionImport = function (functionImports, name) {
+        /// <summary>Looks up a function import by name.</summary>
+        /// <param name="properties" type="Array" mayBeNull="true">Array of function import objects as per EDM metadata.</param>
+        /// <param name="name" type="String">Name to look for.</param>
+        /// <returns type="Object">The entity set object; null if not found.</returns>
+
+        return find(functionImports, function (functionImport) {
+            return functionImport.name === name;
+        });
+    };
+
+    var lookupNavigationPropertyType = function (navigationProperty, metadata) {
+        /// <summary>Looks up the target entity type for a navigation property.</summary>
+        /// <param name="navigationProperty" type="Object"></param>
+        /// <param name="metadata" type="Object"></param>
+        /// <returns type="String">The entity type name for the specified property, null if not found.</returns>
+
+        var result = null;
+        if (navigationProperty) {
+            var rel = navigationProperty.relationship;
+            var association = forEachSchema(metadata, function (schema) {
+                // The name should be the namespace qualified name in 'ns'.'type' format.
+                var nameOnly = removeNamespace(schema["namespace"], rel);
+                var associations = schema.association;
+                if (nameOnly && associations) {
+                    var i, len;
+                    for (i = 0, len = associations.length; i < len; i++) {
+                        if (associations[i].name === nameOnly) {
+                            return associations[i];
+                        }
+                    }
+                }
+                return null;
+            });
+
+            if (association) {
+                var end = association.end[0];
+                if (end.role !== navigationProperty.toRole) {
+                    end = association.end[1];
+                    // For metadata to be valid, end.role === navigationProperty.toRole now.
+                }
+                result = end.type;
+            }
+        }
+        return result;
+    };
+
+    var lookupNavigationPropertyEntitySet = function (navigationProperty, sourceEntitySetName, metadata) {
+        /// <summary>Looks up the target entityset name for a navigation property.</summary>
+        /// <param name="navigationProperty" type="Object"></param>
+        /// <param name="metadata" type="Object"></param>
+        /// <returns type="String">The entityset name for the specified property, null if not found.</returns>
+
+        if (navigationProperty) {
+            var rel = navigationProperty.relationship;
+            var associationSet = forEachSchema(metadata, function (schema) {
+                var containers = schema.entityContainer;
+                for (var i = 0; i < containers.length; i++) {
+                    var associationSets = containers[i].associationSet;
+                    if (associationSets) {
+                        for (var j = 0; j < associationSets.length; j++) {
+                            if (associationSets[j].association == rel) {
+                                return associationSets[j];
+                            }
+                        }
+                    }
+                }
+                return null;
+            });
+            if (associationSet && associationSet.end[0] && associationSet.end[1]) {
+                return (associationSet.end[0].entitySet == sourceEntitySetName) ? associationSet.end[1].entitySet : associationSet.end[0].entitySet;
+            }
+        }
+        return null;
+    };
+
+    var getEntitySetInfo = function (entitySetName, metadata) {
+        /// <summary>Gets the entitySet info, container name and functionImports for an entitySet</summary>
+        /// <param name="navigationProperty" type="Object"></param>
+        /// <param name="metadata" type="Object"></param>
+        /// <returns type="Object">The info about the entitySet.</returns>
+
+        var info = forEachSchema(metadata, function (schema) {
+            var containers = schema.entityContainer;
+            for (var i = 0; i < containers.length; i++) {
+                var entitySets = containers[i].entitySet;
+                if (entitySets) {
+                    for (var j = 0; j < entitySets.length; j++) {
+                        if (entitySets[j].name == entitySetName) {
+                            return { entitySet: entitySets[j], containerName: containers[i].name, functionImport: containers[i].functionImport };
+                        }
+                    }
+                }
+            }
+            return null;
+        });
+
+        return info;
+    };
+
+    var removeNamespace = function (ns, fullName) {
+        /// <summary>Given an expected namespace prefix, removes it from a full name.</summary>
+        /// <param name="ns" type="String">Expected namespace.</param>
+        /// <param name="fullName" type="String">Full name in 'ns'.'name' form.</param>
+        /// <returns type="String">The local name, null if it isn't found in the expected namespace.</returns>
+
+        if (fullName.indexOf(ns) === 0 && fullName.charAt(ns.length) === ".") {
+            return fullName.substr(ns.length + 1);
+        }
+
+        return null;
+    };
+
+    var lookupInSchema = function (name, schema, kind) {
+        /// <summary>Looks up a schema object by name.</summary>
+        /// <param name="name" type="String">Name (assigned).</param>
+        /// <param name="schema">Schema object as per EDM metadata.</param>
+        /// <param name="kind" type="String">Kind of object to look for as per EDM metadata.</param>
+        /// <returns>An entity type description if the name is found; null otherwise.</returns>
+
+        if (name && schema) {
+            // The name should be the namespace qualified name in 'ns'.'type' format.
+            var nameOnly = removeNamespace(schema["namespace"], name);
+            if (nameOnly) {
+                return find(schema[kind], function (item) {
+                    return item.name === nameOnly;
+                });
+            }
+        }
+        return null;
+    };
+
+    var maxVersion = function (left, right) {
+        /// <summary>Compares to version strings and returns the higher one.</summary>
+        /// <param name="left" type="String">Version string in the form "major.minor.rev"</param>
+        /// <param name="right" type="String">Version string in the form "major.minor.rev"</param>
+        /// <returns type="String">The higher version string.</returns>
+
+        if (left === right) {
+            return left;
+        }
+
+        var leftParts = left.split(".");
+        var rightParts = right.split(".");
+
+        var len = (leftParts.length >= rightParts.length) ?
+            leftParts.length :
+            rightParts.length;
+
+        for (var i = 0; i < len; i++) {
+            var leftVersion = leftParts[i] && parseInt10(leftParts[i]);
+            var rightVersion = rightParts[i] && parseInt10(rightParts[i]);
+            if (leftVersion > rightVersion) {
+                return left;
+            }
+            if (leftVersion < rightVersion) {
+                return right;
+            }
+        }
+    };
+
+    var normalHeaders = {
+        "accept": "Accept",
+        "content-type": "Content-Type",
+        "dataserviceversion": "DataServiceVersion",
+        "maxdataserviceversion": "MaxDataServiceVersion"
+    };
+
+    var normalizeHeaders = function (headers) {
+        /// <summary>Normalizes headers so they can be found with consistent casing.</summary>
+        /// <param name="headers" type="Object">Dictionary of name/value pairs.</param>
+
+        for (var name in headers) {
+            var lowerName = name.toLowerCase();
+            var normalName = normalHeaders[lowerName];
+            if (normalName && name !== normalName) {
+                var val = headers[name];
+                delete headers[name];
+                headers[normalName] = val;
+            }
+        }
+    };
+
+    var parseBool = function (propertyValue) {
+        /// <summary>Parses a string into a boolean value.</summary>
+        /// <param name="propertyValue">Value to parse.</param>
+        /// <returns type="Boolean">true if the property value is 'true'; false otherwise.</returns>
+
+        if (typeof propertyValue === "boolean") {
+            return propertyValue;
+        }
+
+        return typeof propertyValue === "string" && propertyValue.toLowerCase() === "true";
+    };
+
+
+    // The captured indices for this expression are:
+    // 0     - complete input
+    // 1,2,3 - year with optional minus sign, month, day
+    // 4,5,6 - hours, minutes, seconds
+    // 7     - optional milliseconds
+    // 8     - everything else (presumably offset information)
+    var parseDateTimeRE = /^(-?\d{4,})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?(.*)$/;
+
+    var parseDateTimeMaybeOffset = function (value, withOffset, nullOnError) {
+        /// <summary>Parses a string into a DateTime value.</summary>
+        /// <param name="value" type="String">Value to parse.</param>
+        /// <param name="withOffset" type="Boolean">Whether offset is expected.</param>
+        /// <returns type="Date">The parsed value.</returns>
+
+        // We cannot parse this in cases of failure to match or if offset information is specified.
+        var parts = parseDateTimeRE.exec(value);
+        var offset = (parts) ? getCanonicalTimezone(parts[8]) : null;
+
+        if (!parts || (!withOffset && offset !== "Z")) {
+            if (nullOnError) {
+                return null;
+            }
+            throw { message: "Invalid date/time value" };
+        }
+
+        // Pre-parse years, account for year '0' being invalid in dateTime.
+        var year = parseInt10(parts[1]);
+        if (year <= 0) {
+            year++;
+        }
+
+        // Pre-parse optional milliseconds, fill in default. Fail if value is too precise.
+        var ms = parts[7];
+        var ns = 0;
+        if (!ms) {
+            ms = 0;
+        } else {
+            if (ms.length > 7) {
+                if (nullOnError) {
+                    return null;
+                }
+                throw { message: "Cannot parse date/time value to given precision." };
+            }
+
+            ns = formatNumberWidth(ms.substring(3), 4, true);
+            ms = formatNumberWidth(ms.substring(0, 3), 3, true);
+
+            ms = parseInt10(ms);
+            ns = parseInt10(ns);
+        }
+
+        // Pre-parse other time components and offset them if necessary.
+        var hours = parseInt10(parts[4]);
+        var minutes = parseInt10(parts[5]);
+        var seconds = parseInt10(parts[6]) || 0;
+        if (offset !== "Z") {
+            // The offset is reversed to get back the UTC date, which is
+            // what the API will eventually have.
+            var timezone = parseTimezone(offset);
+            var direction = -(timezone.d);
+            hours += timezone.h * direction;
+            minutes += timezone.m * direction;
+        }
+
+        // Set the date and time separately with setFullYear, so years 0-99 aren't biased like in Date.UTC.
+        var result = new Date();
+        result.setUTCFullYear(
+            year,                       // Year.
+            parseInt10(parts[2]) - 1,   // Month (zero-based for Date.UTC and setFullYear).
+            parseInt10(parts[3])        // Date.
+            );
+        result.setUTCHours(hours, minutes, seconds, ms);
+
+        if (isNaN(result.valueOf())) {
+            if (nullOnError) {
+                return null;
+            }
+            throw { message: "Invalid date/time value" };
+        }
+
+        if (withOffset) {
+            result.__edmType = "Edm.DateTimeOffset";
+            result.__offset = offset;
+        }
+
+        if (ns) {
+            result.__ns = ns;
+        }
+
+        return result;
+    };
+
+    var parseDateTime = function (propertyValue, nullOnError) {
+        /// <summary>Parses a string into a DateTime value.</summary>
+        /// <param name="propertyValue" type="String">Value to parse.</param>
+        /// <returns type="Date">The parsed value.</returns>
+
+        return parseDateTimeMaybeOffset(propertyValue, false, nullOnError);
+    };
+
+    var parseDateTimeOffset = function (propertyValue, nullOnError) {
+        /// <summary>Parses a string into a DateTimeOffset value.</summary>
+        /// <param name="propertyValue" type="String">Value to parse.</param>
+        /// <returns type="Date">The parsed value.</returns>
+        /// <remarks>
+        /// The resulting object is annotated with an __edmType property and
+        /// an __offset property reflecting the original intended offset of
+        /// the value. The time is adjusted for UTC time, as the current
+        /// timezone-aware Date APIs will only work with the local timezone.
+        /// </remarks>
+
+        return parseDateTimeMaybeOffset(propertyValue, true, nullOnError);
+    };
+
+    // The captured indices for this expression are:
+    // 0       - complete input
+    // 1       - direction
+    // 2,3,4   - years, months, days
+    // 5,6,7,8 - hours, minutes, seconds, miliseconds
+
+    var parseTimeRE = /^([+-])?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?/;
+
+    var isEdmDurationValue = function(value) {
+        parseTimeRE.test(value);
+    };
+
+    var parseDuration = function (duration) {
+        /// <summary>Parses a string in xsd:duration format.</summary>
+        /// <param name="duration" type="String">Duration value.</param>
+        /// <remarks>
+        /// This method will throw an exception if the input string has a year or a month component.
+        /// </remarks>
+        /// <returns type="Object">Object representing the time</returns>
+
+        var parts = parseTimeRE.exec(duration);
+
+        if (parts === null) {
+            throw { message: "Invalid duration value." };
+        }
+
+        var years = parts[2] || "0";
+        var months = parts[3] || "0";
+        var days = parseInt10(parts[4] || 0);
+        var hours = parseInt10(parts[5] || 0);
+        var minutes = parseInt10(parts[6] || 0);
+        var seconds = parseFloat(parts[7] || 0);
+
+        if (years !== "0" || months !== "0") {
+            throw { message: "Unsupported duration value." };
+        }
+
+        var ms = parts[8];
+        var ns = 0;
+        if (!ms) {
+            ms = 0;
+        } else {
+            if (ms.length > 7) {
+                throw { message: "Cannot parse duration value to given precision." };
+            }
+
+            ns = formatNumberWidth(ms.substring(3), 4, true);
+            ms = formatNumberWidth(ms.substring(0, 3), 3, true);
+
+            ms = parseInt10(ms);
+            ns = parseInt10(ns);
+        }
+
+        ms += seconds * 1000 + minutes * 60000 + hours * 3600000 + days * 86400000;
+
+        if (parts[1] === "-") {
+            ms = -ms;
+        }
+
+        var result = { ms: ms, __edmType: "Edm.Time" };
+
+        if (ns) {
+            result.ns = ns;
+        }
+        return result;
+    };
+
+    var parseTimezone = function (timezone) {
+        /// <summary>Parses a timezone description in (+|-)nn:nn format.</summary>
+        /// <param name="timezone" type="String">Timezone offset.</param>
+        /// <returns type="Object">
+        /// An object with a (d)irection property of 1 for + and -1 for -,
+        /// offset (h)ours and offset (m)inutes.
+        /// </returns>
+
+        var direction = timezone.substring(0, 1);
+        direction = (direction === "+") ? 1 : -1;
+
+        var offsetHours = parseInt10(timezone.substring(1));
+        var offsetMinutes = parseInt10(timezone.substring(timezone.indexOf(":") + 1));
+        return { d: direction, h: offsetHours, m: offsetMinutes };
+    };
+
+    var prepareRequest = function (request, handler, context) {
+        /// <summary>Prepares a request object so that it can be sent through the network.</summary>
+        /// <param name="request">Object that represents the request to be sent.</param>
+        /// <param name="handler">Handler for data serialization</param>
+        /// <param name="context">Context used for preparing the request</param>
+
+        // Default to GET if no method has been specified.
+        if (!request.method) {
+            request.method = "GET";
+        }
+
+        if (!request.headers) {
+            request.headers = {};
+        } else {
+            normalizeHeaders(request.headers);
+        }
+
+        if (request.headers.Accept === undefined) {
+            request.headers.Accept = handler.accept;
+        }
+
+        if (assigned(request.data) && request.body === undefined) {
+            handler.write(request, context);
+        }
+
+        if (!assigned(request.headers.MaxDataServiceVersion)) {
+            request.headers.MaxDataServiceVersion = handler.maxDataServiceVersion || "1.0";
+        }
+    };
+
+    var traverseInternal = function (item, owner, callback) {
+        /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
+        /// <param name="item" type="Object">Object or array to traverse.</param>
+        /// <param name="callback" type="Function">
+        /// Callback function with key and value, similar to JSON.parse reviver.
+        /// </param>
+        /// <returns type="Object">The object with traversed properties.</returns>
+        /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
+
+        if (item && typeof item === "object") {
+            for (var name in item) {
+                var value = item[name];
+                var result = traverseInternal(value, name, callback);
+                result = callback(name, result, owner);
+                if (result !== value) {
+                    if (value === undefined) {
+                        delete item[name];
+                    } else {
+                        item[name] = result;
+                    }
+                }
+            }
+        }
+
+        return item;
+    };
+
+    var traverse = function (item, callback) {
+        /// <summary>Traverses a tree of objects invoking callback for every value.</summary>
+        /// <param name="item" type="Object">Object or array to traverse.</param>
+        /// <param name="callback" type="Function">
+        /// Callback function with key and value, similar to JSON.parse reviver.
+        /// </param>
+        /// <returns type="Object">The traversed object.</returns>
+        /// <remarks>Unlike the JSON reviver, this won't delete null members.</remarks>
+
+        return callback("", traverseInternal(item, "", callback));
+    };
+
+    // DATAJS INTERNAL START
+    odata.dataItemTypeName = dataItemTypeName;
+    odata.EDM_BINARY = EDM_BINARY;
+    odata.EDM_BOOLEAN = EDM_BOOLEAN;
+    odata.EDM_BYTE = EDM_BYTE;
+    odata.EDM_DATETIME = EDM_DATETIME;
+    odata.EDM_DATETIMEOFFSET = EDM_DATETIMEOFFSET;
+    odata.EDM_DECIMAL = EDM_DECIMAL;
+    odata.EDM_DOUBLE = EDM_DOUBLE;
+    odata.EDM_GEOGRAPHY = EDM_GEOGRAPHY;
+    odata.EDM_GEOGRAPHY_POINT = EDM_GEOGRAPHY_POINT;
+    odata.EDM_GEOGRAPHY_LINESTRING = EDM_GEOGRAPHY_LINESTRING;
+    odata.EDM_GEOGRAPHY_POLYGON = EDM_GEOGRAPHY_POLYGON;
+    odata.EDM_GEOGRAPHY_COLLECTION = EDM_GEOGRAPHY_COLLECTION;
+    odata.EDM_GEOGRAPHY_MULTIPOLYGON = EDM_GEOGRAPHY_MULTIPOLYGON;
+    odata.EDM_GEOGRAPHY_MULTILINESTRING = EDM_GEOGRAPHY_MULTILINESTRING;
+    odata.EDM_GEOGRAPHY_MULTIPOINT = EDM_GEOGRAPHY_MULTIPOINT;
+    odata.EDM_GEOMETRY = EDM_GEOMETRY;
+    odata.EDM_GEOMETRY_POINT = EDM_GEOMETRY_POINT;
+    odata.EDM_GEOMETRY_LINESTRING = EDM_GEOMETRY_LINESTRING;
+    odata.EDM_GEOMETRY_POLYGON = EDM_GEOMETRY_POLYGON;
+    odata.EDM_GEOMETRY_COLLECTION = EDM_GEOMETRY_COLLECTION;
+    odata.EDM_GEOMETRY_MULTIPOLYGON = EDM_GEOMETRY_MULTIPOLYGON;
+    odata.EDM_GEOMETRY_MULTILINESTRING = EDM_GEOMETRY_MULTILINESTRING;
+    odata.EDM_GEOMETRY_MULTIPOINT = EDM_GEOMETRY_MULTIPOINT;
+    odata.EDM_GUID = EDM_GUID;
+    odata.EDM_INT16 = EDM_INT16;
+    odata.EDM_INT32 = EDM_INT32;
+    odata.EDM_INT64 = EDM_INT64;
+    odata.EDM_SBYTE = EDM_SBYTE;
+    odata.EDM_SINGLE = EDM_SINGLE;
+    odata.EDM_STRING = EDM_STRING;
+    odata.EDM_TIME = EDM_TIME;
+    odata.GEOJSON_POINT = GEOJSON_POINT;
+    odata.GEOJSON_LINESTRING = GEOJSON_LINESTRING;
+    odata.GEOJSON_POLYGON = GEOJSON_POLYGON;
+    odata.GEOJSON_MULTIPOINT = GEOJSON_MULTIPOINT;
+    odata.GEOJSON_MULTILINESTRING = GEOJSON_MULTILINESTRING;
+    odata.GEOJSON_MULTIPOLYGON = GEOJSON_MULTIPOLYGON;
+    odata.GEOJSON_GEOMETRYCOLLECTION = GEOJSON_GEOMETRYCOLLECTION;
+    odata.forEachSchema = forEachSchema;
+    odata.formatDateTimeOffset = formatDateTimeOffset;
+    odata.formatDuration = formatDuration;
+    odata.formatNumberWidth = formatNumberWidth;
+    odata.getCanonicalTimezone = getCanonicalTimezone;
+    odata.getCollectionType = getCollectionType;
+    odata.invokeRequest = invokeRequest;
+    odata.isBatch = isBatch;
+    odata.isCollection = isCollection;
+    odata.isCollectionType = isCollectionType;
+    odata.isComplex = isComplex;
+    odata.isDateTimeOffset = isDateTimeOffset;
+    odata.isDeferred = isDeferred;
+    odata.isEntry = isEntry;
+    odata.isFeed = isFeed;
+    odata.isGeographyEdmType = isGeographyEdmType;
+    odata.isGeometryEdmType = isGeometryEdmType;
+    odata.isNamedStream = isNamedStream;
+    odata.isPrimitive = isPrimitive;
+    odata.isPrimitiveEdmType = isPrimitiveEdmType;
+    odata.lookupComplexType = lookupComplexType;
+    odata.lookupDefaultEntityContainer = lookupDefaultEntityContainer;
+    odata.lookupEntityContainer = lookupEntityContainer;
+    odata.lookupEntitySet = lookupEntitySet;
+    odata.lookupEntityType = lookupEntityType;
+    odata.lookupFunctionImport = lookupFunctionImport;
+    odata.lookupNavigationPropertyType = lookupNavigationPropertyType;
+    odata.lookupNavigationPropertyEntitySet = lookupNavigationPropertyEntitySet;
+    odata.lookupInSchema = lookupInSchema;
+    odata.lookupProperty = lookupProperty;
+    odata.lookupInMetadata = lookupInMetadata;
+    odata.getEntitySetInfo = getEntitySetInfo;
+    odata.maxVersion = maxVersion;
+    odata.navigationPropertyKind = navigationPropertyKind;
+    odata.normalizeHeaders = normalizeHeaders;
+    odata.parseBool = parseBool;
+    odata.parseDateTime = parseDateTime;
+    odata.parseDateTimeOffset = parseDateTimeOffset;
+    odata.parseDuration = parseDuration;
+    odata.parseTimezone = parseTimezone;
+    odata.parseInt10 = parseInt10;
+    odata.prepareRequest = prepareRequest;
+    odata.removeNamespace = removeNamespace;
+    odata.traverse = traverse;
+
+    // DATAJS INTERNAL END
+
+    // CONTENT END
+})(this);
\ No newline at end of file