@sap/xsodata
Version:
Expose data from a HANA database as OData V2 service with help of .xsodata files.
375 lines (314 loc) • 10.8 kB
JavaScript
;
const dbutils = require('./../utils/typeConverter');
const typConv = require('./../utils/typeConverter');
const XMLWriter = require('xml-writer');
const contentTypeTools = require('./../utils/checkContentType');
const associationsUtil = require('./../utils/associations');
const Measurement = require('./../utils/measurement');
function writeNavProperty(
xw,
navigation,
associations,
entityType,
areAnnotationsEnabled
) {
xw.startElement('NavigationProperty');
xw.writeAttribute('Name', navigation.name);
const assocName = navigation.association;
const association = associations[assocName];
//
xw.writeAttribute('Relationship', xw._ns + '.' + association.name + 'Type');
xw.writeAttribute(
'FromRole',
associationsUtil.createFromRoleString(
navigation,
association,
entityType
)
);
xw.writeAttribute(
'ToRole',
associationsUtil.createToRoleString(navigation, association, entityType)
);
if (areAnnotationsEnabled) {
writeAnnotationAttributes(
entityType.getNavigationPropertyAnnotations(navigation.name),
xw
);
}
xw.endElement();
}
function writeProperty(
xw,
propertyName,
col,
entityType,
areAnnotationsEnabled
) {
xw.startElement('Property');
xw.writeAttribute('Name', propertyName);
if (entityType.isConcurrentProperty(propertyName)) {
xw.writeAttribute('ConcurrencyMode', 'Fixed');
}
const oOataType = dbutils.dbTypeNameToODataTypeID(col.DATA_TYPE_NAME);
xw.writeAttribute('Type', typConv.oDataTypeIDs[oOataType]);
if (col.IS_NULLABLE !== 'TRUE') {
xw.writeAttribute('Nullable', 'false');
}
if (oOataType === 9) {
//String
xw.writeAttribute('MaxLength', col.LENGTH);
} else if (oOataType === 6) {
//Decimal
xw.writeAttribute('Precision', col.LENGTH);
xw.writeAttribute('Scale', col.SCALE);
}
if (areAnnotationsEnabled) {
writeAnnotationAttributes(
entityType.getPropertyAnnotations(propertyName),
xw
);
}
xw.endElement();
}
function writeAssociationTypes(xw, associations) {
// Associations
for (const assocName in associations) {
if (associations.hasOwnProperty(assocName)) {
const assoc = associations[assocName];
xw.startElement('Association');
xw.writeAttribute('Name', assocName + 'Type');
xw.startElement('End');
xw.writeAttribute(
'Type',
xw._ns + '.' + assoc.principal.type + 'Type'
);
xw.writeAttribute('Role', assoc.principal.type + 'Principal');
xw.writeAttribute('Multiplicity', assoc.principal.multiplicity);
xw.endElement();
xw.startElement('End');
xw.writeAttribute(
'Type',
xw._ns + '.' + assoc.dependent.type + 'Type'
);
xw.writeAttribute('Role', assoc.dependent.type + 'Dependent');
xw.writeAttribute('Multiplicity', assoc.dependent.multiplicity);
xw.endElement();
if (assoc.referentialConstraint) {
xw.startElement('ReferentialConstraint');
xw.startElement('Principal');
xw.writeAttribute('Role', assoc.principal.type + 'Principal');
xw.startElement('PropertyRef');
xw.writeAttribute('Name', assoc.principal.joinproperties[0]);
xw.endElement();
xw.endElement(); //Principal
xw.startElement('Dependent');
xw.writeAttribute('Role', assoc.dependent.type + 'Dependent');
xw.startElement('PropertyRef');
xw.writeAttribute('Name', assoc.dependent.joinproperties[0]);
xw.endElement();
xw.endElement(); //Dependent
xw.endElement(); //ReferentialConstraint
}
xw.endElement(); //Association
}
}
}
function writeEntityTypes(
xw,
entityTypes,
associations,
areAnnotationsEnabled
) {
let i, ii, j;
/**
* @type {EntityType}
* */
let entityType = null;
for (i = 0; i < entityTypes.length; i++) {
entityType = entityTypes[i];
xw.startElement('EntityType');
xw.writeAttribute('Name', entityType.name + 'Type');
if (areAnnotationsEnabled) {
writeAnnotationAttributes(
entityType.getEntityTypeAnnotations(),
xw
);
}
//keys
xw.startElement('Key');
entityType.getKeysOrGenKey().forEach(writeKey);
xw.endElement();
const props = entityType.getPropertiesWithGenKey();
for (j = 0; j < props.length; j++) {
writeProp(props[j], entityType);
}
//nav properties
for (ii = 0; ii < entityType.navProperties.length; ii++) {
const navigation = entityType.navProperties[ii];
writeNavProperty(
xw,
navigation,
associations,
entityType,
areAnnotationsEnabled
);
}
xw.endElement();
}
function writeKey(keyName) {
xw.startElement('PropertyRef');
xw.writeAttribute('Name', keyName);
xw.endElement();
}
function writeProp(property, entityType) {
writeProperty(
xw,
property.COLUMN_NAME,
property,
entityType,
areAnnotationsEnabled
);
}
}
function writeEntitySets(xw, entityTypes, areAnnotationsEnabled) {
for (const entityType of entityTypes) {
xw.startElement('EntitySet');
xw.writeAttribute('Name', entityType.name);
xw.writeAttribute(
'EntityType',
xw._ns + '.' + entityType.name + 'Type'
);
if (areAnnotationsEnabled) {
writeAnnotationAttributes(entityType.getEntitySetAnnotations(), xw);
}
xw.endElement();
}
}
function writeAssociations(xw, associations, areAnnotationsEnabled) {
for (const association of associations) {
xw.startElement('AssociationSet');
xw.writeAttribute('Name', association.name);
xw.writeAttribute(
'Association',
xw._ns + '.' + association.name + 'Type'
);
if (areAnnotationsEnabled) {
writeAnnotationAttributes(
association.getAssociationSetAnnotations(),
xw
);
}
xw.startElement('End');
xw.writeAttribute('Role', association.principal.type + 'Principal');
xw.writeAttribute('EntitySet', association.principal.type);
xw.endElement();
xw.startElement('End');
xw.writeAttribute('Role', association.dependent.type + 'Dependent');
xw.writeAttribute('EntitySet', association.dependent.type);
xw.endElement();
xw.endElement(); //AssociationSet
}
}
exports.process = function (context, asyncDone) {
const response = context.response,
format = context.oData.systemQueryParameters.format,
areAnnotationsEnabled = context.gModel.areAnnotationsEnabled();
context.logger.silly('metadataprocessor', 'process');
context.payloadType = Measurement.measureSync(
contentTypeTools.checkServiceMetadata,
context.request,
format,
'contentTypeTools.checkServiceMetadata'
);
const header = {
'Content-Type': context.payloadType + ';charset=utf-8',
};
const cacheControl = context.gModel.getSetting('metadata', 'cache-control');
if (cacheControl) {
header['cache-control'] = cacheControl;
}
response.writeHead(200, header);
const writer = function writer(string) {
response.write(string);
};
const xw = new XMLWriter(true, writer); //change to false for unformatted output
//xsodata.scenario
xw._ns = context.gModel.getNamespace();
xw.startDocument('1.0', 'utf-8', true);
xw.startElement('edmx:Edmx');
xw.writeAttribute('Version', '1.0');
xw.writeAttribute(
'xmlns:edmx',
'http://schemas.microsoft.com/ado/2007/06/edmx'
);
if (areAnnotationsEnabled) {
xw.writeAttribute('xmlns:sap', 'https://www.sap.com/Protocols/SAPData');
}
xw.startElement('edmx:DataServices');
xw.writeAttribute('m:DataServiceVersion', '2.0');
xw.writeAttribute(
'xmlns:m',
'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'
);
xw.startElement('Schema');
xw.writeAttribute('Namespace', xw._ns);
xw.writeAttribute(
'xmlns:d',
'http://schemas.microsoft.com/ado/2007/08/dataservices'
);
xw.writeAttribute(
'xmlns:m',
'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'
);
xw.writeAttribute('xmlns', 'http://schemas.microsoft.com/ado/2008/09/edm');
const entityTypes = context.gModel.getEntityTypes();
const associations = context.gModel.getAssociationsMap();
Measurement.measureSync(
writeEntityTypes,
xw,
entityTypes,
associations,
areAnnotationsEnabled,
'writeEntityTypes'
);
Measurement.measureSync(
writeAssociationTypes,
xw,
associations,
'writeAssociationTypes'
);
//Entity Container
xw.startElement('EntityContainer');
xw.writeAttribute('Name', 'v2');
xw.writeAttribute('m:IsDefaultEntityContainer', 'true');
writeEntitySets(xw, entityTypes, areAnnotationsEnabled);
writeAssociations(
xw,
context.gModel.getAssociations(),
areAnnotationsEnabled
);
xw.endElement(); //EntityContainer
xw.endDocument();
response.write(xw.toString());
return asyncDone(null, context);
};
exports._writeAnnotationAttributes = writeAnnotationAttributes;
/**
* Writes CSDL annotation attributes to the current EDM element.
*
* @param {Object} annotations - object with the properties representing the annotation attributes
* @param {Object} xmlWriter - XMLWriter instance, which is used for the metadata serialization
*/
function writeAnnotationAttributes(annotations, xmlWriter) {
let annotation;
if (!annotations) {
return;
}
for (annotation in annotations) {
if (annotations.hasOwnProperty(annotation)) {
xmlWriter.writeAttribute(annotation, annotations[annotation]);
}
}
}
exports.writeEntityTypes = writeEntityTypes;