All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cds.adapter.odata.v4.utils.mapper.V4EdmxFlavourMapper Maven / Gradle / Ivy

There is a newer version: 3.2.0
Show newest version
/**************************************************************************
 * (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();
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy