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

com.sap.cds.util.CdsModelUtils Maven / Gradle / Ivy

There is a newer version: 3.8.0
Show newest version
/************************************************************************
 * © 2020-2022 SAP SE or an SAP affiliate company. All rights reserved. *
 ************************************************************************/
package com.sap.cds.util;

import static com.sap.cds.impl.AssociationAnalyzer.refElements;
import static java.util.stream.Collectors.toSet;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
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.sap.cds.impl.parser.VersionParser;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.reflect.impl.CdsVersion;
import com.sap.cds.reflect.impl.DraftAdapter;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;

public class CdsModelUtils {

	private static final String UNDERSCORE = "_";
	private static final CdsVersion DEFAULT_COMPILER_VERSION = new CdsVersion(1, 0, 0, 0);

	private CdsModelUtils() {
	}

	public static boolean isSingleValued(CdsType associationElement) {
		return associationElement.as(CdsAssociationType.class).getCardinality().getTargetMax().equals("1");
	}

	public static boolean isToMany(CdsType associationElement) {
		return associationElement.as(CdsAssociationType.class).getCardinality().getTargetMax().equals("*");
	}

	public static boolean isManyTo(CdsType associationElement) {
		return !associationElement.as(CdsAssociationType.class).getCardinality().getSourceMax().equals("1");
	}

	public static boolean managedToOne(CdsType associationElement) {
		if (associationElement.isAssociation()) {
			CdsAssociationType assoc = associationElement.as(CdsAssociationType.class);
			return isSingleValued(assoc) && !assoc.onCondition().isPresent();
		}
		return false;
	}

	public static Set targetKeys(CdsElement assoc) {
		return keyNames(assocType(assoc).getTarget());
	}

	public static Set assocKeys(CdsElement forwardMappedAssoc) {
		Set assocKeys = refElements(forwardMappedAssoc).map(CdsElement::getName).collect(toSet());
		if (assocKeys.isEmpty()) {
			assocKeys = new OnConditionAnalyzer(forwardMappedAssoc, false).getFkMapping().values().stream()
					.flatMap(CqnValue::ofRef).map(CqnReference::lastSegment).collect(toSet());
		}
		return assocKeys;
	}

	public static Set keyNames(CdsStructuredType type) {
		return resolveKeys(type).collect(Collectors.toSet());
	}

	private static Stream resolveKeys(CdsStructuredType entity) {
		return entity.keyElements().filter(k -> !k.isVirtual()).flatMap(CdsModelUtils::resolveKeys);
	}

	private static Stream resolveKeys(CdsElement element) {
		if (element.getType().isAssociation()) {
			return refElements(element) //
					.flatMap(CdsModelUtils::resolveKeys) //
					.map(k -> element.getName() + UNDERSCORE + k);
		}
		return Stream.of(element.getName());
	}

	private static CdsAssociationType assocType(CdsElement element) {
		return element.getType().as(CdsAssociationType.class);
	}

	public static Optional findElement(CdsStructuredType struct, CqnElementRef ref) {
		String path = relativePath(struct, ref.segments());

		return struct.findElement(path);
	}

	public static CdsElement element(CdsStructuredType struct, CqnElementRef ref) {
		return element(struct, ref.segments());
	}

	public static CdsElement element(CdsStructuredType struct, List segments) {
		String path = relativePath(struct, segments);

		return struct.getElement(path);
	}

	private static String relativePath(CdsStructuredType struct, List segments) {
		StringBuilder path = new StringBuilder();
		for (int i = 0; i < segments.size(); i++) {
			String segmentId = segments.get(i).id();
			if (i == 0 && (segmentId.equals(struct.getQualifiedName()) || segmentId.equals("$self"))) {
				continue;
			}
			path.append('.');
			path.append(segmentId);
		}
		return path.substring(1);
	}

	public static boolean isContextElementRef(CqnElementRef ref) {
		switch (ref.firstSegment()) {
		case "$now":
		case "$at":
		case "$user":
			return true;
		default:
			return false;
		}
	}

	public static List entities(CdsModel model, List segments) {
		List entities = new ArrayList<>(segments.size());
		Iterator iter = segments.iterator();
		if (iter.hasNext()) {
			CdsEntity e = model.getEntity(iter.next().id());
			entities.add(e);
			while (iter.hasNext()) {
				e = e.getTargetOf(iter.next().id());
				entities.add(e);
			}
		}
		return entities;
	}

	public static List entities(CdsEntity root, List segments) {
		LinkedList entities = new LinkedList<>();
		entities.add(root);
		boolean firstSegment = true;
		for (Segment seg : segments) {
			if (firstSegment && root.getQualifiedName().equals(seg.id())) {
				continue;
			}
			entities.add(entities.getLast().getTargetOf(seg.id()));
			firstSegment = false;
		}
		return entities;
	}

	public static CdsEntity entity(CdsEntity root, List segments) {
		return ((LinkedList) entities(root, segments)).getLast();
	}

	public static CdsStructuredType target(CdsStructuredType root, List segments) {
		CdsStructuredType target = root;
		for (Segment seg : segments) {
			target = target.getTargetOf(seg.id());
		}

		return target;
	}

	public static CdsEntity entity(CdsModel model, List segments) {
		CdsEntity entity = model.getEntity(segments.get(0).id());

		return entity(entity, segments);
	}

	public static CdsEntity entity(CdsModel model, CqnStructuredTypeRef ref) {
		CdsEntity entity = model.getEntity(ref.firstSegment());

		return entity(entity, ref.segments());
	}

	public static boolean isReverseAssociation(CdsElement assoc) {
		CdsAssociationType association = assoc.getType();
		if (association.getCardinality().getTargetMax().equalsIgnoreCase("*")) {
			return true;
		}
		return referencesAllKeysOfSource(assoc);
	}

	public static CdsVersion compilerVersion(CdsModel model) {
		String creator = model.getMeta(CdsConstants.CREATOR);
		if (creator == null) {
			return DEFAULT_COMPILER_VERSION;
		}
		try {
			return VersionParser.parse(creator.substring("CDS Compiler v".length()));
		} catch (IllegalArgumentException e) {
			return DEFAULT_COMPILER_VERSION;
		}
	}

	private static boolean referencesAllKeysOfSource(CdsElement assoc) {
		CdsAssociationType association = assoc.getType();
		Optional onCond = association.onCondition();
		if (!onCond.isPresent()) {
			return false;
		}

		Set sourceKeys = keyNames(assoc.getDeclaringType());
		sourceKeys.remove(DraftAdapter.IS_ACTIVE_ENTITY); // TODO CDSJAVA-2385
		CqnVisitor visitor = new CqnVisitor() {
			@Override
			public void visit(CqnElementRef ref) {
				if (ref.lastSegment().equals("$self")) {
					sourceKeys.clear();
				} else if (ref.size() == 1) {
					sourceKeys.remove(ref.lastSegment());
				}
			}
		};
		onCond.get().accept(visitor);

		return sourceKeys.isEmpty();
	}

	public static String getDoc(JsonNode csn) {
		if (csn.has("doc")) {
			return csn.get("doc").asText();
		}
		return null;
	}

	public static boolean isCascading(CascadeType cascadeType, CdsElement association) {
		CdsType type = association.getType();
		if (type.isAssociation()) {
			Optional> cascade = association.findAnnotation("cascade." + cascadeType);
			if (!cascade.isPresent()) {
				cascade = association.findAnnotation("cascade.all");
			}
			return cascade.map(CdsAnnotation::getValue)
					.orElseGet(() -> type.as(CdsAssociationType.class).isComposition());
		}
		return false;
	}

	public static String getFullRefPath(CqnSelectListValue val) {
		if (val.isRef()) {
			return val.asRef().path();
		}
		return val.displayName();
	}

	public enum CascadeType {
		ALL, INSERT, UPDATE, DELETE;

		@Override
		public String toString() {
			return name().toLowerCase(Locale.US);
		}
	}

	/**
	 * Iteratively resolves every managed to-one association that is found within
	 * the provided association and its association reference mapping until only
	 * simple type elements have been reached. The ref id paths towards these
	 * transitively targeted simple type elements are kept track of and returned
	 * together with these elements in a stream of maps.
	 *
	 * The resolution of these managed to-one associations potentially constructs a
	 * multi-level tree of ref id paths with the ref id of the provided CDS element
	 * as the root node and the transitively targeted (simple type) elements as leaf
	 * nodes.
	 *
	 * Each map of the stream contains the leaf node element as key and its
	 * respective ref id path as value.
	 *
	 * @param element an association or simple type element
	 * @return a stream of transitively targeted simple type elements and their
	 *         respective ref id paths derived from the provided CDS element and its
	 *         association mapping
	 */
	public static Stream>> resolveManagedToOneAssociationMapping(CdsElement element) {
		Deque stack = new ArrayDeque<>();
		stack.push(new CdsElementRefSegHolder(element, new ArrayList<>()));

		Stream>> stream = Stream.empty();
		CdsElementRefSegHolder holder;
		while (!stack.isEmpty()) {
			holder = stack.pop();
			CdsElement current = holder.getElement();
			final List currentPath = holder.getPath();

			if (managedToOne(current.getType())) {
				currentPath.add(current.getName());
				List refElements = refElements(current).collect(Collectors.toList());

				Stream s;
				if (refElements.isEmpty()) {
					CdsAssociationType assocType = CdsModelUtils.assocType(current);
					s = assocType.getTarget().keyElements();
				} else {
					s = refElements.stream();
				}
				s.filter(key -> !key.isVirtual())
						.forEach(key -> stack.push(new CdsElementRefSegHolder(key, new ArrayList<>(currentPath))));
			} else if (current.getType().isSimple()) {
				currentPath.add(current.getName());
				Map> m = new HashMap<>();
				m.put(current, new ArrayList<>(currentPath));
				stream = Stream.concat(Stream.of(m), stream);
			}
		}
		return stream;
	}

	private static class CdsElementRefSegHolder {

		private final CdsElement element;
		private final List path;

		public CdsElementRefSegHolder(CdsElement element, List path) {
			this.element = element;
			this.path = path;
		}

		public CdsElement getElement() {
			return element;
		}

		public List getPath() {
			return path;
		}

	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy