http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/odata.js
----------------------------------------------------------------------
diff --git a/lib/odata.js b/lib/odata.js
new file mode 100644
index 0000000..6f660f6
--- /dev/null
+++ b/lib/odata.js
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+ /** @module odata */
+
+// Imports
+var odataUtils    = exports.utils     = require('./odata/odatautils.js');
+var odataHandler  = exports.handler   = require('./odata/handler.js');
+var odataMetadata = exports.metadata  = require('./odata/metadata.js');
+var odataNet      = exports.net       = require('./odata/net.js');
+var odataJson     = exports.json      = require('./odata/json.js');
+                    exports.batch     = require('./odata/batch.js');
+                    
+
+
+var utils = require('./utils.js');
+var assigned = utils.assigned;
+
+var defined = utils.defined;
+var throwErrorCallback = utils.throwErrorCallback;
+
+var invokeRequest = odataUtils.invokeRequest;
+var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION;
+var prepareRequest = odataUtils.prepareRequest;
+var metadataParser = odataMetadata.metadataParser;
+
+// CONTENT START
+
+var handlers = [odataJson.jsonHandler, odataHandler.textHandler];
+
+/** Dispatches an operation to handlers.
+ * @param {String} handlerMethod - Name of handler method to invoke.
+ * @param {Object} requestOrResponse - request/response argument for delegated 
call.
+ * @param {Object} context - context argument for delegated call.
+ */
+function dispatchHandler(handlerMethod, requestOrResponse, context) {
+
+    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" };
+    }
+}
+
+/** Default success handler for OData.
+ * @param data - Data to process.
+ */
+exports.defaultSuccess = function (data) {
+
+    window.alert(window.JSON.stringify(data));
+};
+
+exports.defaultError = throwErrorCallback;
+
+exports.defaultHandler = {
+
+        /** Reads the body of the specified response by delegating to JSON 
handlers.
+        * @param response - Response object.
+        * @param context - Operation context.
+        */
+        read: function (response, context) {
+
+            if (response && assigned(response.body) && 
response.headers["Content-Type"]) {
+                dispatchHandler("read", response, context);
+            }
+        },
+
+        /** Write the body of the specified request by delegating to JSON 
handlers.
+        * @param request - Reques tobject.
+        * @param context - Operation context.
+        */
+        write: function (request, context) {
+
+            dispatchHandler("write", request, context);
+        },
+
+        maxDataServiceVersion: MAX_DATA_SERVICE_VERSION,
+        accept: "application/json;q=0.9, */*;q=0.1"
+    };
+
+exports.defaultMetadata = []; //TODO check why is the defaultMetadata an 
Array? and not an Object.
+
+/** Reads data from the specified URL.
+ * @param urlOrRequest - URL to read data from.
+ * @param {Function} [success] - 
+ * @param {Function} [error] - 
+ * @param {Object} [handler] - 
+ * @param {Object} [httpClient] - 
+ * @param {Object} [metadata] - 
+ */
+exports.read = function (urlOrRequest, success, error, handler, httpClient, 
metadata) {
+
+    var request;
+    if (urlOrRequest instanceof String || typeof urlOrRequest === "string") {
+        request = { requestUri: urlOrRequest };
+    } else {
+        request = urlOrRequest;
+    }
+
+    return exports.request(request, success, error, handler, httpClient, 
metadata);
+};
+
+/** Sends a request containing OData payload to a server.
+ * @param {Object} request - Object that represents the request to be sent.
+ * @param {Function} [success] - 
+ * @param {Function} [error] - 
+ * @param {Object} [handler] - 
+ * @param {Object} [httpClient] - 
+ * @param {Object} [metadata] - 
+ */
+exports.request = function (request, success, error, handler, httpClient, 
metadata) {
+
+    success = success || exports.defaultSuccess;
+    error = error || exports.defaultError;
+    handler = handler || exports.defaultHandler;
+    httpClient = httpClient || odataNet.defaultHttpClient;
+    metadata = metadata || exports.defaultMetadata;
+
+    // Augment the request with additional defaults.
+    request.recognizeDates = utils.defined(request.recognizeDates, 
odataJson.jsonHandler.recognizeDates);
+    request.callbackParameterName = 
utils.defined(request.callbackParameterName, 
odataNet.defaultHttpClient.callbackParameterName);
+    request.formatQueryString = utils.defined(request.formatQueryString, 
odataNet.defaultHttpClient.formatQueryString);
+    request.enableJsonpCallback = utils.defined(request.enableJsonpCallback, 
odataNet.defaultHttpClient.enableJsonpCallback);
+
+    // 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
+    };
+
+    try {
+        odataUtils.prepareRequest(request, handler, context);
+        return odataUtils.invokeRequest(request, success, error, handler, 
httpClient, context);
+    } catch (err) {
+        // errors in success handler for sync requests are catched here and 
result in error handler calls. 
+        // So here we fix this and throw that error further.
+        if (err.bIsSuccessHandlerError) {
+            throw err;
+        } else {
+            error(err);
+        }
+    }
+
+};
+
+/** Parses the csdl metadata to ODataJS metatdata format. This method can be 
used when the metadata is retrieved using something other than odatajs
+ * @param {string} csdlMetadataDocument - A string that represents the entire 
csdl metadata.
+ * @returns {Object} An object that has the representation of the metadata in 
odatajs format.
+ */
+exports.parseMetadata = function (csdlMetadataDocument) {
+
+    return metadataParser(null, csdlMetadataDocument);
+};
+
+// Configure the batch handler to use the default handler for the batch parts.
+exports.batch.batchHandler.partHandler = exports.defaultHandler;
+exports.metadataHandler =  odataMetadata.metadataHandler;
+exports.jsonHandler =  odataJson.jsonHandler;

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/odata/batch.js
----------------------------------------------------------------------
diff --git a/lib/odata/batch.js b/lib/odata/batch.js
new file mode 100644
index 0000000..d33ac53
--- /dev/null
+++ b/lib/odata/batch.js
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+/** @module odata/batch */
+
+var utils    = require('./../utils.js');
+var odataUtils    = require('./odatautils.js');
+var odataHandler = require('./handler.js');
+
+var extend = utils.extend;
+var isArray = utils.isArray;
+var trimString = utils.trimString;
+
+var contentType = odataHandler.contentType;
+var handler = odataHandler.handler;
+var isBatch = odataUtils.isBatch;
+var MAX_DATA_SERVICE_VERSION = odataHandler.MAX_DATA_SERVICE_VERSION;
+var normalizeHeaders = odataUtils.normalizeHeaders;
+//TODO var payloadTypeOf = odata.payloadTypeOf;
+var prepareRequest = odataUtils.prepareRequest;
+
+
+// Imports
+
+// CONTENT START
+var batchMediaType = "multipart/mixed";
+var responseStatusRegex = /^HTTP\/1\.\d (\d{3}) (.*)$/i;
+var responseHeaderRegex = /^([^()<>@,;:\\"\/[\]?={} \t]+)\s?:\s?(.*)/;
+
+/** Calculates a random 16 bit number and returns it in hexadecimal format.
+ * @returns {String} A 16-bit number in hex format.
+ */
+function hex16() {
+
+    return Math.floor((1 + Math.random()) * 0x10000).toString(16).substr(1);
+}
+
+/** Creates a string that can be used as a multipart request boundary.
+ * @param {String} [prefix] - 
+ * @returns {String} Boundary string of the format: 
<prefix><hex16>-<hex16>-<hex16>
+ */
+function createBoundary(prefix) {
+
+    return prefix + hex16() + "-" + hex16() + "-" + hex16();
+}
+
+/** Gets the handler for data serialization of individual requests / responses 
in a batch.
+ * @param context - Context used for data serialization.
+ * @returns Handler object
+ */
+function partHandler(context) {
+
+    return context.handler.partHandler;
+}
+
+/** Gets the current boundary used for parsing the body of a multipart 
response.
+ * @param context - Context used for parsing a multipart response.
+ * @returns {String} Boundary string.
+ */
+function currentBoundary(context) {
+    var boundaries = context.boundaries;
+    return boundaries[boundaries.length - 1];
+}
+
+/** Parses a batch response.
+ * @param handler - This handler.
+ * @param {String} text - Batch text.
+ * @param {Object} context - Object with parsing context.
+ * @return An object representation of the batch.
+ */
+function batchParser(handler, text, context) {
+
+    var boundary = context.contentType.properties["boundary"];
+    return { __batchResponses: readBatch(text, { boundaries: [boundary], 
handlerContext: context }) };
+}
+
+/** Serializes a batch object representation into text.
+ * @param handler - This handler.
+ * @param {Object} data - Representation of a batch.
+ * @param {Object} context - Object with parsing context.
+ * @return An text representation of the batch object; undefined if not 
applicable.#
+ */
+function batchSerializer(handler, data, context) {
+
+    var cType = context.contentType = context.contentType || 
contentType(batchMediaType);
+    if (cType.mediaType === batchMediaType) {
+        return writeBatch(data, context);
+    }
+}
+
+/** Parses a multipart/mixed response body from from the position defined by 
the context.
+ * @param {String}  text - Body of the multipart/mixed response.
+ * @param context - Context used for parsing.
+ * @return Array of objects representing the individual responses.
+ */
+function readBatch(text, context) {
+    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 = null;
+
+    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;
+}
+
+/** Parses the http headers in the text from the position defined by the 
context.
+ * @param {String} text - Text containing an http response's headers
+ * @param context - Context used for parsing.
+ * @returns Object containing the headers as key value pairs.
+ * This function doesn't support split headers and it will stop reading when 
it hits two consecutive line breaks.
+*/
+function readHeaders(text, context) {
+    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;
+}
+
+/** Parses an HTTP response.
+ * @param {String} text -Text representing the http response.
+ * @param context optional - Context used for parsing.
+ * @param {String} delimiter -String used as delimiter of the multipart 
response parts.
+ * @return Object representing the http response.
+ */
+function readResponse(text, context, delimiter) {
+    // 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)
+    };
+}
+
+/** Returns a substring from the position defined by the context up to the 
next line break (CRLF).
+ * @param {String} text - Input string.
+ * @param context - Context used for reading the input string.
+ * @returns {String} Substring to the first ocurrence of a line break or null 
if none can be found. 
+ */
+function readLine(text, context) {
+
+    return readTo(text, context, "\r\n");
+}
+
+/** 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.
+ * @param {String} text - Input string.
+ * @param context - Context used for reading the input string.
+ * @param {String} [str] - Substring to read up to.
+ * @returns {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.
+ */
+function readTo(text, context, str) {
+    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);
+}
+
+/** Serializes a batch request object to a string.
+ * @param data - Batch request object in payload representation format
+ * @param context - Context used for the serialization
+ * @returns {String} String representing the batch request
+ */
+function writeBatch(data, context) {
+    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;
+}
+
+/** Creates the delimiter that indicates that start or end of an individual 
request.
+ * @param {String} boundary Boundary string used to indicate the start of the 
request
+ * @param {Boolean} close - Flag indicating that a close delimiter string 
should be generated
+ * @returns {String} Delimiter string
+ */
+function writeBatchPartDelimiter(boundary, close) {
+    var result = "\r\n--" + boundary;
+    if (close) {
+        result += "--";
+    }
+
+    return result + "\r\n";
+}
+
+/** 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.
+ * @param part - Request or change set object in payload representation format
+ * @param context - Object containing context information used for the 
serialization
+ * @param {boolean} [nested] - 
+ * @returns {String} String representing the serialized part
+ * A change set is an array of request objects and they cannot be nested 
inside other change sets.
+ */
+function writeBatchPart(part, context, nested) {
+    
+
+    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;
+}
+
+/** Serializes a request object to a string.
+ * @param request - Request object to serialize
+ * @returns {String} String representing the serialized request
+ */
+function writeRequest(request) {
+    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;
+}
+
+
+
+/** batchHandler (see {@link module:odata/batch~batchParser}) */
+exports.batchHandler = handler(batchParser, batchSerializer, batchMediaType, 
MAX_DATA_SERVICE_VERSION);
+
+/** batchSerializer (see {@link module:odata/batch~batchSerializer}) */
+exports.batchSerializer = batchSerializer;
+
+/** writeRequest (see {@link module:odata/batch~writeRequest}) */
+exports.writeRequest = writeRequest;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/odata/handler.js
----------------------------------------------------------------------
diff --git a/lib/odata/handler.js b/lib/odata/handler.js
new file mode 100644
index 0000000..4c7260a
--- /dev/null
+++ b/lib/odata/handler.js
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+/** @module odata/handler */
+
+
+var utils    = require('./../utils.js');
+var oDataUtils    = require('./odatautils.js');
+
+// Imports.
+var assigned = utils.assigned;
+var extend = utils.extend;
+var trimString = utils.trimString;
+var maxVersion = oDataUtils.maxVersion;
+var MAX_DATA_SERVICE_VERSION = "4.0";
+
+/** Parses a string into an object with media type and properties.
+ * @param {String} str - String with media type to parse.
+ * @return null if the string is empty; an object with 'mediaType' and a 
'properties' dictionary otherwise.
+ */
+function contentType(str) {
+
+    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 };
+}
+
+/** Serializes an object with media type and properties dictionary into a 
string.
+ * @param contentType - Object with media type and properties dictionary to 
serialize.
+ * @return String representation of the media type object; undefined if 
contentType is null or undefined.
+ */
+function contentTypeToString(contentType) {
+    if (!contentType) {
+        return undefined;
+    }
+
+    var result = contentType.mediaType;
+    var property;
+    for (property in contentType.properties) {
+        result += ";" + property + "=" + contentType.properties[property];
+    }
+    return result;
+}
+
+/** Creates an object that is going to be used as the context for the 
handler's parser and serializer.
+ * @param contentType - Object with media type and properties dictionary.
+ * @param {String} dataServiceVersion - String indicating the version of the 
protocol to use.
+ * @param context - Operation context.
+ * @param handler - Handler object that is processing a resquest or response.
+ * @return Context object.
+ */
+function createReadWriteContext(contentType, dataServiceVersion, context, 
handler) {
+
+    var rwContext = {};
+    extend(rwContext, context);
+    extend(rwContext, {
+        contentType: contentType,
+        dataServiceVersion: dataServiceVersion,
+        handler: handler
+    });
+
+    return rwContext;
+}
+
+/** 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.
+ * @param request - Request object on which the header will be set.
+ * @param {String} name - Header name.
+ * @param {String} value - Header value.
+ */
+function fixRequestHeader(request, name, value) {
+    if (!request) {
+        return;
+    }
+
+    var headers = request.headers;
+    if (!headers[name]) {
+        headers[name] = value;
+    }
+}
+
+/** Sets the DataServiceVersion header of the request if its value is not yet 
defined or of a lower version.
+ * @param request - Request object on which the header will be set.
+ * @param {String} version - Version value.
+ *  If the request has already a version value higher than the one supplied 
the this function does nothing.
+ */
+function fixDataServiceVersionHeader(request, version) {   
+
+    if (request) {
+        var headers = request.headers;
+        var dsv = headers["OData-Version"];
+        headers["OData-Version"] = dsv ? maxVersion(dsv, version) : version;
+    }
+}
+
+/** Gets the value of a request or response header.
+ * @param requestOrResponse - Object representing a request or a response.
+ * @param {String} name - Name of the header to retrieve.
+ * @returns {String} String value of the header; undefined if the header 
cannot be found.
+ */
+function getRequestOrResponseHeader(requestOrResponse, name) {
+
+    var headers = requestOrResponse.headers;
+    return (headers && headers[name]) || undefined;
+}
+
+/** Gets the value of the Content-Type header from a request or response.
+ * @param requestOrResponse - Object representing a request or a response.
+ * @returns {Object} Object with 'mediaType' and a 'properties' dictionary; 
null in case that the header is not found or doesn't have a value.
+ */
+function getContentType(requestOrResponse) {
+
+    return contentType(getRequestOrResponseHeader(requestOrResponse, 
"Content-Type"));
+}
+
+var versionRE = /^\s?(\d+\.\d+);?.*$/;
+/** Gets the value of the DataServiceVersion header from a request or response.
+ * @param requestOrResponse - Object representing a request or a response.
+ * @returns {String} Data service version; undefined if the header cannot be 
found.
+ */
+function getDataServiceVersion(requestOrResponse) {
+
+    var value = getRequestOrResponseHeader(requestOrResponse, "OData-Version");
+    if (value) {
+        var matches = versionRE.exec(value);
+        if (matches && matches.length) {
+            return matches[1];
+        }
+    }
+
+    // Fall through and return undefined.
+}
+
+/** Checks that a handler can process a particular mime type.
+ * @param handler - Handler object that is processing a resquest or response.
+ * @param cType - Object with 'mediaType' and a 'properties' dictionary.
+ * @returns {Boolean} True if the handler can process the mime type; false 
otherwise.
+ *
+ * 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.
+ */
+function handlerAccepts(handler, cType) {
+    return handler.accept.indexOf(cType.mediaType) >= 0;
+}
+
+/** Invokes the parser associated with a handler for reading the payload of a 
HTTP response.
+ * @param handler - Handler object that is processing the response.
+ * @param {Function} parseCallback - Parser function that will process the 
response payload.
+ * @param response - HTTP response whose payload is going to be processed.
+ * @param context - Object used as the context for processing the response.
+ * @returns {Boolean} True if the handler processed the response payload and 
the response.data property was set; false otherwise.
+ */
+function handlerRead(handler, parseCallback, response, context) {
+
+    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;
+}
+
+/** Invokes the serializer associated with a handler for generating the 
payload of a HTTP request.
+ * @param handler - Handler object that is processing the request.
+ * @param {Function} serializeCallback - Serializer function that will 
generate the request payload.
+ * @param request - HTTP request whose payload is going to be generated.
+ * @param context - Object used as the context for serializing the request.
+ * @returns {Boolean} True if the handler serialized the request payload and 
the request.body property was set; false otherwise.
+ */
+function handlerWrite(handler, serializeCallback, request, context) {
+    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 || "4.0");
+
+            fixRequestHeader(request, "Content-Type", 
contentTypeToString(writeContext.contentType));
+            fixRequestHeader(request, "OData-MaxVersion", 
handler.maxDataServiceVersion);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/** Creates a handler object for processing HTTP requests and responses.
+ * @param {Function} parseCallback - Parser function that will process the 
response payload.
+ * @param {Function} serializeCallback - Serializer function that will 
generate the request payload.
+ * @param {String} accept - String containing a comma separated list of the 
mime types that this handler can work with.
+ * @param {String} maxDataServiceVersion - String indicating the highest 
version of the protocol that this handler can work with.
+ * @returns {Object} Handler object.
+ */
+function handler(parseCallback, serializeCallback, accept, 
maxDataServiceVersion) {
+
+    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);
+        }
+    };
+}
+
+function textParse(handler, body /*, context */) {
+    return body;
+}
+
+function textSerialize(handler, data /*, context */) {
+    if (assigned(data)) {
+        return data.toString();
+    } else {
+        return undefined;
+    }
+}
+
+
+
+
+exports.textHandler = handler(textParse, textSerialize, "text/plain", 
MAX_DATA_SERVICE_VERSION);
+exports.contentType = contentType;
+exports.contentTypeToString = contentTypeToString;
+exports.handler = handler;
+exports.createReadWriteContext = createReadWriteContext;
+exports.fixRequestHeader = fixRequestHeader;
+exports.getRequestOrResponseHeader = getRequestOrResponseHeader;
+exports.getContentType = getContentType;
+exports.getDataServiceVersion = getDataServiceVersion;
+exports.MAX_DATA_SERVICE_VERSION = MAX_DATA_SERVICE_VERSION;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/odata/json.js
----------------------------------------------------------------------
diff --git a/lib/odata/json.js b/lib/odata/json.js
new file mode 100644
index 0000000..f00b54c
--- /dev/null
+++ b/lib/odata/json.js
@@ -0,0 +1,1008 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** @module odata/json */
+
+
+
+var utils        = require('./../utils.js');
+var oDataUtils   = require('./odatautils.js');
+var oDataHandler = require('./handler.js');
+
+var odataNs = "odata";
+var odataAnnotationPrefix = odataNs + ".";
+var contextUrlAnnotation = "@" + odataAnnotationPrefix + "context";
+
+var assigned = utils.assigned;
+var defined = utils.defined;
+var isArray = utils.isArray;
+//var isDate = utils.isDate;
+var isObject = utils.isObject;
+//var normalizeURI = utils.normalizeURI;
+var parseInt10 = utils.parseInt10;
+var getFormatKind = utils.getFormatKind;
+var convertByteArrayToHexString = utils.convertByteArrayToHexString;
+
+
+var formatDateTimeOffset = oDataUtils.formatDateTimeOffset;
+var formatDuration = oDataUtils.formatDuration;
+var formatNumberWidth = oDataUtils.formatNumberWidth;
+var getCanonicalTimezone = oDataUtils.getCanonicalTimezone;
+var handler = oDataUtils.handler;
+var isComplex = oDataUtils.isComplex;
+var isPrimitive = oDataUtils.isPrimitive;
+var isCollectionType = oDataUtils.isCollectionType;
+var lookupComplexType = oDataUtils.lookupComplexType;
+var lookupEntityType = oDataUtils.lookupEntityType;
+var lookupSingleton = oDataUtils.lookupSingleton;
+var lookupEntitySet = oDataUtils.lookupEntitySet;
+var lookupDefaultEntityContainer = oDataUtils.lookupDefaultEntityContainer;
+var lookupProperty = oDataUtils.lookupProperty;
+var MAX_DATA_SERVICE_VERSION = oDataUtils.MAX_DATA_SERVICE_VERSION;
+var maxVersion = oDataUtils.maxVersion;
+
+var isPrimitiveEdmType = oDataUtils.isPrimitiveEdmType;
+var isGeographyEdmType = oDataUtils.isGeographyEdmType;
+var isGeometryEdmType = oDataUtils.isGeometryEdmType;
+
+var PAYLOADTYPE_FEED = "f";
+var PAYLOADTYPE_ENTRY = "e";
+var PAYLOADTYPE_PROPERTY = "p";
+var PAYLOADTYPE_COLLECTION = "c";
+var PAYLOADTYPE_ENUMERATION_PROPERTY = "enum";
+var PAYLOADTYPE_SVCDOC = "s";
+var PAYLOADTYPE_ENTITY_REF_LINK = "erl";
+var PAYLOADTYPE_ENTITY_REF_LINKS = "erls";
+
+var PAYLOADTYPE_VALUE = "v";
+
+var PAYLOADTYPE_DELTA = "d";
+var DELTATYPE_FEED = "f";
+var DELTATYPE_DELETED_ENTRY = "de";
+var DELTATYPE_LINK = "l";
+var DELTATYPE_DELETED_LINK = "dl";
+
+var jsonMediaType = "application/json";
+var jsonContentType = oDataHandler.contentType(jsonMediaType);
+
+var jsonSerializableMetadata = ["@odata.id", "@odata.type"];
+
+
+
+
+
+/** Extend JSON OData payload with metadata
+ * @param handler - This handler.
+ * @param text - Payload text (this parser also handles pre-parsed objects).
+ * @param {Object} context - Object with parsing context.
+ * @return An object representation of the OData payload.
+ */
+function jsonParser(handler, text, context) {
+    var recognizeDates = defined(context.recognizeDates, 
handler.recognizeDates);
+    var model = context.metadata;
+    var json = (typeof text === "string") ? JSON.parse(text) : text;
+    var metadataContentType;
+    if (assigned(context.contentType) && 
assigned(context.contentType.properties)) {
+        metadataContentType = 
context.contentType.properties["odata.metadata"]; //TODO convert to lower 
before comparism
+    }
+
+    var payloadFormat = getFormatKind(metadataContentType, 1); // none: 0, 
minimal: 1, full: 2
+
+    // No errors should be throw out if we could not parse the json payload, 
instead we should just return the original json object.
+    if (payloadFormat === 0) {
+        return json;
+    }
+    else if (payloadFormat === 1) {
+        return addMinimalMetadataToJsonPayload(json, model, recognizeDates);
+    }
+    else if (payloadFormat === 2) {
+        // to do: using the EDM Model to get the type of each property instead 
of just guessing.
+        return addFullMetadataToJsonPayload(json, model, recognizeDates);
+    }
+    else {
+        return json;
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// 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+)?\)\/$/;
+
+
+// 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 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;
+    }
+};
+
+/** Serializes a ODataJs payload structure to the wire format which can be 
send to the server
+ * @param handler - This handler.
+ * @param data - Data to serialize.
+ * @param {Object} context - Object with serialization context.
+ * @returns {String} The string representation of data.
+ */
+function jsonSerializer(handler, data, context) {
+
+    var dataServiceVersion = context.dataServiceVersion || "4.0";
+    var cType = context.contentType = context.contentType || jsonContentType;
+
+    if (cType && cType.mediaType === jsonContentType.mediaType) {
+        context.dataServiceVersion = maxVersion(dataServiceVersion, "4.0");
+        var newdata = formatJsonRequestPayload(data);
+        if (newdata) {
+            return JSON.stringify(newdata,jsonReplacer);
+        }
+    }
+    return undefined;
+}
+
+
+
+
+/** Convert OData objects for serialisation in to a new data structure
+ * @param data - Data to serialize.
+ * @returns {String} The string representation of data.
+ */
+function formatJsonRequestPayload(data) {
+    if (!data) {
+        return data;
+    }
+
+    if (isPrimitive(data)) {
+        return data;
+    }
+
+    if (isArray(data)) {
+        var newArrayData = [];
+        var i, len;
+        for (i = 0, len = data.length; i < len; i++) {
+            newArrayData[i] = formatJsonRequestPayload(data[i]);
+        }
+
+        return newArrayData;
+    }
+
+    var newdata = {};
+    for (var property in data) {
+        if (isJsonSerializableProperty(property)) {
+            newdata[property] = formatJsonRequestPayload(data[property]);
+        }
+    }
+
+    return newdata;
+}
+
+/** Determine form the attribute name if the attribute is a serializable 
property
+ * @param attribute
+ * @returns {boolean}
+ */
+function isJsonSerializableProperty(attribute) {
+    if (!attribute) {
+        return false;
+    }
+
+    if (attribute.indexOf("@odata.") == -1) {
+        return true;
+    }
+
+    var i, len;
+    for (i = 0, len = jsonSerializableMetadata.length; i < len; i++) {
+        var name = jsonSerializableMetadata[i];
+        if (attribute.indexOf(name) != -1) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/** Creates an object containing information for the json payload.
+ * @param {String} kind - JSON payload kind
+ * @param {String} type - Type name of the JSON payload.
+ * @returns {Object} Object with kind and type fields.
+ */
+function jsonMakePayloadInfo(kind, type) {
+    return { kind: kind, type: type || null };
+}
+
+
+
+/** Add metadata to an JSON payload complex object containing full metadata
+ * @param {Object} data - Data structure to be extended
+ * @param {Object} model - Metadata model
+ * @param {Boolean} recognizeDates - Flag indicating whether datetime literal 
strings should be converted to JavaScript Date objects.
+ */
+function addFullMetadataToJsonPayload(data, model, recognizeDates) {
+    var type;
+    if (utils.isObject(data)) {
+        for (var key in data) {
+            if (data.hasOwnProperty(key)) {
+                if (key.indexOf('@') === -1) {
+                    if (utils.isArray(data[key])) {
+                        for (var i = 0; i < data[key].length; ++i) {
+                            addFullMetadataToJsonPayload(data[key][i], model, 
recognizeDates);
+                        }
+                    } else if (utils.isObject(data[key])) {
+                        if (data[key] !== null) {
+                            //don't step into geo.. objects
+                            type = data[key+'@odata.type'];
+                            if (!type) {
+                                //type unknown
+                                addFullMetadataToJsonPayload(data[key], model, 
recognizeDates);
+                            } else {
+                                type = type.substring(1);
+                                if  (isGeographyEdmType(type) || 
isGeometryEdmType(type)) {
+                                    // don't add type info for geo* types
+                                } else {
+                                    addFullMetadataToJsonPayload(data[key], 
model, recognizeDates);
+                                }
+                            }
+                        }
+                    } else {
+                        type = data[key + '@odata.type'];
+
+                        // On .Net OData library, some basic EDM type is 
omitted, e.g. Edm.String, Edm.Int, and etc.
+                        // For the full metadata payload, we need to full fill 
the @data.type for each property if it is missing.
+                        // We do this is to help the OlingoJS consumers to 
easily get the type of each property.
+                        if (!assigned(type)) {
+                            // Guessing the "type" from the type of the value 
is not the right way here.
+                            // To do: we need to get the type from metadata 
instead of guessing.
+                            var typeFromObject = typeof data[key];
+                            if (typeFromObject === 'string') {
+                                addType(data, key, 'String');
+                            } else if (typeFromObject === 'boolean') {
+                                addType(data, key, 'Boolean');
+                            } else if (typeFromObject === 'number') {
+                                if (data[key] % 1 === 0) { // has fraction
+                                    addType(data, key, 'Int32'); // the biggst 
integer
+                                } else {
+                                    addType(data, key, 'Decimal'); // the 
biggst float single,doulbe,decimal
+                                }
+                            }
+                        }
+                        else {
+                            if (recognizeDates) {
+                                convertDatesNoEdm(data, key, 
type.substring(1));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return data;
+}
+
+/** Loop through the properties of an JSON payload object, look up the type 
info of the property and call
+ * the appropriate add*MetadataToJsonPayloadObject function
+ * @param {Object} data - Data structure to be extended
+ * @param {String} objectInfoType - Information about the data 
(name,type,typename,...)
+ * @param {String} baseURI - Base Url
+ * @param {Object} model - Metadata model
+ * @param {Boolean} recognizeDates - Flag indicating whether datetime literal 
strings should be converted to JavaScript Date objects.
+ */
+function checkProperties(data, objectInfoType, baseURI, model, recognizeDates) 
{
+    for (var name in data) {
+        if (name.indexOf("@") === -1) {
+            var curType = objectInfoType;
+            var propertyValue = data[name];
+            var property = lookupProperty(curType.property,name); //TODO SK 
add check for parent type
+
+            while (( property === null) && (curType.baseType !== undefined)) {
+                curType = lookupEntityType(curType.baseType, model);
+                property = lookupProperty(curType.property,name);
+            }
+
+            if ( isArray(propertyValue)) {
+                //data[name+'@odata.type'] = '#' + property.type;
+                if (isCollectionType(property.type)) {
+                    
addTypeColNoEdm(data,name,property.type.substring(11,property.type.length-1));
+                } else {
+                    addTypeNoEdm(data,name,property.type);
+                }
+
+
+                for ( var i = 0; i < propertyValue.length; i++) {
+                    addMetadataToJsonMinimalPayloadComplex(propertyValue[i], 
property, baseURI, model, recognizeDates);
+                }
+            } else if (isObject(propertyValue) && (propertyValue !== null)) {
+                addMetadataToJsonMinimalPayloadComplex(propertyValue, 
property, baseURI, model, recognizeDates);
+            } else {
+                //data[name+'@odata.type'] = '#' + property.type;
+                addTypeNoEdm(data,name,property.type);
+                if (recognizeDates) {
+                    convertDates(data, name, property.type);
+                }
+            }
+        }
+    }
+}
+
+
+
+/** Add metadata to an JSON payload object containing minimal metadata
+ * @param {Object} data - Json response payload object
+ * @param {Object} model - Object describing an OData conceptual schema
+ * @param {Boolean} recognizeDates - Flag indicating whether datetime literal 
strings should be converted to JavaScript Date objects.
+ * @returns {Object} Object in the library's representation.
+ */
+function addMinimalMetadataToJsonPayload(data, model, recognizeDates) {
+
+    if (!assigned(model) || isArray(model)) {
+        return data;
+    }
+
+    var baseURI = data[contextUrlAnnotation];
+    var payloadInfo = createPayloadInfo(data, model);
+
+    switch (payloadInfo.detectedPayloadKind) {
+
+        case PAYLOADTYPE_VALUE:
+            if (payloadInfo.type !== null) {
+                return addMetadataToJsonMinimalPayloadEntity(data, 
payloadInfo, baseURI, model, recognizeDates);
+            } else {
+                return addTypeNoEdm(data,'value', payloadInfo.typeName);
+            }
+
+        case PAYLOADTYPE_FEED:
+            return addMetadataToJsonMinimalPayloadFeed(data, model, 
payloadInfo, baseURI, recognizeDates);
+
+        case PAYLOADTYPE_ENTRY:
+            return addMetadataToJsonMinimalPayloadEntity(data, payloadInfo, 
baseURI, model, recognizeDates);
+
+        case PAYLOADTYPE_COLLECTION:
+            return addMetadataToJsonMinimalPayloadCollection(data, model, 
payloadInfo, baseURI, recognizeDates);
+
+        case PAYLOADTYPE_PROPERTY:
+            if (payloadInfo.type !== null) {
+                return addMetadataToJsonMinimalPayloadEntity(data, 
payloadInfo, baseURI, model, recognizeDates);
+            } else {
+                return addTypeNoEdm(data,'value', payloadInfo.typeName);
+            }
+
+        case PAYLOADTYPE_SVCDOC:
+            return data;
+
+        case PAYLOADTYPE_LINKS:
+            return data;
+    }
+
+    return data;
+}
+
+/** Add metadata to an JSON payload feed object containing minimal metadata
+ * @param {Object} data - Data structure to be extended
+ * @param {Object} model - Metadata model
+ * @param {String} feedInfo - Information about the data 
(name,type,typename,...)
+ * @param {String} baseURI - Base Url
+ * @param {Boolean} recognizeDates - Flag indicating whether datetime literal 
strings should be converted to JavaScript Date objects.
+ */
+function addMetadataToJsonMinimalPayloadFeed(data, model, feedInfo, baseURI, 
recognizeDates) {
+    var entries = [];
+    var items = data.value;
+    var i,len;
+    var entry;
+    for (i = 0, len = items.length; i < len; i++) {
+        var item = items[i];
+        if ( defined(item['@odata.type'])) { // in case of mixed feeds
+            var typeName = item['@odata.type'].substring(1);
+            var type = lookupEntityType( typeName, model);
+            var entryInfo = {
+                contentTypeOdata : feedInfo.contentTypeOdata,
+                detectedPayloadKind : feedInfo.detectedPayloadKind,
+                name : feedInfo.name,
+                type : type,
+                typeName : typeName
+            };
+
+            entry = addMetadataToJsonMinimalPayloadEntity(item, entryInfo, 
baseURI, model, recognizeDates);
+        } else {
+            entry = addMetadataToJsonMinimalPayloadEntity(item, feedInfo, 
baseURI, model, recognizeDates);
+        }
+
+        entries.push(entry);
+    }
+    data.value = entries;
+    return data;
+}
+
+
+/** Add metadata to an JSON payload entity object containing minimal metadata
+ * @param {Object} data - Data structure to be extended
+ * @param {String} objectInfo - Information about the data 
(name,type,typename,...)
+ * @param {String} baseURI - Base Url
+ * @param {Object} model - Metadata model
+ * @param {Boolean} recognizeDates - Flag indicating whether datetime literal 
strings should be converted to JavaScript Date objects.
+ */
+function addMetadataToJsonMinimalPayloadEntity(data, objectInfo, baseURI, 
model, recognizeDates) {
+    addType(data,'',objectInfo.typeName);
+
+    var keyType = objectInfo.type;
+    while ((defined(keyType)) && ( keyType.key === undefined) && 
(keyType.baseType !== undefined)) {
+        keyType = lookupEntityType(keyType.baseType, model);
+    }
+
+    if (keyType.key !== undefined) {
+        var lastIdSegment = objectInfo.name + jsonGetEntryKey(data, keyType);
+        data['@odata.id'] = baseURI.substring(0, 
baseURI.lastIndexOf("$metadata")) + lastIdSegment;
+        data['@odata.editLink'] = lastIdSegment;
+    }
+
+    //var serviceURI = baseURI.substring(0, baseURI.lastIndexOf("$metadata"));
+
+    checkProperties(data, objectInfo.type, baseURI, model, recognizeDates);
+
+    return data;
+}
+
+/** Add metadata to an JSON payload complex object containing minimal metadata
+ * @param {Object} data - Data structure to be extended
+ * @param {String} property - Information about the data 
(name,type,typename,...)
+ * @param {String} baseURI - Base Url
+ * @param {Object} model - Metadata model
+ * @param {Boolean} recognizeDates - Flag indicating whether datetime literal 
strings should be converted to JavaScript Date objects.
+ */
+function addMetadataToJsonMinimalPayloadComplex(data, property, baseURI, 
model, recognizeDates) {
+    var type = property.type;
+    if (isCollectionType(property.type)) {
+        type =property.type.substring(11,property.type.length-1);
+    }
+
+    addType(data,'',property.type);
+
+    var propertyType = lookupComplexType(type, model);
+    if (propertyType === null)  {
+        return; //TODO check what to do if the type is not known e.g. type 
#GeometryCollection
+    }
+
+    checkProperties(data, propertyType, baseURI, model, recognizeDates);
+}
+
+/** Add metadata to an JSON payload collection object containing minimal 
metadata
+ * @param {Object} data - Data structure to be extended
+ * @param {Object} model - Metadata model
+ * @param {String} collectionInfo - Information about the data 
(name,type,typename,...)
+ * @param {String} baseURI - Base Url
+ * @param {Boolean} recognizeDates - Flag indicating whether datetime literal 
strings should be converted to JavaScript Date objects.
+ */
+function addMetadataToJsonMinimalPayloadCollection(data, model, 
collectionInfo, baseURI, recognizeDates) {
+
+    addTypeColNoEdm(data,'', collectionInfo.typeName);
+
+    if (collectionInfo.type !== null) {
+        var entries = [];
+
+        var items = data.value;
+        var i,len;
+        var entry;
+        for (i = 0, len = items.length; i < len; i++) {
+            var item = items[i];
+            if ( defined(item['@odata.type'])) { // in case of mixed 
collections
+                var typeName = item['@odata.type'].substring(1);
+                var type = lookupEntityType( typeName, model);
+                var entryInfo = {
+                    contentTypeOdata : collectionInfo.contentTypeOdata,
+                    detectedPayloadKind : collectionInfo.detectedPayloadKind,
+                    name : collectionInfo.name,
+                    type : type,
+                    typeName : typeName
+                };
+
+                entry = addMetadataToJsonMinimalPayloadEntity(item, entryInfo, 
baseURI, model, recognizeDates);
+            } else {
+                entry = addMetadataToJsonMinimalPayloadEntity(item, 
collectionInfo, baseURI, model, recognizeDates);
+            }
+
+            entries.push(entry);
+        }
+        data.value = entries;
+    }
+    return data;
+}
+
+/** Add an OData type tag to an JSON payload object
+ * @param {Object} data - Data structure to be extended
+ * @param {String} name - Name of the property whose type is set
+ * @param {String} value - Type name
+ */
+function addType(data, name, value ) {
+    var fullName = name + '@odata.type';
+
+    if ( data[fullName] === undefined) {
+        data[fullName] = '#' + value;
+    }
+}
+
+/** Add an OData type tag to an JSON payload object collection (without "Edm." 
namespace)
+ * @param {Object} data - Data structure to be extended
+ * @param {String} name - Name of the property whose type is set
+ * @param {String} typeName - Type name
+ */
+function addTypeColNoEdm(data, name, typeName ) {
+    var fullName = name + '@odata.type';
+
+    if ( data[fullName] === undefined) {
+        if ( typeName.substring(0,4)==='Edm.') {
+            data[fullName] = '#Collection('+typeName.substring(4)+ ')';
+        } else {
+            data[fullName] = '#Collection('+typeName+ ')';
+        }
+    }
+}
+
+
+/** Add an OData type tag to an JSON payload object (without "Edm." namespace)
+ * @param {Object} data - Data structure to be extended
+ * @param {String} name - Name of the property whose type is set
+ * @param {String} value - Type name
+ */
+function addTypeNoEdm(data, name, value ) {
+    var fullName = name + '@odata.type';
+
+    if ( data[fullName] === undefined) {
+        if ( value.substring(0,4)==='Edm.') {
+            data[fullName] = '#' + value.substring(4);
+        } else {
+            data[fullName] = '#' + value;
+        }
+    }
+    return data;
+}
+/** Convert the date/time format of an property from the JSON payload object 
(without "Edm." namespace)
+ * @param {Object} data - Data structure to be extended
+ * @param propertyName - Name of the property to be changed
+ * @param type - Type
+ */
+function convertDates(data, propertyName,type) {
+    if (type === 'Edm.Date') {
+        data[propertyName] = oDataUtils.parseDate(data[propertyName], true);
+    } else if (type === 'Edm.DateTimeOffset') {
+        data[propertyName] = 
oDataUtils.parseDateTimeOffset(data[propertyName], true);
+    } else if (type === 'Edm.Duration') {
+        data[propertyName] = oDataUtils.parseDuration(data[propertyName], 
true);
+    } else if (type === 'Edm.Time') {
+        data[propertyName] = oDataUtils.parseTime(data[propertyName], true);
+    }
+}
+
+/** Convert the date/time format of an property from the JSON payload object
+ * @param {Object} data - Data structure to be extended
+ * @param propertyName - Name of the property to be changed
+ * @param type - Type
+ */
+function convertDatesNoEdm(data, propertyName,type) {
+    if (type === 'Date') {
+        data[propertyName] = oDataUtils.parseDate(data[propertyName], true);
+    } else if (type === 'DateTimeOffset') {
+        data[propertyName] = 
oDataUtils.parseDateTimeOffset(data[propertyName], true);
+    } else if (type === 'Duration') {
+        data[propertyName] = oDataUtils.parseDuration(data[propertyName], 
true);
+    } else if (type === 'Time') {
+        data[propertyName] = oDataUtils.parseTime(data[propertyName], true);
+    }
+}
+
+/** Formats a value according to Uri literal format
+ * @param value - Value to be formatted.
+ * @param type - Edm type of the value
+ * @returns {string} Value after formatting
+ */
+function formatLiteral(value, type) {
+
+    value = "" + formatRawLiteral(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;
+    }
+}
+
+/** convert raw byteArray to hexString if the property is an binary property
+ * @param value - Value to be formatted.
+ * @param type - Edm type of the value
+ * @returns {string} Value after formatting
+ */
+function formatRawLiteral(value, type) {
+    switch (type) {
+        case "Edm.Binary":
+            return convertByteArrayToHexString(value);
+        default:
+            return value;
+    }
+}
+
+/** Formats the given minutes into (+/-)hh:mm format.
+ * @param {Number} minutes - Number of minutes to format.
+ * @returns {String} The minutes in (+/-)hh:mm format.
+ */
+function minutesToOffset(minutes) {
+
+    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);
+}
+
+/** Parses the JSON Date representation into a Date object.
+ * @param {String} value - String value.
+ * @returns {Date} A Date object if the value matches one; falsy otherwise.
+ */
+function parseJsonDateString(value) {
+
+    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.
+}
+
+/** Creates an object containing information for the context
+ * @param {String} fragments - Uri fragment
+ * @param {Object} model - Object describing an OData conceptual schema
+ * @returns {Object} type(optional)  object containing type information for 
entity- and complex-types ( null if a typeName is a primitive)
+ */
+function parseContextUriFragment( fragments, model ) {
+    var ret = {};
+
+    if (fragments.indexOf('/') === -1 ) {
+        if (fragments.length === 0) {
+            // Capter 10.1
+            ret.detectedPayloadKind = PAYLOADTYPE_SVCDOC;
+            return ret;
+        } else if (fragments === 'Edm.Null') {
+            // Capter 10.15
+            ret.detectedPayloadKind = PAYLOADTYPE_VALUE;
+            ret.isNullProperty = true;
+            return ret;
+        } else if (fragments === 'Collection($ref)') {
+            // Capter 10.11
+            ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINKS;
+            return ret;
+        } else if (fragments === '$ref') {
+            // Capter 10.12
+            ret.detectedPayloadKind = PAYLOADTYPE_ENTITY_REF_LINK;
+            return ret;
+        } else {
+            //TODO check for navigation resource
+        }
+    }
+
+    ret.type = undefined;
+    ret.typeName = undefined;
+
+    var fragmentParts = fragments.split("/");
+    var type;
+
+    for(var i = 0; i < fragmentParts.length; ++i) {
+        var fragment = fragmentParts[i];
+        if (ret.typeName === undefined) {
+            //preparation
+            if ( fragment.indexOf('(') !== -1 ) {
+                //remove the query function, cut fragment to matching '('
+                var index = fragment.length - 2 ;
+                for ( var rCount = 1; rCount > 0 && index > 0; --index) {
+                    if ( fragment.charAt(index)=='(') {
+                        rCount --;
+                    } else if ( fragment.charAt(index)==')') {
+                        rCount ++;
+                    }
+                }
+
+                if (index === 0) {
+                    //TODO throw error
+                }
+
+                //remove the projected entity from the fragment; TODO decide 
if we want to store the projected entity
+                var inPharenthesis = 
fragment.substring(index+2,fragment.length - 1);
+                fragment = fragment.substring(0,index+1);
+
+                if (utils.startsWith(fragment, 'Collection')) {
+                    ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION;
+                    // Capter 10.14
+                    ret.typeName = inPharenthesis;
+
+                    type = lookupEntityType(ret.typeName, model);
+                    if ( type !== null) {
+                        ret.type = type;
+                        continue;
+                    }
+                    type = lookupComplexType(ret.typeName, model);
+                    if ( type !== null) {
+                        ret.type = type;
+                        continue;
+                    }
+
+                    ret.type = null;//in case of #Collection(Edm.String) only 
lastTypeName is filled
+                    continue;
+                } else {
+                    // projection: Capter 10.7, 10.8 and 10.9
+                    ret.projection = inPharenthesis;
+                }
+            }
+
+
+            if (jsonIsPrimitiveType(fragment)) {
+                ret.typeName = fragment;
+                ret.type = null;
+                ret.detectedPayloadKind = PAYLOADTYPE_VALUE;
+                continue;
+            }
+
+            var container = lookupDefaultEntityContainer(model);
+
+            //check for entity
+            var entitySet = lookupEntitySet(container.entitySet, fragment);
+            if ( entitySet !== null) {
+                ret.typeName = entitySet.entityType;
+                ret.type = lookupEntityType( ret.typeName, model);
+                ret.name = fragment;
+                ret.detectedPayloadKind = PAYLOADTYPE_FEED;
+                // Capter 10.2
+                continue;
+            }
+
+            //check for singleton
+            var singleton = lookupSingleton(container.singleton, fragment);
+            if ( singleton !== null) {
+                ret.typeName = singleton.entityType;
+                ret.type = lookupEntityType( ret.typeName, model);
+                ret.name = fragment;
+                ret.detectedPayloadKind =  PAYLOADTYPE_ENTRY;
+                // Capter 10.4
+                continue;
+            }
+
+
+
+            //TODO throw ERROR
+        } else {
+            //check for $entity
+            if (utils.endsWith(fragment, '$entity') && 
(ret.detectedPayloadKind === PAYLOADTYPE_FEED)) {
+                //TODO ret.name = fragment;
+                ret.detectedPayloadKind = PAYLOADTYPE_ENTRY;
+                // Capter 10.3 and 10.6
+                continue;
+            }
+
+            //check for derived types
+            if (fragment.indexOf('.') !== -1) {
+                // Capter 10.6
+                ret.typeName = fragment;
+                type = lookupEntityType(ret.typeName, model);
+                if ( type !== null) {
+                    ret.type = type;
+                    continue;
+                }
+                type = lookupComplexType(ret.typeName, model);
+                if ( type !== null) {
+                    ret.type = type;
+                    continue;
+                }
+
+                //TODO throw ERROR invalid type
+            }
+
+            //check for property value
+            if ( ret.detectedPayloadKind === PAYLOADTYPE_FEED || 
ret.detectedPayloadKind === PAYLOADTYPE_ENTRY) {
+                var property = lookupProperty(ret.type.property, fragment);
+                if (property !== null) {
+                    //PAYLOADTYPE_COLLECTION
+                    ret.typeName = property.type;
+
+
+                    if (utils.startsWith(property.type, 'Collection')) {
+                        ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION;
+                        var tmp12 =  
property.type.substring(10+1,property.type.length - 1);
+                        ret.typeName = tmp12;
+                        ret.type = lookupComplexType(tmp12, model);
+                        ret.detectedPayloadKind = PAYLOADTYPE_COLLECTION;
+                    } else {
+                        ret.type = lookupComplexType(property.type, model);
+                        ret.detectedPayloadKind = PAYLOADTYPE_PROPERTY;
+                    }
+
+                    ret.name = fragment;
+                    // Capter 10.15
+                }
+                continue;
+            }
+
+            if (fragment === '$delta') {
+                ret.deltaKind = DELTATYPE_FEED;
+                continue;
+            } else if (utils.endsWith(fragment, '/$deletedEntity')) {
+                ret.deltaKind = DELTATYPE_DELETED_ENTRY;
+                continue;
+            } else if (utils.endsWith(fragment, '/$link')) {
+                ret.deltaKind = DELTATYPE_LINK;
+                continue;
+            } else if (utils.endsWith(fragment, '/$deletedLink')) {
+                ret.deltaKind = DELTATYPE_DELETED_LINK;
+                continue;
+            }
+            //TODO throw ERROr
+        }
+    }
+
+    return ret;
+}
+
+
+/** Infers the information describing the JSON payload from its metadata 
annotation, structure, and data model.
+ * @param {Object} data - Json response payload object.
+ * @param {Object} model - Object describing an OData conceptual schema.
+ * 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 payload.
+ * @return Object with kind and type fields. Null if there is no metadata 
annotation or the payload info cannot be obtained..
+ */
+function createPayloadInfo(data, model) {
+    var metadataUri = data[contextUrlAnnotation];
+    if (!metadataUri || typeof metadataUri !== "string") {
+        return null;
+    }
+
+    var fragmentStart = metadataUri.lastIndexOf("#");
+    if (fragmentStart === -1) {
+        return jsonMakePayloadInfo(PAYLOADTYPE_SVCDOC);
+    }
+
+    var fragment = metadataUri.substring(fragmentStart + 1);
+    return parseContextUriFragment(fragment,model);
+}
+/** Gets the key of an entry.
+ * @param {Object} data - JSON entry.
+ * @param {Object} data - EDM entity model for key loockup.
+ * @returns {string} Entry instance key.
+ */
+function jsonGetEntryKey(data, entityModel) {
+
+    var entityInstanceKey;
+    var entityKeys = entityModel.key[0].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;
+}
+/** Determines whether a type name is a primitive type in a JSON payload.
+ * @param {String} typeName - Type name to test.
+ * @returns {Boolean} True if the type name an EDM primitive type or an OData 
spatial type; false otherwise.
+ */
+function jsonIsPrimitiveType(typeName) {
+    return isPrimitiveEdmType(typeName) || isGeographyEdmType(typeName) || 
isGeometryEdmType(typeName);
+}
+
+
+var jsonHandler = oDataHandler.handler(jsonParser, jsonSerializer, 
jsonMediaType, MAX_DATA_SERVICE_VERSION);
+jsonHandler.recognizeDates = false;
+
+exports.createPayloadInfo = createPayloadInfo;
+exports.jsonHandler = jsonHandler;
+exports.jsonParser = jsonParser;
+exports.jsonSerializer = jsonSerializer;
+exports.parseJsonDateString = parseJsonDateString;
\ No newline at end of file

Reply via email to