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

com.sap.cds.adapter.odata.v2.query.ExpressionToCqnVisitor Maven / Gradle / Ivy

There is a newer version: 3.6.0
Show newest version
/**************************************************************************
 * (C) 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
 **************************************************************************/
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.sap.cds.adapter.odata.v2.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.odata2.api.edm.EdmLiteral;
import org.apache.olingo.odata2.api.edm.EdmTyped;
import org.apache.olingo.odata2.api.uri.expression.BinaryExpression;
import org.apache.olingo.odata2.api.uri.expression.BinaryOperator;
import org.apache.olingo.odata2.api.uri.expression.ExpressionVisitor;
import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
import org.apache.olingo.odata2.api.uri.expression.LiteralExpression;
import org.apache.olingo.odata2.api.uri.expression.MemberExpression;
import org.apache.olingo.odata2.api.uri.expression.MethodExpression;
import org.apache.olingo.odata2.api.uri.expression.MethodOperator;
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderExpression;
import org.apache.olingo.odata2.api.uri.expression.PropertyExpression;
import org.apache.olingo.odata2.api.uri.expression.SortOrder;
import org.apache.olingo.odata2.api.uri.expression.UnaryExpression;
import org.apache.olingo.odata2.api.uri.expression.UnaryOperator;
import org.apache.olingo.odata2.core.edm.EdmNull;

import com.sap.cds.adapter.odata.v2.utils.TypeConverterUtils;
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.Value;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.utils.CdsErrorStatuses;
import com.sap.cds.services.utils.ErrorStatusException;

public class ExpressionToCqnVisitor implements ExpressionVisitor {

	private static final String NULL_KEYWORD = "null";
	private boolean caseInsensitive = false;

	public ExpressionToCqnVisitor(boolean caseInsensitive) {
		this.caseInsensitive = caseInsensitive;
	}

	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator 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(right);
		case GT:
			return ((Value) left).gt(right);
		case LE:
			return ((Value) left).le(right);
		case LT:
			return ((Value) left).lt(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);

		default:
			throw new ErrorStatusException(CdsErrorStatuses.UNSUPPORTED_OPERATOR, operator);
		}
	}

	@Override
	public Object visitUnary(UnaryExpression unaryExpression, UnaryOperator operator, Object operand) {
		switch (operator) {
		case NOT:
			return ((Predicate) operand).not();
		default:
			throw new ErrorStatusException(ErrorStatuses.NOT_IMPLEMENTED);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public Object visitMethod(MethodExpression methodExpression, MethodOperator 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 CQL.contains(value, substring, caseInsensitive);
		case "STARTSWITH":
			Value prefix = (Value) parameters.get(1);
			return CQL.startsWith(value, prefix, caseInsensitive);
		case "ENDSWITH":
			Value suffix = (Value) parameters.get(1);
			return CQL.endsWith(value, suffix, caseInsensitive);
		case "SUBSTRINGOF":
			Value searchstring = (Value) parameters.get(1);
			return searchstring.contains((Value) value, caseInsensitive);
		case "REPLACE":
			// TODO replace with dedicated builder functions once available in CDS4J
			Value oldString = (Value) parameters.get(1);
			Value replacementString = (Value) parameters.get(2);
			return func(methodCall.name(), value, oldString, replacementString);
		default:
			throw new ErrorStatusException(CdsErrorStatuses.UNSUPPORTED_METHOD, func);
		}
	}

	@Override
	public Value visitLiteral(LiteralExpression literal, EdmLiteral edmLiteral) {
		if (NULL_KEYWORD.equals(edmLiteral.getLiteral()) || edmLiteral.getType() instanceof EdmNull) {
			return CqnNull.getInstance();
		}
		Object literalValue = TypeConverterUtils.convertToType(edmLiteral.getType(), edmLiteral.getLiteral());
		return val(literalValue);
	}

	@Override
	public ElementRef visitMember(MemberExpression member, Object path, Object property) {
		if (path instanceof CqnElementRef p1 && property instanceof CqnElementRef p2) {
			List segments = new ArrayList<>(p1.size() + p2.size());
			segments.addAll(p1.segments());
			segments.addAll(p2.segments());
			return CQL.get(segments);
		}
		throw new UnsupportedOperationException("Currently only members of type 'CqnElementRef' are supported.");
	}

	@Override
	public Object visitFilterExpression(FilterExpression filterExpression, String expressionString, Object expression) {
		throw new UnsupportedOperationException("Not yet implemented");
	}

	@Override
	public Object visitOrderByExpression(OrderByExpression orderByExpression, String expressionString,
			List orders) {
		throw new UnsupportedOperationException("Not yet implemented");
	}

	@Override
	public Object visitOrder(OrderExpression orderExpression, Object filterResult, SortOrder sortOrder) {
		throw new UnsupportedOperationException("Not yet implemented");
	}

	@Override
	public ElementRef visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmProperty) {
		return CQL.get(uriLiteral);
	}
}