com.sap.cds.reflect.impl.CdsModelReader Maven / Gradle / Ivy
/************************************************************************
* (C) 2020-2023 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.reflect.impl;
import static com.sap.cds.reflect.impl.CdsOperationReader.readParameterList;
import static com.sap.cds.reflect.impl.CdsOperationReader.readReturnType;
import static com.sap.cds.reflect.impl.CdsStructuredTypeReader.readElementList;
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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
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.CdsAction;
import com.sap.cds.reflect.CdsFunction;
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;
import com.sap.cds.util.NameResolver;
import com.sap.cds.util.StructuredTypeResolver;
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 = Caffeine.newBuilder().weakValues().maximumSize(5)
.build();
private static final Cache draftModels = Caffeine.newBuilder().weakValues().maximumSize(5)
.build();
private final CdsModelBuilder cdsModel = CdsModelBuilder.create();
private NameResolver nameResolver;
private StructuredTypeResolver structResolver;
private final Config config;
// called by service loader for public API
public CdsModelReader() {
this(new CdsModelReader.Config.Builder().build());
}
private CdsModelReader(Config config) {
this.config = config;
}
@Override
public CdsModel readCsn(InputStream is) {
return read(is);
}
@Override
public CdsModel readCsn(String csn) {
return read(new CdsModelReader.Config.Builder().build(), csn, false);
}
public static CdsModel read(InputStream is) {
return read(new CdsModelReader.Config.Builder().build(), is, false);
}
/**
* @deprecated use
* {@link CdsModelReader#read(CdsModelReader.Config config, InputStream is, boolean adaptDraftEntities)}
* instead
* @param is the csn as InputStream
* @param adaptDraftEntities whether to add draft transformations
* @return the CDS model
*/
@Deprecated
public static CdsModel read(InputStream is, boolean adaptDraftEntities) {
return read(new CdsModelReader.Config.Builder().setIncludeUIAnnotations(true).build(), is, adaptDraftEntities);
}
/**
* @deprecated use
* {@link CdsModelReader#read(CdsModelReader.Config config, String csn, boolean adaptDraftEntities)}
* instead
* @param csn the csn as String
* @param adaptDraftEntities whether to add draft transformations
* @return the CDS model
*/
@Deprecated
public static CdsModel read(String csn, boolean adaptDraftEntities) {
return read(new CdsModelReader.Config.Builder().setIncludeUIAnnotations(true).build(), csn, adaptDraftEntities);
}
public static CdsModel read(CdsModelReader.Config config, 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(config, CharStreams.toString(reader), adaptDraftEntities);
} catch (Exception e) {
throw new CdsException("Cannot read CDS model: ", e);
}
}
public static CdsModel read(CdsModelReader.Config config, JsonNode jObject) {
return read(config, jObject, false);
}
public static CdsModel read(CdsModelReader.Config config, JsonNode jObject, boolean adaptDraftEntities) {
return read(config, jObject.toString(), adaptDraftEntities);
}
public static CdsModel read(CdsModelReader.Config config, String csn, boolean adaptDraftEntities) {
return read(config, Collections.singletonList(csn), adaptDraftEntities);
}
public static CdsModel read(CdsModelReader.Config config, List csnList, boolean adaptDraftEntities) {
return readCached(config, csnList, adaptDraftEntities);
}
private static CdsModel readCached(CdsModelReader.Config config, List csnList, boolean adaptDraftEntities) {
HashCode hash = hasher.hashString(csnList.stream().collect(Collectors.joining()), UTF_8);
Cache cache = adaptDraftEntities ? draftModels : nonDraftModels;
try {
return cache.get(hash, h -> parse(config, csnList, adaptDraftEntities));
} catch (RuntimeException e) {
throw new CdsException("Cannot load CDS model: ", e);
}
}
private static CdsModel parse(CdsModelReader.Config config, List csnList, boolean adaptDraftEntities) {
try {
List jObjects = new ArrayList<>();
for (String csn : csnList) {
jObjects.add(jackson.readTree(csn));
}
return new CdsModelReader(config).parseNodes(jObjects, 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 parseNodes(List csnList, boolean adaptDraftEntities) {
Set qualifiedDefinitionNames = new HashSet();
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<>();
Map structuredObjects = new HashMap<>();
DraftAdapter draftAdapter = new DraftAdapter(adaptDraftEntities, entityObjects, serviceObjects);
for (JsonNode csn : csnList) {
JsonNode definitions = asObject(csn.get(CdsConstants.DEFINITIONS));
Iterator> fields = definitions.fields();
while (fields.hasNext()) {
String name = fields.next().getKey();
JsonNode object = definitions.get(name);
String kind = object.get(CdsConstants.KIND).asText();
switch (kind) {
case CdsConstants.ASPECT:
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);
}
}
}
qualifiedDefinitionNames.addAll(entityObjects.keySet());
qualifiedDefinitionNames.addAll(eventObjects.keySet());
qualifiedDefinitionNames.addAll(typeObjects.keySet());
qualifiedDefinitionNames.addAll(actionObjects.keySet());
qualifiedDefinitionNames.addAll(functionObjects.keySet());
nameResolver = new NameResolver(qualifiedDefinitionNames, serviceObjects.keySet());
draftAdapter.adaptDraftEntities();
// meta is always read from first CSN
readMetaInfo(csnList.get(0));
readEntities(entityObjects);
readServices(serviceObjects);
readUnboundActions(actionObjects);
readUnboundFunctions(functionObjects);
readTypes(typeObjects);
readAnnotations(contextObjects);
structuredObjects.putAll(entityObjects);
structuredObjects.putAll(eventObjects);
structuredObjects.putAll(typeObjects);
structResolver = new StructuredTypeResolver(structuredObjects);
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 entityObjects) {
entityObjects.forEach((qualifiedName, o) -> {
CdsEntityBuilder entity = CdsEntityReader.read(config, nameResolver.getDefinitionName(qualifiedName),
qualifiedName, o);
cdsModel.addEntity(entity);
});
}
private void readServices(Map serviceDefs) {
for (Entry entry : serviceDefs.entrySet()) {
CdsServiceBuilder service = new CdsServiceBuilder(CdsAnnotationReader.read(config, 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(config, path, nameResolver.getDefinitionName(path), csn);
}
if (csn.has(CdsConstants.ITEMS)) {
return new CdsArrayedTypeReader(config).readWithoutType(path, nameResolver.getDefinitionName(path), csn);
}
if (csn.has(CdsConstants.TYPE)) {
JsonNode typeName = csn.get(CdsConstants.TYPE);
String type = typeName.asText();
if (type.equals(CdsConstants.ASSOCIATION) || type.equals(CdsConstants.COMPOSITION)) {
return new CdsAssociationReader(config, cdsModel, null).read(path, csn);
}
if (type.startsWith("cds.")) {
return CdsSimpleTypeReader.read(config, path, nameResolver.getDefinitionName(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 InvalidCsnException("Failed to read type " + path);
}
private void readAnnotations(Map contextObjects) {
contextObjects.forEach((key, value) -> cdsModel.addAnnotations(key, CdsAnnotationReader.read(config, 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(config, entry.getKey(), entry.getValue(),
cdsModel, structResolver);
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(config, "", itemsTypeJSON, cdsModel, structResolver));
arrayedType.setItemsType(itemsType);
}
}
}
}
private void readEvents(Map eventObjects) {
for (Entry entry : eventObjects.entrySet()) {
CdsEventBuilder event = CdsEventReader.read(config, entry.getKey(),
nameResolver.getDefinitionName(entry.getKey()), entry.getValue(), cdsModel, structResolver);
cdsModel.addEvent(event);
}
}
private void addElementsAndParamsToEntities(Map entityObjects) {
for (Entry entry : entityObjects.entrySet()) {
cdsModel.findEntity(entry.getKey()).ifPresent(entity -> {
List> elements = readElementList(config, entry.getKey(), entry.getValue(),
cdsModel, structResolver);
entity.addElements(elements);
List params = CdsParameterReader.read(config, 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(config, a.getQualifiedName(), actionNode, cdsModel,
structResolver);
a.addParameters(params);
a.setReturnType(readReturnType(config, actionNode, cdsModel, structResolver));
});
});
}
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(config, f.getQualifiedName(), functionNode,
cdsModel, structResolver);
f.addParameters(params);
f.setReturnType(readReturnType(config, functionNode, cdsModel, structResolver));
});
});
}
private void addElementsToUnboundActions(Map actionObjects) {
for (Entry entry : actionObjects.entrySet()) {
Optional> actionOptional = cdsModel.findAction(entry.getKey());
if (actionOptional.isPresent()) {
CdsOperationBuilder action = actionOptional.get();
List params = readParameterList(config, entry.getKey(), entry.getValue(), cdsModel,
structResolver);
action.addParameters(params);
action.setReturnType(readReturnType(config, entry.getValue(), cdsModel, structResolver));
}
}
}
private void addElementsToUnboundFunctions(Map functionObjects) {
for (Entry entry : functionObjects.entrySet()) {
Optional> functionOptional = cdsModel.findFunction(entry.getKey());
if (functionOptional.isPresent()) {
CdsOperationBuilder function = functionOptional.get();
List params = readParameterList(config, entry.getKey(), entry.getValue(), cdsModel,
structResolver);
function.addParameters(params);
function.setReturnType(readReturnType(config, entry.getValue(), cdsModel, structResolver));
}
}
}
private void readUnboundActions(Map actionDefs) {
for (Entry entry : actionDefs.entrySet()) {
CdsOperationBuilder action = CdsOperationReader.readAction(config, entry.getKey(),
nameResolver.getDefinitionName(entry.getKey()), entry.getValue());
cdsModel.addAction(action);
}
}
private void readUnboundFunctions(Map functionDefs) {
for (Entry entry : functionDefs.entrySet()) {
CdsOperationBuilder function = CdsOperationReader.readFunction(config, entry.getKey(),
nameResolver.getDefinitionName(entry.getKey()), entry.getValue());
cdsModel.addFunction(function);
}
}
public static CdsTypeBuilder> readType(CdsModelReader.Config config, String pathToElement, JsonNode csn,
CdsModelBuilder model, StructuredTypeResolver structResolver) {
if (csn.has(CdsConstants.TYPE)) {
JsonNode typeName = csn.get(CdsConstants.TYPE);
String type = typeName.asText();
if (type.equals(CdsConstants.ASSOCIATION) || type.equals(CdsConstants.COMPOSITION)) {
// inline defined association
return new CdsAssociationReader(config, model, structResolver).read(pathToElement, csn);
}
if (type.startsWith("cds.")) {
// TODO -> type does not start with cds*?
return CdsSimpleTypeReader.read(config, pathToElement, "", csn);
}
if (typeName.has("ref")) {
ArrayNode refNode = typeName.withArray("ref");
if (refNode.size() == 0) {
throw new CdsException("Empty ref object encoutered for Element " + pathToElement);
}
JsonNode elementCsn = structResolver.getElementNode(refNode);
return findType(elementCsn, model)
.orElseGet(() -> readType(config, pathToElement, elementCsn, model, structResolver));
}
}
if (csn.has(CdsConstants.ELEMENTS) || csn.has(CdsConstants.PAYLOAD)) {
// inline defined struct
return CdsStructuredTypeReader.read(config, "", csn, model, structResolver);
}
if (csn.has(CdsConstants.ITEMS)) {
// inline defined array
return new CdsArrayedTypeReader(config, model, structResolver).read(pathToElement, csn);
}
// computed element without type, e.g. result of function call
return CdsSimpleTypeReader.read(config, pathToElement, pathToElement, csn);
}
public static Optional> findType(JsonNode csn, CdsModelBuilder model) {
JsonNode typeName = csn.get(CdsConstants.TYPE);
if (typeName != null && CdsConstants.SELF.equals(typeName.asText())) {
return Optional.of(CdsSymbolicTypeBuilder.SELF);
}
if (typeName != null && !typeName.has("ref")) {
return model.findType(typeName.asText());
}
return Optional.empty();
}
/**
* Contains configuration options for the {@link CdsModelReader}.
*/
public static class Config {
private boolean readDocs;
private boolean includeUIAnnotations;
private Config() {
}
public boolean readDocs() {
return readDocs;
}
public boolean includeUIAnnotations() {
return includeUIAnnotations;
}
public static class Builder {
private boolean readDocs;
private boolean includeUIAnnotations;
public Builder setReadDocs(boolean readDocs) {
this.readDocs = readDocs;
return this;
}
public Builder setIncludeUIAnnotations(boolean includeUIAnnotations) {
this.includeUIAnnotations = includeUIAnnotations;
return this;
}
public CdsModelReader.Config build() {
CdsModelReader.Config config = new Config();
config.readDocs = this.readDocs;
config.includeUIAnnotations = this.includeUIAnnotations;
return config;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy