
com.sap.cds.reflect.impl.CdsModelReader Maven / Gradle / Ivy
/*******************************************************************
* © 2020 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.reflect.impl;
import static com.sap.cds.reflect.impl.CdsStructuredTypeReader.readElementList;
import static com.sap.cds.reflect.impl.CdsUnboundActionAndFunctionReader.readParameterList;
import static com.sap.cds.reflect.impl.CdsUnboundActionAndFunctionReader.readReturnType;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.io.CharStreams;
import com.sap.cds.CdsException;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsParameter;
import com.sap.cds.reflect.impl.CdsEntityReader.CdsParameterReader;
import com.sap.cds.reflect.impl.reader.issuecollector.IssueCollector;
import com.sap.cds.reflect.impl.reader.issuecollector.IssueCollectorFactory;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;
public class CdsModelReader implements CdsModel.Reader {
private static final IssueCollector issueCollector = IssueCollectorFactory.getIssueCollector(CdsModelReader.class);
private static final ObjectMapper jackson = new ObjectMapper();
private static final HashFunction hasher = Hashing.goodFastHash(160);
private static final Cache nonDraftModels = CacheBuilder.newBuilder().maximumSize(100).build();
private static final Cache draftModels = CacheBuilder.newBuilder().maximumSize(100).build();
private final CdsModelBuilder cdsModel = CdsModelBuilder.create();
@Override
public CdsModel readCsn(InputStream is) {
return read(is);
}
@Override
public CdsModel readCsn(String csn) {
return read(csn, false);
}
public static CdsModel read(InputStream is) {
return read(is, false);
}
public static CdsModel read(InputStream is, boolean adaptDraftEntities) {
if (is == null) {
throw new CdsException("Cannot read CDS model: InputStream must not be null");
}
try (final Reader reader = new InputStreamReader(is, UTF_8)) {
return read(CharStreams.toString(reader), adaptDraftEntities);
} catch (Exception e) {
throw new CdsException("Cannot read CDS model: ", e);
}
}
public static CdsModel read(String csn, boolean adaptDraftEntities) {
return readCached(csn, adaptDraftEntities);
}
public static CdsModel read(JsonNode jObject) {
return read(jObject, false);
}
public static CdsModel read(JsonNode jObject, boolean adaptDraftEntities) {
return readCached(jObject.toString(), adaptDraftEntities);
}
private static CdsModel readCached(String csn, boolean adaptDraftEntities) {
HashCode hash = hasher.hashString(csn, UTF_8);
Cache cache = adaptDraftEntities ? draftModels : nonDraftModels;
try {
return cache.get(hash, () -> parse(csn, adaptDraftEntities));
} catch (ExecutionException e) {
throw new CdsException("Cannot load CDS model: ", e);
}
}
private static CdsModel parse(String csn, boolean adaptDraftEntities) {
try {
JsonNode jObject = jackson.readTree(csn);
return new CdsModelReader().parse(jObject, adaptDraftEntities);
} catch (Exception e) { // NOSONAR
throw new CdsException("Cannot parse CDS model: ", e);
}
}
private static JsonNode asObject(JsonNode element) {
if (element != null) {
return element;
}
return new ObjectNode(null);
}
private CdsModel parse(JsonNode csn, boolean adaptDraftEntities) {
JsonNode definitions = asObject(csn.get(CdsConstants.DEFINITIONS));
Map typeObjects = new HashMap<>();
Map contextObjects = new HashMap<>();
Map serviceObjects = new HashMap<>();
Map entityObjects = new HashMap<>();
Map actionObjects = new HashMap<>();
Map functionObjects = new HashMap<>();
Map eventObjects = new HashMap<>();
Iterator> fields = definitions.fields();
DraftAdapter draftAdapter = new DraftAdapter(adaptDraftEntities, entityObjects);
while (fields.hasNext()) {
String name = fields.next().getKey();
JsonNode object = definitions.get(name);
String kind = object.get(CdsConstants.KIND).asText();
switch (kind) {
case CdsConstants.TYPE:
typeObjects.put(name, object);
break;
case CdsConstants.ENTITY:
entityObjects.put(name, object);
draftAdapter.processEntity(name, object);
break;
case CdsConstants.ACTION:
actionObjects.put(name, object);
break;
case CdsConstants.FUNCTION:
functionObjects.put(name, object);
break;
case CdsConstants.CONTEXT:
contextObjects.put(name, object);
break;
case CdsConstants.SERVICE:
serviceObjects.put(name, object);
break;
case CdsConstants.EVENT:
eventObjects.put(name, object);
break;
default:
issueCollector.unrecognized(name,
"The CDS model contains a definition with name '%s' that has an unrecognized type '%s'.", name,
kind);
}
}
draftAdapter.adaptDraftEntities();
readMetaInfo(csn);
readEntities(entityObjects);
readServices(serviceObjects);
readUnboundActions(actionObjects);
readUnboundFunctions(functionObjects);
readTypes(typeObjects);
readAnnotations(contextObjects);
readEvents(eventObjects);
addElementsToTypes(typeObjects);
addTypesToArrayedTypes(typeObjects);
addElementsAndParamsToEntities(entityObjects);
addElementsToUnboundActions(actionObjects);
addElementsToUnboundFunctions(functionObjects);
addElementsToBoundActions(entityObjects);
addElementsToBoundFunctions(entityObjects);
return cdsModel.build();
}
private void readMetaInfo(JsonNode csn) {
if (csn.has(CdsConstants.VERSION)) {
JsonNode version = asObject(csn.get(CdsConstants.VERSION));
cdsModel.addMeta(CdsConstants.VERSION, version.get(CdsConstants.CSN).asText());
}
if (csn.has(CdsConstants.META)) {
JsonNode meta = asObject(csn.get(CdsConstants.META));
meta.fields().forEachRemaining(e -> {
Object value;
try {
value = jackson.readValue(e.getValue().toString(), TypeFactory.unknownType());
} catch (IOException e1) {
value = e.getValue().toString();
}
cdsModel.addMeta(e.getKey(), value);
});
}
}
private void readEntities(Map entityDefs) {
for (Entry entry : entityDefs.entrySet()) {
CdsEntityBuilder entity = CdsEntityReader.read(entry.getKey(), entry.getValue());
cdsModel.addEntity(entity);
}
}
private void readServices(Map serviceDefs) {
for (Entry entry : serviceDefs.entrySet()) {
CdsServiceBuilder service = new CdsServiceBuilder(CdsAnnotationReader.read(entry.getValue()),
entry.getKey());
cdsModel.addService(service);
}
}
private void readTypes(Map typeObjects) {
for (Entry entry : typeObjects.entrySet()) {
CdsTypeBuilder> type = readTypeDefinition(entry.getKey(), entry.getValue(), typeObjects);
cdsModel.addType(entry.getKey(), type);
}
}
private CdsTypeBuilder> readTypeDefinition(String path, JsonNode csn, Map typeDefs) {
if (csn.has(CdsConstants.ELEMENTS)) {
// return empty struct as a placeholder
return CdsStructuredTypeReader.readWithoutElements(path, csn);
}
if (csn.has(CdsConstants.ITEMS)) {
return CdsArrayedTypeReader.readWithoutType(path, csn);
}
JsonNode typeName = csn.get(CdsConstants.TYPE);
if (typeName != null) {
String type = typeName.asText();
if (type.equals(CdsConstants.ASSOCIATION) || type.equals(CdsConstants.COMPOSITION)) {
return new CdsAssociationReader(cdsModel).read(path, csn);
}
if (type.startsWith("cds.")) {
return CdsSimpleTypeReader.read(path, csn);
}
if (typeDefs.containsKey(type)) {
// Resolve types that are based on other custom types
// only relevant if CSN has not been transformed 4 odata
// TODO support derived types
JsonNode jsonNode = typeDefs.get(type);
return readTypeDefinition(type, jsonNode, typeDefs);
}
}
throw new CdsException("Failed to read type " + path);
}
private void readAnnotations(Map contextObjects) {
contextObjects.forEach((key, value) -> cdsModel.addAnnotations(key, CdsAnnotationReader.read(value)));
}
private void addElementsToTypes(Map typeObjects) {
for (Entry entry : typeObjects.entrySet()) {
Optional> typeDefinition = cdsModel.findType(entry.getKey());
if (typeDefinition.isPresent()) {
CdsTypeBuilder> type = typeDefinition.get();
if (type.isStructured()) {
CdsStructuredTypeBuilder> structuredType = (CdsStructuredTypeBuilder>) type;
List> elements = readElementList(entry.getKey(), entry.getValue(), cdsModel);
structuredType.addElements(elements);
}
}
}
}
private void addTypesToArrayedTypes(Map typeObjects) {
for (Entry entry : typeObjects.entrySet()) {
Optional> typeDefinition = cdsModel.findType(entry.getKey());
if (typeDefinition.isPresent()) {
CdsTypeBuilder> type = typeDefinition.get();
if (type.isArrayed()) {
CdsArrayedTypeBuilder arrayedType = (CdsArrayedTypeBuilder) type;
JsonNode itemsTypeJSON = entry.getValue().get(CdsConstants.ITEMS);
CdsTypeBuilder> itemsType = findType(itemsTypeJSON, cdsModel)
.orElseGet(() -> readType("", itemsTypeJSON, cdsModel));
arrayedType.setItemsType(itemsType);
}
}
}
}
private void readEvents(Map nodeObjects) {
for (Entry entry : nodeObjects.entrySet()) {
CdsEventBuilder event = CdsEventReader.read(entry.getKey(), entry.getValue(), cdsModel);
cdsModel.addEvent(event);
}
}
private void addElementsAndParamsToEntities(Map entityObjects) {
for (Entry entry : entityObjects.entrySet()) {
cdsModel.findEntity(entry.getKey()).ifPresent(entity -> {
List> elements = readElementList(entry.getKey(), entry.getValue(), cdsModel);
entity.addElements(elements);
List params = CdsParameterReader.read(entity.getQualifiedName(), entry.getValue(),
cdsModel::findType);
entity.addParams(params);
});
}
}
private void addElementsToBoundActions(Map entityObjects) {
Stream boundedActionEntities = cdsModel.concreteEntities()
.filter(e -> e.actions().findFirst().isPresent());
boundedActionEntities.forEach(e -> {
e.actions().forEach(a -> {
JsonNode actionNode = entityObjects.get(e.getQualifiedName()).get(CdsConstants.ACTIONS)
.get(a.getQualifiedName());
List params = readParameterList(a.getQualifiedName(), actionNode, cdsModel);
a.addParameters(params);
a.setReturnType(readReturnType(actionNode, cdsModel));
});
});
}
private void addElementsToBoundFunctions(Map entityObjects) {
Stream boundedFunctionEntities = cdsModel.concreteEntities()
.filter(e -> e.functions().findFirst().isPresent());
boundedFunctionEntities.forEach(e -> {
e.functions().forEach(f -> {
JsonNode functionNode = entityObjects.get(e.getQualifiedName()).get(CdsConstants.ACTIONS)
.get(f.getQualifiedName());
List params = readParameterList(f.getQualifiedName(), functionNode, cdsModel);
f.addParameters(params);
f.setReturnType(readReturnType(functionNode, cdsModel));
});
});
}
private void addElementsToUnboundActions(Map actionObjects) {
for (Entry entry : actionObjects.entrySet()) {
Optional actionOptional = cdsModel.findAction(entry.getKey());
if (actionOptional.isPresent()) {
CdsActionBuilder action = actionOptional.get();
List params = readParameterList(entry.getKey(), entry.getValue(), cdsModel);
action.addParameters(params);
action.setReturnType(readReturnType(entry.getValue(), cdsModel));
}
}
}
private void addElementsToUnboundFunctions(Map functionObjects) {
for (Entry entry : functionObjects.entrySet()) {
Optional functionOptional = cdsModel.findFunction(entry.getKey());
if (functionOptional.isPresent()) {
CdsFunctionBuilder function = functionOptional.get();
List params = readParameterList(entry.getKey(), entry.getValue(), cdsModel);
function.addParameters(params);
function.setReturnType(readReturnType(entry.getValue(), cdsModel));
}
}
}
private void readUnboundActions(Map actionDefs) {
for (Entry entry : actionDefs.entrySet()) {
CdsActionBuilder action = CdsUnboundActionAndFunctionReader.readAction(entry.getKey(), entry.getValue());
cdsModel.addAction(action);
}
}
private void readUnboundFunctions(Map functionDefs) {
for (Entry entry : functionDefs.entrySet()) {
CdsFunctionBuilder function = CdsUnboundActionAndFunctionReader.readFunction(entry.getKey(),
entry.getValue());
cdsModel.addFunction(function);
}
}
public static CdsTypeBuilder> readType(String pathToElement, JsonNode csn, CdsModelBuilder model) {
if (csn.has(CdsConstants.ELEMENTS) || csn.has(CdsConstants.PAYLOAD)) {
// inline defined struct
return CdsStructuredTypeReader.read("", csn, model);
}
if (csn.has(CdsConstants.ITEMS)) {
return CdsArrayedTypeReader.read(pathToElement, csn, model);
}
JsonNode typeName = csn.get(CdsConstants.TYPE);
if (typeName != null) {
String type = typeName.asText();
if (type.equals(CdsConstants.ASSOCIATION) || type.equals(CdsConstants.COMPOSITION)) {
// inline defined association
return new CdsAssociationReader(model).read(pathToElement, csn);
}
if (type.startsWith("cds.")) {
return CdsSimpleTypeReader.read(pathToElement, csn);
}
}
return CdsSimpleTypeReader.read(pathToElement, csn);
}
public static Optional> findType(JsonNode csn, CdsModelBuilder model) {
JsonNode typeName = csn.get(CdsConstants.TYPE);
if (typeName != null) {
return model.findType(typeName.asText());
}
return Optional.empty();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy