blob: 1141831e4a6a0027f8f46b41b6b26d12a16c8d09 [file] [log] [blame]
/*******************************************************************************
* 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 org.apache.olingo.odata2.jpa.processor.core;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import org.apache.olingo.odata2.api.edm.EdmException;
import org.apache.olingo.odata2.api.edm.EdmLiteral;
import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
import org.apache.olingo.odata2.api.edm.EdmMapping;
import org.apache.olingo.odata2.api.edm.EdmProperty;
import org.apache.olingo.odata2.api.edm.EdmSimpleType;
import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
import org.apache.olingo.odata2.api.edm.EdmSimpleTypeKind;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.exception.ODataNotImplementedException;
import org.apache.olingo.odata2.api.uri.KeyPredicate;
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.CommonExpression;
import org.apache.olingo.odata2.api.uri.expression.ExpressionKind;
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.jpa.processor.api.exception.ODataJPARuntimeException;
import org.apache.olingo.odata2.jpa.processor.api.jpql.JPQLStatement;
/**
* This class contains utility methods for parsing the filter expressions built by core library from user OData Query.
*
*
*
*/
public class ODataExpressionParser {
public static final String EMPTY = ""; //$NON-NLS-1$
public static final ThreadLocal<Integer> methodFlag = new ThreadLocal<Integer>();
/**
* This method returns the parsed where condition corresponding to the filter input in the user query.
*
* @param whereExpression
*
* @return Parsed where condition String
* @throws ODataException
*/
public static String parseToJPAWhereExpression(final CommonExpression whereExpression, final String tableAlias)
throws ODataException {
switch (whereExpression.getKind()) {
case UNARY:
final UnaryExpression unaryExpression = (UnaryExpression) whereExpression;
final String operand = parseToJPAWhereExpression(unaryExpression.getOperand(), tableAlias);
switch (unaryExpression.getOperator()) {
case NOT:
return JPQLStatement.Operator.NOT + JPQLStatement.DELIMITER.PARENTHESIS_LEFT + operand
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT; //$NON-NLS-1$ //$NON-NLS-2$
case MINUS:
if (operand.startsWith("-")) {
return operand.substring(1);
} else {
return "-" + operand; //$NON-NLS-1$
}
default:
throw new ODataNotImplementedException();
}
case FILTER:
return parseToJPAWhereExpression(((FilterExpression) whereExpression).getExpression(), tableAlias);
case BINARY:
final BinaryExpression binaryExpression = (BinaryExpression) whereExpression;
MethodOperator operator = null;
if (binaryExpression.getLeftOperand().getKind() == ExpressionKind.METHOD) {
operator = ((MethodExpression) binaryExpression.getLeftOperand()).getMethod();
}
if (operator != null && ((binaryExpression.getOperator() == BinaryOperator.EQ) ||
(binaryExpression.getOperator() == BinaryOperator.NE))) {
if (operator == MethodOperator.SUBSTRINGOF) {
methodFlag.set(1);
}
}
final String left = parseToJPAWhereExpression(binaryExpression.getLeftOperand(), tableAlias);
final String right = parseToJPAWhereExpression(binaryExpression.getRightOperand(), tableAlias);
// Special handling for STARTSWITH and ENDSWITH method expression
if (operator != null && (operator == MethodOperator.STARTSWITH || operator == MethodOperator.ENDSWITH)) {
if (!binaryExpression.getOperator().equals(BinaryOperator.EQ)) {
throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.OPERATOR_EQ_NE_MISSING
.addContent(binaryExpression.getOperator().toString()), null);
} else if (right.equals("false")) {
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left.replaceFirst("LIKE", "NOT LIKE")
+ JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
} else {
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left
+ JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
}
}
switch (binaryExpression.getOperator()) {
case AND:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.Operator.AND + JPQLStatement.DELIMITER.SPACE
+ right + JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case OR:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.Operator.OR + JPQLStatement.DELIMITER.SPACE + right
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case EQ:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ (!"null".equals(right) ? JPQLStatement.Operator.EQ : "IS") + JPQLStatement.DELIMITER.SPACE + right
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case NE:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ (!"null".equals(right) ?
JPQLStatement.Operator.NE :
"IS" + JPQLStatement.DELIMITER.SPACE + JPQLStatement.Operator.NOT)
+ JPQLStatement.DELIMITER.SPACE + right
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case LT:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.Operator.LT + JPQLStatement.DELIMITER.SPACE + right
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case LE:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.Operator.LE + JPQLStatement.DELIMITER.SPACE + right
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case GT:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.Operator.GT + JPQLStatement.DELIMITER.SPACE + right
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case GE:
return JPQLStatement.DELIMITER.PARENTHESIS_LEFT + left + JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.Operator.GE + JPQLStatement.DELIMITER.SPACE + right
+ JPQLStatement.DELIMITER.PARENTHESIS_RIGHT;
case PROPERTY_ACCESS:
throw new ODataNotImplementedException();
default:
throw new ODataNotImplementedException();
}
case PROPERTY:
String returnStr = tableAlias + JPQLStatement.DELIMITER.PERIOD
+ getPropertyName(whereExpression);
return returnStr;
case MEMBER:
String memberExpStr = EMPTY;
int i = 0;
MemberExpression member = null;
CommonExpression tempExp = whereExpression;
while (tempExp != null && tempExp.getKind() == ExpressionKind.MEMBER) {
member = (MemberExpression) tempExp;
if (i > 0) {
memberExpStr = JPQLStatement.DELIMITER.PERIOD + memberExpStr;
}
i++;
memberExpStr = getPropertyName(member.getProperty()) + memberExpStr;
tempExp = member.getPath();
}
memberExpStr =
getPropertyName(tempExp) + JPQLStatement.DELIMITER.PERIOD + memberExpStr;
return tableAlias + JPQLStatement.DELIMITER.PERIOD + memberExpStr;
case LITERAL:
final LiteralExpression literal = (LiteralExpression) whereExpression;
final EdmSimpleType literalType = (EdmSimpleType) literal.getEdmType();
EdmLiteral uriLiteral = EdmSimpleTypeKind.parseUriLiteral(literal.getUriLiteral());
return evaluateComparingExpression(uriLiteral.getLiteral(), literalType);
case METHOD:
final MethodExpression methodExpression = (MethodExpression) whereExpression;
String first = parseToJPAWhereExpression(methodExpression.getParameters().get(0), tableAlias);
String second =
methodExpression.getParameterCount() > 1 ? parseToJPAWhereExpression(methodExpression.getParameters().get(1),
tableAlias) : null;
String third =
methodExpression.getParameterCount() > 2 ? parseToJPAWhereExpression(methodExpression.getParameters().get(2),
tableAlias) : null;
switch (methodExpression.getMethod()) {
case SUBSTRING:
third = third != null ? ", " + third : "";
return String.format("SUBSTRING(%s, %s + 1 %s)", first, second, third);
case SUBSTRINGOF:
if (methodFlag.get() != null && methodFlag.get() == 1) {
methodFlag.set(null);
return String.format("(CASE WHEN (%s LIKE CONCAT('%%',CONCAT(%s,'%%'))) THEN TRUE ELSE FALSE END)",
second, first);
} else {
return String.format("(CASE WHEN (%s LIKE CONCAT('%%',CONCAT(%s,'%%'))) THEN TRUE ELSE FALSE END) = true",
second, first);
}
case TOLOWER:
return String.format("LOWER(%s)", first);
case STARTSWITH:
// second = second.substring(1, second.length() - 1);
return String.format("%s LIKE CONCAT(%s,'%%')", first, second);
case ENDSWITH:
// second = second.substring(1, second.length() - 1);
return String.format("%s LIKE CONCAT('%%',%s)", first, second);
default:
throw new ODataNotImplementedException();
}
default:
throw new ODataNotImplementedException();
}
}
/**
* This method parses the select clause
*
* @param tableAlias
* @param selectedFields
* @return a select expression
*/
public static String parseToJPASelectExpression(final String tableAlias, final ArrayList<String> selectedFields) {
if ((selectedFields == null) || (selectedFields.size() == 0)) {
return tableAlias;
}
String selectClause = EMPTY;
Iterator<String> itr = selectedFields.iterator();
int count = 0;
while (itr.hasNext()) {
selectClause = selectClause + tableAlias + JPQLStatement.DELIMITER.PERIOD + itr.next();
count++;
if (count < selectedFields.size()) {
selectClause = selectClause + JPQLStatement.DELIMITER.COMMA + JPQLStatement.DELIMITER.SPACE;
}
}
return selectClause;
}
/**
* This method parses the order by condition in the query.
*
* @param orderByExpression
* @return a map of JPA attributes and their sort order
* @throws ODataJPARuntimeException
*/
public static String parseToJPAOrderByExpression(final OrderByExpression orderByExpression,
final String tableAlias) throws ODataJPARuntimeException {
String jpqlOrderByExpression = "";
if (orderByExpression != null && orderByExpression.getOrders() != null) {
List<OrderExpression> orderBys = orderByExpression.getOrders();
String orderByField = null;
String orderByDirection = null;
for (OrderExpression orderBy : orderBys) {
try {
if (orderBy.getExpression().getKind() == ExpressionKind.MEMBER) {
orderByField = parseToJPAWhereExpression(orderBy.getExpression(), tableAlias);
} else {
orderByField = tableAlias + JPQLStatement.DELIMITER.PERIOD + getPropertyName(orderBy.getExpression());
}
orderByDirection = (orderBy.getSortOrder() == SortOrder.asc) ? EMPTY :
JPQLStatement.DELIMITER.SPACE + "DESC"; //$NON-NLS-1$
jpqlOrderByExpression += orderByField + orderByDirection + " , ";
} catch (EdmException e) {
throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.GENERAL.addContent(e.getMessage()), e);
} catch (ODataException e) {
throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.GENERAL.addContent(e.getMessage()), e);
}
}
}
return normalizeOrderByExpression(jpqlOrderByExpression);
}
private static String normalizeOrderByExpression(final String jpqlOrderByExpression) {
if (jpqlOrderByExpression != "") {
return jpqlOrderByExpression.substring(0, jpqlOrderByExpression.length() - 3);
} else {
return jpqlOrderByExpression;
}
}
/**
* This method evaluated the where expression for read of an entity based on the keys specified in the query.
*
* @param keyPredicates
* @return the evaluated where expression
*/
public static String parseKeyPredicates(final List<KeyPredicate> keyPredicates, final String tableAlias)
throws ODataJPARuntimeException {
String literal = null;
String propertyName = null;
EdmSimpleType edmSimpleType = null;
StringBuilder keyFilters = new StringBuilder();
int i = 0;
for (KeyPredicate keyPredicate : keyPredicates) {
if (i > 0) {
keyFilters.append(JPQLStatement.DELIMITER.SPACE + JPQLStatement.Operator.AND + JPQLStatement.DELIMITER.SPACE);
}
i++;
literal = keyPredicate.getLiteral();
try {
propertyName = keyPredicate.getProperty().getMapping().getInternalName();
edmSimpleType = (EdmSimpleType) keyPredicate.getProperty().getType();
} catch (EdmException e) {
throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.GENERAL.addContent(e.getMessage()), e);
}
literal = evaluateComparingExpression(literal, edmSimpleType);
if (edmSimpleType == EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance()
|| edmSimpleType == EdmSimpleTypeKind.DateTimeOffset.getEdmSimpleTypeInstance()) {
literal = literal.substring(literal.indexOf('\''), literal.indexOf('}'));
}
keyFilters.append(tableAlias + JPQLStatement.DELIMITER.PERIOD + propertyName + JPQLStatement.DELIMITER.SPACE
+ JPQLStatement.Operator.EQ + JPQLStatement.DELIMITER.SPACE + literal);
}
if (keyFilters.length() > 0) {
return keyFilters.toString();
} else {
return null;
}
}
public static String parseKeyPropertiesToJPAOrderByExpression(
final List<EdmProperty> edmPropertylist, final String tableAlias) throws ODataJPARuntimeException {
String propertyName = null;
String orderExpression = "";
if (edmPropertylist == null) {
return orderExpression;
}
for (EdmProperty edmProperty : edmPropertylist) {
try {
EdmMapping mapping = edmProperty.getMapping();
if (mapping != null && mapping.getInternalName() != null) {
propertyName = mapping.getInternalName();// For embedded/complex keys
} else {
propertyName = edmProperty.getName();
}
} catch (EdmException e) {
throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.GENERAL.addContent(e.getMessage()), e);
}
orderExpression += tableAlias + JPQLStatement.DELIMITER.PERIOD + propertyName + " , ";
}
return normalizeOrderByExpression(orderExpression);
}
/**
* This method evaluates the expression based on the type instance. Used for adding escape characters where necessary.
*
* @param uriLiteral
* @param edmSimpleType
* @return the evaluated expression
* @throws ODataJPARuntimeException
*/
private static String evaluateComparingExpression(String uriLiteral, final EdmSimpleType edmSimpleType)
throws ODataJPARuntimeException {
if (EdmSimpleTypeKind.String.getEdmSimpleTypeInstance().isCompatible(edmSimpleType)
|| EdmSimpleTypeKind.Guid.getEdmSimpleTypeInstance().isCompatible(edmSimpleType)) {
uriLiteral = uriLiteral.replaceAll("'", "''");
uriLiteral = "'" + uriLiteral + "'"; //$NON-NLS-1$ //$NON-NLS-2$
} else if (EdmSimpleTypeKind.DateTime.getEdmSimpleTypeInstance().isCompatible(edmSimpleType)
|| EdmSimpleTypeKind.DateTimeOffset.getEdmSimpleTypeInstance().isCompatible(edmSimpleType)) {
try {
Calendar datetime =
(Calendar) edmSimpleType.valueOfString(uriLiteral, EdmLiteralKind.DEFAULT, null, edmSimpleType
.getDefaultType());
String year = String.format("%04d", datetime.get(Calendar.YEAR));
String month = String.format("%02d", datetime.get(Calendar.MONTH) + 1);
String day = String.format("%02d", datetime.get(Calendar.DAY_OF_MONTH));
String hour = String.format("%02d", datetime.get(Calendar.HOUR_OF_DAY));
String min = String.format("%02d", datetime.get(Calendar.MINUTE));
String sec = String.format("%02d", datetime.get(Calendar.SECOND));
uriLiteral =
JPQLStatement.DELIMITER.LEFT_BRACE + JPQLStatement.KEYWORD.TIMESTAMP + JPQLStatement.DELIMITER.SPACE + "\'"
+ year + JPQLStatement.DELIMITER.HYPHEN + month + JPQLStatement.DELIMITER.HYPHEN + day
+ JPQLStatement.DELIMITER.SPACE + hour + JPQLStatement.DELIMITER.COLON + min
+ JPQLStatement.DELIMITER.COLON + sec + JPQLStatement.KEYWORD.OFFSET + "\'"
+ JPQLStatement.DELIMITER.RIGHT_BRACE;
} catch (EdmSimpleTypeException e) {
throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.GENERAL.addContent(e.getMessage()), e);
}
} else if (EdmSimpleTypeKind.Time.getEdmSimpleTypeInstance().isCompatible(edmSimpleType)) {
try {
Calendar time =
(Calendar) edmSimpleType.valueOfString(uriLiteral, EdmLiteralKind.DEFAULT, null, edmSimpleType
.getDefaultType());
String hourValue = String.format("%02d", time.get(Calendar.HOUR_OF_DAY));
String minValue = String.format("%02d", time.get(Calendar.MINUTE));
String secValue = String.format("%02d", time.get(Calendar.SECOND));
uriLiteral =
"\'" + hourValue + JPQLStatement.DELIMITER.COLON + minValue + JPQLStatement.DELIMITER.COLON + secValue
+ "\'";
} catch (EdmSimpleTypeException e) {
throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.GENERAL.addContent(e.getMessage()), e);
}
} else if (Long.class.equals(edmSimpleType.getDefaultType())) {
uriLiteral = uriLiteral + JPQLStatement.DELIMITER.LONG; //$NON-NLS-1$
}
return uriLiteral;
}
private static String getPropertyName(final CommonExpression whereExpression) throws EdmException {
EdmProperty property = ((EdmProperty) ((PropertyExpression) whereExpression).getEdmProperty());
EdmMapping mapping = property.getMapping();
String name = mapping != null ? mapping.getInternalName() : property.getName();
return name;
}
}