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

com.sap.cds.impl.DraftUtils Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/**************************************************************************
 * (C) 2019-2023 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import com.sap.cds.impl.parser.token.CqnBoolLiteral;
import com.sap.cds.impl.parser.token.RefSegmentImpl;
import com.sap.cds.jdbc.spi.TableNameResolver;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.Value;
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.CqnSelect;
import com.sap.cds.ql.cqn.CqnSelectListItem;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnSortSpecification;
import com.sap.cds.ql.cqn.CqnSortSpecification.Order;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.impl.ExpressionVisitor;
import com.sap.cds.ql.impl.LeanModifier;
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.impl.DraftAdapter;

import static com.sap.cds.reflect.impl.DraftAdapter.HAS_ACTIVE_ENTITY;
import static com.sap.cds.reflect.impl.DraftAdapter.HAS_DRAFT_ENTITY;
import static com.sap.cds.reflect.impl.DraftAdapter.IS_ACTIVE_ENTITY;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.joining;

public class DraftUtils {
	private static final String DRAFT_ANNOTATION = "odata.draft.enabled";
	private static final String DRAFT_PREPARE_ANNOTATION = "Common.DraftNode.PreparationAction";
	private static final String DRAFT = "_drafts";

	public enum Element {
		IS_ACTIVE(IS_ACTIVE_ENTITY), HAS_ACTIVE(HAS_ACTIVE_ENTITY), HAS_DRAFT(HAS_DRAFT_ENTITY),
		DRAFT_UUID(DraftAdapter.DRAFT_UUID);

		private final String name;

		Element(String name) {
			this.name = name;
		}
	}

	public static boolean isDraftEnabled(CdsStructuredType targetType) {
		return targetType.getAnnotationValue(DRAFT_ANNOTATION, false)
				|| targetType.findAnnotation(DRAFT_PREPARE_ANNOTATION).isPresent();
	}

	public static boolean isDraftView(CdsStructuredType type) {
		return type.getQualifiedName().endsWith(DRAFT);
	}

	public static boolean isActive(CdsStructuredType type) {
		return isDraftEnabled(type) && !isDraftView(type);
	}

	public static String activeEntity(Context context, TableNameResolver tableResolver, CdsEntity cdsEntity, Set draftElements) {
		String table = tableResolver.tableName(cdsEntity);
		if (draftElements.isEmpty()) {
			return table;
		}
		StringBuilder subquery = new StringBuilder("(SELECT ACTIVE.*");
		if (draftElements.contains(Element.IS_ACTIVE)) {
			subquery.append(", true as IsActiveEntity");
		}
		if (draftElements.contains(Element.HAS_ACTIVE)) {
			subquery.append(", false as HasActiveEntity");
		}
		boolean joinInactive = false;
		if (draftElements.contains(Element.HAS_DRAFT)) {
			subquery.append(", COALESCE(DRAFT.HasActiveEntity, false) as HasDraftEntity");
			joinInactive = true;
		}
		if (draftElements.contains(Element.DRAFT_UUID)) {
			subquery.append(", DRAFT.DraftAdministrativeData_DraftUUID as DraftAdministrativeData_DraftUUID");
			joinInactive = true;
		}
		subquery.append(" from ");
		subquery.append(table);
		subquery.append(" ACTIVE");
		if (joinInactive) {
			String draftEntityName = cdsEntity.getQualifiedName() + DRAFT;
			String draftTable = tableResolver.tableName(context.getCdsModel().getEntity(draftEntityName));
			subquery.append(" left outer join ");
			subquery.append(draftTable);
			subquery.append(" DRAFT on ");
			subquery.append(on(context, cdsEntity));
		}
		subquery.append(")");
		return subquery.toString();
	}

	private static String on(Context context, CdsEntity cdsEntity) {
		return cdsEntity.concreteNonAssociationElements().filter(e -> e.isKey())
				.filter(e -> !IS_ACTIVE_ENTITY.equals(e.getName())).sorted(comparing(CdsElement::getName))
				.map(e -> context.getDbContext().getSqlMapping(cdsEntity).columnName(e))
				.map(n -> "ACTIVE." + n + " = " + "DRAFT." + n).collect(joining(" AND "));
	}

	public static Optional draftElement(CdsElement element) {
		return draftElement(element.getName());
	}

	public static Optional draftElement(String name) {
		for (Element e : Element.values()) {
			if (e.name.equals(name)) {
				return Optional.of(e);
			}
		}

		return Optional.empty();
	}

	public static CqnSelect resolveConstantElements(CdsModel model, CdsStructuredType target, CqnSelect select) {
		if (!isActive(target)) {
			return select;
		}

		ReplaceIsActiveModifier modifier = new ReplaceIsActiveModifier(target);
		select = CQL.copy(select, new LeanModifier() {

			@Override
			public CqnStructuredTypeRef ref(CqnStructuredTypeRef ref) {
				CdsStructuredType type = model.getEntity(ref.firstSegment());
				List original = ref.segments();
				List copy = new ArrayList<>(original.size());
				boolean changed = false;

				Iterator iter = original.iterator();
				CqnReference.Segment seg = iter.next();
				do {
					CqnReference.Segment s = seg;
					if (isActive(type) && seg.filter().isPresent()) {
						CqnPredicate filter = CQL.copy(seg.filter().get(), new ReplaceIsActiveModifier(type)); // NOSONAR
						s = RefSegmentImpl.refSegment(seg.id(), filter);
						changed = true;
					}
					copy.add(s);
					if (!iter.hasNext()) {
						break;
					}
					seg = iter.next();
					type = type.getTargetOf(seg.id());

				} while (true);

				if (changed) {
					return CQL.to(copy).asRef();
				}
				return ref;
			}

			@Override
			public Predicate where(Predicate where) {
				return CQL.copy(where, modifier);
			}

			@Override
			public CqnSortSpecification sort(Value value, Order order) {
				CqnValue val = ExpressionVisitor.copy(value, modifier);
				return CQL.sort(val, order);
			}

			@Override
			public List items(List items) {
				List newItems = new ArrayList<>(items.size());
				for (CqnSelectListItem sli : items) {
					if (sli.isValue()) {
						CqnSelectListValue slv = sli.asValue();
						String displayName = slv.displayName();
						CqnElementRef ref = null;
						if (slv.isRef()) {
							ref = slv.asRef();
						} else if (slv.value().isRef()) {
							ref = slv.value().asRef();
						}
						if (ref != null && ref.size() == 1) {
							switch (ref.lastSegment()) {
							case IS_ACTIVE_ENTITY:
								sli = CqnBoolLiteral.TRUE.as(displayName);
								break;
							case HAS_ACTIVE_ENTITY:
								sli = CqnBoolLiteral.FALSE.as(displayName);
								break;
							}
						}
					}
					newItems.add(sli);
				}
				return newItems;
			}

		});

		return select;
	}

	private static final class ReplaceIsActiveModifier implements LeanModifier {

		private final CdsStructuredType target;

		public ReplaceIsActiveModifier(CdsStructuredType target) {
			this.target = target;
		}

		@Override
		public CqnValue ref(CqnElementRef ref) {

			CdsStructuredType declaringType = target;
			Iterator iter = ref.segments().iterator();
			CqnReference.Segment seg = iter.next();

			while (iter.hasNext()) {
				declaringType = declaringType.getTargetOf(seg.id());
				seg = iter.next();
			}

			if (!isActive(declaringType)) {
				return ref;
			}

			switch (seg.id()) {
			case IS_ACTIVE_ENTITY:
				return CqnBoolLiteral.TRUE;
			case HAS_ACTIVE_ENTITY:
				return CqnBoolLiteral.FALSE;
			default:
				return ref;
			}
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy