com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmSchema Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cds-adapter-odata-v4 Show documentation
Show all versions of cds-adapter-odata-v4 Show documentation
OData V4 adapter for CDS Services Java
/**************************************************************************
* (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
**************************************************************************/
package com.sap.cds.adapter.odata.v4.metadata.cds;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.fqn;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.isIncluded;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.isParameterized;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.name;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.nameElement;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.nameParameter;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.nameReturn;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.structuredTypeFqn;
import static com.sap.cds.adapter.odata.v4.metadata.cds.CdsServiceEdmUtils.target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edm.geo.SRID;
import org.apache.olingo.commons.api.edm.provider.CsdlAction;
import org.apache.olingo.commons.api.edm.provider.CsdlComplexType;
import org.apache.olingo.commons.api.edm.provider.CsdlEntityContainer;
import org.apache.olingo.commons.api.edm.provider.CsdlEntityType;
import org.apache.olingo.commons.api.edm.provider.CsdlFunction;
import org.apache.olingo.commons.api.edm.provider.CsdlNamed;
import org.apache.olingo.commons.api.edm.provider.CsdlNavigationProperty;
import org.apache.olingo.commons.api.edm.provider.CsdlParameter;
import org.apache.olingo.commons.api.edm.provider.CsdlProperty;
import org.apache.olingo.commons.api.edm.provider.CsdlPropertyRef;
import org.apache.olingo.commons.api.edm.provider.CsdlReturnType;
import org.apache.olingo.commons.api.edm.provider.CsdlSchema;
import com.sap.cds.adapter.odata.v4.utils.mapper.EdmxFlavourMapper;
import com.sap.cds.adapter.odata.v4.utils.mapper.EdmxFlavourMapper.Mapping;
import com.sap.cds.reflect.CdsAction;
import com.sap.cds.reflect.CdsAnnotatable;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsArrayedType;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsBoundAction;
import com.sap.cds.reflect.CdsBoundFunction;
import com.sap.cds.reflect.CdsDefinition;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsFunction;
import com.sap.cds.reflect.CdsKind;
import com.sap.cds.reflect.CdsOperation;
import com.sap.cds.reflect.CdsParameter;
import com.sap.cds.reflect.CdsService;
import com.sap.cds.reflect.CdsSimpleType;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.services.draft.Drafts;
import com.sap.cds.services.utils.DraftUtils;
import com.sap.cds.services.utils.model.CdsAnnotations;
import com.sap.cds.util.CdsModelUtils;
class CdsServiceEdmSchema extends CsdlSchema {
private enum EntityTypeKind { DEFAULT, PARAMETERIZED_PARAMETERS, PARAMETERIZED_TYPE }
private static final record CdsEntityInfo(CdsEntity entity, EntityTypeKind kind) {}
private static final record CdsOperationInfo(T operation, CdsEntity boundTo) {}
private final CdsService service;
private final EdmxFlavourMapper edmxFlavourMapper;
private final Map entityLookup = new HashMap<>();
private final Map>> actionLookup = new HashMap<>();
private final Map>> functionLookup = new HashMap<>();
private final Map complexTypeLookup = new HashMap<>();
private final Map cached = new HashMap<>();
CdsServiceEdmSchema(CdsService service, CsdlEntityContainer entityContainer, EdmxFlavourMapper edmxFlavourMapper) {
this.service = service;
this.edmxFlavourMapper = edmxFlavourMapper;
service.entities().filter(CdsServiceEdmUtils::isIncluded).forEach(e -> {
if (isParameterized(e)) {
entityLookup.put(name(e) + "Parameters", new CdsEntityInfo(e, EntityTypeKind.PARAMETERIZED_PARAMETERS));
entityLookup.put(name(e) + "Type", new CdsEntityInfo(e, EntityTypeKind.PARAMETERIZED_TYPE));
} else {
entityLookup.put(name(e), new CdsEntityInfo(e, EntityTypeKind.DEFAULT));
}
e.actions().forEach(a -> actionLookup.compute(name(a), (k, v) -> addOrInit(v, new CdsOperationInfo<>(a, e))));
e.functions().forEach(f -> functionLookup.compute(name(f), (k, v) -> addOrInit(v, new CdsOperationInfo<>(f, e))));
});
service.actions().forEach(a -> actionLookup.compute(name(a), (k, v) -> addOrInit(v, new CdsOperationInfo<>(a, null))));
service.functions().forEach(f -> functionLookup.compute(name(f), (k, v) -> addOrInit(v, new CdsOperationInfo<>(f, null))));
setNamespace(service.getQualifiedName());
setEntityContainer(entityContainer);
setEntityTypes(null);
setComplexTypes(null);
setActions(null);
setFunctions(null);
}
@Override
public CsdlEntityType getEntityType(String name) {
CdsEntityInfo info = entityLookup.get(name);
if (info != null) {
return buildEntityType(info);
}
return null;
}
@Override
public List getEntityTypes() {
return entityLookup.values().stream().map(this::buildEntityType).toList();
}
private CsdlEntityType buildEntityType(CdsEntityInfo info) {
CdsEntity entity = info.entity();
EntityTypeKind kind = info.kind();
Function super CdsDefinition, ? extends CsdlNamed> builder = e -> {
CsdlEntityType entityType = new CsdlEntityType();
entityType.setKey(new ArrayList<>());
if (kind == EntityTypeKind.DEFAULT || kind == EntityTypeKind.PARAMETERIZED_TYPE) {
entityType.setName(name(entity) + (kind == EntityTypeKind.PARAMETERIZED_TYPE ? "Type" : ""));
entityType.setOpenType(entity.getAnnotationValue("open", false));
edmxFlavourMapper.createMappings(entity).forEach(mapped -> {
if (mapped.getTargetElement().getType().isAssociation()) {
entityType.getNavigationProperties().add(createNavigationProperty(mapped.getEdmxName(), mapped.getTargetElement(), entity));
} else if (isIncluded(mapped.getTargetElement())) {
entityType.getProperties().add(createProperty(mapped, entityType.getName()));
if (mapped.getRootElement().isKey()) {
entityType.getKey().add(new CsdlPropertyRef().setName(mapped.getEdmxName()));
}
}
});
if (kind == EntityTypeKind.PARAMETERIZED_TYPE) {
CsdlNavigationProperty parametersNavigation = new CsdlNavigationProperty();
parametersNavigation.setName("Parameters");
parametersNavigation.setType(fqn(entity) + "Parameters");
entityType.getNavigationProperties().add(parametersNavigation);
}
} else {
entityType.setName(name(entity) + "Parameters");
entity.params().forEach(parameter -> {
entityType.getKey().add(new CsdlPropertyRef().setName(parameter.getName()));
CsdlProperty property = new CsdlProperty();
property.setName(parameter.getName());
property.setType(type(parameter.getType(), parameter, CdsServiceEdmUtils::name));
property.setCollection(parameter.getType().isArrayed());
property.setNullable(false);
if (parameter.getType().isSimple()) {
SimpleTypeProperties properties = simpleTypeProperties(parameter.getType().as(CdsSimpleType.class), property.getType(), parameter);
property.setMaxLength(properties.maxLength());
property.setPrecision(properties.precision());
property.setScale(properties.scale());
property.setScaleAsString(properties.scaleAsString());
property.setSrid(properties.srid());
}
entityType.getProperties().add(property);
});
CsdlNavigationProperty parametersNavigation = new CsdlNavigationProperty();
parametersNavigation.setName("Set");
parametersNavigation.setType(fqn(entity) + "Type");
parametersNavigation.setCollection(true);
parametersNavigation.setContainsTarget(true);
entityType.getNavigationProperties().add(parametersNavigation);
}
if (entityType.getKey().isEmpty()) {
entityType.setKey(null);
}
return entityType;
};
if (kind == EntityTypeKind.DEFAULT) {
return (CsdlEntityType) cached.computeIfAbsent(entity, builder);
} else {
// entity types from parameterized views are not stored in cached
// as there are two csdl definitions created from the CDS definition
return (CsdlEntityType) builder.apply(entity);
}
}
@Override
public CsdlComplexType getComplexType(String name) {
CdsStructuredType structuredType = complexTypeLookup.get(name);
if (structuredType != null) {
return buildComplexType(name, structuredType);
}
return null;
}
@Override
public List getComplexTypes() {
// ensure complex types in all entities, actions and functions are found
getEntityTypes();
getActions();
getFunctions();
int previousSize;
List complexTypes;
do {
previousSize = complexTypeLookup.size();
// iteration might detect new complex types
complexTypes = new HashSet<>(complexTypeLookup.entrySet()).stream().map(e -> buildComplexType(e.getKey(), e.getValue())).toList();
} while (previousSize != complexTypeLookup.size());
return complexTypes;
}
private CsdlComplexType buildComplexType(String name, CdsStructuredType type) {
return (CsdlComplexType) cached.computeIfAbsent(type, s -> {
CsdlComplexType complexType = new CsdlComplexType();
complexType.setName(name);
complexType.setOpenType(type.getAnnotationValue("open", false));
edmxFlavourMapper.createMappings(type).forEach(mapped -> {
if (mapped.getTargetElement().getType().isAssociation()) {
complexType.getNavigationProperties().add(createNavigationProperty(mapped.getEdmxName(), mapped.getTargetElement(), type));
} else if (isIncluded(mapped.getTargetElement())) {
complexType.getProperties().add(createProperty(mapped, complexType.getName()));
}
});
return complexType;
});
}
private CsdlProperty createProperty(Mapping mapping, String parentName) {
CsdlProperty property = new CsdlProperty();
property.setName(mapping.getEdmxName());
CdsElement element = mapping.getTargetElement();
property.setType(type(element.getType(), element, def -> nameElement(def, parentName, mapping.getEdmxName())));
property.setCollection(element.getType().isArrayed());
boolean notNull = mapping.getRootElement().isKey() || element.isNotNull() || property.isCollection();
property.setNullable(!notNull); // TODO support explicit null for collections
if (element.getType().isSimple()) {
SimpleTypeProperties properties = simpleTypeProperties(element.getType().as(CdsSimpleType.class), property.getType(), element);
property.setMaxLength(properties.maxLength());
property.setPrecision(properties.precision());
property.setScale(properties.scale());
property.setScaleAsString(properties.scaleAsString());
property.setSrid(properties.srid());
}
return property;
}
private CsdlNavigationProperty createNavigationProperty(String mappedName, CdsElement assoc, CdsStructuredType declarator) {
CdsEntity target = target(declarator, assoc);
CsdlNavigationProperty navProperty = new CsdlNavigationProperty();
navProperty.setName(mappedName);
navProperty.setType(fqn(target) + (isParameterized(target) ? "Parameters" : ""));
navProperty.setCollection(CdsModelUtils.isToMany(assoc.getType()));
navProperty.setNullable(!assoc.isNotNull());
navProperty.setContainsTarget(declarator.getAnnotationValue("odata.contained",
DraftUtils.isDraftEnabled(declarator) && assoc.getName().equals(Drafts.DRAFT_ADMINISTRATIVE_DATA)));
return navProperty;
}
@Override
public List getActions(String name) {
List> actions = actionLookup.get(name);
if (actions != null) {
return actions.stream().map(this::buildAction).toList();
}
return Collections.emptyList();
}
@Override
public List getActions() {
return actionLookup.values().stream().flatMap(Collection::stream).map(this::buildAction).toList();
}
private CsdlAction buildAction(CdsOperationInfo info) {
CdsAction action = info.operation();
return (CsdlAction) cached.computeIfAbsent(action, a -> {
CsdlAction csdlAction = new CsdlAction();
csdlAction.setName(name(action));
if (action instanceof CdsBoundAction boundAction) {
csdlAction.setBound(true);
csdlAction.getParameters().add(createBindingParameter(boundAction.getBindingParameter(), action, info.boundTo()));
}
action.parameters().forEach(parameter ->
csdlAction.getParameters().add(createParameter(parameter, action))
);
action.returnType().ifPresent(type -> csdlAction.setReturnType(createReturnType(type, action)));
return csdlAction;
});
}
@Override
public List getFunctions(String name) {
List> functions = functionLookup.get(name);
if (functions != null) {
return functions.stream().map(this::buildFunction).toList();
}
return Collections.emptyList();
}
@Override
public List getFunctions() {
return functionLookup.values().stream().flatMap(Collection::stream).map(this::buildFunction).toList();
}
private CsdlFunction buildFunction(CdsOperationInfo info) {
CdsFunction function = info.operation();
return (CsdlFunction) cached.computeIfAbsent(function, f -> {
CsdlFunction csdlFunction = new CsdlFunction();
csdlFunction.setName(name(function));
if (function instanceof CdsBoundFunction boundFunction) {
csdlFunction.setBound(true);
csdlFunction.getParameters().add(createBindingParameter(boundFunction.getBindingParameter(), function, info.boundTo()));
}
function.parameters().forEach(parameter ->
csdlFunction.getParameters().add(createParameter(parameter, function))
);
csdlFunction.setReturnType(createReturnType(function.getReturnType(), function));
return csdlFunction;
});
}
private CsdlParameter createBindingParameter(CdsParameter bindingParameter, CdsOperation operation, CdsEntity boundTo) {
String bindingParameterName;
boolean isCollection;
boolean notNull;
if (bindingParameter != null) {
bindingParameterName = bindingParameter.getName();
isCollection = bindingParameter.getType().isArrayed();
notNull = bindingParameter.isNotNull();
} else {
bindingParameterName = operation.getAnnotationValue("cds.odata.bindingparameter.name", "in");
isCollection = operation.getAnnotationValue("cds.odata.bindingparameter.collection", false);
notNull = false;
}
CsdlParameter csdlBindingParameter = new CsdlParameter();
csdlBindingParameter.setName(bindingParameterName);
csdlBindingParameter.setType(fqn(boundTo) + (isParameterized(boundTo) ? "Type" : ""));
csdlBindingParameter.setCollection(isCollection);
// TODO binding parameter should always be notNull -> CDS Compiler defaults to nullable however
csdlBindingParameter.setNullable(!notNull && !csdlBindingParameter.isCollection());
return csdlBindingParameter;
}
private CsdlParameter createParameter(CdsParameter parameter, CdsOperation operation) {
CsdlParameter csdlParameter = new CsdlParameter();
csdlParameter.setName(parameter.getName());
csdlParameter.setType(type(parameter.getType(), parameter, def -> nameParameter(def, operation, parameter)));
csdlParameter.setCollection(parameter.getType().isArrayed());
csdlParameter.setNullable(!parameter.isNotNull() && !csdlParameter.isCollection()); // TODO support explicit null for collections
if (parameter.getType().isSimple()) {
SimpleTypeProperties properties = simpleTypeProperties(parameter.getType().as(CdsSimpleType.class), csdlParameter.getType(), parameter);
csdlParameter.setMaxLength(properties.maxLength());
csdlParameter.setPrecision(properties.precision());
csdlParameter.setScale(properties.scale());
csdlParameter.setSrid(properties.srid());
}
return csdlParameter;
}
private CsdlReturnType createReturnType(CdsType type, CdsOperation operation) {
CsdlReturnType returnType = new CsdlReturnType();
returnType.setType(type(type, type, def -> nameReturn(def, operation)));
returnType.setCollection(type.isArrayed());
returnType.setNullable(!returnType.isCollection()); // TODO isNotNull() on return type + support explicit null for collections?
if (type.isSimple()) {
SimpleTypeProperties properties = simpleTypeProperties(type.as(CdsSimpleType.class), returnType.getType(), type);
returnType.setMaxLength(properties.maxLength());
returnType.setPrecision(properties.precision());
returnType.setScale(properties.scale());
returnType.setSrid(properties.srid());
}
return returnType;
}
private FullQualifiedName type(CdsType type, CdsAnnotatable declarator, Function anonymousNameSupplier) {
if (type.isStructured()) {
CdsStructuredType structuredType = type.as(CdsStructuredType.class);
FullQualifiedName fqn = structuredTypeFqn(service, structuredType, anonymousNameSupplier);
if (type.getKind() != CdsKind.ENTITY) {
complexTypeLookup.put(fqn.getName(), structuredType);
}
return fqn;
} else if (type.isArrayed()) {
return type(type.as(CdsArrayedType.class).getItemsType(), declarator, anonymousNameSupplier);
} else if (type.isSimple()) {
String edmType;
if (isODataTypeAnnotated(declarator)) {
edmType = declarator.getAnnotationValue("odata.Type", null);
} else if (declarator != null && CdsAnnotations.CORE_MEDIA_TYPE.getOrDefault(declarator) != null) {
edmType = "Edm.Stream";
} else {
edmType = simpleType(type.as(CdsSimpleType.class).getType());
}
return new FullQualifiedName(edmType);
}
return null;
}
private static String simpleType(CdsBaseType type) {
return switch (type) {
case UUID -> "Edm.Guid";
case BOOLEAN -> "Edm.Boolean";
case UINT8, HANA_TINYINT -> "Edm.Byte";
case INT16, HANA_SMALLINT -> "Edm.Int16";
case INT32, INTEGER -> "Edm.Int32";
case INT64, INTEGER64 -> "Edm.Int64";
case DECIMAL, HANA_SMALLDECIMAL -> "Edm.Decimal";
case HANA_REAL -> "Edm.Single";
case DOUBLE -> "Edm.Double";
case DATE -> "Edm.Date";
case TIME -> "Edm.TimeOfDay";
case DATETIME, TIMESTAMP -> "Edm.DateTimeOffset";
case STRING, LARGE_STRING, HANA_CHAR, HANA_NCHAR, HANA_VARCHAR, HANA_CLOB -> "Edm.String";
case BINARY, LARGE_BINARY, HANA_BINARY -> "Edm.Binary";
case HANA_ST_POINT -> "Edm.GeometryPoint";
case HANA_ST_GEOMETRY -> "Edm.Geometry";
default -> null;
};
}
private static final Set MAX_LENGTH_FACET_TYPES = Set.of("Edm.String", "Edm.Stream", "Edm.Binary");
private static final Set PRECISION_FACET_TYPES = Set.of("Edm.Decimal", "Edm.DateTimeOffset", "Edm.Duration", "Edm.TimeOfDay");
private static final Set SRID_FACET_TYPES = Set.of("Edm.Geography", "Edm.GeographyPoint", "Edm.GeographyLineString",
"Edm.GeographyPolygon", "Edm.GeographyMultiPoint", "Edm.GeographyMultiLineString", "Edm.GeographyMultiPolygon",
"Edm.GeographyCollection", "Edm.Geometry", "Edm.GeometryPoint", "Edm.GeometryLineString", "Edm.GeometryPolygon",
"Edm.GeometryMultiPoint", "Edm.GeometryMultiLineString", "Edm.GeometryMultiPolygon", "Edm.GeometryCollection");
private static final Set ALL_TYPES = Stream.of(MAX_LENGTH_FACET_TYPES, PRECISION_FACET_TYPES, SRID_FACET_TYPES,
Set.of("Edm.Boolean", "Edm.Byte", "Edm.Double", "Edm.Guid", "Edm.Int16", "Edm.Int32", "Edm.Int64", "Edm.SByte", "Edm.Single"))
.flatMap(Set::stream).collect(Collectors.toSet());
record SimpleTypeProperties(Integer maxLength, Integer precision, Integer scale, String scaleAsString, SRID srid) {}
private static SimpleTypeProperties simpleTypeProperties(CdsSimpleType simpleType, String edmType, CdsAnnotatable declarator) {
boolean hasOdataType = isODataTypeAnnotated(declarator);
Integer maxLength = null;
if (MAX_LENGTH_FACET_TYPES.contains(edmType)) {
if (hasOdataType) {
maxLength = declarator.getAnnotationValue("odata.MaxLength", null);
} else {
maxLength = simpleType.get("length");
}
}
Integer precision = null;
if (PRECISION_FACET_TYPES.contains(edmType)) {
if (hasOdataType) {
precision = declarator.getAnnotationValue("odata.Precision", null);
} else {
if (simpleType.getType() == CdsBaseType.TIMESTAMP) {
precision = 7;
} else {
precision = simpleType.get("precision");
}
}
}
Integer scale = null;
String scaleAsString = null;
if ("Edm.Decimal".equals(edmType)) {
if (hasOdataType) {
Object scaleObj = declarator.getAnnotationValue("odata.Scale", null);
if (scaleObj instanceof Integer s) {
scale = s;
scaleAsString = String.valueOf(s);
} else if (scaleObj instanceof String s) {
scaleAsString = s;
}
} else {
scale = simpleType.get("scale");
if (scale != null) {
scaleAsString = String.valueOf(scale);
}
// default to variable scale if nothing is specified
if (precision == null && scale == null) {
scaleAsString = "variable";
}
}
}
SRID srid = null;
if (SRID_FACET_TYPES.contains(edmType)) {
Object sridObj;
if (hasOdataType) {
sridObj = declarator.getAnnotationValue("odata.SRID", null);
} else {
sridObj = simpleType.get("srid");
}
if (sridObj != null) {
srid = SRID.valueOf(sridObj.toString());
}
}
return new SimpleTypeProperties(maxLength, precision, scale, scaleAsString, srid);
}
private static boolean isODataTypeAnnotated(CdsAnnotatable element) {
return element != null && element.findAnnotation("odata.Type")
.map(CdsAnnotation::getValue).filter(ALL_TYPES::contains).isPresent();
}
private static List addOrInit(List list, T value) {
if (list == null) {
list = new ArrayList<>();
}
list.add(value);
return list;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy