Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.cds.adapter.odata.v4.utils.mapper.V4EdmxFlavourMapper Maven / Gradle / Ivy
/**************************************************************************
* (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
**************************************************************************/
package com.sap.cds.adapter.odata.v4.utils.mapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.sap.cds.impl.AssociationAnalyzer;
import com.sap.cds.impl.DataProcessor;
import com.sap.cds.ql.cqn.Path;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsKind;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.util.DataUtils;
public class V4EdmxFlavourMapper implements EdmxFlavourMapper {
private final boolean toCsn;
/**
* @param toCsn true, if data should be mapped from EDMX to CSN structure, false
* if vice versa.
*/
public V4EdmxFlavourMapper(boolean toCsn) {
this.toCsn = toCsn;
}
@Override
public String remap(String element, CdsStructuredType type) {
return createMappings(type, true, false)
.filter(m -> toCsn ? m.getEdmxName().equals(element)
: m.getCsnName().equals(element))
.map(m -> toCsn ? m.getCsnName() : m.getEdmxName()).findAny().orElse(element);
}
@Override
public >> T remap(T entries, CdsStructuredType entryType,
Function isExpanded) {
DataProcessor.create().withDepthFirst().action(new DataProcessor.Action() {
@Override
public void entries(Path path, CdsElement element, CdsStructuredType type,
Iterable> data) {
remap(path, element, type, data, isExpanded);
}
}).process(entries, entryType);
return entries;
}
private void remap(Path path, CdsElement element, CdsStructuredType type,
Iterable> data, Function isExpanded) {
boolean insideArray = Stream
.concat(StreamSupport.stream(path.spliterator(), false).map(p -> p.element()), Stream.of(element))
.filter(Objects::nonNull).anyMatch(e -> e.getType().isArrayed());
boolean entityRoot = (path.iterator().hasNext() ? path.root().type() : type) instanceof CdsEntity;
List mappings = createMappings(type, !insideArray && entityRoot, false)
.sorted((m1, m2) -> Boolean.compare(m1.isForeignKey, m2.isForeignKey))
.collect(Collectors.toList());
for (Map map : data) {
for (MappingV4 mapping : mappings) {
// EDMX (flat) -> CSN (structured)
if (toCsn) {
if (map.containsKey(mapping.getEdmxName())) { // avoid nulls
Object value = map.remove(mapping.getEdmxName());
if (!DataUtils.containsKey(map, mapping.getCsnName(), true)) {
DataUtils.putPath(map, mapping.getCsnName(), value);
}
}
// CSN (structured) -> EDMX (flat)
} else {
// due to depth-first approach keys in nested maps have already been renamed to
// their EDMX name
String nextLevelCsnName = mapping.element.getName() + "." + mapping.innerMapping.getEdmxName();
if (DataUtils.containsKey(map, nextLevelCsnName)) {
Object value = DataUtils.getOrDefault(map, nextLevelCsnName, null);
boolean expanded = isExpanded.apply(join(path, element, mapping.element));
if (!mapping.isForeignKey || (!expanded && containsOnlyFKs(mapping, mappings, map))) {
removeDeep(map, nextLevelCsnName);
}
map.put(mapping.getEdmxName(), value);
}
}
}
}
}
@Override
public Stream createMappings(CdsStructuredType type) {
return createMappings(type, type.getKind() == CdsKind.ENTITY, true);
}
private Stream createMappings(CdsStructuredType type, boolean flattenStructs, boolean includeUnmapped) {
return createMappings(type, flattenStructs, false, false, includeUnmapped)
.filter(m -> includeUnmapped || !Objects.equals(m.getEdmxName(), m.getCsnName()));
}
private Stream createMappings(CdsStructuredType type, boolean flattenStructs,
boolean isForeignKey, boolean insideStruct, boolean includeUnmapped) {
return type.elements()
.flatMap(e -> createMappings(e, flattenStructs, isForeignKey, insideStruct, includeUnmapped));
}
private Stream createMappings(CdsElement element, boolean flattenStructs,
boolean isForeignKey, boolean insideStruct, boolean includeUnmapped) {
CdsType type = element.getType();
if (type.isStructured()) {
if (includeUnmapped && !flattenStructs) {
return Stream.of(new MappingV4(element, false));
}
return createMappings(type.as(CdsStructuredType.class), flattenStructs, isForeignKey, true, false)
.map(i -> new MappingV4(element, false, i));
} else if (type.isAssociation()) {
Stream mappings = AssociationAnalyzer.refElements(element)
.flatMap(k -> createMappings(k, flattenStructs, true, false, includeUnmapped))
.map(i -> new MappingV4(element, true, i));
if ((includeUnmapped && !isForeignKey) || (flattenStructs && insideStruct)) {
return Stream.concat(Stream.of(new MappingV4(element, false)), mappings);
}
return mappings;
} else if (includeUnmapped || flattenStructs || isForeignKey) {
return Stream.of(new MappingV4(element, isForeignKey));
}
return Stream.empty();
}
private String join(Path path, CdsElement parent, CdsElement element) {
List segments = new ArrayList<>();
path.iterator().forEachRemaining(s -> {
if (s.element() != null) {
segments.add(s.segment().id());
}
});
if (parent != null) {
segments.add(parent.getName());
}
segments.add(element.getName());
return String.join(".", segments);
}
private boolean containsOnlyFKs(MappingV4 current, List others, Map map) {
if (current.isForeignKey && current.element.getType().isAssociation()) {
Object assoc = map.get(current.element.getName());
if (assoc instanceof Map map1) {
List fks = others.stream()
.filter(o -> o.element.getName().equals(current.element.getName()))
.map(o -> o.innerMapping.getEdmxName()).collect(Collectors.toList());
return fks.containsAll(map1.keySet());
}
}
return false;
}
private void removeDeep(Map data, String path) {
int lastDot = path.lastIndexOf(".");
if (lastDot > 0) {
String start = path.substring(0, lastDot);
String end = path.substring(lastDot + 1);
Map last = DataUtils.getOrDefault(data, start, null);
if (last != null) {
last.remove(end);
if (last.isEmpty()) {
removeDeep(data, start);
}
}
} else {
data.remove(path);
}
}
private class MappingV4 implements Mapping {
private final CdsElement element;
private final boolean isForeignKey;
private final Mapping innerMapping;
public MappingV4(CdsElement element, boolean isForeignKey) {
this(element, isForeignKey, null);
}
public MappingV4(CdsElement element, boolean isForeignKey, Mapping innerMapping) {
this.element = element;
this.isForeignKey = isForeignKey;
this.innerMapping = innerMapping;
}
@Override
public CdsElement getTargetElement() {
return innerMapping == null ? element : innerMapping.getTargetElement();
}
@Override
public CdsElement getRootElement() {
return element;
}
public String getEdmxName() {
return innerMapping == null ? element.getName()
: element.getName() + "_" + innerMapping.getEdmxName();
}
public String getCsnName() {
return innerMapping == null ? element.getName()
: element.getName() + "." + innerMapping.getCsnName();
}
}
}