import BatchRequest from "../../common/batch/BatchRequest" ; import BatchTableInsertEntityOptionalParams from "./BatchTableInsertEntityOptionalParams" ; import TableStorageContext from "../context/TableStorageContext" ; import Context from "../generated/Context" ; import TableHandler from "../handlers/TableHandler" ; import { TableBatchSerialization } from "./TableBatchSerialization" ; import TableBatchOperation from "./TableBatchOperation" ; import BatchTableDeleteEntityOptionalParams from "./BatchTableDeleteEntityOptionalParams" ; import BatchTableUpdateEntityOptionalParams from "./BatchTableUpdateEntityOptionalParams" ; import BatchTableMergeEntityOptionalParams from "./BatchTableMergeEntityOptionalParams" ; import BatchTableQueryEntitiesWithPartitionAndRowKeyOptionalParams from "./BatchTableQueryEntitiesWithPartitionAndRowKeyOptionalParams" ; import { TableQueryEntitiesOptionalParams , TableQueryEntitiesWithPartitionAndRowKeyOptionalParams } from "../generated/artifacts/models" ; import BatchTableQueryEntitiesOptionalParams from "./BatchTableQueryEntitiesOptionalParams" ; export class export default class TableBatchOrchestrator { private batchOperations : TableBatchOperation [ ] = [ ] ; private requests : BatchRequest [ ] = [ ] ; private serialization = new TableBatchSerialization ( ) ; private context : TableStorageContext ; private parentHandler : TableHandler ; private wasError : boolean = false ; private errorResponse : string = "" ; public constructor ( context , handler ) { this . context = context ; this . parentHandler = handler ; } param { string } batchRequestBody return { * } memberof public async processBatchRequestAndSerializeResponse ( batchRequestBody ) { this . batchOperations = this . serialization . deserializeBatchRequest ( batchRequestBody ) ; if ( this . batchOperations . length > 100 ) { this . wasError = true ; this . errorResponse = this . serialization . serializeGeneralRequestError ( "0:The batch request operation exceeds the maximum 100 changes per change set." , this . context . xMsRequestID ) ; } else { await this . submitRequestsToHandlers ( ) ; } return this . serializeResponses ( ) ; } private return { * } memberof private async submitRequestsToHandlers ( ) { this . batchOperations . forEach ( ( operation ) => { const request = new BatchRequest ( operation ) ; this . requests . push ( request ) ; } ) ; let contentID = 1 ; if ( this . requests . length > 0 ) { for ( const singleReq of this . requests ) { try { singleReq . response = await this . routeAndDispatchBatchRequest ( singleReq , this . context , contentID ) ; } catch ( err ) { this . wasError = true ; this . errorResponse = this . serialization . serializeError ( err , contentID , singleReq ) ; break ; } contentID ++ ; } } } private return { * } memberof private serializeResponses ( ) { let responseString = "" ; const batchBoundary = this . serialization . batchBoundary . replace ( "batch" , "batchresponse" ) ; let changesetBoundary = this . serialization . changesetBoundary . replace ( "changeset" , "changesetresponse" ) ; responseString += batchBoundary + "\r\n" ; responseString += "Content-Type: multipart/mixed; boundary=" + changesetBoundary + "\r\n" ; const changesetBoundaryClose = "--" + changesetBoundary + "--\r\n" ; changesetBoundary = "\r\n--" + changesetBoundary ; if ( this . wasError === false ) { this . requests . forEach ( ( request ) => { responseString += changesetBoundary ; responseString += request . response ; responseString += "\r\n\r\n" ; } ) ; } else { responseString += changesetBoundary + "\r\n" ; responseString += "Content-Type: application/http\r\n" ; responseString += "Content-Transfer-Encoding: binary\r\n" ; responseString += "\r\n" ; responseString += this . errorResponse ; } responseString += changesetBoundaryClose ; responseString += batchBoundary + "--\r\n" ; return responseString ; } private param { BatchRequest } request param { Context } context param { number } contentID return { * } memberof private async routeAndDispatchBatchRequest ( request , context , contentID ) { const batchContextClone = Object . create ( context ) ; batchContextClone . tableName = request . getPath ( ) ; batchContextClone . path = request . getPath ( ) ; let response ; let __return ; try { switch ( request . getMethod ( ) ) { case "POST" : ( { __return , response } = await this . handleBatchInsert ( request , response , batchContextClone , contentID ) ) ; break ; case "PUT" : ( { __return , response } = await this . handleBatchUpdate ( request , response , batchContextClone , contentID ) ) ; break ; case "DELETE" : ( { __return , response } = await this . handleBatchDelete ( request , response , batchContextClone , contentID ) ) ; break ; case "GET" : ( { __return , response } = await this . handleBatchQuery ( request , response , batchContextClone , contentID ) ) ; break ; case "CONNECT" : throw new Error ( "Connect Method unsupported in batch." ) ; break ; case "HEAD" : throw new Error ( "Head Method unsupported in batch." ) ; break ; case "OPTIONS" : throw new Error ( "Options Method unsupported in batch." ) ; break ; case "TRACE" : throw new Error ( "Trace Method unsupported in batch." ) ; break ; case "PATCH" : ( { __return , response } = await this . handleBatchMerge ( request , response , batchContextClone , contentID ) ) ; break ; default : ( { __return , response } = await this . handleBatchMerge ( request , response , batchContextClone , contentID ) ) ; } } catch ( batchException ) { throw batchException ; } return __return ; } private param { BatchRequest } request return { * } memberof private extractRowAndPartitionKeys ( request ) { let partitionKey ; let rowKey ; const url = decodeURI ( request . getUrl ( ) ) ; const partKeyMatch = url . match ( /(?<=PartitionKey=')(.+)(?=',)/gi ) ; partitionKey = partKeyMatch ? partKeyMatch [ 0 ] : "" ; const rowKeyMatch = url . match ( /(?<=RowKey=')(.+)(?='\))/gi ) ; rowKey = rowKeyMatch ? rowKeyMatch [ 0 ] : "" ; if ( partitionKey === "" || rowKey === "" ) { const body = request . getBody ( ) ; if ( body !== "" ) { const jsonBody = JSON . parse ( body ? body : "{}" ) ; partitionKey = jsonBody . PartitionKey ; rowKey = jsonBody . RowKey ; } } else { partitionKey = decodeURIComponent ( partitionKey ) ; rowKey = decodeURIComponent ( rowKey ) ; } return { partitionKey , rowKey } ; } private param { BatchRequest } request param { * } response param { * } batchContextClone param { number } contentID return { * } memberof private async handleBatchInsert ( request , response , batchContextClone , contentID ) { request . ingestOptionalParams ( new BatchTableInsertEntityOptionalParams ( ) ) ; const updatedContext = batchContextClone as TableStorageContext ; updatedContext . request = request ; response = await this . parentHandler . insertEntity ( request . getPath ( ) , request . params as BatchTableInsertEntityOptionalParams , updatedContext ) ; return { __return : this . serialization . serializeTableInsertEntityBatchResponse ( request , response ) , response } ; } private param { BatchRequest } request param { * } response param { * } batchContextClone param { number } contentID return { * } memberof private async handleBatchDelete ( request , response , batchContextClone , contentID ) { request . ingestOptionalParams ( new BatchTableDeleteEntityOptionalParams ( ) ) ; const updatedContext = batchContextClone as TableStorageContext ; updatedContext . request = request ; let partitionKey ; let rowKey ; const ifmatch = request . getHeader ( "if-match" ) || "*" ; ( { partitionKey , rowKey } = this . extractRowAndPartitionKeys ( request ) ) ; response = await this . parentHandler . deleteEntity ( request . getPath ( ) , partitionKey , rowKey , ifmatch , request . params as BatchTableDeleteEntityOptionalParams , updatedContext ) ; return { __return : this . serialization . serializeTableDeleteEntityBatchResponse ( request , response ) , response } ; } private param { BatchRequest } request param { * } response param { * } batchContextClone param { number } contentID return { * } memberof private async handleBatchUpdate ( request , response , batchContextClone , contentID ) { request . ingestOptionalParams ( new BatchTableUpdateEntityOptionalParams ( ) ) ; const updatedContext = batchContextClone as TableStorageContext ; updatedContext . request = request ; let partitionKey ; let rowKey ; ( { partitionKey , rowKey } = this . extractRowAndPartitionKeys ( request ) ) ; response = await this . parentHandler . updateEntity ( request . getPath ( ) , partitionKey , rowKey , request . params as BatchTableUpdateEntityOptionalParams , updatedContext ) ; return { __return : this . serialization . serializeTableUpdateEntityBatchResponse ( request , response ) , response } ; } private param { BatchRequest } request param { * } response param { * } batchContextClone param { number } contentID return { * } memberof private async handleBatchQuery ( request , response , batchContextClone , contentID ) { let partitionKey ; let rowKey ; ( { partitionKey , rowKey } = this . extractRowAndPartitionKeys ( request ) ) ; const updatedContext = batchContextClone as TableStorageContext ; if ( null !== partitionKey && null != rowKey ) { request . ingestOptionalParams ( new BatchTableQueryEntitiesWithPartitionAndRowKeyOptionalParams ( ) ) ; updatedContext . request = request ; response = await this . parentHandler . queryEntitiesWithPartitionAndRowKey ( request . getPath ( ) , partitionKey , rowKey , request . params as TableQueryEntitiesWithPartitionAndRowKeyOptionalParams , updatedContext ) ; return { __return : await this . serialization . serializeTableQueryEntityWithPartitionAndRowKeyBatchResponse ( request , response ) , response } ; } else { request . ingestOptionalParams ( new BatchTableQueryEntitiesOptionalParams ( ) ) ; updatedContext . request = request ; response = await this . parentHandler . queryEntities ( request . getPath ( ) , request . params as TableQueryEntitiesOptionalParams , updatedContext ) ; return { __return : await this . serialization . serializeTableQueryEntityBatchResponse ( request , response ) , response } ; } } private param { BatchRequest } request param { * } response param { * } batchContextClone param { number } contentID return { * } memberof private async handleBatchMerge ( request , response , batchContextClone , contentID ) { request . ingestOptionalParams ( new BatchTableMergeEntityOptionalParams ( ) ) ; const updatedContext = batchContextClone as TableStorageContext ; updatedContext . request = request ; let partitionKey ; let rowKey ; ( { partitionKey , rowKey } = this . extractRowAndPartitionKeys ( request ) ) ; response = await this . parentHandler . mergeEntity ( request . getPath ( ) , partitionKey , rowKey , request . params as BatchTableMergeEntityOptionalParams , updatedContext ) ; return { __return : this . serialization . serializeTablMergeEntityBatchResponse ( request , response ) , response } ; } }