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;
};
}
}
}