com.sap.cds.impl.DraftUtils Maven / Gradle / Ivy
/**************************************************************************
* (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.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.cqn.Modifier;
import com.sap.cds.ql.impl.ExpressionVisitor;
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;
}
ReplaceIsActiveModifier modifier = new ReplaceIsActiveModifier(target);
select = CQL.copy(select, new Modifier() {
@Override
public CqnStructuredTypeRef ref(CqnStructuredTypeRef ref) {
CdsStructuredType type = model.getEntity(ref.firstSegment());
List extends CqnReference.Segment> original = ref.segments();
List copy = new ArrayList<>(original.size());
boolean changed = false;
Iterator extends CqnReference.Segment> 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);
return changed ? CQL.to(copy).asRef() : 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 Modifier {
private final CdsStructuredType target;
public ReplaceIsActiveModifier(CdsStructuredType target) {
this.target = target;
}
@Override
public CqnValue ref(CqnElementRef ref) {
CdsStructuredType declaringType = target;
Iterator extends CqnReference.Segment> 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;
}
}
}
}