UNPKG

@sap/odata-v4

Version:

OData V4.0 server library

212 lines (170 loc) 6.41 kB
'use strict'; const EventEmitter = require('events'); const ContentReader = require('./ContentDeserializer'); const MultipartReader = require('./MultipartReader'); const OdataRequestInBatch = require('../core/OdataRequestInBatch'); const PlainHttpRequest = require('../core/PlainHttpRequest'); const DeserializationError = require('../errors/DeserializationError'); /** * Create a list of OdataRequestInBatch from an incoming batch request. */ class BatchRequestListBuilder { /** * Constructor * * @param {Object} options */ constructor(options) { this._options = options || {}; /** * @type {OdataRequestInBatch[]} * @private */ this._requestInBatchList = []; // create emitter which is injected into readers this._emitter = new EventEmitter(); this.setupEvents(); /* * Temporary helper */ this._currentPlainHttpRequest = null; this._currentRequestId = null; this._previousRequestId = null; this._previousRequestInBatch = null; this._currentBuffer = null; this._multipartLevel = 0; this._batchBoundary = ''; /** * Used to produces IDs like '~0', '~1', '~2', ... * @type {number} * @private */ this._autoRequestID = -1; } /** * Returns the boundary used in the multipart document * * @returns {string} */ getBatchBoundary() { return this._batchBoundary; } /** * Prepares the internal state for reading the next part */ clearForNextRequest() { this._currentPlainHttpRequest = null; this._currentRequestId = null; } /** * Calculates an request id if the request id is not already set * * @returns {string} */ calculateRequestId() { if (this._currentRequestId) { return this._currentRequestId; } return this.generateNewRequestID(); } /** * Generates an request id * * @param {string} tag * @returns {string} */ generateNewRequestID(tag) { const infix = tag | ''; this._autoRequestID++; return '~' + infix + this._autoRequestID.toString(); } /** * Starts reading the incomming batch request * * @param {OdataRequest} request * @param {Function} cb */ build(request, cb) { const source = request.getIncomingRequest(); // create a body reader const parser = new MultipartReader(source.headers); parser.setEmitter(this._emitter); // create reader and start with ContentReader const reader = new ContentReader(parser, this._emitter); source.pipe(reader).on('finish', () => { cb(null, this._requestInBatchList); }).on('error', cb); } /** * Registers the events emitted by the multipart parser */ setupEvents() { this._emitter.on(ContentReader.EVENTS.MULTIPART_START, (boundary) => { this._multipartLevel++; if (this._multipartLevel === 1) { this._batchBoundary = boundary; } else if (this._multipartLevel === 2) { this._curAtomicityGroup = boundary; // TODO add counter ... } else if (this._multipartLevel === 3) { throw new DeserializationError('Nested changesets are not allowed.'); } }); this._emitter.on(ContentReader.EVENTS.PART_START, () => { this.clearForNextRequest(); }); this._emitter.on(ContentReader.EVENTS.REQUEST_START, () => { this._currentPlainHttpRequest = new PlainHttpRequest(); }); this._emitter.on(ContentReader.EVENTS.REQUEST_REQUESTLINE, (requestLine) => { this._currentPlainHttpRequest.setRequestLine(requestLine); }); this._emitter.on(ContentReader.EVENTS.REQUEST_HEADERS, (headers) => { this._currentPlainHttpRequest.setHeaders(headers); }); this._emitter.on(ContentReader.EVENTS.REQUEST_BODY_START, () => { this._currentBuffer = Buffer.from([]); }); this._emitter.on(ContentReader.EVENTS.REQUEST_BODY_DATA, (data) => { this._currentBuffer = Buffer.concat([this._currentBuffer, data]); }); this._emitter.on(ContentReader.EVENTS.REQUEST_BODY_END, () => { this._currentPlainHttpRequest.setBody(this._currentBuffer); }); this._emitter.on(ContentReader.EVENTS.UNCONSUMED, () => { throw new DeserializationError('Invalid OData batch request, invalid data at request end'); }); this._emitter.on(ContentReader.EVENTS.REQUEST_END, () => { let requestID = this.calculateRequestId(); const odataRequestInBatch = new OdataRequestInBatch( this._currentPlainHttpRequest, this._options.resolutionStrategy, requestID ); // add dependency for ordered execution (as defined in specification) if (this._previousRequestId) { if (this._multipartLevel === 1) { if (this._previousRequestInBatch && this._previousRequestInBatch.getAtomicityGroupId()) { odataRequestInBatch.addDependsOn(this._previousRequestInBatch.getAtomicityGroupId()); } else { odataRequestInBatch.addDependsOn(this._previousRequestId); } } } if (this._curAtomicityGroup) { odataRequestInBatch.setAtomicityGroupId(this._curAtomicityGroup); } this._requestInBatchList.push(odataRequestInBatch); this._previousRequestId = requestID; this._previousRequestInBatch = odataRequestInBatch; }); this._emitter.on(ContentReader.EVENTS.PART_HEADERS, (headers) => { const contendId = headers['content-id']; this._currentRequestId = contendId ? contendId.getValue() : undefined; }); this._emitter.on(ContentReader.EVENTS.MULTIPART_END, () => { this._multipartLevel--; this._curAtomicityGroup = null; }); } } module.exports = BatchRequestListBuilder;