Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.sap.cds.adapter.odata.v4.query.ExpressionParser Maven / Gradle / Ivy
/**************************************************************************
* (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
**************************************************************************/
package com.sap.cds.adapter.odata.v4.query;
import static com.sap.cds.ql.CQL.func;
import static com.sap.cds.ql.CQL.val;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.apache.olingo.commons.api.edm.EdmEnumType;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.UriResourceLambdaAll;
import org.apache.olingo.server.api.uri.UriResourceLambdaAny;
import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression.StandardMethod;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitException;
import org.apache.olingo.server.api.uri.queryoption.expression.ExpressionVisitor;
import org.apache.olingo.server.api.uri.queryoption.expression.Literal;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind;
import com.google.common.annotations.VisibleForTesting;
import com.sap.cds.adapter.odata.v4.query.apply.ElementAggregator;
import com.sap.cds.adapter.odata.v4.utils.TypeConverterUtils;
import com.sap.cds.adapter.odata.v4.utils.mapper.EdmxFlavourMapper;
import com.sap.cds.impl.builder.model.CqnNull;
import com.sap.cds.ql.CQL;
import com.sap.cds.ql.ElementRef;
import com.sap.cds.ql.Predicate;
import com.sap.cds.ql.StructuredType;
import com.sap.cds.ql.Value;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnToken;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;
import com.sap.cds.util.CdsModelUtils;
public class ExpressionParser {
private final CdsStructuredType rootType;
private final EdmxFlavourMapper elementMapper;
public ExpressionParser(CdsStructuredType rootType, EdmxFlavourMapper elementMapper) {
this.rootType = rootType;
this.elementMapper = elementMapper;
}
public CqnValue parseValue(Expression expression) {
try {
return (CqnValue) expression.accept(new ExpressionToCqnVisitor());
} catch (ExpressionVisitException | ODataApplicationException e) {
throw new ErrorStatusException(CdsErrorStatuses.VALUE_PARSING_FAILED, e);
}
}
public CqnToken parseToken(Expression expression) {
try {
return (CqnToken) expression.accept(new ExpressionToCqnVisitor());
} catch (ExpressionVisitException | ODataApplicationException e) {
throw new ErrorStatusException(CdsErrorStatuses.VALUE_PARSING_FAILED, e);
}
}
public CqnStructuredTypeRef parseStructuredTypeRef(Expression expression) {
try {
return (CqnStructuredTypeRef) expression.accept(new ExpressionToCqnVisitor());
} catch (ExpressionVisitException | ODataApplicationException e) {
throw new ErrorStatusException(CdsErrorStatuses.VALUE_PARSING_FAILED, e);
}
}
public CqnPredicate parseFilter(Expression expression) {
try {
return (CqnPredicate) expression.accept(new ExpressionToCqnVisitor());
} catch (ODataApplicationException | ExpressionVisitException e) {
throw new ErrorStatusException(CdsErrorStatuses.FILTER_PARSING_FAILED, e);
}
}
public List toSegmentList(List uriResourceParts) {
List segmentList = new ArrayList<>();
CdsStructuredType type = rootType;
String prefix = null;
for (UriResource part : uriResourceParts) {
if (part.getKind() == UriResourceKind.root) {
prefix = rootType.getQualifier() + ".";
continue; // TODO? skip so far
}
if (part.getKind() != UriResourceKind.lambdaVariable) {
if (type == null) {
// uri contains navigation property or complex type property
// however the previous property already returned a simple element
throw new ErrorStatusException(CdsErrorStatuses.UNEXPECTED_URI_RESOURCE, part.getKind());
}
String segment = elementMapper.remap(part.getSegmentValue(), type);
type = structuredType(type.findElement(segment).orElse(null));
for (String id : segment.split("\\.")) {
if (prefix != null) {
id = prefix + id;
prefix = null;
}
segmentList.add(CQL.refSegment(id));
}
}
}
return segmentList;
}
public String remap(String element) {
return elementMapper.remap(element, rootType);
}
private static CdsStructuredType structuredType(CdsElement element) {
if (element != null) {
CdsType type = element.getType();
if (type.isStructured()) {
return type.as(CdsStructuredType.class);
}
if (type.isAssociation()) {
return type.as(CdsAssociationType.class).getTarget();
}
}
return null;
}
public CdsStructuredType getRootType() {
return rootType;
}
@VisibleForTesting
class ExpressionToCqnVisitor implements ExpressionVisitor {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object visitBinaryOperator(BinaryOperatorKind operator, Object left, Object right) {
switch (operator) {
case AND:
return ((Predicate) left).and((Predicate) right);
case OR:
return ((Predicate) left).or((Predicate) right);
case EQ:
if (left instanceof Predicate lhs && right instanceof Predicate rhs) {
Predicate bothTrue = lhs.and(rhs);
Predicate bothFalse = lhs.not().and(rhs.not());
return bothTrue.or(bothFalse);
}
return ((Value) left).is((Value) right);
case NE:
if (left instanceof Predicate lhs && right instanceof Predicate rhs) {
Predicate eitherTrue = lhs.or(rhs);
Predicate eitherFalse = lhs.not().or(rhs.not());
return eitherTrue.and(eitherFalse);
}
return ((Value) left).isNot((Value) right);
case GE:
return ((Value) left).ge((Value) right);
case GT:
return ((Value) left).gt((Value) right);
case LE:
return ((Value) left).le((Value) right);
case LT:
return ((Value) left).lt((Value) right);
case ADD:
return ((Value) left).plus((Value) right);
case SUB:
return ((Value) left).minus((Value) right);
case MUL:
return ((Value) left).times((Value) right);
case DIV:
return ((Value) left).dividedBy((Value) right);
case MOD:
case HAS:
default:
throw new ErrorStatusException(CdsErrorStatuses.UNSUPPORTED_OPERATOR, operator);
}
}
@Override
public Object visitUnaryOperator(UnaryOperatorKind operator, Object operand) {
switch (operator) {
case NOT:
return ((Predicate) operand).not();
default:
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
}
}
@Override
@SuppressWarnings("unchecked")
public Object visitMethodCall(MethodKind methodCall, List parameters) {
Value> value = (Value>) parameters.get(0);
String func = methodCall.name().toUpperCase(Locale.US);
switch (func) {
case "TOUPPER":
return value.toUpper();
case "TOLOWER":
return value.toLower();
case "LENGTH":
case "TRIM":
// TODO replace with dedicated builder functions once available in CDS4J
return func(methodCall.name(), value);
case "SUBSTRING":
Value start = null;
if (parameters.size() >= 2) {
start = (Value) parameters.get(1);
}
switch (parameters.size()) {
case 2:
return value.substring(start);
case 3:
Value length = (Value) parameters.get(2);
return value.substring(start, length);
default:
throw new ErrorStatusException(CdsErrorStatuses.INVALID_SUBSTRING);
}
case "CONTAINS":
Value substring = (Value) parameters.get(1);
return value.contains(substring);
case "STARTSWITH":
Value prefix = (Value) parameters.get(1);
return value.startsWith(prefix);
case "ENDSWITH":
Value suffix = (Value) parameters.get(1);
return value.endsWith(suffix);
case "COMPUTE_AGGREGATE":
return value;
case "MATCHES_PATTERN":
return matchesPattern(value, parameters.subList(1, parameters.size()));
default:
throw new ErrorStatusException(CdsErrorStatuses.UNSUPPORTED_METHOD, func);
}
}
@SuppressWarnings("unchecked")
private Predicate matchesPattern(Value> value, List parameters) {
var options = parameters.size() > 1 ? ((Value)parameters.get(1)).asLiteral().asString().value() : "";
if (options.isBlank()) {
return value.matchesPattern((Value) parameters.get(0));
} else {
return value.matchesPattern((Value) parameters.get(0), CQL.val(options));
}
}
@Override
public Object visitLambdaExpression(String lambdaFunction, String lambdaVariable, Expression expression) {
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
}
@Override
public Value> visitLiteral(Literal literal) {
if (literal.getText() == null || literal.getType() == null) {
return CqnNull.getInstance();
}
Object literalValue = TypeConverterUtils.convertToType(literal.getType(), literal.getText());
return val(literalValue);
}
@Override
public CqnToken visitMember(Member member) throws ExpressionVisitException, ODataApplicationException {
List uriResourceParts = member.getResourcePath().getUriResourceParts();
UriResource lastPart = uriResourceParts.get(uriResourceParts.size() - 1);
if (lastPart.getKind() == UriResourceKind.lambdaAny) {
StructuredType> ref = CQL.to(toSegmentList(uriResourceParts.subList(0, uriResourceParts.size() - 1)));
UriResourceLambdaAny lambdaAny = (UriResourceLambdaAny) lastPart;
if (lambdaAny.getExpression() != null) {
CdsStructuredType target = CdsModelUtils.target(rootType, ref.asRef().segments());
CqnPredicate pred = (CqnPredicate) lambdaAny.getExpression().accept((new ExpressionParser(target, elementMapper)).new ExpressionToCqnVisitor());
return ref.anyMatch(pred);
}
return ref.exists();
}
if (lastPart.getKind() == UriResourceKind.lambdaAll) {
StructuredType> ref = CQL.to(toSegmentList(uriResourceParts.subList(0, uriResourceParts.size() - 1)));
UriResourceLambdaAll lambdaAll = (UriResourceLambdaAll) lastPart;
CdsStructuredType target = CdsModelUtils.target(rootType, ref.asRef().segments());
Predicate pred = (Predicate) lambdaAll.getExpression().accept((new ExpressionParser(target, elementMapper)).new ExpressionToCqnVisitor());
return ref.allMatch(pred);
}
if (lastPart.getKind() == UriResourceKind.entitySet) {
return CQL.to(toSegmentList(uriResourceParts)).asRef();
}
return CQL.get(toSegmentList(uriResourceParts));
}
@Override
public Object visitAlias(String aliasName) {
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
}
@Override
public Object visitTypeLiteral(EdmType type) {
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
}
@Override
public Object visitLambdaReference(String variableName) {
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
}
@Override
public Object visitEnum(EdmEnumType type, @SuppressWarnings("rawtypes") List enumValues) {
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public Predicate visitBinaryOperator(BinaryOperatorKind operator, Object left, List right) {
if (operator == BinaryOperatorKind.IN) {
Value[] items = right.toArray(new Value[right.size()]);
return ((Value) left).in(items);
}
throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
}
@Override
public CqnValue visitComputeAggregate(StandardMethod standardMethod, UriInfo path, Object expression)
throws ExpressionVisitException, ODataApplicationException {
ElementAggregator aggregator = new ElementAggregator(ExpressionParser.this);
Value> func;
if (standardMethod != null) {
func = aggregator.toFunctionCall((Value>) expression, standardMethod);
} else {
List uriResourceParts = path.asUriInfoResource().getUriResourceParts();
ElementRef ref = CQL.get(toSegmentList(uriResourceParts));
func = aggregator.customAggregate(ref);
}
return func;
}
}
}