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

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

The newest version!
/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
package com.sap.cds.impl;

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 com.sap.cds.util.CdsModelUtils.concreteKeyNames;
import static java.util.stream.Collectors.joining;

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.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.Modifier;
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;

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 concreteKeyNames(cdsEntity).stream()
				.sorted()
				.map(e -> cdsEntity.getElement(e))
				.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;
		}

		return CQL.copy(select, new ReplaceIsActiveModifier(model));
	}

	private static final class ReplaceIsActiveModifier implements Modifier {

		private final CdsModel model;

		public ReplaceIsActiveModifier(CdsModel model) {
			this.model = model;
		}

		@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();
			Segment seg = iter.next();
			do {
				Segment s = seg;
				if (isActive(type) && seg.filter().isPresent()) {
					CqnPredicate filter = CQL.copy(seg.filter().get(), new ReplaceIsActiveModifier(model)); // 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);

			return changed ? CQL.to(copy).asRef() : ref;
		}

		@Override
		public CqnValue ref(CqnElementRef ref) {
			return switch (ref.path()) {
				// do not replace paths with size > 1 (leading to LEFT OUTER joins) with true/false
				// we need to check if the target exists
				case IS_ACTIVE_ENTITY -> CqnBoolLiteral.TRUE;
				case HAS_ACTIVE_ENTITY -> CqnBoolLiteral.FALSE;
				default -> ref;
			};
		}

	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy