/** * 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); } } }