/**
* 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.cxf.jaxrs.ext.search.odata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.cxf.common.util.UrlUtils;
import org.apache.cxf.jaxrs.ext.search.AbstractSearchConditionParser;
import org.apache.cxf.jaxrs.ext.search.AndSearchCondition;
import org.apache.cxf.jaxrs.ext.search.Beanspector.TypeInfo;
import org.apache.cxf.jaxrs.ext.search.ConditionType;
import org.apache.cxf.jaxrs.ext.search.OrSearchCondition;
import org.apache.cxf.jaxrs.ext.search.PrimitiveSearchCondition;
import org.apache.cxf.jaxrs.ext.search.SearchCondition;
import org.apache.cxf.jaxrs.ext.search.SearchParseException;
import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheckCondition;
import org.apache.cxf.jaxrs.ext.search.collections.CollectionCheckInfo;
import org.apache.olingo.odata2.api.edm.EdmLiteral;
import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
import org.apache.olingo.odata2.api.edm.EdmSimpleType;
import org.apache.olingo.odata2.api.edm.EdmSimpleTypeException;
import org.apache.olingo.odata2.api.edm.EdmTyped;
import org.apache.olingo.odata2.api.exception.ODataApplicationException;
import org.apache.olingo.odata2.api.exception.ODataMessageException;
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.uri.expression.FilterParser;
import org.apache.olingo.odata2.core.uri.expression.FilterParserImpl;
public class ODataParser<T> extends AbstractSearchConditionParser<T> {
private final FilterParser parser;
private static class TypedProperty {
private final TypeInfo typeInfo;
private final String propertyName;
TypedProperty(final TypeInfo typeInfo, final String propertyName) {
this.typeInfo = typeInfo;
this.propertyName = propertyName;
}
}
private static class TypedValue {
private final Object value;
private final String literal;
private final Class< ? > typeClass;
TypedValue(final Class< ? > typeClass, final String literal, final Object value) {
this.literal = literal;
this.value = value;
this.typeClass = typeClass;
}
}
private class FilterExpressionVisitor implements ExpressionVisitor {
private final T condition;
FilterExpressionVisitor(final T condition) {
this.condition = condition;
}
@Override
public Object visitFilterExpression(FilterExpression filterExpression, String expressionString,
Object expression) {
return expression;
}
@Override
@SuppressWarnings("unchecked")
public Object visitBinary(BinaryExpression binaryExpression, BinaryOperator operator,
Object leftSide, Object rightSide) {
// AND / OR operate on search conditions
if (operator == BinaryOperator.AND || operator == BinaryOperator.OR) {
if (leftSide instanceof SearchCondition && rightSide instanceof SearchCondition) {
final List< SearchCondition< T > > conditions = new ArrayList< SearchCondition< T > >(2);
conditions.add((SearchCondition< T >)leftSide);
conditions.add((SearchCondition< T >)rightSide);
if (operator == BinaryOperator.AND) {
return new AndSearchCondition< T >(conditions);
} else if (operator == BinaryOperator.OR) {
return new OrSearchCondition< T >(conditions);
}
} else {
throw new SearchParseException(
"Unsupported binary operation arguments (SearchCondition expected): "
+ leftSide + ", " + rightSide);
}
}
// Property could be either on left side (Name eq 'Tom') or
// right side ('Tom' eq Name)
TypedValue value = null;
TypedProperty property = null;
if (leftSide instanceof TypedProperty && rightSide instanceof TypedValue) {
property = (TypedProperty)leftSide;
value = (TypedValue)rightSide;
} else if (rightSide instanceof TypedProperty && leftSide instanceof TypedValue) {
property = (TypedProperty)rightSide;
value = (TypedValue)leftSide;
} else {
throw new SearchParseException(
"Unsupported binary operation arguments (TypedValue or TypedProperty expected): "
+ leftSide + ", " + rightSide);
}
ConditionType conditionType = null;
switch (operator) {
case EQ:
conditionType = ConditionType.EQUALS;
break;
case NE:
conditionType = ConditionType.NOT_EQUALS;
break;
case LT:
conditionType = ConditionType.LESS_THAN;
break;
case LE:
conditionType = ConditionType.LESS_OR_EQUALS;
break;
case GT:
conditionType = ConditionType.GREATER_THAN;
break;
case GE:
conditionType = ConditionType.GREATER_OR_EQUALS;
break;
default:
throw new SearchParseException("Unsupported binary operation: " + operator);
}
Object typedValue = null;
// If property type and value type are compatible, just use them
if (property.typeInfo.getWrappedTypeClass().isAssignableFrom(value.typeClass)) {
typedValue = value.value;
} else { // Property type and value type are not compatible and convert / cast are required
String valueStr = value.literal;
if (isDecodeQueryValues()) {
valueStr = UrlUtils.urlDecode(valueStr);
}
typedValue = parseType(property.propertyName, null, null, property.propertyName,
property.typeInfo, valueStr);
}
final CollectionCheckInfo checkInfo = property.typeInfo.getCollectionCheckInfo();
if (checkInfo != null) {
return new CollectionCheckCondition< T >(property.propertyName, typedValue,
property.typeInfo.getGenericType(), conditionType, condition, checkInfo);
}
return new PrimitiveSearchCondition< T >(property.propertyName, typedValue,
property.typeInfo.getGenericType(), conditionType, condition);
}
@Override
public Object visitLiteral(LiteralExpression literal, EdmLiteral edmLiteral) {
try {
final EdmSimpleType type = edmLiteral.getType();
final Object value = type.valueOfString(edmLiteral.getLiteral(),
EdmLiteralKind.DEFAULT, null, type.getDefaultType());
return new TypedValue(type.getDefaultType(), edmLiteral.getLiteral(), value);
} catch (EdmSimpleTypeException ex) {
throw new SearchParseException("Failed to convert literal to a typed form: " + literal, ex);
}
}
@Override
public Object visitProperty(PropertyExpression propertyExpression, String uriLiteral, EdmTyped edmProperty) {
String setter = getActualSetterName(uriLiteral);
final TypeInfo typeInfo = ODataParser.this.getTypeInfo(setter, null);
return new TypedProperty(typeInfo, setter);
}
@Override
public Object visitMethod(MethodExpression methodExpression, MethodOperator method, List<Object> parameters) {
throw new SearchParseException("Unsupported operation visitMethod: " + methodExpression
+ "," + method + "," + parameters);
}
@Override
public Object visitMember(MemberExpression memberExpression, Object path, Object property) {
throw new SearchParseException("Unsupported operation visitMember: "
+ memberExpression + "," + path + "," + property);
}
@Override
public Object visitUnary(UnaryExpression unaryExpression, UnaryOperator operator, Object operand) {
throw new SearchParseException("Unsupported operation visitUnary: " + unaryExpression
+ "," + operator + "," + operand);
}
@Override
public Object visitOrderByExpression(OrderByExpression orderByExpression, String expressionString,
List<Object> orders) {
throw new SearchParseException("Unsupported operation visitOrderByExpression: "
+ orderByExpression + "," + expressionString + "," + orders);
}
@Override
public Object visitOrder(OrderExpression orderExpression, Object filterResult, SortOrder sortOrder) {
throw new SearchParseException("Unsupported operation visitOrder: " + orderExpression
+ "," + filterResult + "," + sortOrder);
}
}
/**
* Creates OData parser.
*
* @param conditionClass - class of T used to create condition objects. Class T must have
* accessible no-arguments constructor and complementary setters to these used in
* OData $filter expressions.
*/
public ODataParser(final Class< T > conditionClass) {
this(conditionClass, Collections.<String, String>emptyMap());
}
/**
* Creates OData parser.
*
* @param tclass - class of T used to create condition objects in built syntax tree. Class T must have
* accessible no-arg constructor and complementary setters to these used in
* OData $filter expressions.
* @param contextProperties
*/
public ODataParser(Class<T> tclass, Map<String, String> contextProperties) {
this(tclass, contextProperties, null);
}
/**
* Creates OData parser.
*
* @param tclass - class of T used to create condition objects in built syntax tree. Class T must have
* accessible no-arg constructor and complementary setters to these used in
* OData $filter expressions.
* @param contextProperties
*/
public ODataParser(Class<T> tclass,
Map<String, String> contextProperties,
Map<String, String> beanProperties) {
super(tclass, contextProperties, beanProperties);
this.parser = new FilterParserImpl(null);
}
@Override
@SuppressWarnings("unchecked")
public SearchCondition<T> parse(String searchExpression) throws SearchParseException {
try {
final T condition = conditionClass.newInstance();
final FilterExpression expression = parser.parseFilterString(searchExpression);
final FilterExpressionVisitor visitor = new FilterExpressionVisitor(condition);
return (SearchCondition< T >)expression.accept(visitor);
} catch (ODataMessageException ex) {
throw new SearchParseException(ex);
} catch (ODataApplicationException ex) {
throw new SearchParseException(ex);
} catch (InstantiationException ex) {
throw new SearchParseException(ex);
} catch (IllegalAccessException ex) {
throw new SearchParseException(ex);
}
}
}