@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
245 lines (206 loc) • 8.76 kB
JavaScript
'use strict';
const typeConverter = require('./../utils/typeConverter');
const dbs = require('./../db/dbSegment');
const NotFound = require('./../utils/errors/http/notFound');
const InternalError = require('./../utils/errors/internalError');
const configuration = require('./../configuration');
const model = require('../model/model.js');
exports.JsonBuilder = JsonBuilder;
function JsonBuilder(context) {
this._ns = context.gModel.getNamespace();
this._baseUrl = context.uriTree.baseUrl;
this._context = context;
}
JsonBuilder.prototype.createConverterArray = function (dbSeg) {
return dbSeg.getSelectedPropsWithGenKey().map(function toConverter(property) {
return dbSeg.entityType.converterMapToJsonPayload[property];
});
};
JsonBuilder.prototype.serializeEntity = function (dbSegment) {
var entity = this._serializeEntity(dbSegment);
return toODataJson(this._context, entity);
};
JsonBuilder.prototype._serializeEntity = function (dbSeg) {
var rows = dbSeg.getRowsWithGenKey();
if (rows.length === 0) {
if (dbSeg.entityType._entityType.parameters && dbSeg.entityType._entityType.parameters.viaKey === true) {
throw new NotFound('Entity with provided input parameter and key not found.');
} else {
throw new NotFound('Resource not found.');
}
}
var converter = this.createConverterArray(dbSeg);
return this.serializeRow(this._context, dbSeg, rows[0], converter);
};
JsonBuilder.prototype.serializeProperty = function (dbSeg) {
if (dbSeg.getRowsWithGenKey().length > 1) {
throw new InternalError('Too many rows for serializing a property.');
}
var property = dbSeg.singleProperty;
var converter;
var result = {};
converter = dbSeg.entityType.converterMapToJsonPayload[property];
result[property] = converter(dbSeg.getRowsWithGenKey()[0][property]);
return toODataJson(this._context, result);
};
JsonBuilder.prototype.serializeFeed = function (dbSegment, inlineCount) {
var results = this._serializeFeed(dbSegment, inlineCount);
var feed = {
"__count": inlineCount,
"results": results
};
return toODataJson(this._context, feed);
};
JsonBuilder.prototype._serializeFeed = function (dbSeg) {
var rows = dbSeg.getRowsWithGenKey();
var results = [];
var converter = this.createConverterArray(dbSeg);
for (var i = 0; i < rows.length; i++) {
var line = this.serializeRow(this._context, dbSeg, rows[i], converter);
results.push(line);
}
return results;
};
JsonBuilder.prototype.serializeNavMany = function (context, dbSeg, parent1row) {
var rows = dbSeg.getRowsWithGenKey();
var results = [];
var row = rows[dbSeg.sql.readPosition];
var converter = this.createConverterArray(dbSeg);
while (row && (row['0row'] === parent1row)) {
var line = this.serializeRow(context, dbSeg, row, converter);
results.push(line);
//next
dbSeg.sql.readPosition++;
row = rows[dbSeg.sql.readPosition];
}
return results;
};
JsonBuilder.prototype.serializeNavOne = function (context, dbSeg, parent1row) {
var rows = dbSeg.getRowsWithGenKey();
var results = [];
var row = rows[dbSeg.sql.readPosition];
var converter = this.createConverterArray(dbSeg);
while (row && (row['0row'] === parent1row)) {
var line = this.serializeRow(context, dbSeg, row, converter);
results.push(line);
//next
dbSeg.sql.readPosition++;
row = rows[dbSeg.sql.readPosition];
}
if (results.length > 1) {
context.logger.silly("json", "Error: Too many records for nav to one: " + results.length + ', ' + parent1row);
throw new InternalError('Too many records for nav to one');
}
return results[0];
};
JsonBuilder.prototype.createAbsUrl = function (dbSeg, row) {
var ret = this._baseUrl + dbSeg.entityType.name;
var keysProperties = dbSeg.getKeysProperties();
ret += '(';
ret += keysProperties.map(toValues).filter(v => v !== undefined).join(',');
return ret + ')';
function toValues(keyProperty, index, array) {
var value;
if (dbSeg.entityType.kind === model.entityKind.calculationView && dbSeg.entityType._entityType.parameters.viaKey === true) {
if (row[keyProperty.COLUMN_NAME] === null) {
value = null;
} else if (row[keyProperty.COLUMN_NAME] !== undefined) {
// although no input parameter given from client the application MUST have filled the input parameters as key (undefined via db is not possible)
value = typeConverter.serializeDbValueToUriLiteral(row[keyProperty.COLUMN_NAME], keyProperty);
} else {
if (dbSeg.hasAliasedKeyPropertiesOnCalcView()) {
let foundKeyFieldAlias = null;
for (let i = 0; i < dbSeg._aliasedKeyPropertiesOnCalcView.length; i++) {
if (dbSeg._aliasedKeyPropertiesOnCalcView[i].property === keyProperty.COLUMN_NAME &&
dbSeg._aliasedKeyPropertiesOnCalcView[i].table === dbSeg._Alias) {
foundKeyFieldAlias = dbSeg._aliasedKeyPropertiesOnCalcView[i].alias;
break; // there must not be more than one entry!
}
}
if (foundKeyFieldAlias !== null && row[foundKeyFieldAlias] !== undefined) {
value = typeConverter.serializeDbValueToUriLiteral(row[foundKeyFieldAlias], keyProperty);
} else {
value = null;
}
} else {
value = null;
}
}
} else {
value = typeConverter.serializeDbValueToUriLiteral(row[keyProperty.COLUMN_NAME], keyProperty);
}
if (array.length === 1) {
return value;
}
return keyProperty.COLUMN_NAME + '=' + value;
}
};
JsonBuilder.prototype.serializeRow = function (context, dbSeg, row, converter) {
var ret = {};
var url = this.createAbsUrl(dbSeg, row);
if (context.oData.dbSegment.isLinks) {
ret.uri = url;
return ret;
}
ret.__metadata = {
uri: url,
type: this._ns + '.' + dbSeg.entityType.name + 'Type'
};
if (row.__etag) {
ret.__metadata.etag = 'W/"' + row.__etag + '"';
}
if (!dbSeg.hasAliasedKeyPropertiesOnCalcView()) {
dbSeg.getSelectedPropsWithGenKey().forEach(function toConverter(property, index) {
ret[property] = converter[index](row[property]);
});
} else {
let selectedProperties = dbSeg.getSelectedPropsWithGenKey();
for (let i = 0; i < selectedProperties.length; i++) {
if (!row[selectedProperties[i]]) {
let aliasedKeyProperty = dbSeg.getAliasedKeyPropertyOnCalcView(selectedProperties[i]);
if (aliasedKeyProperty !== null && aliasedKeyProperty.alias) {
ret[selectedProperties[i]] = converter[i](row[aliasedKeyProperty.alias]);
} else {
ret[selectedProperties[i]] = converter[i](row[selectedProperties[i]]); // keeps null
}
} else {
ret[selectedProperties[i]] = converter[i](row[selectedProperties[i]]);
}
}
}
for (let i = 0; i < dbSeg._SelectedNavigations.length; i++) {
var sn = dbSeg._SelectedNavigations[i];
var navDbSeg = dbSeg.getRelevantNavigationSegments()[sn];
if (navDbSeg && navDbSeg.isExpand) {
ret[sn] = this.serializeDbSeg(context, navDbSeg, row['1row']);
} else {
ret[sn] = { __deferred: { uri: url + '/' + sn } };
}
}
return ret;
};
JsonBuilder.prototype.serializeDbSeg = function (context, dbSeg, parent1row) {
var d;
if (dbSeg.kind === dbs.DBS_Entity) {
if (dbSeg.isCollection) {
d = { results: this._serializeFeed(context, dbSeg) };
} else {
d = this._serializeEntity(context, dbSeg);
}
} else if (dbSeg.kind === dbs.DBS_Navigation) {
if (dbSeg.isCollection) {
d = { results: this.serializeNavMany(context, dbSeg, parent1row) };
} else {
d = this.serializeNavOne(context, dbSeg, parent1row);
}
}
return d;
};
function toODataJson(context, object) {
var response = { d: object };
if (context.mode === configuration.modes.development) {
return JSON.stringify(response, null, 4);
} else {
return JSON.stringify(response, null, 0);
}
}