/// <summary> /// Creates a <see cref="ODataJsonLightBatchPayloadItemPropertiesCache"/> /// and subsequently scans the JSON object for known properties and caches them. /// </summary> /// <param name="jsonBatchReader">The JSON batch reader.</param> /// <returns>A <see cref="ODataJsonLightBatchPayloadItemPropertiesCache"/> instance.</returns> internal static ODataJsonLightBatchPayloadItemPropertiesCache Create(ODataJsonLightBatchReader jsonBatchReader) { Debug.Assert(jsonBatchReader != null, $"{nameof(jsonBatchReader)} != null"); ODataJsonLightBatchPayloadItemPropertiesCache jsonLightBatchPayloadItemPropertiesCache = new ODataJsonLightBatchPayloadItemPropertiesCache(jsonBatchReader); jsonLightBatchPayloadItemPropertiesCache.ScanJsonProperties(); return(jsonLightBatchPayloadItemPropertiesCache); }
/// <summary> /// Asynchronously creates a <see cref="ODataJsonLightBatchPayloadItemPropertiesCache"/> /// and subsequently scans the JSON object for known properties and caches them. /// </summary> /// <param name="jsonBatchReader">The JSON batch reader.</param> /// <returns> /// A task that represents the asynchronous write operation. /// The value of the TResult parameter contains a <see cref="ODataJsonLightBatchPayloadItemPropertiesCache"/> instance. /// </returns> internal static async Task <ODataJsonLightBatchPayloadItemPropertiesCache> CreateAsync(ODataJsonLightBatchReader jsonBatchReader) { Debug.Assert(jsonBatchReader != null, $"{nameof(jsonBatchReader)} != null"); ODataJsonLightBatchPayloadItemPropertiesCache jsonLightBatchPayloadItemPropertiesCache = new ODataJsonLightBatchPayloadItemPropertiesCache(jsonBatchReader); await jsonLightBatchPayloadItemPropertiesCache.ScanJsonPropertiesAsync() .ConfigureAwait(false); return(jsonLightBatchPayloadItemPropertiesCache); }
/// <summary> /// Implementation of the reader logic when in state 'Operation'. /// </summary> /// <returns>The batch reader state after the read.</returns> protected override ODataBatchReaderState ReadAtOperationImplementation() { if (this.JsonLightInputContext.JsonReader.NodeType != JsonNodeType.StartObject) { // No more requests in the batch. return(HandleMessagesEnd()); } // Load the message properties if there is no cached item for processing. if (this.messagePropertiesCache == null) { // Load the message details since operation is detected. this.messagePropertiesCache = ODataJsonLightBatchPayloadItemPropertiesCache.Create(this); } // Calculate and return next state with changeset state detection. return(DetectChangesetStates(this.messagePropertiesCache)); }
/// <summary> /// Asynchronous implementation of the reader logic when in state 'Start'. /// </summary> /// <returns> /// A task that represents the asynchronous read operation. /// The value of the TResult parameter contains the batch reader state after the read. /// </returns> protected override async Task <ODataBatchReaderState> ReadAtStartImplementationAsync() { Debug.Assert(this.State == ODataBatchReaderState.Initial, $"{nameof(this.State)} == {nameof(ODataBatchReaderState.Initial)}"); if (mode == ReaderMode.NotDetected) { // The stream should be positioned at the beginning of the batch envelope. // Need to detect whether we are reading request or response. Stay in Initial state upon return. await DetectReaderModeAsync() .ConfigureAwait(false); return(ODataBatchReaderState.Initial); } else { // The stream should be positioned at the beginning of requests array. await this.StartReadingBatchArrayAsync() .ConfigureAwait(false); Debug.Assert(this.messagePropertiesCache == null, $"{nameof(this.messagePropertiesCache)} == null"); this.messagePropertiesCache = await ODataJsonLightBatchPayloadItemPropertiesCache.CreateAsync(this) .ConfigureAwait(false); string currentGroup = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); if (currentGroup == null) { return(ODataBatchReaderState.Operation); } else { HandleNewAtomicGroupStart( (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId), currentGroup); return(ODataBatchReaderState.ChangesetStart); } } }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationResponseMessage"/> for reading the content of a /// batch response. /// </summary> /// <returns>The message that can be used to read the content of the batch response from.</returns> protected override ODataBatchOperationResponseMessage CreateOperationResponseMessageImplementation() { Debug.Assert(this.mode == ReaderMode.Responses, "this.mode == ReaderMode.Responses"); Debug.Assert(this.messagePropertiesCache != null, "this.responsePropertiesCache != null"); // body. Use empty stream when request body is not present. Stream bodyContentStream = (Stream)this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameBody) ?? new ODataJsonLightBatchBodyContentReaderStream(this); int statusCode = (int) this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameStatus); string contentId = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); string groupId = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); ODataBatchOperationHeaders headers = (ODataBatchOperationHeaders) this.messagePropertiesCache.GetPropertyValue(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameHeaders); // Reset the response property cache since all data in cache has been processed. // So that new instance can be created during subsequent read in operation state. this.messagePropertiesCache = null; // In responses we don't need to use our batch URL resolver, since there are no cross referencing URLs // so use the URL resolver from the batch message instead. ODataBatchOperationResponseMessage responseMessage = BuildOperationResponseMessage( () => bodyContentStream, statusCode, headers, contentId, groupId); //// NOTE: Content-IDs for cross referencing are only supported in request messages; in responses //// we allow a Content-ID header but don't process it (i.e., don't add the content ID to the URL resolver). return(responseMessage); }
/// <summary> /// Examine changeset states for the current message and setup reader state accordingly if /// changeset related state transition is detected. /// </summary> /// <param name="messagePropertiesCache">Current message properties.</param> /// <returns>The next state for the reader.</returns> private ODataBatchReaderState DetectChangesetStates(ODataJsonLightBatchPayloadItemPropertiesCache messagePropertiesCache) { // Validate message Id. string valueId = (string)messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); string currentGroup = (string)messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); // ChangesetEnd check first; If not, check for changesetStart. bool changesetEnd = this.atomicGroups.IsChangesetEnd(currentGroup); bool changesetStart = false; if (!changesetEnd) { if (currentGroup != null) { // Add message Id to atomic group (create new group if needed). // Also detect changeset start. changesetStart = this.atomicGroups.AddMessageIdAndGroupId(valueId, currentGroup); } } // If we have changeset state change detected, set the state here. ODataBatchReaderState nextState = ODataBatchReaderState.Operation; if (changesetEnd) { nextState = ODataBatchReaderState.ChangesetEnd; } else if (changesetStart) { nextState = ODataBatchReaderState.ChangesetStart; } return(nextState); }
/// <summary> /// Implementation of the reader logic when in state 'Start'. /// </summary> /// <returns>The batch reader state after the read.</returns> protected override ODataBatchReaderState ReadAtStartImplementation() { Debug.Assert(this.State == ODataBatchReaderState.Initial, "this.State == ODataBatchReaderState.Initial"); if (mode == ReaderMode.NotDetected) { // The stream should be positioned at the beginning of the batch envelope. // Need to detect whether we are reading request or response. Stay in Initial state upon return. DetectReaderMode(); return(ODataBatchReaderState.Initial); } else { // The stream should be positioned at the beginning of requests array. this.StartReadingBatchArray(); Debug.Assert(this.messagePropertiesCache == null, "this.messagePropertiesCache == null"); this.messagePropertiesCache = new ODataJsonLightBatchPayloadItemPropertiesCache(this); string currentGroup = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); if (currentGroup == null) { return(ODataBatchReaderState.Operation); } else { HandleNewAtomicGroupStart( (string) this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId), currentGroup); return(ODataBatchReaderState.ChangesetStart); } } }
/// <summary> /// Returns the cached <see cref="ODataBatchOperationRequestMessage"/> for reading the content of an operation /// in a batch request. /// </summary> /// <returns>The message that can be used to read the content of the batch request operation from.</returns> protected override ODataBatchOperationRequestMessage CreateOperationRequestMessageImplementation() { Debug.Assert(this.mode == ReaderMode.Requests, "this.mode == ReaderMode.Requests"); Debug.Assert(this.messagePropertiesCache != null, "this.messagePropertiesCache != null"); // id string id = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameId); // atomicityGroup string atomicityGroupId = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup); if (id != null) { this.requestIds.Add(id); } if (atomicityGroupId != null) { this.requestIds.Add(atomicityGroupId); } // dependsOn // Flatten the dependsOn list by converting every groupId into request Ids, so that the caller // can decide, at the earliest opportunity, whether the depending request can be invoked. // Note that the forward reference of dependsOn id is not allowed, so the atomicGroups should have accurate // information of atomicGroup that needs to be flattened. IList <string> dependsOnReqIds = null; List <string> dependsOn = (List <string>) this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameDependsOn); if (dependsOn != null && dependsOn.Count != 0) { ValidateDependsOnId(dependsOn, atomicityGroupId, id); dependsOnReqIds = atomicGroups.GetFlattenedMessageIds(dependsOn); } // header ODataBatchOperationHeaders headers = (ODataBatchOperationHeaders)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameHeaders); // Add the atomicityGroup request header. if (atomicityGroupId != null) { headers.Add(ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameAtomicityGroup, atomicityGroupId); } // body. Use empty stream when request body is not present. Stream bodyContentStream = (Stream)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameBody) ?? new ODataJsonLightBatchBodyContentReaderStream(this); // method. Support case-insensitive value of HTTP methods. string httpMethod = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameMethod); ValidateRequiredProperty(httpMethod, ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameMethod); httpMethod = httpMethod.ToUpperInvariant(); // url string url = (string)this.messagePropertiesCache.GetPropertyValue( ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameUrl); ValidateRequiredProperty(url, ODataJsonLightBatchPayloadItemPropertiesCache.PropertyNameUrl); // escape any colons in the query string portion of the url int queryOptionSeparator = url.IndexOf('?'); int firstColon = url.IndexOf(':'); if (queryOptionSeparator > 0 && firstColon > 0 && queryOptionSeparator < firstColon) { url = url.Substring(0, queryOptionSeparator) + url.Substring(queryOptionSeparator).Replace(":", "%3A"); } Uri requestUri = new Uri(url, UriKind.RelativeOrAbsolute); // Reset the request property cache since all data in cache has been processed. // So that new instance can be created during subsequent read in operation state. this.messagePropertiesCache = null; ODataBatchOperationRequestMessage requestMessage = BuildOperationRequestMessage( () => bodyContentStream, httpMethod, requestUri, headers, id, atomicityGroupId, dependsOnReqIds, /*dependsOnIdsValidationRequired*/ true); return(requestMessage); }