[OLINGO-834] $expand parser in Java + clean-up Signed-off-by: Christian Amend <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/8925274c Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/8925274c Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/8925274c Branch: refs/heads/OLINGO-834_Filter_Parser Commit: 8925274c0b5bd6936c3f6c1d3ab55608ced2cf13 Parents: 8919d3e Author: Klaus Straubinger <[email protected]> Authored: Thu Jan 7 13:55:34 2016 +0100 Committer: Christian Amend <[email protected]> Committed: Thu Jan 7 14:02:09 2016 +0100 ---------------------------------------------------------------------- .../tecsvc/client/FilterSystemQueryITCase.java | 7 +- .../tecsvc/client/OrderBySystemQueryITCase.java | 7 +- .../server/api/uri/queryoption/ExpandItem.java | 1 - .../server/core/uri/parser/ExpandParser.java | 282 +++ .../core/uri/parser/ExpressionParser.java | 332 +-- .../server/core/uri/parser/FilterParser.java | 11 +- .../olingo/server/core/uri/parser/Parser.java | 234 +- .../server/core/uri/parser/ParserHelper.java | 34 +- .../core/uri/parser/ResourcePathParser.java | 62 +- .../server/core/uri/parser/SearchParser.java | 108 + .../server/core/uri/parser/UriContext.java | 115 - .../core/uri/parser/UriParseTreeVisitor.java | 2313 ------------------ .../server/core/uri/parser/UriTokenizer.java | 211 +- .../uri/queryoption/expression/AliasImpl.java | 4 + .../queryoption/expression/EnumerationImpl.java | 6 + .../queryoption/expression/LambdaRefImpl.java | 5 + .../queryoption/expression/TypeLiteralImpl.java | 5 + .../server-core-exceptions-i18n.properties | 8 +- .../core/uri/parser/ExpressionParserTest.java | 14 +- .../core/uri/parser/UriTokenizerTest.java | 82 +- .../search/SearchParserAndTokenizerTest.java | 44 +- .../core/uri/antlr/TestFullResourcePath.java | 1311 +++++----- .../olingo/server/core/uri/antlr/TestLexer.java | 450 ++-- .../core/uri/antlr/TestUriParserImpl.java | 220 +- .../server/core/uri/parser/ParserTest.java | 56 +- .../core/uri/testutil/ExpandValidator.java | 19 +- .../core/uri/testutil/FilterValidator.java | 56 +- .../core/uri/testutil/TestUriValidator.java | 38 +- .../core/uri/testutil/TokenValidator.java | 127 - 29 files changed, 1941 insertions(+), 4221 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java ---------------------------------------------------------------------- diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java index ca6eb21..6865a65 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/FilterSystemQueryITCase.java @@ -35,11 +35,8 @@ import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.http.HttpHeader; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; -// TODO -@Ignore public class FilterSystemQueryITCase extends AbstractParamTecSvcITCase { private static final String ES_COMP_ALL_PRIM = "ESCompAllPrim"; @@ -212,7 +209,7 @@ public class FilterSystemQueryITCase extends AbstractParamTecSvcITCase { sendRequest(ES_TWO_KEY_NAV, "PropertyComp/PropertyComp/PropertyBoolean eq not null"); assertEquals(0, result.getBody().getEntities().size()); - result = sendRequest(ES_TWO_KEY_NAV, "PropertyComp/PropertyComp/PropertyBoolean eq 0 add -(5 add null)"); + result = sendRequest(ES_TWO_KEY_NAV, "PropertyComp/PropertyComp/PropertyInt16 eq 0 add -(5 add null)"); assertEquals(0, result.getBody().getEntities().size()); } @@ -357,7 +354,7 @@ public class FilterSystemQueryITCase extends AbstractParamTecSvcITCase { @Test public void notOperator() { - ODataRetrieveResponse<ClientEntitySet> result = sendRequest(ES_TWO_KEY_NAV, "not(PropertyInt16 eq 1)"); + ODataRetrieveResponse<ClientEntitySet> result = sendRequest(ES_TWO_KEY_NAV, "not (PropertyInt16 eq 1)"); assertEquals(2, result.getBody().getEntities().size()); ClientEntity clientEntity = result.getBody().getEntities().get(0); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java ---------------------------------------------------------------------- diff --git a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java index 0e31b33..8e16a70 100644 --- a/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java +++ b/fit/src/test/java/org/apache/olingo/fit/tecsvc/client/OrderBySystemQueryITCase.java @@ -30,11 +30,8 @@ import org.apache.olingo.client.api.domain.ClientEntitySet; import org.apache.olingo.client.api.domain.ClientValuable; import org.apache.olingo.commons.api.http.HttpStatusCode; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; -// TODO -@Ignore public class OrderBySystemQueryITCase extends AbstractParamTecSvcITCase { private static final String ES_TWO_PRIM = "ESTwoPrim"; @@ -74,7 +71,7 @@ public class OrderBySystemQueryITCase extends AbstractParamTecSvcITCase { @Test public void multipleOrderBy() { - final ODataRetrieveResponse<ClientEntitySet> response = sendRequest(ES_ALL_PRIM, "PropertyByte, PropertyInt16"); + final ODataRetrieveResponse<ClientEntitySet> response = sendRequest(ES_ALL_PRIM, "PropertyByte,PropertyInt16"); assertEquals(3, response.getBody().getEntities().size()); ClientEntity clientEntity = response.getBody().getEntities().get(0); @@ -90,7 +87,7 @@ public class OrderBySystemQueryITCase extends AbstractParamTecSvcITCase { @Test public void multipleOrderByDescending() { final ODataRetrieveResponse<ClientEntitySet> response = - sendRequest(ES_ALL_PRIM, "PropertyByte, PropertyInt16 desc"); + sendRequest(ES_ALL_PRIM, "PropertyByte,PropertyInt16 desc"); assertEquals(3, response.getBody().getEntities().size()); ClientEntity clientEntity = response.getBody().getEntities().get(0); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ExpandItem.java ---------------------------------------------------------------------- diff --git a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ExpandItem.java b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ExpandItem.java index a16c137..fccf844 100644 --- a/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ExpandItem.java +++ b/lib/server-api/src/main/java/org/apache/olingo/server/api/uri/queryoption/ExpandItem.java @@ -38,7 +38,6 @@ public interface ExpandItem { FilterOption getFilterOption(); /** - * <b>CURRENTLY NOT SUPPORTED. WILL ALWAYS RETURN NULL</b> * @return Information of the option $search when used within $expand */ SearchOption getSearchOption(); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java new file mode 100644 index 0000000..d8209d8 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpandParser.java @@ -0,0 +1,282 @@ +/* + * 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 org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.edm.EdmEntityType; +import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.commons.api.edm.EdmProperty; +import org.apache.olingo.commons.api.edm.EdmStructuredType; +import org.apache.olingo.commons.api.edm.FullQualifiedName; +import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; +import org.apache.olingo.commons.api.ex.ODataRuntimeException; +import org.apache.olingo.server.api.OData; +import org.apache.olingo.server.api.uri.UriInfoKind; +import org.apache.olingo.server.api.uri.UriResourceNavigation; +import org.apache.olingo.server.api.uri.UriResourcePartTyped; +import org.apache.olingo.server.api.uri.queryoption.ExpandItem; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; +import org.apache.olingo.server.api.uri.queryoption.LevelsExpandOption; +import org.apache.olingo.server.api.uri.queryoption.SystemQueryOption; +import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind; +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.UriResourceRefImpl; +import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; +import org.apache.olingo.server.core.uri.queryoption.CountOptionImpl; +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.LevelsOptionImpl; +import org.apache.olingo.server.core.uri.queryoption.SkipOptionImpl; +import org.apache.olingo.server.core.uri.queryoption.TopOptionImpl; +import org.apache.olingo.server.core.uri.validator.UriValidationException; + +public class ExpandParser { + + private final Edm edm; + private final OData odata; + + public ExpandParser(final Edm edm, final OData odata) { + this.edm = edm; + this.odata = odata; + } + + public ExpandOption parse(UriTokenizer tokenizer, final EdmStructuredType referencedType) + throws UriParserException, UriValidationException { + ExpandOptionImpl expandOption = new ExpandOptionImpl(); + do { + final ExpandItem item = parseItem(tokenizer, referencedType); + expandOption.addExpandItem(item); + } while (tokenizer.next(TokenKind.COMMA)); + + return expandOption; + } + + private ExpandItem parseItem(UriTokenizer tokenizer, final EdmStructuredType referencedType) + throws UriParserException, UriValidationException { + ExpandItemImpl item = new ExpandItemImpl(); + if (tokenizer.next(TokenKind.STAR)) { + item.setIsStar(true); + if (tokenizer.next(TokenKind.SLASH)) { + ParserHelper.requireNext(tokenizer, TokenKind.REF); + item.setIsRef(true); + } else if (tokenizer.next(TokenKind.OPEN)) { + ParserHelper.requireNext(tokenizer, TokenKind.LEVELS); + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + item.setSystemQueryOption((SystemQueryOption) parseLevels(tokenizer)); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + } + + } else { + final EdmStructuredType typeCast = parseTypeCast(tokenizer, referencedType); + if (typeCast != null) { + item.setTypeFilter(typeCast); + ParserHelper.requireNext(tokenizer, TokenKind.SLASH); + } + + UriInfoImpl resource = parseExpandPath(tokenizer, referencedType); + + UriResourcePartTyped lastPart = (UriResourcePartTyped) resource.getLastResourcePart(); + + boolean hasSlash = false; + if (tokenizer.next(TokenKind.SLASH)) { + hasSlash = true; + if (lastPart instanceof UriResourceNavigation) { + UriResourceNavigationPropertyImpl navigationResource = (UriResourceNavigationPropertyImpl) lastPart; + final EdmNavigationProperty navigationProperty = navigationResource.getProperty(); + final EdmStructuredType typeCastSuffix = parseTypeCast(tokenizer, navigationProperty.getType()); + if (typeCastSuffix != null) { + if (navigationProperty.isCollection()) { + navigationResource.setCollectionTypeFilter(typeCastSuffix); + } else { + navigationResource.setEntryTypeFilter(typeCastSuffix); + } + hasSlash = false; + } + } + } + + final EdmStructuredType newReferencedType = (EdmStructuredType) lastPart.getType(); + final boolean newReferencedIsCollection = lastPart.isCollection(); + if (hasSlash || tokenizer.next(TokenKind.SLASH)) { + if (tokenizer.next(TokenKind.REF)) { + resource.addResourcePart(new UriResourceRefImpl()); + parseOptions(tokenizer, newReferencedType, newReferencedIsCollection, item, true, false); + } else { + ParserHelper.requireNext(tokenizer, TokenKind.COUNT); + resource.addResourcePart(new UriResourceCountImpl()); + parseOptions(tokenizer, newReferencedType, newReferencedIsCollection, item, false, true); + } + } else { + parseOptions(tokenizer, newReferencedType, newReferencedIsCollection, item, false, false); + } + + item.setResourcePath(resource); + } + + return item; + } + + private EdmStructuredType parseTypeCast(UriTokenizer tokenizer, final EdmStructuredType referencedType) + throws UriParserException { + if (tokenizer.next(TokenKind.QualifiedName)) { + final FullQualifiedName qualifiedName = new FullQualifiedName(tokenizer.getText()); + final EdmStructuredType type = referencedType instanceof EdmEntityType ? + edm.getEntityType(qualifiedName) : + edm.getComplexType(qualifiedName); + if (type == null) { + throw new UriParserSemanticException("Type '" + qualifiedName + "' not found.", + UriParserSemanticException.MessageKeys.UNKNOWN_PART, qualifiedName.getFullQualifiedNameAsString()); + } else { + if (!type.compatibleTo(referencedType)) { + throw new UriParserSemanticException("The type cast '" + qualifiedName + "' is not compatible.", + UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER, type.getName()); + } + } + return type; + } + return null; + } + + private UriInfoImpl parseExpandPath(UriTokenizer tokenizer, final EdmStructuredType referencedType) + throws UriParserException { + UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource); + + EdmStructuredType type = referencedType; + String name = null; + while (tokenizer.next(TokenKind.ODataIdentifier)) { + name = tokenizer.getText(); + final EdmProperty property = referencedType.getStructuralProperty(name); + if (property != null && property.getType().getKind() == EdmTypeKind.COMPLEX) { + type = (EdmStructuredType) property.getType(); + UriResourceComplexPropertyImpl complexResource = new UriResourceComplexPropertyImpl(property); + ParserHelper.requireNext(tokenizer, TokenKind.SLASH); + final EdmStructuredType typeCast = parseTypeCast(tokenizer, type); + if (typeCast != null) { + complexResource.setTypeFilter(typeCast); + ParserHelper.requireNext(tokenizer, TokenKind.SLASH); + type = typeCast; + } + resource.addResourcePart(complexResource); + } + } + + final EdmNavigationProperty navigationProperty = type.getNavigationProperty(name); + if (navigationProperty == null) { + // TODO: could also have been star after complex property (and maybe type cast) + throw new UriParserSemanticException( + "Navigation Property '" + name + "' not found in type '" + type.getFullQualifiedName() + "'.", + UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE, type.getName(), name); + } else { + resource.addResourcePart(new UriResourceNavigationPropertyImpl(navigationProperty)); + } + + return resource; + } + + private void parseOptions(UriTokenizer tokenizer, + final EdmStructuredType referencedType, final boolean referencedIsCollection, + ExpandItemImpl item, + final boolean forRef, final boolean forCount) throws UriParserException, UriValidationException { + if (tokenizer.next(TokenKind.OPEN)) { + do { + SystemQueryOption systemQueryOption; + if (!forCount && tokenizer.next(TokenKind.COUNT)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + ParserHelper.requireNext(tokenizer, TokenKind.BooleanValue); + CountOptionImpl countOption = new CountOptionImpl(); + countOption.setText(tokenizer.getText()); + countOption.setValue(Boolean.parseBoolean(tokenizer.getText())); + systemQueryOption = countOption; + + } else if (!forRef && !forCount && tokenizer.next(TokenKind.EXPAND)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + systemQueryOption = new ExpandParser(edm, odata).parse(tokenizer, referencedType); + + } else if (tokenizer.next(TokenKind.FILTER)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + systemQueryOption = new FilterParser(edm, odata).parse(tokenizer, referencedType, null); + + } else if (!forRef && !forCount && tokenizer.next(TokenKind.LEVELS)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + systemQueryOption = (SystemQueryOption) parseLevels(tokenizer); + + } else if (!forCount && tokenizer.next(TokenKind.ORDERBY)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + systemQueryOption = new OrderByParser(edm, odata).parse(tokenizer, referencedType, null); + + } else if (tokenizer.next(TokenKind.SEARCH)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + systemQueryOption = new SearchParser().parse(tokenizer); + + } else if (!forRef && !forCount && tokenizer.next(TokenKind.SELECT)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + systemQueryOption = new SelectParser(edm).parse(tokenizer, referencedType, referencedIsCollection); + + } else if (!forCount && tokenizer.next(TokenKind.SKIP)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + ParserHelper.requireNext(tokenizer, TokenKind.IntegerValue); + final int value = ParserHelper.parseNonNegativeInteger(SystemQueryOptionKind.SKIP.toString(), + tokenizer.getText(), true); + SkipOptionImpl skipOption = new SkipOptionImpl(); + skipOption.setText(tokenizer.getText()); + skipOption.setValue(value); + systemQueryOption = skipOption; + + } else if (!forCount && tokenizer.next(TokenKind.TOP)) { + ParserHelper.requireNext(tokenizer, TokenKind.EQ); + ParserHelper.requireNext(tokenizer, TokenKind.IntegerValue); + final int value = ParserHelper.parseNonNegativeInteger(SystemQueryOptionKind.TOP.toString(), + tokenizer.getText(), true); + TopOptionImpl topOption = new TopOptionImpl(); + topOption.setText(tokenizer.getText()); + topOption.setValue(value); + systemQueryOption = topOption; + + } else { + throw new UriParserSyntaxException("Allowed query option expected.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + try { + item.setSystemQueryOption(systemQueryOption); + } catch (final ODataRuntimeException e) { + throw new UriParserSyntaxException("Double system query option '" + systemQueryOption.getName() + "'.", e, + UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, systemQueryOption.getName()); + } + } while (tokenizer.next(TokenKind.SEMI)); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + } + } + + private LevelsExpandOption parseLevels(UriTokenizer tokenizer) throws UriParserException { + final LevelsOptionImpl option = new LevelsOptionImpl(); + if (tokenizer.next(TokenKind.MAX)) { + option.setText(tokenizer.getText()); + option.setMax(); + } else { + ParserHelper.requireNext(tokenizer, TokenKind.IntegerValue); + option.setText(tokenizer.getText()); + option.setValue( + ParserHelper.parseNonNegativeInteger(SystemQueryOptionKind.LEVELS.toString(), tokenizer.getText(), false)); + } + return option; + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java index 049880f..6fa415f 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java @@ -47,9 +47,6 @@ import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.commons.api.edm.EdmTypeDefinition; import org.apache.olingo.commons.api.edm.FullQualifiedName; import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; -import org.apache.olingo.commons.core.edm.primitivetype.EdmByte; -import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; -import org.apache.olingo.commons.core.edm.primitivetype.EdmSByte; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResourceFunction; @@ -161,7 +158,7 @@ public class ExpressionParser { private static final Map<TokenKind, EdmPrimitiveTypeKind> tokenToPrimitiveType; static { - /* Enum and null are not present in the map. These have to be handled differently */ + /* Enum and null are not present in the map. These have to be handled differently. */ Map<TokenKind, EdmPrimitiveTypeKind> temp = new HashMap<TokenKind, EdmPrimitiveTypeKind>(); temp.put(TokenKind.BooleanValue, EdmPrimitiveTypeKind.Boolean); temp.put(TokenKind.StringValue, EdmPrimitiveTypeKind.String); @@ -242,9 +239,9 @@ public class ExpressionParser { } private Expression parseExprRel() throws UriParserException, UriValidationException { - if(tokenizer.next(TokenKind.IsofMethod)) { - // The isof method is a terminal. So no further operators are allowed - return parseIsOfMethod(TokenKind.IsofMethod); + if (tokenizer.next(TokenKind.IsofMethod)) { + // The isof method is a terminal. So no further operators are allowed. + return parseIsOfOrCastMethod(MethodKind.ISOF); } else { Expression left = parseExprAdd(); TokenKind operatorTokenKind = ParserHelper.next(tokenizer, @@ -264,30 +261,25 @@ public class ExpressionParser { } } - private Expression parseIsOfMethod(final TokenKind lastToken) throws UriParserException, UriValidationException { - if(lastToken == TokenKind.IsofMethod) { - // The TokenKind 'IsOfMethod' consumes also the opening parenthesis - - // The first parameter could be an expression or a type literal - final List<Expression> parameters = new ArrayList<Expression>(); + private Expression parseIsOfOrCastMethod(final MethodKind kind) throws UriParserException, UriValidationException { + // The TokenKind 'IsOfMethod' consumes also the opening parenthesis. + // The first parameter could be an expression or a type literal. + List<Expression> parameters = new ArrayList<Expression>(); + parameters.add(parseExpression()); + if (!(parameters.get(0) instanceof TypeLiteral)) { + // The first parameter is not a type literal, so there must be a second parameter. + ParserHelper.requireNext(tokenizer, TokenKind.COMMA); parameters.add(parseExpression()); - if(!(parameters.get(0) instanceof TypeLiteral)) { - // The first parameter is not a type literal, so there must be a second parameter - ParserHelper.requireNext(tokenizer, TokenKind.COMMA); - parameters.add(parseExpression()); - - // The second parameter must be a type literal - if(!(parameters.get(1) instanceof TypeLiteral)) { - throw new UriParserSemanticException("Type literal extected", - UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER); - } + + // The second parameter must be a type literal. + if (!(parameters.get(1) instanceof TypeLiteral)) { + throw new UriParserSemanticException("Type literal expected.", + UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER); } - - ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); - return new MethodImpl(MethodKind.ISOF, parameters); - } else { - throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX); } + + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + return new MethodImpl(kind, parameters); } private Expression parseExprAdd() throws UriParserException, UriValidationException { @@ -296,9 +288,9 @@ public class ExpressionParser { // Null for everything other than ADD or SUB while (operatorTokenKind != null) { final Expression right = parseExprMul(); - checkAddSubTypes(left, right, operatorTokenKind == TokenKind.AddOperator); - left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, - odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double)); + final EdmType resultType = getAddSubTypeAndCheckLeftAndRight(left, right, + operatorTokenKind == TokenKind.SubOperator); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, resultType); operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.AddOperator, TokenKind.SubOperator); } return left; @@ -328,10 +320,7 @@ public class ExpressionParser { } private Expression parseExprUnary() throws UriParserException, UriValidationException { - final TokenKind operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.MINUS, TokenKind.NotOperator, - TokenKind.CastMethod); - - if(operatorTokenKind == TokenKind.MINUS) { + if (tokenizer.next(TokenKind.MinusOperator)) { final Expression expression = parseExprPrimary(); checkType(expression, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, @@ -339,37 +328,14 @@ public class ExpressionParser { EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double, EdmPrimitiveTypeKind.Duration); return new UnaryImpl(UnaryOperatorKind.MINUS, expression, getType(expression)); - } else if(operatorTokenKind == TokenKind.NotOperator) { + } else if (tokenizer.next(TokenKind.NotOperator)) { final Expression expression = parseExprPrimary(); checkType(expression, EdmPrimitiveTypeKind.Boolean); return new UnaryImpl(UnaryOperatorKind.NOT, expression, getType(expression)); - } else if(operatorTokenKind == TokenKind.CastMethod) { - return parseCastMethod(operatorTokenKind); + } else if (tokenizer.next(TokenKind.CastMethod)) { + return parseIsOfOrCastMethod(MethodKind.CAST); } else { - final Expression expression = parseExprPrimary(); - return expression; - } - } - - private Expression parseCastMethod(final TokenKind lastToken) throws UriParserException, UriValidationException { - // The TokenKind 'CastMethod' consumes also the opening parenthesis - if(lastToken == TokenKind.CastMethod) { - final List<Expression> parameters = new ArrayList<Expression>(); - parameters.add(parseExpression()); - - if(!(parameters.get(0) instanceof TypeLiteral)) { - ParserHelper.requireNext(tokenizer, TokenKind.COMMA); - parameters.add(parseExpression()); - if(!(parameters.get(1) instanceof TypeLiteral)) { - throw new UriParserSemanticException("Type literal extected", - UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER); - } - } - - ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); - return new MethodImpl(MethodKind.CAST, parameters); - } else { - throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX); + return parseExprPrimary(); } } @@ -433,22 +399,20 @@ public class ExpressionParser { private Expression parseMethod(final TokenKind nextMethod) throws UriParserException, UriValidationException { // The method token text includes the opening parenthesis so that method calls can be recognized unambiguously. // OData identifiers have to be considered after that. - final MethodKind methodKind = tokenToMethod.get(nextMethod); return new MethodImpl(methodKind, parseMethodParameters(methodKind)); } - private Expression parsePrimitive(TokenKind nextPrimitive) throws UriParserException { + private Expression parsePrimitive(final TokenKind primitiveTokenKind) throws UriParserException { final String primitiveValueLiteral = tokenizer.getText(); - if (nextPrimitive == TokenKind.EnumValue) { + if (primitiveTokenKind == TokenKind.EnumValue) { return createEnumExpression(primitiveValueLiteral); } else { - EdmPrimitiveTypeKind primitiveTypeKind = tokenToPrimitiveType.get(nextPrimitive); - - if(primitiveTypeKind == EdmPrimitiveTypeKind.Int64) { + EdmPrimitiveTypeKind primitiveTypeKind = tokenToPrimitiveType.get(primitiveTokenKind); + if (primitiveTypeKind == EdmPrimitiveTypeKind.Int64) { primitiveTypeKind = determineIntegerType(primitiveValueLiteral); } - + final EdmPrimitiveType type = primitiveTypeKind == null ? // Null handling null : @@ -472,12 +436,11 @@ public class ExpressionParser { } else { typeKind = EdmPrimitiveTypeKind.Int64; } - } catch (NumberFormatException e) { - // This should never happen. Because the tokenizer has figured out that the literal is an integer - throw new UriParserSyntaxException(intValueAsString + " is not an integer", + } catch (final NumberFormatException e) { + // This should never happen because the tokenizer has figured out that the literal is an integer. + throw new UriParserSyntaxException("'" + intValueAsString + "' is not an integer.", e, UriParserSyntaxException.MessageKeys.SYNTAX); } - return typeKind; } @@ -622,19 +585,19 @@ public class ExpressionParser { if (filterType == null) { filterType = edm.getComplexType(fullQualifiedName); } - - if(filterType == null) { - filterType = getEdmType(fullQualifiedName); + + if (filterType == null) { + filterType = getPrimitiveType(fullQualifiedName); } - - if(filterType == null) { + + if (filterType == null) { filterType = edm.getEnumType(fullQualifiedName); } - - if(filterType == null) { + + if (filterType == null) { filterType = edm.getTypeDefinition(fullQualifiedName); } - + if (filterType != null) { if (tokenizer.next(TokenKind.SLASH)) { // Leading type cast @@ -642,30 +605,29 @@ public class ExpressionParser { startTypeFilter = filterType; final TokenKind tokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, TokenKind.ODataIdentifier); - parseMemberExpression(tokenKind, uriInfo, new UriResourceStartingTypeFilterImpl(filterType, false), - false); + parseMemberExpression(tokenKind, uriInfo, new UriResourceStartingTypeFilterImpl(filterType, false), false); } else { // Type literal return new TypeLiteralImpl(filterType); } } else { - // Must be bound or unbound function. + // Must be bound or unbound function. parseFunction(fullQualifiedName, uriInfo, referringType, true); } } else if (lastTokenKind == TokenKind.ODataIdentifier) { parseFirstMemberODataIdentifier(uriInfo); } - + return new MemberImpl(uriInfo, startTypeFilter); } - private EdmType getEdmType(final FullQualifiedName fullQualifiedName) { - if(!fullQualifiedName.getNamespace().equals(EdmPrimitiveType.EDM_NAMESPACE)) { + private EdmType getPrimitiveType(final FullQualifiedName fullQualifiedName) { + if (EdmPrimitiveType.EDM_NAMESPACE.equals(fullQualifiedName.getNamespace())) { + final EdmPrimitiveTypeKind primitiveTypeKind = EdmPrimitiveTypeKind.valueOf(fullQualifiedName.getName()); + return primitiveTypeKind == null ? null : odata.createPrimitiveTypeInstance(primitiveTypeKind); + } else { return null; } - - final EdmPrimitiveTypeKind primitiveTypeKind = EdmPrimitiveTypeKind.valueOfFQN(fullQualifiedName); - return primitiveTypeKind == null ? null : EdmPrimitiveTypeFactory.getInstance(primitiveTypeKind); } private void parseDollarRoot(UriInfoImpl uriInfo) throws UriParserException, UriValidationException { @@ -694,7 +656,7 @@ public class ExpressionParser { parseSingleNavigationExpr(uriInfo, resource); } - private void parseDollarIt(UriInfoImpl uriInfo, EdmType referringType) + private void parseDollarIt(UriInfoImpl uriInfo, final EdmType referringType) throws UriParserException, UriValidationException { UriResourceItImpl itResource = new UriResourceItImpl(referringType, false); uriInfo.addResourcePart(itResource); @@ -759,16 +721,14 @@ public class ExpressionParser { if (edmEntityType != null) { if (allowTypeFilter) { setTypeFilter(lastResource, edmEntityType); - - if(tokenizer.next(TokenKind.SLASH)) { - final TokenKind nextTokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, - TokenKind.ODataIdentifier); - if(nextTokenKind == TokenKind.ODataIdentifier) { - parsePropertyPathExpr(uriInfo, lastResource); - } else if(nextTokenKind == TokenKind.QualifiedName) { + + if (tokenizer.next(TokenKind.SLASH)) { + if (tokenizer.next(TokenKind.QualifiedName)) { parseBoundFunction(fullQualifiedName, uriInfo, lastResource); + } else if (tokenizer.next(TokenKind.ODataIdentifier)) { + parsePropertyPathExpr(uriInfo, lastResource); } else { - throw new UriParserSyntaxException("Extected OData Identifier or Full Qualified Name", + throw new UriParserSyntaxException("Expected OData Identifier or Full Qualified Name.", UriParserSyntaxException.MessageKeys.SYNTAX); } } @@ -814,7 +774,9 @@ public class ExpressionParser { if (property == null) { throw new UriParserSemanticException("Unknown property.", - UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE, oDataIdentifier); + UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE, + lastType.getFullQualifiedName().getFullQualifiedNameAsString(), + oDataIdentifier); } if (property.getType() instanceof EdmComplexType) { @@ -823,7 +785,9 @@ public class ExpressionParser { uriInfo.addResourcePart(complexResource); if (property.isCollection()) { - parseCollectionPathExpr(uriInfo, complexResource); + if (tokenizer.next(TokenKind.SLASH)) { + parseCollectionPathExpr(uriInfo, complexResource); + } } else { parseComplexPathExpr(uriInfo, complexResource); } @@ -847,7 +811,9 @@ public class ExpressionParser { uriInfo.addResourcePart(primitiveResource); if (property.isCollection()) { - parseCollectionPathExpr(uriInfo, primitiveResource); + if (tokenizer.next(TokenKind.SLASH)) { + parseCollectionPathExpr(uriInfo, primitiveResource); + } } else { parseSinglePathExpr(uriInfo, primitiveResource); } @@ -856,7 +822,6 @@ public class ExpressionParser { private void parseSingleNavigationExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource) throws UriParserException, UriValidationException { - // TODO: Is that correct? if (tokenizer.next(TokenKind.SLASH)) { final TokenKind tokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, TokenKind.ODataIdentifier); parseMemberExpression(tokenKind, uriInfo, lastResource, true); @@ -865,8 +830,22 @@ public class ExpressionParser { private void parseCollectionNavigationExpr(UriInfoImpl uriInfo, UriResourcePartTyped lastResource) throws UriParserException, UriValidationException { - // TODO: Is type cast missing? - if (tokenizer.next(TokenKind.OPEN)) { + boolean hasSlash = false; + if (tokenizer.next(TokenKind.SLASH)) { + hasSlash = true; + if (tokenizer.next(TokenKind.QualifiedName)) { + final FullQualifiedName qualifiedName = new FullQualifiedName(tokenizer.getText()); + final EdmEntityType edmEntityType = edm.getEntityType(qualifiedName); + if (edmEntityType == null) { + parseBoundFunction(qualifiedName, uriInfo, lastResource); + } else { + setTypeFilter(lastResource, edmEntityType); + } + hasSlash = false; + } + } + + if (!hasSlash && tokenizer.next(TokenKind.OPEN)) { if (lastResource instanceof UriResourceNavigation) { ((UriResourceNavigationPropertyImpl) lastResource).setKeyPredicates( ParserHelper.parseNavigationKeyPredicate(tokenizer, @@ -883,7 +862,10 @@ public class ExpressionParser { } parseSingleNavigationExpr(uriInfo, lastResource); } - parseCollectionPathExpr(uriInfo, lastResource); + + if (hasSlash || tokenizer.next(TokenKind.SLASH)) { + parseCollectionPathExpr(uriInfo, lastResource); + } } private void parseSinglePathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource) @@ -932,25 +914,23 @@ public class ExpressionParser { private void parseCollectionPathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource) throws UriParserException, UriValidationException { - - if (tokenizer.next(TokenKind.SLASH)) { - if (tokenizer.next(TokenKind.COUNT)) { - uriInfo.addResourcePart(new UriResourceCountImpl()); - } else if (tokenizer.next(TokenKind.ANY)) { - uriInfo.addResourcePart(parseLambdaRest(TokenKind.ANY, lastResource)); - } else if (tokenizer.next(TokenKind.ALL)) { - uriInfo.addResourcePart(parseLambdaRest(TokenKind.ALL, lastResource)); - } else if (tokenizer.next(TokenKind.QualifiedName)) { - final FullQualifiedName fullQualifiedName = new FullQualifiedName(tokenizer.getText()); - parseBoundFunction(fullQualifiedName, uriInfo, lastResource); - } + // The initial slash (see grammar) must have been checked and consumed by the caller. + if (tokenizer.next(TokenKind.COUNT)) { + uriInfo.addResourcePart(new UriResourceCountImpl()); + } else if (tokenizer.next(TokenKind.ANY)) { + uriInfo.addResourcePart(parseLambdaRest(TokenKind.ANY, lastResource)); + } else if (tokenizer.next(TokenKind.ALL)) { + uriInfo.addResourcePart(parseLambdaRest(TokenKind.ALL, lastResource)); + } else if (tokenizer.next(TokenKind.QualifiedName)) { + final FullQualifiedName fullQualifiedName = new FullQualifiedName(tokenizer.getText()); + parseBoundFunction(fullQualifiedName, uriInfo, lastResource); } } private void parseFunction(final FullQualifiedName fullQualifiedName, UriInfoImpl uriInfo, final EdmType lastType, final boolean lastIsCollection) throws UriParserException, UriValidationException { - final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, true); + final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true); final List<String> parameterNames = ParserHelper.getParameterNames(parameters); final EdmFunction boundFunction = edm.getBoundFunction(fullQualifiedName, lastType.getFullQualifiedName(), lastIsCollection, parameterNames); @@ -966,18 +946,19 @@ public class ExpressionParser { return; } - throw new UriParserSemanticException("No function found.", + throw new UriParserSemanticException("No function '" + fullQualifiedName + "' found.", UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, fullQualifiedName.getFullQualifiedNameAsString()); } private void parseBoundFunction(final FullQualifiedName fullQualifiedName, UriInfoImpl uriInfo, final UriResourcePartTyped lastResource) throws UriParserException, UriValidationException { - final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, true); + final EdmType type = lastResource.getType(); + final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true); final List<String> parameterNames = ParserHelper.getParameterNames(parameters); final EdmFunction boundFunction = edm.getBoundFunction(fullQualifiedName, - lastResource.getType().getFullQualifiedName(), lastResource.isCollection(), parameterNames); + type.getFullQualifiedName(), lastResource.isCollection(), parameterNames); if (boundFunction == null) { - throw new UriParserSemanticException("Bound function not found.", + throw new UriParserSemanticException("Bound function '" + fullQualifiedName + "' not found.", UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, fullQualifiedName.getFullQualifiedNameAsString()); } parseFunctionRest(uriInfo, boundFunction, parameters); @@ -995,19 +976,23 @@ public class ExpressionParser { if (function.isComposable()) { if (edmType instanceof EdmEntityType ) { if (isCollection) { - parseCollectionNavigationExpr(uriInfo, functionResource); + parseCollectionNavigationExpr(uriInfo, functionResource); } else { parseSingleNavigationExpr(uriInfo, functionResource); } } else if (edmType instanceof EdmComplexType) { if (isCollection) { - parseCollectionPathExpr(uriInfo, functionResource); + if (tokenizer.next(TokenKind.SLASH)) { + parseCollectionPathExpr(uriInfo, functionResource); + } } else { parseComplexPathExpr(uriInfo, functionResource); } } else if (edmType instanceof EdmPrimitiveType) { if (isCollection) { - parseCollectionPathExpr(uriInfo, functionResource); + if (tokenizer.next(TokenKind.SLASH)) { + parseCollectionPathExpr(uriInfo, functionResource); + } } else { parseSinglePathExpr(uriInfo, functionResource); } @@ -1077,7 +1062,7 @@ public class ExpressionParser { TokenKind.YearMethod); } - private EdmType getType(final Expression expression) throws UriParserException { + protected static EdmType getType(final Expression expression) throws UriParserException { EdmType type; if (expression instanceof Literal) { type = ((Literal) expression).getType(); @@ -1108,13 +1093,12 @@ public class ExpressionParser { return type; } - private boolean isType(final Expression expression, final EdmPrimitiveTypeKind... kinds) throws UriParserException { - final EdmType expressionType = getType(expression); - if (expressionType == null) { + private boolean isType(final EdmType type, final EdmPrimitiveTypeKind... kinds) throws UriParserException { + if (type == null) { return true; } for (final EdmPrimitiveTypeKind kind : kinds) { - if (expressionType.equals(odata.createPrimitiveTypeInstance(kind))) { + if (type.equals(odata.createPrimitiveTypeInstance(kind))) { return true; } } @@ -1122,12 +1106,13 @@ public class ExpressionParser { } private void checkType(final Expression expression, final EdmPrimitiveTypeKind... kinds) throws UriParserException { - if (!isType(expression, kinds)) { + final EdmType type = getType(expression); + if (!isType(type, kinds)) { throw new UriParserSemanticException("Incompatible type.", UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, // TODO: better message - getType(expression) == null ? + type == null ? "" : - getType(expression).getFullQualifiedName().getFullQualifiedNameAsString()); + type.getFullQualifiedName().getFullQualifiedNameAsString()); } } @@ -1137,21 +1122,21 @@ public class ExpressionParser { if (leftType == null || rightType == null || leftType.equals(rightType)) { return; } - + // Numeric promotion for Edm.Byte and Edm.SByte - if((leftType instanceof EdmByte || leftType instanceof EdmSByte) - && (rightType instanceof EdmByte || rightType instanceof EdmSByte)) { + if (isType(leftType, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte) + && isType(rightType, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte)) { return; } - + if (leftType.getKind() != EdmTypeKind.PRIMITIVE || rightType.getKind() != EdmTypeKind.PRIMITIVE || !(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType) - || ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) - { + || ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) { throw new UriParserSemanticException("Incompatible types.", - UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE, leftType.getFullQualifiedName().toString(), - rightType.getFullQualifiedName().toString()); + UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE, + leftType.getFullQualifiedName().getFullQualifiedNameAsString(), + rightType.getFullQualifiedName().getFullQualifiedNameAsString()); } } @@ -1164,12 +1149,12 @@ public class ExpressionParser { } return type; } - + private boolean isEnumType(final Expression expression) throws UriParserException { final EdmType expressionType = getType(expression); return expressionType == null || expressionType.getKind() == EdmTypeKind.ENUM - || isType(expression, + || isType(expressionType, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte); } @@ -1208,44 +1193,69 @@ public class ExpressionParser { EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.TimeOfDay, EdmPrimitiveTypeKind.DateTimeOffset, EdmPrimitiveTypeKind.Duration); if (!(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType) - || ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) { + || ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) { throw new UriParserSemanticException("Incompatible types.", - UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, ""); // TODO: better message + UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE, + leftType.getFullQualifiedName().getFullQualifiedNameAsString(), + rightType.getFullQualifiedName().getFullQualifiedNameAsString()); } } - private void checkAddSubTypes(final Expression left, final Expression right, final boolean isAdd) + private EdmType getAddSubTypeAndCheckLeftAndRight(final Expression left, final Expression right, final boolean isSub) throws UriParserException { final EdmType leftType = getType(left); final EdmType rightType = getType(right); - if (leftType == null || rightType == null - || isType(left, + if (leftType == null || rightType == null) { + return null; + } + if (isType(leftType, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double) - && isType(right, + && isType(rightType, EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte, EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double)) { - return; + // The result type must be able to handle the overflow, + // so we return always a wider type than the types of the operands. + if (isType(leftType, EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double) + || isType(rightType, + EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double)) { + return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double); + } else if (isType(leftType, EdmPrimitiveTypeKind.Int64) || isType(rightType, EdmPrimitiveTypeKind.Int64)) { + return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal); + } else if (isType(leftType, EdmPrimitiveTypeKind.Int32) || isType(rightType, EdmPrimitiveTypeKind.Int32)) { + return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int64); + } else if (isType(leftType, EdmPrimitiveTypeKind.Int16) || isType(rightType, EdmPrimitiveTypeKind.Int16)) { + return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int32); + } else { + return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int16); + } } - if (isType(left, EdmPrimitiveTypeKind.DateTimeOffset) - && (isType(right, EdmPrimitiveTypeKind.Duration) - || isType(right, EdmPrimitiveTypeKind.DateTimeOffset) && !isAdd)) { - return; + if ((isType(leftType, EdmPrimitiveTypeKind.DateTimeOffset) + || isType(leftType, EdmPrimitiveTypeKind.Duration)) + && isType(rightType, EdmPrimitiveTypeKind.Duration)) { + return leftType; } - if (isType(left, EdmPrimitiveTypeKind.Duration) && isType(right, EdmPrimitiveTypeKind.Duration) - || isType(left, EdmPrimitiveTypeKind.Date) - && (isType(right, EdmPrimitiveTypeKind.Duration) || isType(right, EdmPrimitiveTypeKind.Date) && !isAdd)) { - return; + if (isType(leftType, EdmPrimitiveTypeKind.Date) && isType(rightType, EdmPrimitiveTypeKind.Duration)) { + return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.DateTimeOffset); + } + if (isSub + && (isType(leftType, EdmPrimitiveTypeKind.DateTimeOffset) + && isType(rightType, EdmPrimitiveTypeKind.DateTimeOffset) + || isType(leftType, EdmPrimitiveTypeKind.Date) + && isType(rightType, EdmPrimitiveTypeKind.Date))) { + return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Duration); } throw new UriParserSemanticException("Incompatible types.", - UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, ""); // TODO: better message + UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE, + leftType.getFullQualifiedName().getFullQualifiedNameAsString(), + rightType.getFullQualifiedName().getFullQualifiedNameAsString()); } private void checkStructuredTypeFilter(final EdmType type, final EdmType filterType) throws UriParserException { - if (!(filterType instanceof EdmStructuredType && ((EdmStructuredType)filterType).compatibleTo(type))) { + if (!(filterType instanceof EdmStructuredType && ((EdmStructuredType) filterType).compatibleTo(type))) { throw new UriParserSemanticException("Incompatible type filter.", UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER, filterType.getFullQualifiedName().getFullQualifiedNameAsString()); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java index e2767a1..dd73009 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/FilterParser.java @@ -21,6 +21,7 @@ package org.apache.olingo.server.core.uri.parser; import java.util.Collection; import org.apache.olingo.commons.api.edm.Edm; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.edm.EdmType; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.queryoption.FilterOption; @@ -43,7 +44,13 @@ public class FilterParser { throws UriParserException, UriValidationException { final Expression filterExpression = new ExpressionParser(edm, odata) .parse(tokenizer, referencedType, crossjoinEntitySetNames); - // TODO: Check that the expression is boolean. - return new FilterOptionImpl().setExpression(filterExpression); + final EdmType type = ExpressionParser.getType(filterExpression); + if (type == null || type.equals(odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean))) { + return new FilterOptionImpl().setExpression(filterExpression); + } else { + throw new UriParserSemanticException("Filter expressions must be boolean.", + UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE, + "Edm.Boolean", type.getFullQualifiedName().getFullQualifiedNameAsString()); + } } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java index 47efda5..3125fa6 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java @@ -18,18 +18,11 @@ */ package org.apache.olingo.server.core.uri.parser; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; -import org.antlr.v4.runtime.ANTLRInputStream; -import org.antlr.v4.runtime.BailErrorStrategy; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.ParserRuleContext; -import org.antlr.v4.runtime.RecognitionException; -import org.antlr.v4.runtime.atn.PredictionMode; -import org.antlr.v4.runtime.misc.ParseCancellationException; import org.apache.olingo.commons.api.edm.Edm; -import org.apache.olingo.commons.api.edm.EdmEntityContainer; import org.apache.olingo.commons.api.edm.EdmEntitySet; import org.apache.olingo.commons.api.edm.EdmStructuredType; import org.apache.olingo.commons.api.edm.EdmType; @@ -52,14 +45,10 @@ import org.apache.olingo.server.api.uri.queryoption.SystemQueryOptionKind; 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.UriResourceStartingTypeFilterImpl; -import org.apache.olingo.server.core.uri.antlr.UriLexer; -import org.apache.olingo.server.core.uri.antlr.UriParserParser; -import org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemsEOFContext; import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; import org.apache.olingo.server.core.uri.parser.search.SearchParser; import org.apache.olingo.server.core.uri.queryoption.AliasQueryOptionImpl; import org.apache.olingo.server.core.uri.queryoption.CountOptionImpl; -import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl; import org.apache.olingo.server.core.uri.queryoption.FormatOptionImpl; import org.apache.olingo.server.core.uri.queryoption.IdOptionImpl; import org.apache.olingo.server.core.uri.queryoption.SkipOptionImpl; @@ -77,8 +66,6 @@ public class Parser { private final Edm edm; private final OData odata; - private enum ParserEntryRules { ExpandItems } - public Parser(final Edm edm, final OData odata) { this.edm = edm; this.odata = odata; @@ -87,8 +74,9 @@ public class Parser { public UriInfo parseUri(final String path, final String query, final String fragment) throws UriParserException, UriValidationException { - UriContext context = new UriContext(); - UriParseTreeVisitor uriParseTreeVisitor = new UriParseTreeVisitor(edm, context); + UriInfoImpl contextUriInfo = new UriInfoImpl(); + Deque<EdmType> contextTypes = new ArrayDeque<EdmType>(); + boolean contextIsCollection = false; final List<String> pathSegmentsDecoded = UriDecoder.splitAndDecodePath(path); final int numberOfSegments = pathSegmentsDecoded.size(); @@ -98,49 +86,46 @@ public class Parser { if (firstSegment.isEmpty()) { ensureLastSegment(firstSegment, 0, numberOfSegments); - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.service); + contextUriInfo.setKind(UriInfoKind.service); } else if (firstSegment.equals("$batch")) { ensureLastSegment(firstSegment, 1, numberOfSegments); - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.batch); + contextUriInfo.setKind(UriInfoKind.batch); } else if (firstSegment.equals("$metadata")) { ensureLastSegment(firstSegment, 1, numberOfSegments); - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.metadata); - context.contextUriInfo.setFragment(fragment); + contextUriInfo.setKind(UriInfoKind.metadata); + contextUriInfo.setFragment(fragment); } else if (firstSegment.equals("$all")) { ensureLastSegment(firstSegment, 1, numberOfSegments); - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.all); + contextUriInfo.setKind(UriInfoKind.all); // This loads nearly the whole schema, but sooner or later '$all' needs all entity sets anyway. for (final EdmEntitySet entitySet : edm.getEntityContainer().getEntitySets()) { - context.contextTypes.push(entitySet.getEntityType()); + contextTypes.push(entitySet.getEntityType()); } - context.isCollection = true; + contextIsCollection = true; } else if (firstSegment.equals("$entity")) { if (numberOfSegments > 1) { final String typeCastSegment = pathSegmentsDecoded.get(1); ensureLastSegment(typeCastSegment, 2, numberOfSegments); - context.contextUriInfo = new ResourcePathParser(edm).parseDollarEntityTypeCast(typeCastSegment); - context.contextTypes.push(context.contextUriInfo.getEntityTypeCast()); + contextUriInfo = new ResourcePathParser(edm).parseDollarEntityTypeCast(typeCastSegment); + contextTypes.push(contextUriInfo.getEntityTypeCast()); } else { - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.entityId); + contextUriInfo.setKind(UriInfoKind.entityId); // The type of the entity is not known until the $id query option has been parsed. + // TODO: Set the type (needed for the evaluation of system query options). } - context.isCollection = false; + contextIsCollection = false; } else if (firstSegment.startsWith("$crossjoin")) { ensureLastSegment(firstSegment, 1, numberOfSegments); - context.contextUriInfo = new ResourcePathParser(edm).parseCrossjoinSegment(firstSegment); - final EdmEntityContainer container = edm.getEntityContainer(); - for (final String name : context.contextUriInfo.getEntitySetNames()) { - context.contextTypes.push(container.getEntitySet(name).getEntityType()); - } - context.isCollection = true; + contextUriInfo = new ResourcePathParser(edm).parseCrossjoinSegment(firstSegment); + contextIsCollection = true; } else { - context.contextUriInfo = new UriInfoImpl().setKind(UriInfoKind.resource); + contextUriInfo.setKind(UriInfoKind.resource); final ResourcePathParser resourcePathParser = new ResourcePathParser(edm); int count = 0; UriResource lastSegment = null; @@ -168,7 +153,7 @@ public class Parser { } else { lastSegment = segment; } - context.contextUriInfo.addResourcePart(segment); + contextUriInfo.addResourcePart(segment); } } @@ -176,9 +161,9 @@ public class Parser { final UriResourcePartTyped typed = (UriResourcePartTyped) lastSegment; final EdmType type = ParserHelper.getTypeInformation(typed); if (type != null) { // could be null for, e.g., actions without return type - context.contextTypes.push(type); + contextTypes.push(type); } - context.isCollection = typed.isCollection(); + contextIsCollection = typed.isCollection(); } } @@ -191,9 +176,10 @@ public class Parser { SystemQueryOption systemOption = null; if (optionName.equals(SystemQueryOptionKind.FILTER.toString())) { UriTokenizer filterTokenizer = new UriTokenizer(optionValue); - // The Referring type could also be a primitive type not only a structured type - systemOption = new FilterParser(edm, odata).parse(filterTokenizer, context.contextTypes.peek(), - context.contextUriInfo.getEntitySetNames()); + // The referring type could be a primitive type or a structured type. + systemOption = new FilterParser(edm, odata).parse(filterTokenizer, + contextTypes.peek(), + contextUriInfo.getEntitySetNames()); checkOptionEOF(filterTokenizer, optionName, optionValue); } else if (optionName.equals(SystemQueryOptionKind.FORMAT.toString())) { @@ -211,19 +197,26 @@ public class Parser { systemOption = formatOption; } else if (optionName.equals(SystemQueryOptionKind.EXPAND.toString())) { - try { - ExpandItemsEOFContext ctxExpandItems = - (ExpandItemsEOFContext) parseRule(optionValue, ParserEntryRules.ExpandItems); - systemOption = (ExpandOptionImpl) uriParseTreeVisitor.visitExpandItemsEOF(ctxExpandItems); - } catch (final ParseCancellationException e) { - throw e.getCause() instanceof UriParserException ? - (UriParserException) e.getCause() : - new UriParserSyntaxException("Syntax error", e, UriParserSyntaxException.MessageKeys.SYNTAX); + if (contextTypes.peek() instanceof EdmStructuredType + || !contextUriInfo.getEntitySetNames().isEmpty() + || contextUriInfo.getKind() == UriInfoKind.entityId) { // TODO: Remove once the type has been set above. + UriTokenizer expandTokenizer = new UriTokenizer(optionValue); + systemOption = new ExpandParser(edm, odata).parse(expandTokenizer, + contextTypes.peek() instanceof EdmStructuredType ? (EdmStructuredType) contextTypes.peek() : null); + checkOptionEOF(expandTokenizer, optionName, optionValue); + } else { + throw new UriValidationException("Expand is only allowed on structured types!", + UriValidationException.MessageKeys.SYSTEM_QUERY_OPTION_NOT_ALLOWED, optionName); } } else if (optionName.equals(SystemQueryOptionKind.ID.toString())) { IdOptionImpl idOption = new IdOptionImpl(); idOption.setText(optionValue); + if (optionValue == null || optionValue.isEmpty()) { + throw new UriParserSyntaxException("Illegal value of $id option!", + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, + optionName, optionValue); + } idOption.setValue(optionValue); systemOption = idOption; @@ -234,10 +227,8 @@ public class Parser { } else if (optionName.equals(SystemQueryOptionKind.ORDERBY.toString())) { UriTokenizer orderByTokenizer = new UriTokenizer(optionValue); systemOption = new OrderByParser(edm, odata).parse(orderByTokenizer, - context.contextTypes.peek() instanceof EdmStructuredType ? - (EdmStructuredType) context.contextTypes.peek() : - null, - context.contextUriInfo.getEntitySetNames()); + contextTypes.peek() instanceof EdmStructuredType ? (EdmStructuredType) contextTypes.peek() : null, + contextUriInfo.getEntitySetNames()); checkOptionEOF(orderByTokenizer, optionName, optionValue); } else if (optionName.equals(SystemQueryOptionKind.SEARCH.toString())) { @@ -246,40 +237,31 @@ public class Parser { } else if (optionName.equals(SystemQueryOptionKind.SELECT.toString())) { UriTokenizer selectTokenizer = new UriTokenizer(optionValue); systemOption = new SelectParser(edm).parse(selectTokenizer, - context.contextTypes.peek() instanceof EdmStructuredType ? - (EdmStructuredType) context.contextTypes.peek() : - null, - context.isCollection); + contextTypes.peek() instanceof EdmStructuredType ? (EdmStructuredType) contextTypes.peek() : null, + contextIsCollection); checkOptionEOF(selectTokenizer, optionName, optionValue); } else if (optionName.equals(SystemQueryOptionKind.SKIP.toString())) { SkipOptionImpl skipOption = new SkipOptionImpl(); skipOption.setText(optionValue); - try { - skipOption.setValue(Integer.parseInt(optionValue)); - } catch (final NumberFormatException e) { - throw new UriParserSyntaxException("Illegal value of $skip option!", e, - UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, - optionName, optionValue); - } + skipOption.setValue(ParserHelper.parseNonNegativeInteger(optionName, optionValue, true)); systemOption = skipOption; } else if (optionName.equals(SystemQueryOptionKind.SKIPTOKEN.toString())) { SkipTokenOptionImpl skipTokenOption = new SkipTokenOptionImpl(); skipTokenOption.setText(optionValue); + if (optionValue == null || optionValue.isEmpty()) { + throw new UriParserSyntaxException("Illegal value of $skiptoken option!", + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, + optionName, optionValue); + } skipTokenOption.setValue(optionValue); systemOption = skipTokenOption; } else if (optionName.equals(SystemQueryOptionKind.TOP.toString())) { TopOptionImpl topOption = new TopOptionImpl(); topOption.setText(optionValue); - try { - topOption.setValue(Integer.parseInt(optionValue)); - } catch (final NumberFormatException e) { - throw new UriParserSyntaxException("Illegal value of $top option!", e, - UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, - optionName, optionValue); - } + topOption.setValue(ParserHelper.parseNonNegativeInteger(optionName, optionValue, true)); systemOption = topOption; } else if (optionName.equals(SystemQueryOptionKind.COUNT.toString())) { @@ -299,14 +281,14 @@ public class Parser { UriParserSyntaxException.MessageKeys.UNKNOWN_SYSTEM_QUERY_OPTION, optionName); } try { - context.contextUriInfo.setSystemQueryOption(systemOption); + contextUriInfo.setSystemQueryOption(systemOption); } catch (final ODataRuntimeException e) { throw new UriParserSyntaxException("Double system query option!", e, UriParserSyntaxException.MessageKeys.DOUBLE_SYSTEM_QUERY_OPTION, optionName); } } else if (optionName.startsWith(AT)) { - if (context.contextUriInfo.getAlias(optionName) == null) { + if (contextUriInfo.getAlias(optionName) == null) { // TODO: Create a proper alias-value parser that can parse also common expressions. Expression expression = null; UriTokenizer aliasTokenizer = new UriTokenizer(optionValue); @@ -318,13 +300,13 @@ public class Parser { } else { UriTokenizer aliasValueTokenizer = new UriTokenizer(optionValue); expression = new ExpressionParser(edm, odata).parse(aliasValueTokenizer, null, - context.contextUriInfo.getEntitySetNames()); + contextUriInfo.getEntitySetNames()); if (!aliasValueTokenizer.next(TokenKind.EOF)) { throw new UriParserSyntaxException("Illegal value for alias '" + optionName + "'.", UriParserSyntaxException.MessageKeys.SYNTAX); } } - context.contextUriInfo.addAlias((AliasQueryOption) new AliasQueryOptionImpl() + contextUriInfo.addAlias((AliasQueryOption) new AliasQueryOptionImpl() .setAliasValue(expression) .setName(optionName) .setText(NULL.equals(optionValue) ? null : optionValue)); @@ -334,11 +316,11 @@ public class Parser { } } else { - context.contextUriInfo.addCustomQueryOption((CustomQueryOption) option); + contextUriInfo.addCustomQueryOption((CustomQueryOption) option); } } - return context.contextUriInfo; + return contextUriInfo; } private void ensureLastSegment(final String segment, final int pos, final int size) @@ -362,102 +344,4 @@ public class Parser { optionName, optionValue); } } - - private ParserRuleContext parseRule(final String input, final ParserEntryRules entryPoint) - throws UriParserSyntaxException { - UriParserParser parser = null; - UriLexer lexer = null; - ParserRuleContext ret = null; - - // Use 2 stage approach to improve performance - // see https://github.com/antlr/antlr4/issues/192 - - // stage = 1 - try { - - // create parser - lexer = new UriLexer(new ANTLRInputStream(input)); - parser = new UriParserParser(new CommonTokenStream(lexer)); - - // Set error strategy - addStage1ErrorStrategy(parser); - - // Set error collector - addStage1ErrorListener(parser); - - // user the faster LL parsing - parser.getInterpreter().setPredictionMode(PredictionMode.SLL); - - // parse - switch (entryPoint) { - case ExpandItems: - lexer.mode(Lexer.DEFAULT_MODE); - ret = parser.expandItemsEOF(); - break; - default: - break; - - } - - } catch (ParseCancellationException hardException) { - // stage = 2 - try { - - // create parser - lexer = new UriLexer(new ANTLRInputStream(input)); - parser = new UriParserParser(new CommonTokenStream(lexer)); - - // Set error strategy - addStage2ErrorStrategy(parser); - - // Set error collector - addStage2ErrorListener(parser); - - // Use the slower SLL parsing - parser.getInterpreter().setPredictionMode(PredictionMode.LL); - - // parse - switch (entryPoint) { - case ExpandItems: - lexer.mode(Lexer.DEFAULT_MODE); - ret = parser.expandItemsEOF(); - break; - default: - break; - } - - } catch (final RecognitionException weakException) { - throw new UriParserSyntaxException("Error in syntax", weakException, - UriParserSyntaxException.MessageKeys.SYNTAX); - - // exceptionOnStage = 2; - } - } catch (final RecognitionException hardException) { - throw new UriParserSyntaxException("Error in syntax", hardException, - UriParserSyntaxException.MessageKeys.SYNTAX); - } - - return ret; - } - - protected void addStage1ErrorStrategy(final UriParserParser parser) { - // Throw exception at first syntax error - parser.setErrorHandler(new BailErrorStrategy()); - - } - - protected void addStage2ErrorStrategy(final UriParserParser parser) { - // Throw exception at first syntax error - parser.setErrorHandler(new BailErrorStrategy()); - } - - protected void addStage1ErrorListener(final UriParserParser parser) { - // No error logging to System.out or System.err, only exceptions used (depending on ErrorStrategy) - parser.removeErrorListeners(); - } - - protected void addStage2ErrorListener(final UriParserParser parser) { - // No error logging to System.out or System.err, only exceptions used (depending on ErrorStrategy) - parser.removeErrorListeners(); - } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java index 65ee461..7f4abf7 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.olingo.commons.api.edm.Edm; import org.apache.olingo.commons.api.edm.EdmEntityType; import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef; import org.apache.olingo.commons.api.edm.EdmNavigationProperty; @@ -36,6 +37,8 @@ import org.apache.olingo.commons.api.edm.constants.EdmTypeKind; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.uri.UriParameter; import org.apache.olingo.server.api.uri.UriResourcePartTyped; +import org.apache.olingo.server.api.uri.queryoption.expression.Expression; +import org.apache.olingo.server.api.uri.queryoption.expression.Literal; import org.apache.olingo.server.core.ODataImpl; import org.apache.olingo.server.core.uri.UriParameterImpl; import org.apache.olingo.server.core.uri.UriResourceTypedImpl; @@ -88,8 +91,9 @@ public class ParserHelper { TokenKind.EnumValue); } - protected static List<UriParameter> parseFunctionParameters(UriTokenizer tokenizer, final boolean withComplex) - throws UriParserException { + protected static List<UriParameter> parseFunctionParameters(UriTokenizer tokenizer, + final Edm edm, final EdmType referringType, final boolean withComplex) + throws UriParserException, UriValidationException { List<UriParameter> parameters = new ArrayList<UriParameter>(); ParserHelper.requireNext(tokenizer, TokenKind.OPEN); if (tokenizer.next(TokenKind.CLOSE)) { @@ -115,6 +119,13 @@ public class ParserHelper { throw new UriParserSemanticException("A JSON array or object is not allowed as parameter value.", UriParserSemanticException.MessageKeys.COMPLEX_PARAMETER_IN_RESOURCE_PATH, tokenizer.getText()); } + } else if (withComplex) { + final Expression expression = new ExpressionParser(edm, odata).parse(tokenizer, referringType, null); + parameters.add(new UriParameterImpl().setName(name) + .setText(expression instanceof Literal ? + "null".equals(((Literal) expression).getText()) ? null : ((Literal) expression).getText() : + null) + .setExpression(expression instanceof Literal ? null : expression)); } else if (nextPrimitiveValue(tokenizer) == null) { throw new UriParserSemanticException("Wrong parameter value.", UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, ""); @@ -387,4 +398,23 @@ public class ParserHelper { return type; } + + protected static int parseNonNegativeInteger(final String optionName, final String optionValue, + final boolean zeroAllowed) throws UriParserException { + int value; + try { + value = Integer.parseInt(optionValue); + } catch (final NumberFormatException e) { + throw new UriParserSyntaxException("Illegal value of '" + optionName + "' option!", e, + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, + optionName, optionValue); + } + if (value > 0 || value == 0 && zeroAllowed) { + return value; + } else { + throw new UriParserSyntaxException("Illegal value of '" + optionName + "' option!", + UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, + optionName, optionValue); + } + } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java index 1cd4d7a..87cb91a 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java @@ -261,30 +261,33 @@ public class ResourcePathParser { throws UriParserException, UriValidationException { final FullQualifiedName name = new FullQualifiedName(tokenizer.getText()); requireTyped(previous, name.getFullQualifiedNameAsString()); - final UriResourcePartTyped previousTyped = (UriResourcePartTyped) previous; - final EdmType previousTypeFilter = getPreviousTypeFilter(previousTyped); - final EdmType previousType = previousTypeFilter == null ? previousTyped.getType() : previousTypeFilter; - final EdmAction boundAction = edm.getBoundAction(name, - previousType.getFullQualifiedName(), - previousTyped.isCollection()); - if (boundAction != null) { - ParserHelper.requireTokenEnd(tokenizer); - return new UriResourceActionImpl(boundAction); - } - EdmStructuredType type = edm.getEntityType(name); - if (type == null) { - type = edm.getComplexType(name); - } - if (type != null) { - return typeCast(name, type, previousTyped); - } - if (tokenizer.next(TokenKind.EOF)) { - throw new UriParserSemanticException("Type '" + name.getFullQualifiedNameAsString() + "' not found.", - UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name.getFullQualifiedNameAsString()); - } - return functionCall(null, name, - previousType.getFullQualifiedName(), - previousTyped.isCollection()); + final UriResourcePartTyped previousTyped = (UriResourcePartTyped) previous; + final EdmType previousTypeFilter = getPreviousTypeFilter(previousTyped); + final EdmType previousType = previousTypeFilter == null ? previousTyped.getType() : previousTypeFilter; + + // We check for bound actions first because they cannot be followed by anything. + final EdmAction boundAction = + edm.getBoundAction(name, previousType.getFullQualifiedName(), previousTyped.isCollection()); + if (boundAction != null) { + ParserHelper.requireTokenEnd(tokenizer); + return new UriResourceActionImpl(boundAction); + } + + // Type casts can be syntactically indistinguishable from bound function calls in the case of additional keys. + // But normally they are shorter, so they come next. + final EdmStructuredType type = previousTyped.getType() instanceof EdmEntityType ? + edm.getEntityType(name) : + edm.getComplexType(name); + if (type != null) { + return typeCast(name, type, previousTyped); + } + if (tokenizer.next(TokenKind.EOF)) { + throw new UriParserSemanticException("Type '" + name.getFullQualifiedNameAsString() + "' not found.", + UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name.getFullQualifiedNameAsString()); + } + + // Now a bound function call is the only remaining option. + return functionCall(null, name, previousType.getFullQualifiedName(), previousTyped.isCollection()); } private void requireTyped(final UriResource previous, final String forWhat) throws UriParserException { @@ -317,8 +320,13 @@ public class ResourcePathParser { ((UriResourceWithKeysImpl) previousTyped).setEntryTypeFilter(type); } if (tokenizer.next(TokenKind.OPEN)) { - ((UriResourceWithKeysImpl) previousTyped).setKeyPredicates( - ParserHelper.parseKeyPredicate(tokenizer, (EdmEntityType) type, null)); + final List<UriParameter> keys = ParserHelper.parseKeyPredicate(tokenizer, (EdmEntityType) type, null); + if (previousTyped.isCollection()) { + ((UriResourceWithKeysImpl) previousTyped).setKeyPredicates(keys); + } else { + throw new UriParserSemanticException("Key not allowed here.", + UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED); + } } } else { previousTypeFilter = ((UriResourceTypedImpl) previousTyped).getTypeFilter(); @@ -351,7 +359,7 @@ public class ResourcePathParser { private UriResource functionCall(final EdmFunctionImport edmFunctionImport, final FullQualifiedName boundFunctionName, final FullQualifiedName bindingParameterTypeName, final boolean isBindingParameterCollection) throws UriParserException, UriValidationException { - final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, false); + final List<UriParameter> parameters = ParserHelper.parseFunctionParameters(tokenizer, edm, null, false); final List<String> names = ParserHelper.getParameterNames(parameters); EdmFunction function = null; if (edmFunctionImport != null) { http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SearchParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SearchParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SearchParser.java new file mode 100644 index 0000000..a072327 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SearchParser.java @@ -0,0 +1,108 @@ +/* + * 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 org.apache.olingo.server.api.uri.queryoption.SearchOption; +import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind; +import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression; +import org.apache.olingo.server.api.uri.queryoption.search.SearchTerm; +import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; +import org.apache.olingo.server.core.uri.parser.search.SearchBinaryImpl; +import org.apache.olingo.server.core.uri.parser.search.SearchParserException; +import org.apache.olingo.server.core.uri.parser.search.SearchTermImpl; +import org.apache.olingo.server.core.uri.parser.search.SearchUnaryImpl; +import org.apache.olingo.server.core.uri.queryoption.SearchOptionImpl; + +/** + * Parses search expressions according to the following (rewritten) grammar: + * <pre> + * SearchExpr ::= ExprOR + * ExprOR ::= ExprAnd ('OR' ExprAnd)* + * ExprAnd ::= Term ('AND'? Term)* + * Term ::= ('NOT'? (Word | Phrase)) | ('(' SearchExpr ')') + * </pre> + */ +public class SearchParser { + + public SearchOption parse(UriTokenizer tokenizer) throws SearchParserException { + SearchOptionImpl searchOption = new SearchOptionImpl(); + searchOption.setSearchExpression(processExprOr(tokenizer)); + return searchOption; + } + + private SearchExpression processExprOr(UriTokenizer tokenizer) throws SearchParserException { + SearchExpression left = processExprAnd(tokenizer); + + while (tokenizer.next(TokenKind.OrOperatorSearch)) { + final SearchExpression right = processExprAnd(tokenizer); + left = new SearchBinaryImpl(left, SearchBinaryOperatorKind.OR, right); + } + + return left; + } + + private SearchExpression processExprAnd(UriTokenizer tokenizer) throws SearchParserException { + SearchExpression left = processTerm(tokenizer); + + while (tokenizer.next(TokenKind.AndOperatorSearch)) { // Could be whitespace or whitespace-surrounded 'AND'. + final SearchExpression right = processTerm(tokenizer); + left = new SearchBinaryImpl(left, SearchBinaryOperatorKind.AND, right); + } + + return left; + } + + private SearchExpression processTerm(UriTokenizer tokenizer) throws SearchParserException { + if (tokenizer.next(TokenKind.OPEN)) { + final SearchExpression expr = processExprOr(tokenizer); + if (!tokenizer.next(TokenKind.CLOSE)) { + throw new SearchParserException("Missing close parenthesis after open parenthesis.", + SearchParserException.MessageKeys.MISSING_CLOSE); + } + return expr; + } else if (tokenizer.next(TokenKind.NotOperatorSearch)) { + return processNot(tokenizer); + } else if (tokenizer.next(TokenKind.Word)) { + return new SearchTermImpl(tokenizer.getText()); + } else if (tokenizer.next(TokenKind.Phrase)) { + return processPhrase(tokenizer); + } else { + throw new SearchParserException("Expected PHRASE or WORD not found.", + SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN, "PHRASE, WORD", ""); + } + } + + private SearchExpression processNot(UriTokenizer tokenizer) throws SearchParserException { + if (tokenizer.next(TokenKind.Word)) { + return new SearchUnaryImpl(new SearchTermImpl(tokenizer.getText())); + } else if (tokenizer.next(TokenKind.Phrase)) { + return new SearchUnaryImpl(processPhrase(tokenizer)); + } else { + throw new SearchParserException("NOT must be followed by a term.", + SearchParserException.MessageKeys.INVALID_NOT_OPERAND, ""); + } + } + + private SearchTerm processPhrase(UriTokenizer tokenizer) { + final String literal = tokenizer.getText(); + return new SearchTermImpl(literal.substring(1, literal.length() - 1) + .replace("\\\"", "\"") + .replace("\\\\", "\\")); + } +}
