http://git-wip-us.apache.org/repos/asf/olingo-odata3-js/blob/da25dc30/JSLib/src/odata-json.js ---------------------------------------------------------------------- diff --git a/JSLib/src/odata-json.js b/JSLib/src/odata-json.js new file mode 100644 index 0000000..8f11857 --- /dev/null +++ b/JSLib/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-odata3-js/blob/da25dc30/JSLib/src/odata-metadata.js ---------------------------------------------------------------------- diff --git a/JSLib/src/odata-metadata.js b/JSLib/src/odata-metadata.js new file mode 100644 index 0000000..a7ca00a --- /dev/null +++ b/JSLib/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-odata3-js/blob/da25dc30/JSLib/src/odata-net.js ---------------------------------------------------------------------- diff --git a/JSLib/src/odata-net.js b/JSLib/src/odata-net.js new file mode 100644 index 0000000..ad9b8dd --- /dev/null +++ b/JSLib/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, "&").replace(/"/g, """).replace(/\</g, "<"); + 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
