@sap/odata-v4
Version:
OData V4.0 server library
212 lines (170 loc) • 6.41 kB
JavaScript
'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;