blob: ab1a491ebe3065778fd799bb56734fe338bf03cc [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.server.core.uri.parser;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmElement;
import org.apache.olingo.commons.api.edm.EdmFunction;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmParameter;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmReturnType;
import org.apache.olingo.commons.api.edm.EdmStructuredType;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.uri.UriInfo;
import org.apache.olingo.server.api.uri.UriParameter;
import org.apache.olingo.server.api.uri.UriResource;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption;
import org.apache.olingo.server.api.uri.queryoption.ApplyItem;
import org.apache.olingo.server.api.uri.queryoption.ApplyOption;
import org.apache.olingo.server.api.uri.queryoption.ExpandOption;
import org.apache.olingo.server.api.uri.queryoption.FilterOption;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
import org.apache.olingo.server.api.uri.queryoption.apply.Aggregate;
import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression;
import org.apache.olingo.server.api.uri.queryoption.apply.AggregateExpression.StandardMethod;
import org.apache.olingo.server.api.uri.queryoption.apply.BottomTop;
import org.apache.olingo.server.api.uri.queryoption.apply.Compute;
import org.apache.olingo.server.api.uri.queryoption.apply.Concat;
import org.apache.olingo.server.api.uri.queryoption.apply.CustomFunction;
import org.apache.olingo.server.api.uri.queryoption.apply.GroupBy;
import org.apache.olingo.server.api.uri.queryoption.apply.GroupByItem;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl;
import org.apache.olingo.server.core.uri.UriResourceCountImpl;
import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl;
import org.apache.olingo.server.core.uri.UriResourcePrimitivePropertyImpl;
import org.apache.olingo.server.core.uri.UriResourceStartingTypeFilterImpl;
import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
import org.apache.olingo.server.core.uri.queryoption.ApplyOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.ExpandItemImpl;
import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.AggregateExpressionImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.AggregateImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.BottomTopImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.ComputeExpressionImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.ComputeImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.ConcatImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.CustomFunctionImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.DynamicProperty;
import org.apache.olingo.server.core.uri.queryoption.apply.DynamicStructuredType;
import org.apache.olingo.server.core.uri.queryoption.apply.ExpandImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.FilterImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.GroupByImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.GroupByItemImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.IdentityImpl;
import org.apache.olingo.server.core.uri.queryoption.apply.SearchImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl;
import org.apache.olingo.server.core.uri.validator.UriValidationException;
public class ApplyParser {
private static final Map<TokenKind, StandardMethod> TOKEN_KIND_TO_STANDARD_METHOD;
static {
Map<TokenKind, StandardMethod> temp = new EnumMap<>(TokenKind.class);
temp.put(TokenKind.SUM, StandardMethod.SUM);
temp.put(TokenKind.MIN, StandardMethod.MIN);
temp.put(TokenKind.MAX, StandardMethod.MAX);
temp.put(TokenKind.AVERAGE, StandardMethod.AVERAGE);
temp.put(TokenKind.COUNTDISTINCT, StandardMethod.COUNT_DISTINCT);
TOKEN_KIND_TO_STANDARD_METHOD = Collections.unmodifiableMap(temp);
}
private static final Map<TokenKind, BottomTop.Method> TOKEN_KIND_TO_BOTTOM_TOP_METHOD;
static {
Map<TokenKind, BottomTop.Method> temp = new EnumMap<>(TokenKind.class);
temp.put(TokenKind.BottomCountTrafo, BottomTop.Method.BOTTOM_COUNT);
temp.put(TokenKind.BottomPercentTrafo, BottomTop.Method.BOTTOM_PERCENT);
temp.put(TokenKind.BottomSumTrafo, BottomTop.Method.BOTTOM_SUM);
temp.put(TokenKind.TopCountTrafo, BottomTop.Method.TOP_COUNT);
temp.put(TokenKind.TopPercentTrafo, BottomTop.Method.TOP_PERCENT);
temp.put(TokenKind.TopSumTrafo, BottomTop.Method.TOP_SUM);
TOKEN_KIND_TO_BOTTOM_TOP_METHOD = Collections.unmodifiableMap(temp);
}
private final Edm edm;
private final OData odata;
private UriTokenizer tokenizer;
private Collection<String> crossjoinEntitySetNames;
private Map<String, AliasQueryOption> aliases;
public ApplyParser(final Edm edm, final OData odata) {
this.edm = edm;
this.odata = odata;
}
public ApplyOption parse(UriTokenizer tokenizer, EdmStructuredType referencedType,
final Collection<String> crossjoinEntitySetNames, final Map<String, AliasQueryOption> aliases)
throws UriParserException, UriValidationException {
this.tokenizer = tokenizer;
this.crossjoinEntitySetNames = crossjoinEntitySetNames;
this.aliases = aliases;
return parseApply(referencedType);
}
private ApplyOption parseApply(EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
ApplyOptionImpl option = new ApplyOptionImpl();
option.setEdmStructuredType(referencedType);
do {
option.add(parseTrafo(referencedType));
} while (tokenizer.next(TokenKind.SLASH));
return option;
}
private ApplyItem parseTrafo(EdmStructuredType referencedType) throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.AggregateTrafo)) {
return parseAggregateTrafo(referencedType);
} else if (tokenizer.next(TokenKind.IDENTITY)) {
return new IdentityImpl();
} else if (tokenizer.next(TokenKind.ComputeTrafo)) {
return parseComputeTrafo(referencedType);
} else if (tokenizer.next(TokenKind.ConcatMethod)) {
return parseConcatTrafo(referencedType);
} else if (tokenizer.next(TokenKind.ExpandTrafo)) {
return new ExpandImpl().setExpandOption(parseExpandTrafo(referencedType));
} else if (tokenizer.next(TokenKind.FilterTrafo)) {
final FilterOption filterOption = new FilterParser(edm, odata)
.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return new FilterImpl().setFilterOption(filterOption);
} else if (tokenizer.next(TokenKind.GroupByTrafo)) {
return parseGroupByTrafo(referencedType);
} else if (tokenizer.next(TokenKind.SearchTrafo)) {
final SearchOption searchOption = new SearchParser().parse(tokenizer);
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return new SearchImpl().setSearchOption(searchOption);
} else if (tokenizer.next(TokenKind.QualifiedName)) {
return parseCustomFunction(new FullQualifiedName(tokenizer.getText()), referencedType);
} else {
final TokenKind kind = ParserHelper.next(tokenizer,
TokenKind.BottomCountTrafo, TokenKind.BottomPercentTrafo, TokenKind.BottomSumTrafo,
TokenKind.TopCountTrafo, TokenKind.TopPercentTrafo, TokenKind.TopSumTrafo);
if (kind == null) {
throw new UriParserSyntaxException("Invalid apply expression syntax.",
UriParserSyntaxException.MessageKeys.SYNTAX);
} else {
return parseBottomTop(kind, referencedType);
}
}
}
private Aggregate parseAggregateTrafo(EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
AggregateImpl aggregate = new AggregateImpl();
do {
aggregate.addExpression(parseAggregateExpr(referencedType));
} while (tokenizer.next(TokenKind.COMMA));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return aggregate;
}
private AggregateExpression parseAggregateExpr(EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
AggregateExpressionImpl aggregateExpression = new AggregateExpressionImpl();
tokenizer.saveState();
// First try is checking for a (potentially empty) path prefix and the things that could follow it.
UriInfoImpl uriInfo = new UriInfoImpl();
final String identifierLeft = parsePathPrefix(uriInfo, referencedType);
if (identifierLeft != null) {
final String customAggregate = tokenizer.getText();
// A custom aggregate (an OData identifier) is defined in the CustomAggregate
// EDM annotation (in namespace Org.OData.Aggregation.V1) of the structured type or of the entity container.
// Currently we don't look into annotations, so all custom aggregates are allowed and have no type.
uriInfo.addResourcePart(new UriResourcePrimitivePropertyImpl(createDynamicProperty(customAggregate, null)));
aggregateExpression.setPath(uriInfo);
final String alias = parseAsAlias(referencedType, false);
aggregateExpression.setAlias(alias);
if (alias != null) {
((DynamicStructuredType) referencedType).addProperty(createDynamicProperty(alias, null));
}
parseAggregateFrom(aggregateExpression, referencedType);
} else if (tokenizer.next(TokenKind.OPEN)) {
final UriResource lastResourcePart = uriInfo.getLastResourcePart();
if (lastResourcePart == null) {
throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.",
UriParserSyntaxException.MessageKeys.SYNTAX);
}
aggregateExpression.setPath(uriInfo);
DynamicStructuredType inlineType = new DynamicStructuredType((EdmStructuredType)
ParserHelper.getTypeInformation((UriResourcePartTyped) lastResourcePart));
aggregateExpression.setInlineAggregateExpression(parseAggregateExpr(inlineType));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
} else if (tokenizer.next(TokenKind.COUNT)) {
uriInfo.addResourcePart(new UriResourceCountImpl());
aggregateExpression.setPath(uriInfo);
final String alias = parseAsAlias(referencedType, true);
aggregateExpression.setAlias(alias);
((DynamicStructuredType) referencedType).addProperty(
createDynamicProperty(alias,
// The OData standard mandates Edm.Decimal (with no decimals), although counts are always integer.
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal)));
} else {
// No legitimate continuation of a path prefix has been found.
// Second try is checking for a common expression.
tokenizer.returnToSavedState();
final Expression expression = new ExpressionParser(edm, odata)
.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
aggregateExpression.setExpression(expression);
parseAggregateWith(aggregateExpression);
if (aggregateExpression.getStandardMethod() == null && aggregateExpression.getCustomMethod() == null) {
throw new UriParserSyntaxException("Invalid 'aggregateExpr' syntax.",
UriParserSyntaxException.MessageKeys.SYNTAX);
}
final String alias = parseAsAlias(referencedType, true);
aggregateExpression.setAlias(alias);
DynamicProperty dynamicProperty = createDynamicProperty(alias,
// Determine the type for standard methods; there is no way to do this for custom methods.
getTypeForAggregateMethod(aggregateExpression.getStandardMethod(),
ExpressionParser.getType(expression)));
if (aggregateExpression.getStandardMethod() == StandardMethod.SUM
|| aggregateExpression.getStandardMethod() == StandardMethod.AVERAGE) {
//by default a property with no precision/scale defaults to a 0 scale
//this does not work for sum/average in general
dynamicProperty.setScale(Integer.MAX_VALUE);
}
((DynamicStructuredType) referencedType).addProperty(
dynamicProperty);
parseAggregateFrom(aggregateExpression, referencedType);
}
return aggregateExpression;
}
private void parseAggregateWith(AggregateExpressionImpl aggregateExpression) throws UriParserException {
if (tokenizer.next(TokenKind.WithOperator)) {
final TokenKind kind = ParserHelper.next(tokenizer,
TokenKind.SUM, TokenKind.MIN, TokenKind.MAX, TokenKind.AVERAGE, TokenKind.COUNTDISTINCT,
TokenKind.QualifiedName);
if (kind == null) {
throw new UriParserSyntaxException("Invalid 'with' syntax.",
UriParserSyntaxException.MessageKeys.SYNTAX);
} else if (kind == TokenKind.QualifiedName) {
// A custom aggregation method is announced in the CustomAggregationMethods
// EDM annotation (in namespace Org.OData.Aggregation.V1) of the structured type or of the entity container.
// Currently we don't look into annotations, so all custom aggregation methods are allowed and have no type.
aggregateExpression.setCustomMethod(new FullQualifiedName(tokenizer.getText()));
} else {
aggregateExpression.setStandardMethod(TOKEN_KIND_TO_STANDARD_METHOD.get(kind));
}
}
}
private EdmType getTypeForAggregateMethod(final StandardMethod method, final EdmType type) {
if (method == StandardMethod.SUM || method == StandardMethod.AVERAGE || method == StandardMethod.COUNT_DISTINCT) {
return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal);
} else if (method == StandardMethod.MIN || method == StandardMethod.MAX) {
return type;
} else {
return null;
}
}
private String parseAsAlias(final EdmStructuredType referencedType, final boolean isRequired)
throws UriParserException {
if (tokenizer.next(TokenKind.AsOperator)) {
ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
final String name = tokenizer.getText();
if (referencedType.getProperty(name) != null) {
throw new UriParserSemanticException("Alias '" + name + "' is already a property.",
UriParserSemanticException.MessageKeys.IS_PROPERTY, name);
}
return name;
} else if (isRequired) {
throw new UriParserSyntaxException("Expected asAlias not found.", UriParserSyntaxException.MessageKeys.SYNTAX);
}
return null;
}
private void parseAggregateFrom(AggregateExpressionImpl aggregateExpression,
final EdmStructuredType referencedType) throws UriParserException {
while (tokenizer.next(TokenKind.FromOperator)) {
AggregateExpressionImpl from = new AggregateExpressionImpl();
from.setExpression(new MemberImpl(parseGroupingProperty(referencedType), referencedType));
parseAggregateWith(from);
aggregateExpression.addFrom(from);
}
}
private DynamicProperty createDynamicProperty(final String name, final EdmType type) {
return name == null ? null : new DynamicProperty(name, type);
}
private Compute parseComputeTrafo(EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
ComputeImpl compute = new ComputeImpl();
do {
final Expression expression = new ExpressionParser(edm, odata)
.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
final EdmType expressionType = ExpressionParser.getType(expression);
if (expressionType.getKind() != EdmTypeKind.PRIMITIVE) {
throw new UriParserSemanticException("Compute expressions must return primitive values.",
UriParserSemanticException.MessageKeys.ONLY_FOR_PRIMITIVE_TYPES, "compute");
}
final String alias = parseAsAlias(referencedType, true);
((DynamicStructuredType) referencedType).addProperty(createDynamicProperty(alias, expressionType));
compute.addExpression(new ComputeExpressionImpl()
.setExpression(expression)
.setAlias(alias));
} while (tokenizer.next(TokenKind.COMMA));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return compute;
}
private Concat parseConcatTrafo(EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
ConcatImpl concat = new ConcatImpl();
// A common type is used for all sub-transformations.
// If one sub-transformation aggregates properties away,
// this could have unintended consequences for subsequent sub-transformations.
concat.addApplyOption(parseApply(referencedType));
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
do {
concat.addApplyOption(parseApply(referencedType));
} while (tokenizer.next(TokenKind.COMMA));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return concat;
}
private ExpandOption parseExpandTrafo(final EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
ExpandItemImpl item = new ExpandItemImpl();
item.setResourcePath(ExpandParser.parseExpandPath(tokenizer, edm, referencedType, item));
final EdmType type = ParserHelper.getTypeInformation((UriResourcePartTyped)
((UriInfoImpl) item.getResourcePath()).getLastResourcePart());
if (tokenizer.next(TokenKind.COMMA)) {
if (tokenizer.next(TokenKind.FilterTrafo)) {
item.setSystemQueryOption(
new FilterParser(edm, odata).parse(tokenizer,type, crossjoinEntitySetNames, aliases));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
} else {
ParserHelper.requireNext(tokenizer, TokenKind.ExpandTrafo);
item.setSystemQueryOption(parseExpandTrafo((EdmStructuredType) type));
}
}
while (tokenizer.next(TokenKind.COMMA)) {
ParserHelper.requireNext(tokenizer, TokenKind.ExpandTrafo);
final ExpandOption nestedExpand = parseExpandTrafo((EdmStructuredType) type);
if (item.getExpandOption() == null) {
item.setSystemQueryOption(nestedExpand);
} else {
// Add to the existing items.
((ExpandOptionImpl) item.getExpandOption())
.addExpandItem(nestedExpand.getExpandItems().get(0));
}
}
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
ExpandOptionImpl expand = new ExpandOptionImpl();
expand.addExpandItem(item);
return expand;
}
private GroupBy parseGroupByTrafo(final EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
GroupByImpl groupBy = new GroupByImpl();
parseGroupByList(groupBy, referencedType);
if (tokenizer.next(TokenKind.COMMA)) {
groupBy.setApplyOption(parseApply(referencedType));
}
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return groupBy;
}
private void parseGroupByList(GroupByImpl groupBy, final EdmStructuredType referencedType)
throws UriParserException {
ParserHelper.requireNext(tokenizer, TokenKind.OPEN);
do {
groupBy.addGroupByItem(parseGroupByElement(referencedType));
} while (tokenizer.next(TokenKind.COMMA));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
}
private GroupByItem parseGroupByElement(final EdmStructuredType referencedType)
throws UriParserException {
if (tokenizer.next(TokenKind.RollUpSpec)) {
return parseRollUpSpec(referencedType);
} else {
return new GroupByItemImpl().setPath(parseGroupingProperty(referencedType));
}
}
private GroupByItem parseRollUpSpec(final EdmStructuredType referencedType)
throws UriParserException {
GroupByItemImpl item = new GroupByItemImpl();
if (tokenizer.next(TokenKind.ROLLUP_ALL)) {
item.setIsRollupAll();
} else {
item.addRollupItem(new GroupByItemImpl().setPath(
parseGroupingProperty(referencedType)));
}
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
do {
item.addRollupItem(new GroupByItemImpl().setPath(
parseGroupingProperty(referencedType)));
} while (tokenizer.next(TokenKind.COMMA));
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return item;
}
private UriInfo parseGroupingProperty(final EdmStructuredType referencedType) throws UriParserException {
UriInfoImpl uriInfo = new UriInfoImpl();
final String identifierLeft = parsePathPrefix(uriInfo, referencedType);
if (identifierLeft != null) {
throw new UriParserSemanticException("Unknown identifier in grouping property path.",
UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE,
identifierLeft,
uriInfo.getLastResourcePart() != null && uriInfo.getLastResourcePart() instanceof UriResourcePartTyped ?
((UriResourcePartTyped) uriInfo.getLastResourcePart())
.getType().getFullQualifiedName().getFullQualifiedNameAsString() :
"");
}
return uriInfo;
}
/**
* Parses the path prefix and a following OData identifier as one path, deviating from the ABNF.
* @param uriInfo object to be filled with path segments
* @return a parsed but not used OData identifier */
private String parsePathPrefix(UriInfoImpl uriInfo, final EdmStructuredType referencedType)
throws UriParserException {
final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm, referencedType);
if (typeCast != null) {
uriInfo.addResourcePart(new UriResourceStartingTypeFilterImpl(typeCast, true));
ParserHelper.requireNext(tokenizer, TokenKind.SLASH);
}
EdmStructuredType type = typeCast == null ? referencedType : typeCast;
while (tokenizer.next(TokenKind.ODataIdentifier)) {
final String name = tokenizer.getText();
final EdmElement property = type.getProperty(name);
final UriResource segment = parsePathSegment(property);
if (segment == null) {
if (property == null) {
return name;
} else {
uriInfo.addResourcePart(
property instanceof EdmNavigationProperty ?
new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property) :
property.getType().getKind() == EdmTypeKind.COMPLEX ?
new UriResourceComplexPropertyImpl((EdmProperty) property) :
new UriResourcePrimitivePropertyImpl((EdmProperty) property));
return null;
}
} else {
uriInfo.addResourcePart(segment);
}
type = (EdmStructuredType) ParserHelper.getTypeInformation((UriResourcePartTyped) segment);
}
return null;
}
private UriResource parsePathSegment(final EdmElement property) throws UriParserException {
if (property == null
|| !(property.getType().getKind() == EdmTypeKind.COMPLEX
|| property instanceof EdmNavigationProperty)) {
// Could be a customAggregate or $count.
return null;
}
if (tokenizer.next(TokenKind.SLASH)) {
final EdmStructuredType typeCast = ParserHelper.parseTypeCast(tokenizer, edm,
(EdmStructuredType) property.getType());
if (typeCast != null) {
ParserHelper.requireNext(tokenizer, TokenKind.SLASH);
}
return property.getType().getKind() == EdmTypeKind.COMPLEX ?
new UriResourceComplexPropertyImpl((EdmProperty) property).setTypeFilter(typeCast) :
new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property).setCollectionTypeFilter(typeCast);
} else {
return null;
}
}
private CustomFunction parseCustomFunction(final FullQualifiedName functionName,
final EdmStructuredType referencedType) throws UriParserException, UriValidationException {
final List<UriParameter> parameters =
ParserHelper.parseFunctionParameters(tokenizer, edm, referencedType, true, aliases);
final List<String> parameterNames = ParserHelper.getParameterNames(parameters);
final EdmFunction function = edm.getBoundFunction(functionName,
referencedType.getFullQualifiedName(), true, parameterNames);
if (function == null) {
throw new UriParserSemanticException("No function '" + functionName + "' found.",
UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND,
functionName.getFullQualifiedNameAsString());
}
ParserHelper.validateFunctionParameters(function, parameters, edm, referencedType, aliases);
// The binding parameter and the return type must be of type complex or entity collection.
final EdmParameter bindingParameter = function.getParameter(function.getParameterNames().get(0));
final EdmReturnType returnType = function.getReturnType();
if (bindingParameter.getType().getKind() != EdmTypeKind.ENTITY
&& bindingParameter.getType().getKind() != EdmTypeKind.COMPLEX
|| !bindingParameter.isCollection()
|| returnType.getType().getKind() != EdmTypeKind.ENTITY
&& returnType.getType().getKind() != EdmTypeKind.COMPLEX
|| !returnType.isCollection()) {
throw new UriParserSemanticException("Only entity- or complex-collection functions are allowed.",
UriParserSemanticException.MessageKeys.FUNCTION_MUST_USE_COLLECTIONS,
functionName.getFullQualifiedNameAsString());
}
return new CustomFunctionImpl().setFunction(function).setParameters(parameters);
}
private BottomTop parseBottomTop(final TokenKind kind, final EdmStructuredType referencedType)
throws UriParserException, UriValidationException {
BottomTopImpl bottomTop = new BottomTopImpl();
bottomTop.setMethod(TOKEN_KIND_TO_BOTTOM_TOP_METHOD.get(kind));
final ExpressionParser expressionParser = new ExpressionParser(edm, odata);
final Expression number = expressionParser.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
expressionParser.checkIntegerType(number);
bottomTop.setNumber(number);
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
final Expression value = expressionParser.parse(tokenizer, referencedType, crossjoinEntitySetNames, aliases);
expressionParser.checkNumericType(value);
bottomTop.setValue(value);
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return bottomTop;
}
}