com.sap.cds.adapter.odata.v4.utils.EdmUtils 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.utils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.olingo.commons.api.edm.EdmBindingTarget;
import org.apache.olingo.commons.api.edm.EdmElement;
import org.apache.olingo.commons.api.edm.EdmEntityContainer;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmOperation;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmStructuredType;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
import org.apache.olingo.commons.core.edm.primitivetype.EdmStream;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceAction;
import org.apache.olingo.server.api.uri.UriResourceComplexProperty;
import org.apache.olingo.server.api.uri.UriResourceFunction;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
import org.apache.olingo.server.api.uri.UriResourcePrimitiveProperty;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
import org.apache.olingo.server.core.deserializer.helper.ExpandTreeBuilder;
import org.apache.olingo.server.core.deserializer.helper.ExpandTreeBuilderImpl;
import com.sap.cds.Result;
import com.sap.cds.adapter.odata.v4.CdsRequestGlobals;
import com.sap.cds.adapter.odata.v4.processors.request.CdsODataRequest;
import com.sap.cds.reflect.CdsAction;
import com.sap.cds.reflect.CdsArrayedType;
import com.sap.cds.reflect.CdsDefinition;
import com.sap.cds.reflect.CdsFunction;
import com.sap.cds.reflect.CdsKind;
import com.sap.cds.reflect.CdsOperationNotFoundException;
import com.sap.cds.reflect.CdsParameter;
import com.sap.cds.reflect.CdsService;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
public class EdmUtils {
private static final String PARAMETERS = "Parameters"; // suffix for parameter entity type
private static final String TYPE = "Type"; // suffix for entity type of Set navigation
private static final String SET = "Set"; // navigation property on Parameter entity type
private final CdsRequestGlobals globals;
public EdmUtils(CdsRequestGlobals globals) {
this.globals = globals;
}
/**
* Get the target entity set for a given entity type. Generally the entity set
* name is equal to entity type name. Exception is view with parameters, where
* the entity set name is an entity type name without "Parameters" suffix.
*
* @param entityType entity type
* @return entity set name
*/
public EdmBindingTarget getEdmBindingTarget(EdmEntityType entityType) {
EdmBindingTarget target = null;
EdmEntityContainer container = globals.getServiceMetadata().getEdm().getEntityContainer();
// General case: ==
String entityTypeName = entityType.getName();
if ((target = container.getEntitySet(entityTypeName)) != null) {
return target;
}
if ((target = container.getSingleton(entityTypeName)) != null) {
return target;
}
// View with parameter: = - "Parameters"
if (isParametersEntityType(entityType)) {
entityTypeName = entityTypeName.substring(0, entityTypeName.length() - PARAMETERS.length());
if ((target = container.getEntitySet(entityTypeName)) != null) {
return target;
}
}
return target;
}
public boolean isParametersEntityType(EdmStructuredType entityType) {
String name = entityType.getFullQualifiedName().getFullQualifiedNameAsString();
// potential [CdsView]Parameters type
if (name.endsWith(PARAMETERS)) {
EdmNavigationProperty setNavigation = entityType.getNavigationProperty(SET);
// has Set navigation
if (setNavigation != null) {
String setName = setNavigation.getType().getFullQualifiedName().getFullQualifiedNameAsString();
// Set type is named [CdsView]Type and both refer to the same [CdsView]
if (setName.endsWith(TYPE) && name.startsWith(setName.substring(0, setName.length() - TYPE.length()))) {
return true;
}
}
}
return false;
}
public boolean isSetEntityType(EdmStructuredType entityType) {
String name = entityType.getFullQualifiedName().getFullQualifiedNameAsString();
// potential [CdsView]Type type
if (name.endsWith(TYPE)) {
EdmNavigationProperty paramNavigation = entityType.getNavigationProperty(PARAMETERS);
// has Parameters navigation
if (paramNavigation != null) {
String paramName = paramNavigation.getType().getFullQualifiedName().getFullQualifiedNameAsString();
// Parameters type is named [CdsView]Parameters and both refer to the same
// [CdsView]
if (paramName.endsWith(PARAMETERS)
&& name.startsWith(paramName.substring(0, paramName.length() - PARAMETERS.length()))) {
return true;
}
}
}
return false;
}
public String getCdsEntityName(EdmStructuredType entityType) {
String name = entityType.getFullQualifiedName().getFullQualifiedNameAsString();
if (isSetEntityType(entityType)) {
name = name.substring(0, name.length() - TYPE.length());
} else if (isParametersEntityType(entityType)) {
throw new ErrorStatusException(CdsErrorStatuses.INVALID_PARAMETERIZED_VIEW);
}
String cdsName = globals.getCdsEntityNames().get(name);
if (cdsName != null) {
name = cdsName;
}
return name;
}
public CdsStructuredType findStructuredType(EdmType type) {
if (type.getKind() == EdmTypeKind.ENTITY) {
String entityName = getCdsEntityName((EdmEntityType) type);
return globals.getModel().getEntity(entityName);
} else if (type.getKind() == EdmTypeKind.COMPLEX) {
String structName = type.getFullQualifiedName().getFullQualifiedNameAsString();
return globals.getModel().getStructuredType(structName);
}
return null;
}
public EdmOperation getEdmOperation(UriResource resource) {
if (resource.getKind() == UriResourceKind.action) {
return ((UriResourceAction) resource).getAction();
}
if (resource.getKind() == UriResourceKind.function) {
return ((UriResourceFunction) resource).getFunction();
}
throw new ErrorStatusException(CdsErrorStatuses.UNEXPECTED_URI_RESOURCE, resource.getKind());
}
public CdsDefinition getCdsOperation(CdsODataRequest request) {
EdmOperation operation = getEdmOperation(request.getLastTypedResource());
if (operation.getKind() == EdmTypeKind.ACTION) {
if (operation.isBound()) {
return request.getLastEntity().getAction(operation.getName());
} else {
CdsService service = globals.getApplicationService().getDefinition();
return service
.actions().filter(a -> a.getName().equals(operation.getName()))
.findFirst().orElseThrow(
() -> new CdsOperationNotFoundException(CdsKind.ACTION, operation.getName(), service));
}
} else if (operation.getKind() == EdmTypeKind.FUNCTION) {
if (operation.isBound()) {
return request.getLastEntity().getFunction(operation.getName());
} else {
CdsService service = globals.getApplicationService().getDefinition();
return service
.functions().filter(a -> a.getName().equals(operation.getName()))
.findFirst().orElseThrow(() -> new CdsOperationNotFoundException(CdsKind.FUNCTION,
operation.getName(), service));
}
}
return null;
}
public CdsType getCdsOperationReturnType(CdsDefinition operation) {
CdsType type = null;
if (operation instanceof CdsAction) {
type = operation.as(CdsAction.class).returnType().orElse(null);
} else if (operation instanceof CdsFunction) {
type = operation.as(CdsFunction.class).getReturnType();
}
return stripArrayed(type);
}
public Map getCdsOperationParameters(CdsDefinition operation) {
Stream parameters = Stream.empty();
if (operation instanceof CdsAction) {
parameters = operation.as(CdsAction.class).parameters();
} else if (operation instanceof CdsFunction) {
parameters = operation.as(CdsFunction.class).parameters();
}
Map parameterMap = new HashMap<>();
parameters.forEach(p -> parameterMap.put(p.getName(), stripArrayed(p.getType())));
return parameterMap;
}
private CdsType stripArrayed(CdsType type) {
if (type != null && type.isArrayed()) {
return stripArrayed(type.as(CdsArrayedType.class).getItemsType());
}
return type;
}
public Optional getEdmProperty(UriResourcePartTyped resource) {
if (resource instanceof UriResourcePrimitiveProperty property) {
return Optional.of(property.getProperty());
}
if (resource instanceof UriResourceComplexProperty property) {
return Optional.of(property.getProperty());
}
return Optional.empty();
}
public boolean isEdmStream(Optional edmProperty) {
if (edmProperty.isPresent()) {
return edmProperty.get().getType() instanceof EdmStream;
}
return false;
}
public static boolean hasApply(UriInfo uriInfo) {
return uriInfo != null && uriInfo.getApplyOption() != null;
}
public static ExpandOption createExpand(EdmStructuredType type, Result entityRows) {
ExpandTreeBuilder expand = ExpandTreeBuilderImpl.create();
traverseRows(type, entityRows.list(), expand);
return expand.build();
}
public static ExpandOption createExpand(EdmStructuredType type, Map, ?> row) {
ExpandTreeBuilder expand = ExpandTreeBuilderImpl.create();
traverseRow(type, row, expand);
return expand.build();
}
private static void traverseRows(EdmStructuredType type, List> result,
ExpandTreeBuilder expand) {
result.forEach(row -> traverseRow(type, (Map, ?>) row, expand));
}
private static void traverseRow(EdmStructuredType type, Map, ?> entityRow, ExpandTreeBuilder expand) {
// navigation properties
List navigationPropertyNames = type.getNavigationPropertyNames();
for (String navigationPropertyName : navigationPropertyNames) {
Object navigationValue = entityRow.get(navigationPropertyName);
if (navigationValue != null) {
EdmNavigationProperty navigationProperty = type.getNavigationProperty(navigationPropertyName);
followNavigation(navigationProperty, navigationValue, expand);
}
}
for (String propertyName : type.getPropertyNames()) {
EdmElement property = type.getProperty(propertyName);
EdmType propertyType = property.getType();
if (propertyType.getKind() == EdmTypeKind.COMPLEX) {
if (property.isCollection()) {
List> complexValues = (List>) entityRow.get(propertyName);
if (null != complexValues) {
traverseRows((EdmStructuredType)propertyType, complexValues, expand);
}
} else {
Map, ?> complexValue = (Map, ?>) entityRow.get(propertyName);
if (null != complexValue) {
traverseRow((EdmStructuredType)propertyType, complexValue, expand);
}
}
}
}
}
private static void followNavigation(EdmNavigationProperty navigationProperty, Object navigationValue,
ExpandTreeBuilder expand) {
ExpandTreeBuilder childExpand = (expand != null) ? expand.expand(navigationProperty) : null;
EdmEntityType edmEntityType = navigationProperty.getType();
if (navigationProperty.isCollection()) {
List> navigationValueList;
if (navigationValue instanceof List> list) {
navigationValueList = list;
} else {
navigationValueList = Arrays.asList(navigationValue);
}
traverseRows(edmEntityType, navigationValueList, childExpand);
} else {
traverseRow(edmEntityType, (Map, ?>) navigationValue, childExpand);
}
}
}