[OLINGO-834] $expand parser in Java + clean-up

Signed-off-by: Christian Amend <christian.amend@sap.com>
This commit is contained in:
Klaus Straubinger 2016-01-07 13:55:34 +01:00 committed by Christian Amend
parent 8919d3ef11
commit 8925274c0b
29 changed files with 1928 additions and 4208 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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
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",
// 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);
}
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,19 +399,17 @@ 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);
}
@ -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;
}
@ -623,15 +586,15 @@ public class ExpressionParser {
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);
}
@ -642,8 +605,7 @@ 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);
@ -659,13 +621,13 @@ public class ExpressionParser {
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);
@ -760,15 +722,13 @@ public class ExpressionParser {
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()) {
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()) {
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,8 +862,11 @@ public class ExpressionParser {
}
parseSingleNavigationExpr(uriInfo, lastResource);
}
if (hasSlash || tokenizer.next(TokenKind.SLASH)) {
parseCollectionPathExpr(uriInfo, lastResource);
}
}
private void parseSinglePathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
@ -932,8 +914,7 @@ public class ExpressionParser {
private void parseCollectionPathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.SLASH)) {
// 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)) {
@ -945,12 +926,11 @@ public class ExpressionParser {
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);
@ -1001,13 +982,17 @@ public class ExpressionParser {
}
} else if (edmType instanceof EdmComplexType) {
if (isCollection) {
if (tokenizer.next(TokenKind.SLASH)) {
parseCollectionPathExpr(uriInfo, functionResource);
}
} else {
parseComplexPathExpr(uriInfo, functionResource);
}
} else if (edmType instanceof EdmPrimitiveType) {
if (isCollection) {
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());
}
}
@ -1139,19 +1124,19 @@ public class ExpressionParser {
}
// 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());
}
}
@ -1169,7 +1154,7 @@ public class ExpressionParser {
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);
}
@ -1210,42 +1195,67 @@ public class ExpressionParser {
if (!(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType)
|| ((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(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.DateTimeOffset)
|| isType(leftType, EdmPrimitiveTypeKind.Duration))
&& isType(rightType, EdmPrimitiveTypeKind.Duration)) {
return leftType;
}
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());

View File

@ -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.
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());
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}

View File

@ -264,17 +264,20 @@ public class ResourcePathParser {
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());
// 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);
}
EdmStructuredType type = edm.getEntityType(name);
if (type == null) {
type = edm.getComplexType(name);
}
// 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);
}
@ -282,9 +285,9 @@ public class ResourcePathParser {
throw new UriParserSemanticException("Type '" + name.getFullQualifiedNameAsString() + "' not found.",
UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, name.getFullQualifiedNameAsString());
}
return functionCall(null, name,
previousType.getFullQualifiedName(),
previousTyped.isCollection());
// 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) {

View File

@ -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("\\\\", "\\"));
}
}

View File

@ -1,115 +0,0 @@
/*
* 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.ArrayDeque;
import java.util.Deque;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.queryoption.ExpandItemImpl;
import org.apache.olingo.server.core.uri.queryoption.SelectItemImpl;
/**
* UriContext object used for holding information for URI parsing.
*
*/
public class UriContext {
public static class LambdaVariable {
public String name;
public EdmType type;
}
/**
* Hold all currently allowed lambda variables
* As lambda functions can be nested there may be more than one allowed lambda variables at a time while parsing a
* $filter or $orderby expressions.
*/
public Deque<LambdaVariable> allowedLambdaVariables;
/**
* Used to stack type information for nested $expand, $filter query options and other cases.
*/
public Deque<EdmType> contextTypes;
/** Whether the context types are collections. */
public boolean isCollection;
// CHECKSTYLE:OFF (Maven checkstyle)
/**
* Set within method
* {@link org.apache.olingo.server.core.uri.antlr.UriParserBaseVisitor#visitExpandItem(org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandItemContext ctx)}
* and
* {@link org.apache.olingo.server.core.uri.antlr.UriParserBaseVisitor#visitExpandPathExtension(org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandPathExtensionContext ctx)}
* to allow nodes
* deeper in the expand tree at
* {@link org.apache.olingo.server.core.uri.antlr.UriParserBaseVisitor#visitExpandPathExtension(org.apache.olingo.server.core.uri.antlr.UriParserParser.ExpandPathExtensionContext ctx)}
* appending path
* segments to the currently processed {@link ExpandItemImpl}.
*/
public ExpandItemImpl contextExpandItemPath;
// CHECKSTYLE:ON (Maven checkstyle)
//CHECKSTYLE:OFF (Maven checkstyle)
/**
* Set to true in method {@link UriParseTreeVisitor#visitExpandPath} right before
* calling {@link org.apache.olingo.server.core.uri.parser.UriParseTreeVisitor#readResourcePathSegment}
* After reading the path the variable is set back to false
*
* readResourcePathSegment handles all navigation properties, it depends on the context if key predicates are allowed or not.
* In case of expand
*/
public boolean contextVisitExpandResourcePath;
//CHECKSTYLE:ON (Maven checkstyle)
// CHECKSTYLE:OFF (Maven checkstyle)
/**
* Set within method
* {@link org.apache.olingo.server.core.uri.antlr.UriParserBaseVisitor#visitSelectItem(org.apache.olingo.server.core.uri.antlr.UriParserParser.SelectItemContext ctx)}
* to allow
* nodes
* deeper in the expand tree at
* {@link org.apache.olingo.server.core.uri.antlr.UriParserBaseVisitor#visitSelectSegment(org.apache.olingo.server.core.uri.antlr.UriParserParser.SelectSegmentContext ctx)}
* appending path segments to the
* currently processed {@link SelectItemImpl}.
*/
public SelectItemImpl contextSelectItem;
// CHECKSTYLE:ON (Maven checkstyle)
/**
* Stores the currently processed UriInfo objects. There is one URI Info object for the resource path
* and one for each new first member access within $filter and $orderBy options.
*/
public UriInfoImpl contextUriInfo;
public boolean contextReadingFunctionParameters;
/**
* Set to true if the parser operates on query part.
*/
public boolean contextReadingQueryPart;
public UriContext() {
contextExpandItemPath = null;
contextReadingFunctionParameters = false;
contextSelectItem = null;
contextTypes = new ArrayDeque<EdmType>();
allowedLambdaVariables = new ArrayDeque<UriContext.LambdaVariable>();
}
}

View File

@ -40,6 +40,15 @@ public class UriTokenizer {
ROOT,
IT,
EXPAND,
FILTER,
LEVELS,
ORDERBY,
SEARCH,
SELECT,
SKIP,
TOP,
ANY,
ALL,
@ -53,8 +62,9 @@ public class UriTokenizer {
EQ,
STAR,
PLUS,
MINUS,
NULL,
MAX,
// variable-value tokens (convention: mixed case)
ODataIdentifier,
@ -76,6 +86,13 @@ public class UriTokenizer {
jsonArrayOrObject,
Word,
Phrase,
OrOperatorSearch,
AndOperatorSearch,
NotOperatorSearch,
OrOperator,
AndOperator,
EqualsOperator,
@ -90,6 +107,7 @@ public class UriTokenizer {
MulOperator,
DivOperator,
ModOperator,
MinusOperator,
NotOperator,
CastMethod,
@ -161,6 +179,10 @@ public class UriTokenizer {
boolean found = false;
final int previousIndex = index;
switch (allowedTokenKind) {
case EOF:
found = index >= parseString.length();
break;
// Constants
case REF:
found = nextConstant("$ref");
@ -181,6 +203,31 @@ public class UriTokenizer {
found = nextConstant("$it");
break;
case EXPAND:
found = nextConstant("$expand");
break;
case FILTER:
found = nextConstant("$filter");
break;
case LEVELS:
found = nextConstant("$levels");
break;
case ORDERBY:
found = nextConstant("$orderby");
break;
case SEARCH:
found = nextConstant("$search");
break;
case SELECT:
found = nextConstant("$select");
break;
case SKIP:
found = nextConstant("$skip");
break;
case TOP:
found = nextConstant("$top");
break;
case ANY:
found = nextConstant("any");
break;
@ -218,14 +265,12 @@ public class UriTokenizer {
case PLUS:
found = nextCharacter('+');
break;
case MINUS:
found = nextMinus();
break;
case NULL:
found = nextConstant("null");
break;
case EOF:
found = index >= parseString.length();
case MAX:
found = nextConstant("max");
break;
// Identifiers
@ -282,6 +327,25 @@ public class UriTokenizer {
found = nextJsonArrayOrObject();
break;
// Search
case Word:
found = nextWord();
break;
case Phrase:
found = nextPhrase();
break;
// Operators in Search Expressions
case OrOperatorSearch:
found = nextBinaryOperator("OR");
break;
case AndOperatorSearch:
found = nextAndOperatorSearch();
break;
case NotOperatorSearch:
found = nextUnaryOperator("NOT");
break;
// Operators
case OrOperator:
found = nextBinaryOperator("or");
@ -325,8 +389,12 @@ public class UriTokenizer {
case ModOperator:
found = nextBinaryOperator("mod");
break;
case MinusOperator:
// To avoid unnecessary minus operators for negative numbers, we have to check what follows the minus sign.
found = nextCharacter('-') && !nextDigit() && !nextConstant("INF");
break;
case NotOperator:
found = nextConstant("not") && nextWhitespace();
found = nextUnaryOperator("not");
break;
// Methods
@ -444,27 +512,6 @@ public class UriTokenizer {
return found;
}
private boolean nextMinus() {
if(parseString.startsWith("-", index)) {
final int lastGoodIndex = index;
if(nextDoubleValue()) {
index = lastGoodIndex;
return false;
} else if(nextDecimalValue()) {
index = lastGoodIndex;
return false;
} else if(nextIntegerValue(true)) {
index = lastGoodIndex;
return false;
} else {
index++;
return true;
}
}
return false;
}
/**
* Moves past the given string constant if found; otherwise leaves the index unchanged.
* @return whether the constant has been found at the current index
@ -569,34 +616,6 @@ public class UriTokenizer {
}
return count > 0;
}
/**
* Moves past the given whitespace-surrounded operator constant if found;
* otherwise leaves the index unchanged.
* @return whether the operator has been found at the current index
*/
private boolean nextBinaryOperator(final String operator) {
return nextWhitespace() && nextConstant(operator) && nextWhitespace();
}
/**
* Moves past the given method name and its immediately following opening parenthesis if found;
* otherwise leaves the index unchanged.
* @return whether the method has been found at the current index
*/
private boolean nextMethod(final String methodName) {
return nextConstant(methodName) && nextCharacter('(');
}
/**
* Moves past (required) whitespace and the given suffix name if found;
* otherwise leaves the index unchanged.
* @return whether the suffix has been found at the current index
*/
private boolean nextSuffix(final String suffixName) {
return nextWhitespace() && nextConstant(suffixName);
}
/**
* Moves past an OData identifier if found; otherwise leaves the index unchanged.
* @return whether an OData identifier has been found at the current index
@ -650,6 +669,38 @@ public class UriTokenizer {
}
}
/**
* Moves past the given whitespace-surrounded operator constant if found.
* @return whether the operator has been found at the current index
*/
private boolean nextBinaryOperator(final String operator) {
return nextWhitespace() && nextConstant(operator) && nextWhitespace();
}
/**
* Moves past the given whitespace-suffixed operator constant if found.
* @return whether the operator has been found at the current index
*/
private boolean nextUnaryOperator(final String operator) {
return nextConstant(operator) && nextWhitespace();
}
/**
* Moves past the given method name and its immediately following opening parenthesis if found.
* @return whether the method has been found at the current index
*/
private boolean nextMethod(final String methodName) {
return nextConstant(methodName) && nextCharacter('(');
}
/**
* Moves past (required) whitespace and the given suffix name if found.
* @return whether the suffix has been found at the current index
*/
private boolean nextSuffix(final String suffixName) {
return nextWhitespace() && nextConstant(suffixName);
}
private boolean nextParameterAliasName() {
return nextCharacter('@') && nextODataIdentifier();
}
@ -978,4 +1029,52 @@ public class UriTokenizer {
return false;
}
}
private boolean nextAndOperatorSearch() {
if (nextWhitespace()) {
final int lastGoodIndex = index;
if (nextUnaryOperator("OR")) {
return false;
} else if (!(nextUnaryOperator("AND"))) {
index = lastGoodIndex;
}
return true;
} else {
return false;
}
}
private boolean nextWord() {
int count = 0;
while (index < parseString.length()) {
final int code = parseString.codePointAt(index);
if (Character.isUnicodeIdentifierStart(code)) {
count++;
// Unicode characters outside of the Basic Multilingual Plane are represented as two Java characters.
index += Character.isSupplementaryCodePoint(code) ? 2 : 1;
} else {
break;
}
}
final String word = parseString.substring(index - count, index);
return count > 0 && !("OR".equals(word) || "AND".equals(word) || "NOT".equals(word));
}
private boolean nextPhrase() {
if (nextCharacter('"')) {
do {
if (nextCharacter('\\')) {
if (!(nextCharacter('\\') || nextCharacter('"'))) {
return false;
}
} else if (nextCharacter('"')) {
return true;
} else {
index++;
}
} while (index < parseString.length());
return false;
}
return false;
}
}

View File

@ -41,4 +41,8 @@ public class AliasImpl implements Alias {
return visitor.visitAlias(parameterName);
}
@Override
public String toString() {
return parameterName;
}
}

View File

@ -53,4 +53,10 @@ public class EnumerationImpl implements Enumeration {
public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException {
return visitor.visitEnum(type, values);
}
@Override
public String toString() {
return type == null ? null :
type.getFullQualifiedName().getFullQualifiedNameAsString() + getValues();
}
}

View File

@ -40,4 +40,9 @@ public class LambdaRefImpl implements LambdaRef {
public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException {
return visitor.visitLambdaReference(variableText);
}
@Override
public String toString() {
return variableText;
}
}

View File

@ -41,4 +41,9 @@ public class TypeLiteralImpl implements TypeLiteral {
public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException {
return visitor.visitTypeLiteral(type);
}
@Override
public String toString() {
return type == null ? null : type.getFullQualifiedName().getFullQualifiedNameAsString();
}
}

View File

@ -81,11 +81,11 @@ UriParserSemanticException.COMPLEX_PROPERTY_OF_ENTITY_TYPE_EXPECTED=A complex pr
UriParserSemanticException.NOT_FOR_ENTITY_TYPE=Not allowed for entity type.
UriParserSemanticException.PREVIOUS_PART_TYPED=The previous part is typed.
UriParserSemanticException.RESOURCE_NOT_FOUND=Cannot find EntitySet, Singleton, ActionImport or FunctionImport with name '%1$s'.
UriParserSemanticException.NOT_IMPLEMENTED=%1$s is not implemented!
UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is not allowed for Entity Sets, Singeltons, Action Imports and Function Imports. Found '%1$s'.
UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments. Found: '%1$s'.
UriParserSemanticException.NOT_IMPLEMENTED='%1$s' is not implemented!
UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is not allowed for Entity Sets, Singletons, Action Imports and Function Imports; found '%1$s'.
UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments; found: '%1$s'.
UriParserSemanticException.FUNCTION_IMPORT_NOT_ALLOWED=Function Imports are not allowed in $filter or $orderby. Found: '%1$s'.
UriParserSemanticException.TYPES_NOT_COMPATIBLE=Types are not compatible. Left type: '%1$s', right type: '%1$s'.
UriParserSemanticException.TYPES_NOT_COMPATIBLE=The types '%1$s' and '%2$s' are not compatible.
UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported.
UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported.

View File

@ -22,9 +22,11 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.util.Locale;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
@ -260,9 +262,17 @@ public class ExpressionParserTest {
expression = parseMethod(TokenKind.SubstringMethod, "'abc'", "1");
assertEquals("{substring ['abc', 1]}", expression.toString());
assertEquals("{cast [Edm.SByte]}", parseMethod(TokenKind.CastMethod, "Edm.SByte").toString());
assertEquals("{cast [42, Edm.SByte]}", parseMethod(TokenKind.CastMethod, "42", "Edm.SByte").toString());
assertEquals("{isof [Edm.SByte]}", parseMethod(TokenKind.IsofMethod, "Edm.SByte").toString());
assertEquals("{isof [42, Edm.SByte]}", parseMethod(TokenKind.IsofMethod, "42", "Edm.SByte").toString());
wrongExpression("substring('abc')");
wrongExpression("substring('abc',1,2,3)");
wrongExpression("substring(1,2)");
wrongExpression("cast(1,2)");
wrongExpression("isof(Edm.Int16,2)");
}
private Expression parseMethod(TokenKind kind, String... parameters)
@ -288,7 +298,7 @@ public class ExpressionParserTest {
private Expression parseExpression(final String expressionString)
throws UriParserException, UriValidationException {
UriTokenizer tokenizer = new UriTokenizer(expressionString);
Expression expression = new ExpressionParser(null, odata).parse(tokenizer, null, null);
Expression expression = new ExpressionParser(mock(Edm.class), odata).parse(tokenizer, null, null);
assertNotNull(expression);
assertTrue(tokenizer.next(TokenKind.EOF));
return expression;
@ -296,7 +306,7 @@ public class ExpressionParserTest {
private void wrongExpression(final String expressionString) {
try {
new ExpressionParser(null, odata).parse(new UriTokenizer(expressionString), null, null);
new ExpressionParser(mock(Edm.class), odata).parse(new UriTokenizer(expressionString), null, null);
fail("Expected exception not thrown.");
} catch (final UriParserException e) {
assertNotNull(e);

View File

@ -76,7 +76,7 @@ public class UriTokenizerTest {
assertTrue(tokenizer.next(TokenKind.STAR));
assertTrue(tokenizer.next(TokenKind.SLASH));
assertTrue(tokenizer.next(TokenKind.PLUS));
assertTrue(tokenizer.next(TokenKind.MINUS));
assertTrue(tokenizer.next(TokenKind.MinusOperator));
assertTrue(tokenizer.next(TokenKind.EOF));
tokenizer = new UriTokenizer("any(a:true) or all(b:false)");
@ -96,6 +96,45 @@ public class UriTokenizerTest {
assertTrue(tokenizer.next(TokenKind.EOF));
}
@Test
public void systemQueryOptions() {
UriTokenizer tokenizer = new UriTokenizer("$expand=*;$filter=true;$levels=max;$orderby=false");
assertTrue(tokenizer.next(TokenKind.EXPAND));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.STAR));
assertTrue(tokenizer.next(TokenKind.SEMI));
assertTrue(tokenizer.next(TokenKind.FILTER));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.BooleanValue));
assertTrue(tokenizer.next(TokenKind.SEMI));
assertTrue(tokenizer.next(TokenKind.LEVELS));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.MAX));
assertTrue(tokenizer.next(TokenKind.SEMI));
assertTrue(tokenizer.next(TokenKind.ORDERBY));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.BooleanValue));
assertTrue(tokenizer.next(TokenKind.EOF));
tokenizer = new UriTokenizer("$search=A;$select=*;$skip=1;$top=2");
assertTrue(tokenizer.next(TokenKind.SEARCH));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
assertTrue(tokenizer.next(TokenKind.SEMI));
assertTrue(tokenizer.next(TokenKind.SELECT));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.STAR));
assertTrue(tokenizer.next(TokenKind.SEMI));
assertTrue(tokenizer.next(TokenKind.SKIP));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.IntegerValue));
assertTrue(tokenizer.next(TokenKind.SEMI));
assertTrue(tokenizer.next(TokenKind.TOP));
assertTrue(tokenizer.next(TokenKind.EQ));
assertTrue(tokenizer.next(TokenKind.IntegerValue));
assertTrue(tokenizer.next(TokenKind.EOF));
}
@Test
public void identifier() {
assertTrue(new UriTokenizer("name").next(TokenKind.ODataIdentifier));
@ -390,11 +429,11 @@ public class UriTokenizerTest {
assertTrue(tokenizer.next(TokenKind.IntegerValue));
assertTrue(tokenizer.next(TokenKind.EOF));
tokenizer = new UriTokenizer("1ne 2");
tokenizer = new UriTokenizer("-1ne 2");
assertTrue(tokenizer.next(TokenKind.IntegerValue));
assertFalse(tokenizer.next(TokenKind.NotEqualsOperator));
tokenizer = new UriTokenizer("1 ne2");
tokenizer = new UriTokenizer("1 ne-2");
assertTrue(tokenizer.next(TokenKind.IntegerValue));
assertFalse(tokenizer.next(TokenKind.NotEqualsOperator));
@ -404,6 +443,11 @@ public class UriTokenizerTest {
assertTrue(tokenizer.next(TokenKind.IntegerValue));
assertTrue(tokenizer.next(TokenKind.EOF));
assertTrue(new UriTokenizer("-x").next(TokenKind.MinusOperator));
assertFalse(new UriTokenizer("-1").next(TokenKind.MinusOperator));
assertFalse(new UriTokenizer("-INF").next(TokenKind.MinusOperator));
assertFalse(new UriTokenizer("+").next(TokenKind.MinusOperator));
assertFalse(new UriTokenizer("nottrue").next(TokenKind.NotOperator));
assertFalse(new UriTokenizer("no true").next(TokenKind.NotOperator));
@ -484,6 +528,38 @@ public class UriTokenizerTest {
wrongToken(TokenKind.DescSuffix, " desc", 'D');
}
@Test
public void search() {
UriTokenizer tokenizer = new UriTokenizer("a AND b OR NOT \"c\" d");
assertTrue(tokenizer.next(TokenKind.Word));
assertTrue(tokenizer.next(TokenKind.AndOperatorSearch));
assertTrue(tokenizer.next(TokenKind.Word));
assertFalse(tokenizer.next(TokenKind.AndOperatorSearch));
assertTrue(tokenizer.next(TokenKind.OrOperatorSearch));
assertTrue(tokenizer.next(TokenKind.NotOperatorSearch));
assertTrue(tokenizer.next(TokenKind.Phrase));
assertTrue(tokenizer.next(TokenKind.AndOperatorSearch));
assertTrue(tokenizer.next(TokenKind.Word));
assertFalse(tokenizer.next(TokenKind.AndOperatorSearch));
assertFalse(tokenizer.next(TokenKind.Word));
assertFalse(tokenizer.next(TokenKind.Phrase));
assertTrue(tokenizer.next(TokenKind.EOF));
assertTrue(new UriTokenizer("\"a\\\\x\\\"\"").next(TokenKind.Phrase));
assertFalse(new UriTokenizer("\"a\\\"").next(TokenKind.Phrase));
assertFalse(new UriTokenizer("\"a\\x\"").next(TokenKind.Phrase));
wrongToken(TokenKind.Phrase, "\"a\"", '\\');
final String outsideBmpLetter = String.valueOf(Character.toChars(0x10330));
assertTrue(new UriTokenizer("\"" + outsideBmpLetter + "\"").next(TokenKind.Phrase));
assertTrue(new UriTokenizer(outsideBmpLetter).next(TokenKind.Word));
assertFalse(new UriTokenizer("1").next(TokenKind.Word));
assertFalse(new UriTokenizer("AND").next(TokenKind.Word));
assertFalse(new UriTokenizer("OR").next(TokenKind.Word));
assertFalse(new UriTokenizer("NOT").next(TokenKind.Word));
}
private void wrongToken(final TokenKind kind, final String value, final char disturbCharacter) {
assertFalse(new UriTokenizer(disturbCharacter + value).next(kind));

View File

@ -186,7 +186,6 @@ public class SearchParserAndTokenizerTest {
// <Input>http://serviceRoot/Products?$search=blue</Input>
assertQuery("blue").resultsIn("'blue'");
// below cases can not be tested here
// <TestCase Name="5.1.7 Search - on entity container" Rule="odataUri">
// <Input>http://serviceRoot/Model.Container/$all?$search=blue</Input>
@ -194,40 +193,23 @@ public class SearchParserAndTokenizerTest {
// <Input>http://serviceRoot/$all?$search=blue</Input>
}
private static Validator assertQuery(String searchQuery) {
return Validator.init(searchQuery);
return new Validator(searchQuery);
}
private static class Validator {
private boolean log;
private final String searchQuery;
private Validator(String searchQuery) {
this.searchQuery = searchQuery;
}
private static Validator init(String searchQuery) {
return new Validator(searchQuery);
}
@SuppressWarnings("unused")
private Validator withLogging() {
log = true;
return this;
}
private void resultsIn(SearchParserException.MessageKey key)
throws SearchTokenizerException {
private void resultsIn(SearchParserException.MessageKey key) throws SearchTokenizerException {
try {
resultsIn(searchQuery);
} catch (SearchParserException e) {
Assert.assertEquals("SearchParserException with unexpected message '" + e.getMessage() +
"' was thrown.", key, e.getMessageKey());
if(log) {
System.out.println("Caught SearchParserException with message key " +
e.getMessageKey() + " and message " + e.getMessage());
}
return;
}
Assert.fail("SearchParserException with message key " + key.getKey() + " was not thrown.");
@ -236,7 +218,7 @@ public class SearchParserAndTokenizerTest {
public void resultsInExpectedTerm(final String actualToken) throws SearchTokenizerException {
try {
resultsIn(searchQuery);
} catch(SearchParserException e) {
} catch (SearchParserException e) {
Assert.assertEquals(SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN, e.getMessageKey());
Assert.assertEquals("Expected PHRASE||WORD found: " + actualToken, e.getMessage());
}
@ -248,14 +230,10 @@ public class SearchParserAndTokenizerTest {
}
private SearchExpression getSearchExpression() throws SearchParserException, SearchTokenizerException {
SearchParser tokenizer = new SearchParser();
SearchOption result = tokenizer.parse(searchQuery);
SearchOption result = new SearchParser().parse(searchQuery);
Assert.assertNotNull(result);
final SearchExpression searchExpression = result.getSearchExpression();
Assert.assertNotNull(searchExpression);
if (log) {
System.out.println(searchExpression);
}
return searchExpression;
}
}

View File

@ -18,16 +18,22 @@
*/
package org.apache.olingo.server.core.uri.antlr;
import org.antlr.v4.runtime.Lexer;
import org.apache.olingo.server.core.uri.testutil.TokenValidator;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.apache.olingo.server.core.uri.parser.UriTokenizer;
import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
import org.junit.Test;
/**
* Tests originally written for the ANTLR lexer.
*/
public class TestLexer {
private TokenValidator test = null;
private static final String cPCT_ENCODED = "%45%46%47" + "%22" + "%5C";// last two chars are not in
// cPCT_ENCODED_UNESCAPED
// The last two chars are not in cPCT_ENCODED_UNESCAPED.
private static final String cPCT_ENCODED = "%45%46%47" + "%22" + "%5C";
private static final String cUNRESERVED = "ABCabc123-._~";
private static final String cOTHER_DELIMS = "!()*+,;";
private static final String cSUB_DELIMS = "$&'=" + cOTHER_DELIMS;
@ -39,265 +45,279 @@ public class TestLexer {
}
@Test
public void test() {
// test.log(1).run("ESAllPrim?$orderby=PropertyDouble eq 3.5E+38");
}
// ;------------------------------------------------------------------------------
// ; 0. URI
// ;------------------------------------------------------------------------------
@Test
public void testUnary() {
test.run("- a eq a").isAllInput();
public void unary() {
test.run("-a eq a").has(TokenKind.MinusOperator, TokenKind.ODataIdentifier, TokenKind.EqualsOperator,
TokenKind.ODataIdentifier).isInput();
}
@Test
public void testUriTokens() {
test.globalMode(UriLexer.MODE_QUERY);
test.run("#").isText("#").isType(UriLexer.FRAGMENT);
test.run("$count").isText("$count").isType(UriLexer.COUNT);
test.run("$ref").isText("$ref").isType(UriLexer.REF);
test.run("$value").isText("$value").isType(UriLexer.VALUE);
}
// ;------------------------------------------------------------------------------
// ; 2. Query Options
// ;------------------------------------------------------------------------------
@Test
public void testQueryOptionsTokens() {
test.globalMode(UriLexer.MODE_QUERY);
test.run("$skip=1").isAllText("$skip=1").isType(UriLexer.SKIP_QO);
test.run("$skip=2").isAllText("$skip=2").isType(UriLexer.SKIP_QO);
test.run("$skip=123").isAllText("$skip=123").isType(UriLexer.SKIP_QO);
test.run("$top=1").isAllText("$top=1").isType(UriLexer.TOP);
test.run("$top=2").isAllText("$top=2").isType(UriLexer.TOP);
test.run("$top=123").isAllText("$top=123").isType(UriLexer.TOP);
test.run("$levels=1").isAllText("$levels=1").isType(UriLexer.LEVELS);
test.run("$levels=2").isAllText("$levels=2").isType(UriLexer.LEVELS);
test.run("$levels=123").isAllText("$levels=123").isType(UriLexer.LEVELS);
test.run("$levels=max").isAllText("$levels=max").isType(UriLexer.LEVELS);
test.run("$format=atom").isAllText("$format=atom").isType(UriLexer.FORMAT);
test.run("$format=json").isAllText("$format=json").isType(UriLexer.FORMAT);
test.run("$format=xml").isAllText("$format=xml").isType(UriLexer.FORMAT);
test.run("$format=abc/def").isAllText("$format=abc/def").isType(UriLexer.FORMAT);
test.run("$id=123").isAllText("$id=123").isType(UriLexer.ID);
test.run("$id=ABC").isAllText("$id=ABC").isType(UriLexer.ID);
test.run("$skiptoken=ABC").isAllText("$skiptoken=ABC").isType(UriLexer.SKIPTOKEN);
test.run("$skiptoken=ABC").isAllText("$skiptoken=ABC").isType(UriLexer.SKIPTOKEN);
test.run("$search=\"ABC\"").isAllText("$search=\"ABC\"").isType(UriLexer.SEARCH);
test.run("$search=ABC").isAllText("$search=ABC").isType(UriLexer.SEARCH);
test.run("$search=\"A%20B%20C\"").isAllText("$search=\"A%20B%20C\"").isType(UriLexer.SEARCH);
test.run("$search=Test Test").isAllText("$search=Test Test").isType(UriLexer.SEARCH);
test.run("$search=Test&$filter=ABC eq 1").isAllText("$search=Test&$filter=ABC eq 1").isType(UriLexer.SEARCH);
public void uriTokens() {
// test.run("#").isType(TokenKind.FRAGMENT).isInput();
test.run("$count").has(TokenKind.COUNT).isInput();
test.run("$ref").has(TokenKind.REF).isInput();
test.run("$value").has(TokenKind.VALUE).isInput();
}
@Test
public void testQueryOptionsDefaultMode() {
// First set query mode, than use expand(switches to default mode) and use nested system query options
test.globalMode(UriLexer.MODE_QUERY);
test.run("$expand=ABC($skip=1)").isAllText("$expand=ABC($skip=1)").at(4).isType(UriLexer.SKIP_QO);
test.run("$expand=ABC($skip=2)").isAllText("$expand=ABC($skip=2)").at(4).isType(UriLexer.SKIP_QO);
test.run("$expand=ABC($skip=123)").isAllText("$expand=ABC($skip=123)").at(4).isType(UriLexer.SKIP_QO);
test.run("$expand=ABC($search=abc)").isAllText("$expand=ABC($search=abc)").at(4).isType(UriLexer.SEARCH_INLINE);
test.run("$expand=ABC($search=\"123\")").isAllText("$expand=ABC($search=\"123\")")
.at(4).isType(UriLexer.SEARCH_INLINE)
.at(6).isType(UriLexer.SEARCHPHRASE);
test.run("$expand=ABC($top=1)").isAllText("$expand=ABC($top=1)").at(4).isType(UriLexer.TOP);
test.run("$expand=ABC($top=2)").isAllText("$expand=ABC($top=2)").at(4).isType(UriLexer.TOP);
test.run("$expand=ABC($top=123)").isAllText("$expand=ABC($top=123)").at(4).isType(UriLexer.TOP);
public void queryOptionsTokens() {
test.run("$skip=1").has(TokenKind.SKIP, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$skip=2").has(TokenKind.SKIP, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$skip=123").has(TokenKind.SKIP, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$expand=ABC($expand=DEF($skip=1))").isAllText("$expand=ABC($expand=DEF($skip=1))")
.at(8).isType(UriLexer.SKIP_QO);
test.run("$expand=ABC($expand=DEF($skip=2))").isAllText("$expand=ABC($expand=DEF($skip=2))")
.at(8).isType(UriLexer.SKIP_QO);
test.run("$expand=ABC($expand=DEF($skip=123))").isAllText("$expand=ABC($expand=DEF($skip=123))")
.at(8).isType(UriLexer.SKIP_QO);
test.run("$top=1").has(TokenKind.TOP, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$top=2").has(TokenKind.TOP, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$top=123").has(TokenKind.TOP, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$expand=ABC($expand=DEF($top=1))").isAllText("$expand=ABC($expand=DEF($top=1))")
.at(8).isType(UriLexer.TOP);
test.run("$expand=ABC($expand=DEF($top=2))").isAllText("$expand=ABC($expand=DEF($top=2))")
.at(8).isType(UriLexer.TOP);
test.run("$expand=ABC($expand=DEF($top=123))").isAllText("$expand=ABC($expand=DEF($top=123))")
.at(8).isType(UriLexer.TOP);
test.run("$expand=ABC($expand=DEF($search=Test Test))").isAllText("$expand=ABC($expand=DEF($search=Test Test))")
.at(8).isType(UriLexer.SEARCH_INLINE)
.at(10).isType(UriLexer.SEARCHWORD)
.at(12).isType(UriLexer.SEARCHWORD);
test.run("$levels=1").has(TokenKind.LEVELS, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$levels=2").has(TokenKind.LEVELS, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$levels=123").has(TokenKind.LEVELS, TokenKind.EQ, TokenKind.IntegerValue).isInput();
test.run("$levels=max").has(TokenKind.LEVELS, TokenKind.EQ, TokenKind.MAX).isInput();
// test.run("$format=atom").has(TokenKind.FORMAT, TokenKind.EQ, TokenKind.ODataIdentifier).isInput();
// test.run("$format=json").has(TokenKind.FORMAT, TokenKind.EQ, TokenKind.ODataIdentifier).isInput();
// test.run("$format=xml").has(TokenKind.FORMAT,, TokenKind.EQ, TokenKind.ODataIdentifier).isInput();
// test.run("$format=abc/def").has(TokenKind.FORMAT, TokenKind.EQ,
// TokenKind.ODataIdentifier, TokenKind.SLASH, TokenKind.ODataIdentifier).isInput();
// test.run("$id=123").has(TokenKind.ID, TokenKind.EQ, TokenKind.IntegerValue).isInput();
// test.run("$id=ABC").has(TokenKind.ID, TokenKind.EQ, TokenKind.ODataIdentifier).isInput();
// test.run("$skiptoken=ABC").has(TokenKind.SKIPTOKEN, TokenKind.EQ, TokenKind.ODataIdentifier).isInput();
// test.run("$skiptoken=ABC").has(TokenKind.SKIPTOKEN, TokenKind.EQ, TokenKind.ODataIdentifier).isInput();
test.run("$search=\"ABC\"").has(TokenKind.SEARCH, TokenKind.EQ, TokenKind.Phrase).isInput();
test.run("$search=ABC").has(TokenKind.SEARCH, TokenKind.EQ, TokenKind.Word).isInput();
test.run("$search=\"A%20B%20C\"").has(TokenKind.SEARCH, TokenKind.EQ, TokenKind.Phrase).isInput();
test.run("$search=Test Test").has(TokenKind.SEARCH, TokenKind.EQ, TokenKind.Word,
TokenKind.AndOperatorSearch, TokenKind.Word).isInput();
test.run("$search=Test&$filter=ABC eq 1").has(TokenKind.SEARCH, TokenKind.EQ, TokenKind.Word);
}
@Test
public void queryOptionsDefaultMode() {
test.run("$expand=ABC($skip=1)").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SKIP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE).isInput();
test.run("$expand=ABC($skip=123)").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SKIP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE).isInput();
test.run("$expand=ABC($search=abc)").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SEARCH, TokenKind.EQ, TokenKind.Word, TokenKind.CLOSE).isInput();
test.run("$expand=ABC($search=\"123\")").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SEARCH, TokenKind.EQ, TokenKind.Phrase, TokenKind.CLOSE).isInput();
test.run("$expand=ABC($top=1)").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.TOP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE).isInput();
test.run("$expand=ABC($top=123)").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.TOP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE).isInput();
test.run("$expand=ABC($expand=DEF($skip=1))").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SKIP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE, TokenKind.CLOSE)
.isInput();
test.run("$expand=ABC($expand=DEF($skip=123))").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SKIP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE, TokenKind.CLOSE)
.isInput();
test.run("$expand=ABC($expand=DEF($top=1))").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.TOP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE, TokenKind.CLOSE)
.isInput();
test.run("$expand=ABC($expand=DEF($top=123))").has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.TOP, TokenKind.EQ, TokenKind.IntegerValue, TokenKind.CLOSE, TokenKind.CLOSE)
.isInput();
test.run("$expand=ABC($expand=DEF($search=Test Test))")
.has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SEARCH, TokenKind.EQ, TokenKind.Word,
TokenKind.AndOperatorSearch, TokenKind.Word, TokenKind.CLOSE, TokenKind.CLOSE)
.isInput();
test.run("$expand=ABC($expand=DEF($search=\"Test\" \"Test\"))")
.isAllText("$expand=ABC($expand=DEF($search=\"Test\" \"Test\"))")
.at(8).isType(UriLexer.SEARCH_INLINE)
.at(10).isType(UriLexer.SEARCHPHRASE)
.at(12).isType(UriLexer.SEARCHPHRASE);
.has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SEARCH, TokenKind.EQ, TokenKind.Phrase,
TokenKind.AndOperatorSearch, TokenKind.Phrase, TokenKind.CLOSE, TokenKind.CLOSE)
.isInput();
test.run("$expand=ABC($expand=DEF($search=\"Test\" \"Test\";$filter=PropertyInt16 eq 0;$orderby=PropertyInt16))")
.isAllText("$expand=ABC($expand=DEF($search=\"Test\" \"Test\";$filter=PropertyInt16 " +
"eq 0;$orderby=PropertyInt16))")
.at(8).isType(UriLexer.SEARCH_INLINE)
.at(10).isType(UriLexer.SEARCHPHRASE)
.at(12).isType(UriLexer.SEARCHPHRASE)
.at(13).isType(UriLexer.SEMI)
.at(14).isType(UriLexer.FILTER)
.at(22).isType(UriLexer.ORDERBY);
.has(TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.EXPAND, TokenKind.EQ, TokenKind.ODataIdentifier,
TokenKind.OPEN, TokenKind.SEARCH, TokenKind.EQ, TokenKind.Phrase,
TokenKind.AndOperatorSearch, TokenKind.Phrase, TokenKind.SEMI,
TokenKind.FILTER, TokenKind.EQ, TokenKind.ODataIdentifier, TokenKind.EqualsOperator,
TokenKind.IntegerValue, TokenKind.SEMI,
TokenKind.ORDERBY, TokenKind.EQ, TokenKind.ODataIdentifier, TokenKind.CLOSE, TokenKind.CLOSE)
.isInput();
}
// ;------------------------------------------------------------------------------
// ; 4. Expressions
// ;------------------------------------------------------------------------------
@Test
public void testQueryExpressions() {
test.globalMode(Lexer.DEFAULT_MODE);
public void queryExpressions() {
test.run("$it").has(TokenKind.IT).isText("$it");
test.run("$it").isText("$it").isType(UriLexer.IT);
test.run("$filter=contains(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.ContainsMethod).isText("contains(");
test.run("$filter=contains(").at(2).isText("contains(").isType(UriLexer.CONTAINS_WORD);
test.run("$filter=containsabc").at(2).isText("containsabc")
.isType(UriLexer.ODATAIDENTIFIER); // test that this is a ODI
test.run("$filter=startswith(").at(2).isText("startswith(").isType(UriLexer.STARTSWITH_WORD);
test.run("$filter=endswith(").at(2).isText("endswith(").isType(UriLexer.ENDSWITH_WORD);
test.run("$filter=length(").at(2).isText("length(").isType(UriLexer.LENGTH_WORD);
test.run("$filter=indexof(").at(2).isText("indexof(").isType(UriLexer.INDEXOF_WORD);
test.run("$filter=substring(").at(2).isText("substring(").isType(UriLexer.SUBSTRING_WORD);
test.run("$filter=tolower(").at(2).isText("tolower(").isType(UriLexer.TOLOWER_WORD);
test.run("$filter=toupper(").at(2).isText("toupper(").isType(UriLexer.TOUPPER_WORD);
test.run("$filter=trim(").at(2).isText("trim(").isType(UriLexer.TRIM_WORD);
test.run("$filter=concat(").at(2).isText("concat(").isType(UriLexer.CONCAT_WORD);
test.run("$filter=containsabc").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.ODataIdentifier)
.isText("containsabc");
test.run("$filter=startswith(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.StartswithMethod)
.isText("startswith(");
test.run("$filter=endswith(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.EndswithMethod).isText("endswith(");
test.run("$filter=length(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.LengthMethod).isText("length(");
test.run("$filter=indexof(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.IndexofMethod).isText("indexof(");
test.run("$filter=substring(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.SubstringMethod).isText("substring(");
test.run("$filter=tolower(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.TolowerMethod).isText("tolower(");
test.run("$filter=toupper(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.ToupperMethod).isText("toupper(");
test.run("$filter=trim(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.TrimMethod).isText("trim(");
test.run("$filter=concat(").has(TokenKind.FILTER, TokenKind.EQ, TokenKind.ConcatMethod).isText("concat(");
}
// ;------------------------------------------------------------------------------
// ; 7. Literal Data Values
// ;------------------------------------------------------------------------------
@Test
public void testLiteralDataValues() {
test.globalMode(Lexer.DEFAULT_MODE);
public void literalDataValues() {
// null
test.run("null").isInput().isType(UriLexer.NULLVALUE);
test.run("null").has(TokenKind.NULL).isInput();
// binary
test.run("binary'ABCD'").isInput().isType(UriLexer.BINARY);
test.run("BiNaRy'ABCD'").isInput().isType(UriLexer.BINARY);
test.run("binary'ABCD'").has(TokenKind.BinaryValue).isInput();
test.run("BiNaRy'ABCD'").has(TokenKind.BinaryValue).isInput();
// boolean
test.run("true").isInput().isType(UriLexer.TRUE);
test.run("false").isInput().isType(UriLexer.FALSE);
test.run("TrUe").isInput().isType(UriLexer.BOOLEAN);
test.run("FaLsE").isInput().isType(UriLexer.BOOLEAN);
test.run("true").has(TokenKind.BooleanValue).isInput();
test.run("false").has(TokenKind.BooleanValue).isInput();
test.run("TrUe").has(TokenKind.BooleanValue).isInput();
test.run("FaLsE").has(TokenKind.BooleanValue).isInput();
// Lexer rule INT
test.run("123").isInput().isType(UriLexer.INT);
test.run("123456789").isInput().isType(UriLexer.INT);
test.run("+123").isInput().isType(UriLexer.INT);
test.run("+123456789").isInput().isType(UriLexer.INT);
test.run("-123").isInput().isType(UriLexer.INT);
test.run("-123456789").isInput().isType(UriLexer.INT);
test.run("123").has(TokenKind.IntegerValue).isInput();
test.run("123456789").has(TokenKind.IntegerValue).isInput();
test.run("+123").has(TokenKind.IntegerValue).isInput();
test.run("+123456789").has(TokenKind.IntegerValue).isInput();
test.run("-123").has(TokenKind.IntegerValue).isInput();
test.run("-123456789").has(TokenKind.IntegerValue).isInput();
// Lexer rule DECIMAL
test.run("0.1").isInput().isType(UriLexer.DECIMAL);
test.run("1.1").isInput().isType(UriLexer.DECIMAL);
test.run("+0.1").isInput().isType(UriLexer.DECIMAL);
test.run("+1.1").isInput().isType(UriLexer.DECIMAL);
test.run("-0.1").isInput().isType(UriLexer.DECIMAL);
test.run("-1.1").isInput().isType(UriLexer.DECIMAL);
test.run("0.1").has(TokenKind.DecimalValue).isInput();
test.run("1.1").has(TokenKind.DecimalValue).isInput();
test.run("+0.1").has(TokenKind.DecimalValue).isInput();
test.run("+1.1").has(TokenKind.DecimalValue).isInput();
test.run("-0.1").has(TokenKind.DecimalValue).isInput();
test.run("-1.1").has(TokenKind.DecimalValue).isInput();
// Lexer rule EXP
test.run("1.1e+1").isInput().isType(UriLexer.DECIMAL);
test.run("1.1e-1").isInput().isType(UriLexer.DECIMAL);
test.run("1.1e+1").has(TokenKind.DoubleValue).isInput();
test.run("1.1e-1").has(TokenKind.DoubleValue).isInput();
test.run("NaN").isInput().isType(UriLexer.NANINFINITY);
test.run("-INF").isInput().isType(UriLexer.NANINFINITY);
test.run("INF").isInput().isType(UriLexer.NANINFINITY);
test.run("NaN").has(TokenKind.DoubleValue).isInput();
test.run("-INF").has(TokenKind.DoubleValue).isInput();
test.run("INF").has(TokenKind.DoubleValue).isInput();
// Lexer rule GUID
test.run("1234ABCD-12AB-23CD-45EF-123456780ABC").isInput().isType(UriLexer.GUID);
test.run("1234ABCD-12AB-23CD-45EF-123456780ABC").isInput().isType(UriLexer.GUID);
test.run("1234ABCD-12AB-23CD-45EF-123456780ABC").has(TokenKind.GuidValue).isInput();
test.run("1234ABCD-12AB-23CD-45EF-123456780ABC").has(TokenKind.GuidValue).isInput();
// Lexer rule DATE
test.run("2013-11-15").isInput().isType(UriLexer.DATE);
test.run("2013-11-15").has(TokenKind.DateValue).isInput();
// Lexer rule DATETIMEOFFSET
test.run("2013-11-15T13:35Z").isInput().isType(UriLexer.DATETIMEOFFSET);
test.run("2013-11-15T13:35:10Z").isInput().isType(UriLexer.DATETIMEOFFSET);
test.run("2013-11-15T13:35:10.1234Z").isInput().isType(UriLexer.DATETIMEOFFSET);
test.run("2013-11-15T13:35Z").has(TokenKind.DateTimeOffsetValue).isInput();
test.run("2013-11-15T13:35:10Z").has(TokenKind.DateTimeOffsetValue).isInput();
test.run("2013-11-15T13:35:10.1234Z").has(TokenKind.DateTimeOffsetValue).isInput();
test.run("2013-11-15T13:35:10.1234+01:30").isInput().isType(UriLexer.DATETIMEOFFSET);
test.run("2013-11-15T13:35:10.1234-01:12").isInput().isType(UriLexer.DATETIMEOFFSET);
test.run("2013-11-15T13:35:10.1234+01:30").has(TokenKind.DateTimeOffsetValue).isInput();
test.run("2013-11-15T13:35:10.1234-01:12").has(TokenKind.DateTimeOffsetValue).isInput();
test.run("2013-11-15T13:35Z").isInput().isType(UriLexer.DATETIMEOFFSET);
test.run("2013-11-15T13:35Z").has(TokenKind.DateTimeOffsetValue).isInput();
// Lexer rule DURATION
test.run("duration'PT67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT67.89S'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT5M'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT5M67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT5M67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT5M'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT5M67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT5M67.89S'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT4H'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT4H67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT4H67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT4H5M'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT4H5M67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT4H5M67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'PT4H'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT4H67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT4H67.89S'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT4H5M'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT4H5M67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'PT4H5M67.89S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3D'");
test.run("duration'P3DT67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT5M'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT5M67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT5M67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT4H'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT4H67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT4H67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT4H5M'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT4H5M67S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3DT4H5M67.89S'").isInput().isType(UriLexer.DURATION);
test.run("duration'P3D'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT67.89S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT5M'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT5M67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT5M67.89S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT4H'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT4H67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT4H67.89S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT4H5M'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT4H5M67S'").has(TokenKind.DurationValue).isInput();
test.run("duration'P3DT4H5M67.89S'").has(TokenKind.DurationValue).isInput();
test.run("DuRaTiOn'P3DT4H5M67.89S'").isInput().isType(UriLexer.DURATION);
test.run("DuRaTiOn'-P3DT4H5M67.89S'").isInput().isType(UriLexer.DURATION);
test.run("DuRaTiOn'P3DT4H5M67.89S'").has(TokenKind.DurationValue).isInput();
test.run("DuRaTiOn'-P3DT4H5M67.89S'").has(TokenKind.DurationValue).isInput();
test.run("20:00").isInput().isType(UriLexer.TIMEOFDAY);
test.run("20:15:01").isInput().isType(UriLexer.TIMEOFDAY);
test.run("20:15:01.02").isInput().isType(UriLexer.TIMEOFDAY);
test.run("20:00").has(TokenKind.TimeOfDayValue).isInput();
test.run("20:15:01").has(TokenKind.TimeOfDayValue).isInput();
test.run("20:15:01.02").has(TokenKind.TimeOfDayValue).isInput();
test.run("20:15:01.02").isInput().isType(UriLexer.TIMEOFDAY);
test.run("20:15:01.02").has(TokenKind.TimeOfDayValue).isInput();
// String
test.run("'ABC'").isText("'ABC'").isType(UriLexer.STRING);
test.run("'A%20C'").isInput().isType(UriLexer.STRING);
test.run("'%20%20%20ABC'").isInput().isType(UriLexer.STRING);
test.run("'ABC'").has(TokenKind.StringValue).isInput();
test.run("'A%20C'").has(TokenKind.StringValue).isInput();
test.run("'%20%20%20ABC'").has(TokenKind.StringValue).isInput();
}
@Test
public void testDelims() {
String reserved = "/";
test.globalMode(UriLexer.MODE_QUERY);
public void delims() {
final String reserved = "/";
// Test lexer rule UNRESERVED
test.run("$format=A/" + cUNRESERVED).isAllInput().isType(UriLexer.FORMAT);
test.run("$format=A/" + cUNRESERVED + reserved).isType(UriLexer.FORMAT).at(4).isText(cUNRESERVED);
// test.run("$format=A/" + cUNRESERVED).has(TokenKind.FORMAT).isInput();
// test.run("$format=A/" + cUNRESERVED + reserved).has(TokenKind.FORMAT).isText(cUNRESERVED);
// Test lexer rule PCT_ENCODED
test.run("$format=A/" + cPCT_ENCODED).isAllInput().isType(UriLexer.FORMAT);
test.run("$format=A/" + cPCT_ENCODED + reserved).isType(UriLexer.FORMAT).at(4).isText(cPCT_ENCODED);
// test.run("$format=A/" + cPCT_ENCODED).has(TokenKind.FORMAT).isInput();
// test.run("$format=A/" + cPCT_ENCODED + reserved).has(TokenKind.FORMAT).isText(cPCT_ENCODED);
// Test lexer rule SUB_DELIMS
test.run("$format=A/" + cSUB_DELIMS).isAllInput().isType(UriLexer.FORMAT);
test.run("$format=A/" + cSUB_DELIMS + reserved).isType(UriLexer.FORMAT).at(4).isText("$");
// test.run("$format=A/" + cSUB_DELIMS).has(TokenKind.FORMAT).isInput();
// test.run("$format=A/" + cSUB_DELIMS + reserved).has(TokenKind.FORMAT).isText("$");
// Test lexer rule PCHAR rest
test.run("$format=A/:@").isAllText("$format=A/:@").isType(UriLexer.FORMAT);
test.run("$format=A/:@" + reserved).isType(UriLexer.FORMAT).at(4).isText(":@");
// test.run("$format=A/:@").has(TokenKind.FORMAT).isInput();
// test.run("$format=A/:@" + reserved).has(TokenKind.FORMAT).isText(":@");
// Test lexer rule PCHAR all
test.run("$format=" + cPCHAR + "/" + cPCHAR).isAllInput().isType(UriLexer.FORMAT);
// test.run("$format=" + cPCHAR + "/" + cPCHAR).has(TokenKind.FORMAT).isInput();
}
public class TokenValidator {
private String input = null;
private UriTokenizer tokenizer = null;
private String curText = null;
public TokenValidator run(final String uri) {
input = uri;
tokenizer = new UriTokenizer(uri);
curText = "";
return this;
}
public TokenValidator has(final TokenKind... expected) {
for (final TokenKind kind : expected) {
assertTrue(tokenizer.next(kind));
curText += tokenizer.getText();
}
return this;
}
public TokenValidator isText(final String expected) {
assertEquals(expected, tokenizer.getText());
return this;
}
public TokenValidator isInput() {
assertEquals(input, curText);
assertTrue(tokenizer.next(TokenKind.EOF));
return this;
}
}
}

View File

@ -27,7 +27,7 @@ import org.apache.olingo.server.api.edmx.EdmxReference;
import org.apache.olingo.server.api.uri.UriInfoKind;
import org.apache.olingo.server.api.uri.UriResourceKind;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException;
import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;
import org.apache.olingo.server.core.uri.testutil.FilterValidator;
import org.apache.olingo.server.core.uri.testutil.ResourceValidator;
import org.apache.olingo.server.core.uri.testutil.TestUriValidator;
@ -67,8 +67,7 @@ public class TestUriParserImpl {
+ "," + PropertyDateTimeOffset + "," + PropertyDuration + "," + PropertyGuid + "," + PropertyTimeOfDay;
@Test
public void testBoundFunctionImport_VarParameters() {
public void boundFunctionImport_VarParameters() {
// no input
testRes.run("ESKeyNav(1)/olingo.odata.test1.BFCETKeyNavRTETKeyNav()")
.at(0).isUriPathInfoKind(UriResourceKind.entitySet)
@ -89,9 +88,8 @@ public class TestUriParserImpl {
}
@Test
public void testFunctionBound_varReturnType() {
String esTwoKeyNav = "ESTwoKeyNav(PropertyInt16=1,PropertyString='ABC')";
public void functionBound_varReturnType() {
final String esTwoKeyNav = "ESTwoKeyNav(PropertyInt16=1,PropertyString='ABC')";
// returning primitive
testRes.run("ESTwoKeyNav/olingo.odata.test1.BFCESTwoKeyNavRTString()")
@ -151,8 +149,7 @@ public class TestUriParserImpl {
}
@Test
public void runActionImport_VarReturnType() {
public void actionImport_VarReturnType() {
testRes.run(ContainerProvider.AIRT_STRING).isKind(UriInfoKind.resource)
.first()
.isActionImport(ContainerProvider.AIRT_STRING)
@ -189,7 +186,6 @@ public class TestUriParserImpl {
@Test
public void count() {
// count entity set
testRes.run("ESAllPrim/$count")
.at(0)
@ -216,7 +212,7 @@ public class TestUriParserImpl {
}
@Test
public void runCrossJoin() throws Exception {
public void crossJoin() throws Exception {
testUri.run("$crossjoin(ESAllKey)")
.isKind(UriInfoKind.crossjoin)
.isCrossJoinEntityList(Arrays.asList("ESAllKey"));
@ -226,29 +222,15 @@ public class TestUriParserImpl {
.isCrossJoinEntityList(Arrays.asList("ESAllKey", "ESTwoPrim"));
}
@Test(expected = UriValidationException.class)
public void testEntityFailOnValidation1() throws Exception {
@Test
public void entityFailOnValidation() throws Exception {
// simple entity set; with qualifiedentityTypeName; with filter
testUri.run("$entity/olingo.odata.test1.ETTwoPrim", "$filter=PropertyInt16 eq 123&$id=ESAllKey")
.isIdText("ESAllKey")
.goFilter().is("<<PropertyInt16> eq <123>>");
}
@Test(expected = UriParserSyntaxException.class)
public void testEntityFailOnValidation2() throws Exception {
// simple entity set; with qualifiedentityTypeName; with 2xformat(before and after), expand, filter
testUri.run("$entity/olingo.odata.test1.ETTwoPrim",
"$format=xml&$expand=*&abc=123&$id=ESBase&xyz=987&$filter=PropertyInt16 eq 123&$format=atom&$select=*")
.isFormatText("atom")
.isCustomParameter(0, "abc", "123")
.isIdText("ESBase")
.isCustomParameter(1, "xyz", "987")
.isSelectItemStar(0);
testUri.runEx("$entity/olingo.odata.test1.ETTwoPrim", "$filter=PropertyInt16 eq 123&$id=ESAllKey")
.isExValidation(UriValidationException.MessageKeys.SYSTEM_QUERY_OPTION_NOT_ALLOWED);
}
@Test
public void testEntity() throws Exception {
public void entity() throws Exception {
// simple entity set
testUri.run("$entity", "$id=ESAllPrim").isKind(UriInfoKind.entityId)
.isKind(UriInfoKind.entityId)
@ -311,14 +293,11 @@ public class TestUriParserImpl {
.isKind(UriInfoKind.entityId)
.isEntityType(EntityTypeProvider.nameETBase)
.isIdText("ESTwoPrim")
.isExpandText("*")
.goExpand().first().isSegmentStar();
}
@Test
public void entitySet() throws Exception {
// plain entity set
testRes.run("ESAllPrim")
.isEntitySet("ESAllPrim")
@ -342,7 +321,7 @@ public class TestUriParserImpl {
.isKeyPredicate(1, "PropertyString", "'ABC'");
// with all keys
testRes.run("ESAllKey(" + encode(allKeys) + ")")
testRes.run("ESAllKey(" + allKeys + ")")
.isEntitySet("ESAllKey")
.isKeyPredicate(0, "PropertyString", "'ABC'")
.isKeyPredicate(1, "PropertyInt16", "1")
@ -360,10 +339,7 @@ public class TestUriParserImpl {
}
@Test
public void testEntitySet_NavigationProperty() {
// plain entity set ...
public void entitySet_NavigationProperty() {
// with navigation property
testRes.run("ESKeyNav(1)/NavPropertyETTwoKeyNavOne")
.at(0)
@ -467,10 +443,7 @@ public class TestUriParserImpl {
}
@Test
public void testEntitySet_Property() {
// plain entity set ...
public void entitySet_Property() {
// with property
testRes.run("ESAllPrim(1)/PropertyString")
.at(0)
@ -499,8 +472,7 @@ public class TestUriParserImpl {
}
@Test
public void testEntitySet_TypeFilter() {
public void entitySet_TypeFilter() {
// filter
testRes.run("ESTwoPrim/olingo.odata.test1.ETBase")
.at(0)
@ -556,57 +528,53 @@ public class TestUriParserImpl {
.at(1)
.isPrimitiveProperty("AdditionalPropertyString_5", PropertyProvider.nameString, false)
.isType(PropertyProvider.nameString);
}
@Test
public void unary() throws Exception {
testFilter.runOnETAllPrim("not PropertyBoolean").isCompr("<not <PropertyBoolean>>");
testFilter.runOnETAllPrim("-PropertyInt16 eq PropertyInt16").isCompr("<<- <PropertyInt16>> eq <PropertyInt16>>");
testFilter.runOnETAllPrim("not PropertyBoolean").is("<not <PropertyBoolean>>");
testFilter.runOnETAllPrim("-PropertyInt16 eq PropertyInt16").is("<<- <PropertyInt16>> eq <PropertyInt16>>");
}
// TODO: Use correct types.
@Test
@Ignore
public void filterComplexMixedPriority() throws Exception {
testFilter.runOnETAllPrim("PropertyInt16 or PropertyInt32 and PropertyInt64")
.isCompr("<<PropertyInt16> or <<PropertyInt32> and <PropertyInt64>>>");
testFilter.runOnETAllPrim("PropertyInt16 or PropertyInt32 and PropertyInt64 eq PropertyByte")
.isCompr("<<PropertyInt16> or <<PropertyInt32> and <<PropertyInt64> eq <PropertyByte>>>>");
testFilter.runOnETAllPrim("PropertyInt16 or PropertyInt32 eq PropertyInt64 and PropertyByte")
.isCompr("<<PropertyInt16> or <<<PropertyInt32> eq <PropertyInt64>> and <PropertyByte>>>");
testFilter.runOnETAllPrim("PropertyInt16 or PropertyInt32 eq PropertyInt64 and PropertyByte eq PropertySByte")
.isCompr("<<PropertyInt16> or <<<PropertyInt32> eq <PropertyInt64>> "
testFilter.runOnETAllPrim("PropertyBoolean or true and false")
.is("<<PropertyBoolean> or <<true> and <false>>>");
testFilter.runOnETAllPrim("PropertyBoolean or true and PropertyInt64 eq PropertyByte")
.is("<<PropertyBoolean> or <<true> and <<PropertyInt64> eq <PropertyByte>>>>");
testFilter.runOnETAllPrim("PropertyBoolean or PropertyInt32 eq PropertyInt64 and true")
.is("<<PropertyBoolean> or <<<PropertyInt32> eq <PropertyInt64>> and <true>>>");
testFilter.runOnETAllPrim("PropertyBoolean or PropertyInt32 eq PropertyInt64 and PropertyByte eq PropertySByte")
.is("<<PropertyBoolean> or <<<PropertyInt32> eq <PropertyInt64>> "
+ "and <<PropertyByte> eq <PropertySByte>>>>");
testFilter.runOnETAllPrim("PropertyInt16 eq PropertyInt32 or PropertyInt64 and PropertyByte")
.isCompr("<<<PropertyInt16> eq <PropertyInt32>> or <<PropertyInt64> and <PropertyByte>>>");
testFilter.runOnETAllPrim("PropertyInt16 eq PropertyInt32 or PropertyInt64 and PropertyByte eq PropertySByte")
.isCompr("<<<PropertyInt16> eq <PropertyInt32>> "
+ "or <<PropertyInt64> and <<PropertyByte> eq <PropertySByte>>>>");
testFilter.runOnETAllPrim("PropertyInt16 eq PropertyInt32 or PropertyInt64 eq PropertyByte and PropertySByte")
.isCompr("<<<PropertyInt16> eq <PropertyInt32>> "
+ "or <<<PropertyInt64> eq <PropertyByte>> and <PropertySByte>>>");
testFilter.runOnETAllPrim("PropertyInt16 eq PropertyInt32 or PropertyBoolean and true")
.is("<<<PropertyInt16> eq <PropertyInt32>> or <<PropertyBoolean> and <true>>>");
testFilter.runOnETAllPrim("PropertyInt16 eq PropertyInt32 or PropertyBoolean and PropertyByte eq PropertySByte")
.is("<<<PropertyInt16> eq <PropertyInt32>> "
+ "or <<PropertyBoolean> and <<PropertyByte> eq <PropertySByte>>>>");
testFilter.runOnETAllPrim("PropertyInt16 eq PropertyInt32 or PropertyInt64 eq PropertyByte and PropertyBoolean")
.is("<<<PropertyInt16> eq <PropertyInt32>> "
+ "or <<<PropertyInt64> eq <PropertyByte>> and <PropertyBoolean>>>");
testFilter.runOnETAllPrim("PropertyInt16 eq PropertyInt32 or PropertyInt64 eq PropertyByte "
+ "and PropertySByte eq PropertyDecimal")
.isCompr("<<<PropertyInt16> eq <PropertyInt32>> or <<<PropertyInt64> eq <PropertyByte>> "
.is("<<<PropertyInt16> eq <PropertyInt32>> or <<<PropertyInt64> eq <PropertyByte>> "
+ "and <<PropertySByte> eq <PropertyDecimal>>>>");
}
@Test
public void filterSimpleSameBinaryBinaryBinaryPriority() throws Exception {
testFilter.runOnETAllPrim("1 add 2 add 3 add 4").isCompr("<<< <1> add <2>> add <3>> add <4>>");
testFilter.runOnETAllPrim("1 add 2 add 3 div 4").isCompr("<< <1> add <2>> add <<3> div <4>>>");
testFilter.runOnETAllPrim("1 add 2 div 3 add 4").isCompr("<< <1> add <<2> div <3>>> add <4>>");
testFilter.runOnETAllPrim("1 add 2 div 3 div 4").isCompr("< <1> add <<<2> div <3>> div <4>>>");
testFilter.runOnETAllPrim("1 div 2 add 3 add 4").isCompr("<<< <1> div <2>> add <3>> add <4>>");
testFilter.runOnETAllPrim("1 div 2 add 3 div 4").isCompr("<< <1> div <2>> add <<3> div <4>>>");
testFilter.runOnETAllPrim("1 div 2 div 3 add 4").isCompr("<<< <1> div <2>> div <3>> add <4>>");
testFilter.runOnETAllPrim("1 div 2 div 3 div 4").isCompr("<<< <1> div <2>> div <3>> div <4>>");
testFilter.runOnETAllPrim("1 add 2 add 3 add 4 ge 0").isCompr("<<<< <1> add <2>> add <3>> add <4>> ge <0>>");
testFilter.runOnETAllPrim("1 add 2 add 3 div 4 ge 0").isCompr("<<< <1> add <2>> add <<3> div <4>>> ge <0>>");
testFilter.runOnETAllPrim("1 add 2 div 3 add 4 ge 0").isCompr("<<< <1> add <<2> div <3>>> add <4>> ge <0>>");
testFilter.runOnETAllPrim("1 add 2 div 3 div 4 ge 0").isCompr("<< <1> add <<<2> div <3>> div <4>>> ge <0>>");
testFilter.runOnETAllPrim("1 div 2 add 3 add 4 ge 0").isCompr("<<<< <1> div <2>> add <3>> add <4>> ge <0>>");
testFilter.runOnETAllPrim("1 div 2 add 3 div 4 ge 0").isCompr("<<< <1> div <2>> add <<3> div <4>>> ge <0>>");
testFilter.runOnETAllPrim("1 div 2 div 3 add 4 ge 0").isCompr("<<<< <1> div <2>> div <3>> add <4>> ge <0>>");
testFilter.runOnETAllPrim("1 div 2 div 3 div 4 ge 0").isCompr("<<<< <1> div <2>> div <3>> div <4>> ge <0>>");
}
@Test
public void testFunctionImport_VarParameters() {
public void functionImport_VarParameters() {
// no input
testRes.run("FINRTInt16()")
.isFunctionImport("FINRTInt16")
@ -627,7 +595,7 @@ public class TestUriParserImpl {
}
@Test
public void testFunctionImport_VarReturning() {
public void functionImport_VarReturning() {
// returning primitive
testRes.run("FINRTInt16()")
.isFunctionImport("FINRTInt16")
@ -666,8 +634,7 @@ public class TestUriParserImpl {
}
@Test
public void testFunctionImportChain() {
public void functionImportChain() {
// test chain; returning single complex
testRes.run("FICRTCTAllPrimTwoParam(ParameterString='ABC',ParameterInt16=1)/PropertyInt16")
.at(0)
@ -710,12 +677,10 @@ public class TestUriParserImpl {
.isKeyPredicate(1, "PropertyString", "'ABC'")
.at(1)
.isPrimitiveProperty("PropertyInt16", PropertyProvider.nameInt16, false);
}
@Test
public void testMetaData() throws Exception {
public void metaData() throws Exception {
// Parsing the fragment may be used if a uri has to be parsed on the consumer side.
// On the producer side this feature is currently not supported, so the context fragment
// part is only available as text.
@ -879,12 +844,12 @@ public class TestUriParserImpl {
}
@Test
public void testRef() throws Exception {
public void ref() throws Exception {
testUri.run("ESKeyNav(1)/NavPropertyETTwoKeyNavOne/$ref");
}
@Test
public void testSingleton() {
public void singleton() {
// plain singleton
testRes.run("SINav")
.isSingleton("SINav")
@ -892,10 +857,7 @@ public class TestUriParserImpl {
}
@Test
public void testNavigationProperty() {
// plain entity set ...
public void navigationProperty() {
// with navigation property
testRes.run("ESKeyNav(1)/NavPropertyETTwoKeyNavOne")
.at(0).isEntitySet("ESKeyNav")
@ -969,10 +931,7 @@ public class TestUriParserImpl {
}
@Test
public void testSingleton_Property() {
// plain singleton ...
public void singleton_Property() {
// with property
testRes.run("SINav/PropertyInt16")
.at(0)
@ -1002,72 +961,51 @@ public class TestUriParserImpl {
}
@Test
public void testValue() throws Exception {
public void value() throws Exception {
testUri.run("ESAllPrim(1)/PropertyString/$value");
}
@Test(expected = UriValidationException.class)
public void testMemberStartingWithCastFailOnValidation1() throws Exception {
@Test
public void memberStartingWithCastFailOnValidation1() throws Exception {
// on EntityType entry
testUri.run("ESTwoKeyNav(ParameterInt16=1,PropertyString='ABC')",
testUri.runEx("ESTwoKeyNav(Property16=1,PropertyString='ABC')",
"$filter=olingo.odata.test1.ETBaseTwoKeyNav/PropertyDate")
.goFilter().root().isMember()
.isMemberStartType(EntityTypeProvider.nameETBaseTwoKeyNav).goPath()
// .at(0)
// .isUriPathInfoKind(UriResourceKind.startingTypeFilter)
// .isType(EntityTypeProvider.nameETTwoKeyNav, false)
// .isTypeFilterOnEntry(EntityTypeProvider.nameETBaseTwoKeyNav)
.at(0).isType(PropertyProvider.nameDate);
}
@Test(expected = UriValidationException.class)
public void testMemberStartingWithCastFailOnValidation2() throws Exception {
testUri.run("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString='2')",
"$filter=olingo.odata.test1.CTBase/AdditionalPropString")
.goFilter().root().isMember()
.isMemberStartType(ComplexTypeProvider.nameCTBase).goPath()
// .at(0)
// .isUriPathInfoKind(UriResourceKind.startingTypeFilter)
// .isType(ComplexTypeProvider.nameCTTwoPrim, false)
// .isTypeFilterOnEntry(ComplexTypeProvider.nameCTBase)
.at(0).isType(PropertyProvider.nameString);
.isExValidation(UriValidationException.MessageKeys.INVALID_KEY_PROPERTY);
}
@Test
public void testMemberStartingWithCast() throws Exception {
public void memberStartingWithCastFailOnValidation2() throws Exception {
testUri.runEx("FICRTCTTwoPrimTwoParam(ParameterInt16=1,ParameterString='2')",
"$filter=olingo.odata.test1.CTBase/AdditionalPropString")
.isExSemantic(UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE);
}
@Test
public void memberStartingWithCast() throws Exception {
// on EntityType collection
testUri.run("ESTwoKeyNav", "$filter=olingo.odata.test1.ETBaseTwoKeyNav/PropertyDate")
.goFilter().root().isMember()
testFilter.runOnETTwoKeyNav("olingo.odata.test1.ETBaseTwoKeyNav/PropertyDate eq null")
.left()
.isMember()
.isMemberStartType(EntityTypeProvider.nameETBaseTwoKeyNav).goPath()
// .at(0)
// .isUriPathInfoKind(UriResourceKind.startingTypeFilter)
// .isType(EntityTypeProvider.nameETTwoKeyNav, true)
// .isTypeFilterOnCollection(EntityTypeProvider.nameETBaseTwoKeyNav)
.at(0).isType(PropertyProvider.nameDate);
// on Complex collection
testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString='2')",
"$filter=olingo.odata.test1.CTBase/AdditionalPropString")
.goFilter().root().isMember()
"$filter=olingo.odata.test1.CTBase/AdditionalPropString eq null")
.goFilter().left().isMember()
.isMemberStartType(ComplexTypeProvider.nameCTBase).goPath()
// .at(0)
// .isUriPathInfoKind(UriResourceKind.startingTypeFilter)
// .isType(ComplexTypeProvider.nameCTTwoPrim, true)
// .isTypeFilterOnCollection(ComplexTypeProvider.nameCTBase)
.at(0).isType(PropertyProvider.nameString);
}
@Test
public void testComplexTypeCastFollowingAsCollection() throws Exception {
public void complexTypeCastFollowingAsCollection() throws Exception {
testUri.run("FICRTCollCTTwoPrimTwoParam(ParameterInt16=1,ParameterString='2')/olingo.odata.test1.CTBase");
}
@Test
public void testAlias() throws Exception {
testUri.run("ESAllPrim", "$filter=PropertyInt16 eq @p1&@p1=1")
.goFilter().is("<<PropertyInt16> eq <@p1>>");
public void alias() throws Exception {
testFilter.runOnETAllPrim("PropertyInt16 eq @p1&@p1=1")
.is("<<PropertyInt16> eq <@p1>>");
}
@Test
@ -1105,8 +1043,4 @@ public class TestUriParserImpl {
.is("<geo.intersects(<PropertySByte>,<PropertySByte>)>")
.isMethod(MethodKind.GEOINTERSECTS, 2);
}
private final String encode(final String uriPart) {
return uriPart.replaceAll(":", "%3A");
}
}

View File

@ -18,12 +18,18 @@
*/
package org.apache.olingo.server.core.uri.parser;
import java.util.Collections;
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.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmKeyPropertyRef;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.uri.UriInfoKind;
import org.apache.olingo.server.core.uri.testutil.TestUriValidator;
import org.junit.Test;
@ -33,17 +39,57 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* All Tests which involves the <code>Parser</code> implementation
* (and with that also the <code>UriParseTreeVisitor</code>).
* Tests of the <code>Parser</code> implementation that require mocking of the EDM.
*/
public class ParserTest {
@Test
public void navPropertySameNameAsEntitySet() throws Exception {
final String namespace = "namespace";
final String entityTypeName = "ETNavProp";
final FullQualifiedName nameETNavProp = new FullQualifiedName(namespace, entityTypeName);
final String entitySetName = "ESNavProp";
final String keyPropertyName = "a";
EdmProperty keyProperty = Mockito.mock(EdmProperty.class);
Mockito.when(keyProperty.getType())
.thenReturn(OData.newInstance().createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Byte));
EdmKeyPropertyRef keyPropertyRef = Mockito.mock(EdmKeyPropertyRef.class);
Mockito.when(keyPropertyRef.getName()).thenReturn(keyPropertyName);
Mockito.when(keyPropertyRef.getProperty()).thenReturn(keyProperty);
EdmNavigationProperty navProperty = Mockito.mock(EdmNavigationProperty.class);
Mockito.when(navProperty.getName()).thenReturn(entitySetName);
Mockito.when(navProperty.isCollection()).thenReturn(true);
EdmEntityType entityType = Mockito.mock(EdmEntityType.class);
Mockito.when(entityType.getFullQualifiedName()).thenReturn(nameETNavProp);
Mockito.when(entityType.getKeyPredicateNames()).thenReturn(Collections.singletonList(keyPropertyName));
Mockito.when(entityType.getKeyPropertyRefs()).thenReturn(Collections.singletonList(keyPropertyRef));
Mockito.when(entityType.getNavigationProperty(entitySetName)).thenReturn(navProperty);
Mockito.when(navProperty.getType()).thenReturn(entityType);
EdmEntitySet entitySet = Mockito.mock(EdmEntitySet.class);
Mockito.when(entitySet.getName()).thenReturn(entitySetName);
Mockito.when(entitySet.getEntityType()).thenReturn(entityType);
EdmEntityContainer container = Mockito.mock(EdmEntityContainer.class);
Mockito.when(container.getEntitySet(entitySetName)).thenReturn(entitySet);
Edm mockedEdm = Mockito.mock(Edm.class);
Mockito.when(mockedEdm.getEntityContainer()).thenReturn(container);
new TestUriValidator().setEdm(mockedEdm)
.run("ESNavProp(1)/ESNavProp(2)/ESNavProp(3)/ESNavProp")
.goPath()
.at(0).isEntitySet(entitySetName)
.at(0).isKeyPredicate(0, keyPropertyName, "1")
.at(1).isNavProperty(entitySetName, nameETNavProp, false)
.at(1).isKeyPredicate(0, keyPropertyName, "2")
.at(2).isNavProperty(entitySetName, nameETNavProp, false)
.at(2).isKeyPredicate(0, keyPropertyName, "3")
.at(3).isNavProperty(entitySetName, nameETNavProp, true);
}
/**
* Test for EntitySet and NavigationProperty with same name defined in metadata.
* (related to Olingo issue OLINGO-741)
*/
@Test
public void parseEntitySetAndNavigationPropertyWithSameName() throws Exception {
public void expandNavigationPropertyWithSameNameAsEntitySet() throws Exception {
TestUriValidator testUri = new TestUriValidator();
Edm mockEdm = Mockito.mock(Edm.class);
@ -60,7 +106,7 @@ public class ParserTest {
Mockito.when(typeCategory.getNamespace()).thenReturn("NS");
Mockito.when(esCategory.getEntityType()).thenReturn(typeCategory);
Mockito.when(productsNavigation.getName()).thenReturn("Products");
Mockito.when(typeCategory.getProperty("Products")).thenReturn(productsNavigation);
Mockito.when(typeCategory.getNavigationProperty("Products")).thenReturn(productsNavigation);
Mockito.when(container.getEntitySet("Category")).thenReturn(esCategory);
Mockito.when(container.getEntitySet("Products")).thenReturn(esProduct);
Mockito.when(productsType.getFullQualifiedName()).thenReturn(nameProducts);
@ -122,7 +168,7 @@ public class ParserTest {
.isType(new FullQualifiedName("NS", "Category"), false);
fail("Expected exception was not thrown.");
} catch (final UriParserException e) {
assertEquals("NavigationProperty 'Category' not found in type 'NS.Products'", e.getMessage());
assertEquals("Navigation Property 'Category' not found in type 'NS.Products'.", e.getMessage());
}
}
}

View File

@ -19,6 +19,7 @@
package org.apache.olingo.server.core.uri.testutil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -151,12 +152,6 @@ public class ExpandValidator implements TestValidator {
return this;
}
public ExpandValidator isSelectText(final String text) {
final QueryOption option = expandItem.getSelectOption();
assertEquals(text, option.getText());
return this;
}
public ExpandValidator isSelectItemStar(final int index) {
SelectOption select = expandItem.getSelectOption();
SelectItem item = select.getSelectItems().get(index);
@ -171,12 +166,6 @@ public class ExpandValidator implements TestValidator {
return this;
}
public ExpandValidator isFilterOptionText(final String text) {
QueryOption option = expandItem.getFilterOption();
assertEquals(text, option.getText());
return this;
}
public ExpandValidator isFilterSerialized(final String serialized) {
FilterOption filter = expandItem.getFilterOption();
@ -201,7 +190,13 @@ public class ExpandValidator implements TestValidator {
}
public ExpandValidator isExpandStartType(final FullQualifiedName fullName) {
assertNotNull(expandItem.getStartTypeFilter());
assertEquals(fullName, expandItem.getStartTypeFilter().getFullQualifiedName());
return this;
}
public ExpandValidator isSearchSerialized(final String serialized) {
assertEquals(serialized, expandItem.getSearchOption().getSearchExpression().toString());
return this;
}
}

View File

@ -46,13 +46,16 @@ import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.api.uri.queryoption.expression.Method;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.api.uri.queryoption.expression.TypeLiteral;
import org.apache.olingo.server.api.uri.queryoption.expression.Unary;
import org.apache.olingo.server.core.uri.UriResourceFunctionImpl;
import org.apache.olingo.server.core.uri.parser.Parser;
import org.apache.olingo.server.core.uri.parser.UriParserException;
import org.apache.olingo.server.core.uri.parser.UriParserSemanticException;
import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException;
import org.apache.olingo.server.core.uri.queryoption.expression.BinaryImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.MethodImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.UnaryImpl;
import org.apache.olingo.server.core.uri.validator.UriValidationException;
public class FilterValidator implements TestValidator {
@ -127,7 +130,7 @@ public class FilterValidator implements TestValidator {
}
public FilterValidator runOrderByOnETTwoKeyNavEx(final String orderBy) throws UriParserException {
return runUriOrderByEx("ESTwoKeyNav", "$orderby=" + orderBy.trim());
return runUriEx("ESTwoKeyNav", "$orderby=" + orderBy.trim());
}
public FilterValidator runOnETTwoKeyNav(final String filter) throws UriParserException, UriValidationException {
@ -225,19 +228,6 @@ public class FilterValidator implements TestValidator {
return this;
}
public FilterValidator runUriOrderByEx(final String path, final String query) {
exception = null;
try {
new Parser(edm, odata).parseUri(path, query, null);
fail("Expected exception not thrown.");
} catch (final UriParserException e) {
exception = e;
} catch (final UriValidationException e) {
exception = e;
}
return this;
}
// --- Navigation ---
public ExpandValidator goUpToExpandValidator() {
@ -274,8 +264,8 @@ public class FilterValidator implements TestValidator {
// --- Validation ---
/**
* Validates the serialized filterTree against a given filterString
* The given expected filterString is compressed before to allow better readable code in the unit tests
* Validates the serialized filterTree against a given filterString.
* The given expected filterString is compressed before to allow better readable code in the unit tests.
* @param toBeCompr
* @return {@link FilterValidator}
*/
@ -310,14 +300,19 @@ public class FilterValidator implements TestValidator {
EdmType actualType = null;
if (curExpression instanceof Member) {
Member member = (Member) curExpression;
actualType = member.getType();
actualType = ((Member) curExpression).getType();
} else if (curExpression instanceof TypeLiteral) {
TypeLiteral typeLiteral = (TypeLiteral) curExpression;
actualType = typeLiteral.getType();
actualType = ((TypeLiteral) curExpression).getType();
} else if (curExpression instanceof Literal) {
Literal typeLiteral = (Literal) curExpression;
actualType = typeLiteral.getType();
actualType = ((Literal) curExpression).getType();
} else if (curExpression instanceof Enumeration) {
actualType = ((Enumeration) curExpression).getType();
} else if (curExpression instanceof Unary) {
actualType = ((UnaryImpl) curExpression).getType();
} else if (curExpression instanceof Binary) {
actualType = ((BinaryImpl) curExpression).getType();
} else if (curExpression instanceof Method) {
actualType = ((MethodImpl) curExpression).getType();
}
if (actualType == null) {
@ -349,7 +344,6 @@ public class FilterValidator implements TestValidator {
curExpression = ((Binary) curExpression).getRightOperand();
return this;
}
public FilterValidator isLiteral(final String literalText) {
@ -362,8 +356,8 @@ public class FilterValidator implements TestValidator {
return this;
}
public FilterValidator isLiteralType(EdmType edmType) {
if(!(curExpression instanceof Literal)) {
public FilterValidator isLiteralType(final EdmType edmType) {
if (!(curExpression instanceof Literal)) {
fail("Current expression is not a literal");
}
@ -374,7 +368,7 @@ public class FilterValidator implements TestValidator {
}
public FilterValidator isNullLiteralType() {
if(!(curExpression instanceof Literal)) {
if (!(curExpression instanceof Literal)) {
fail("Current expression is not a literal");
}
@ -495,12 +489,4 @@ public class FilterValidator implements TestValidator {
assertEquals(messageKey, exception.getMessageKey());
return this;
}
public FilterValidator isNull() {
return isLiteral("null");
}
public FilterValidator isTrue() {
return isLiteral("true");
}
}

View File

@ -140,6 +140,20 @@ public class TestUriValidator implements TestValidator {
return this;
}
public TestUriValidator isSelectItemStar(final int index) {
final SelectOption select = uriInfo.getSelectOption();
SelectItem item = select.getSelectItems().get(index);
assertTrue(item.isStar());
return this;
}
public TestUriValidator isSelectItemAllOp(final int index, final FullQualifiedName fqn) {
final SelectOption select = uriInfo.getSelectOption();
SelectItem item = select.getSelectItems().get(index);
assertEquals(fqn, item.getAllOperationsInSchemaNameSpace());
return this;
}
// Validation
public TestUriValidator isKind(final UriInfoKind kind) {
assertEquals(kind, uriInfo.getKind());
@ -202,16 +216,6 @@ public class TestUriValidator implements TestValidator {
return this;
}
public TestUriValidator isExpandText(final String text) {
assertEquals(text, uriInfo.getExpandOption().getText());
return this;
}
public TestUriValidator isSelectText(final String text) {
assertEquals(text, uriInfo.getSelectOption().getText());
return this;
}
public TestUriValidator isFormatText(final String text) {
assertEquals(text, uriInfo.getFormatOption().getText());
return this;
@ -234,18 +238,4 @@ public class TestUriValidator implements TestValidator {
assertEquals(fullName, uriInfo.getEntityTypeCast().getFullQualifiedName());
return this;
}
public TestUriValidator isSelectItemStar(final int index) {
final SelectOption select = uriInfo.getSelectOption();
SelectItem item = select.getSelectItems().get(index);
assertTrue(item.isStar());
return this;
}
public TestUriValidator isSelectItemAllOp(final int index, final FullQualifiedName fqn) {
final SelectOption select = uriInfo.getSelectOption();
SelectItem item = select.getSelectItems().get(index);
assertEquals(fqn, item.getAllOperationsInSchemaNameSpace());
return this;
}
}

View File

@ -1,127 +0,0 @@
/*
* 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.testutil;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.Token;
import org.apache.olingo.server.core.uri.antlr.UriLexer;
public class TokenValidator {
private String input = null;
private List<? extends Token> tokens = null;
private Token curToken = null;
private Exception curException = null;
private int startMode;
// --- Execution ---
public TokenValidator run(final String uri) {
input = uri;
tokens = parseInput(uri);
first();
return this;
}
// --- Navigation ---
// navigate within the tokenlist
public TokenValidator first() {
try {
curToken = tokens.get(0);
} catch (IndexOutOfBoundsException ex) {
curToken = null;
}
return this;
}
public TokenValidator last() {
curToken = tokens.get(tokens.size() - 1);
return this;
}
public TokenValidator at(final int index) {
try {
curToken = tokens.get(index);
} catch (IndexOutOfBoundsException ex) {
curToken = null;
}
return this;
}
// --- Validation ---
public TokenValidator isText(final String expected) {
assertEquals(expected, curToken.getText());
return this;
}
public TokenValidator isAllText(final String expected) {
String actual = "";
for (Token curToken : tokens) {
actual += curToken.getText();
}
assertEquals(expected, actual);
return this;
}
public TokenValidator isAllInput() {
String actual = "";
for (Token curToken : tokens) {
actual += curToken.getText();
}
assertEquals(input, actual);
return this;
}
public TokenValidator isInput() {
assertEquals(input, curToken.getText());
return this;
}
public TokenValidator isType(final int expected) {
assertEquals(UriLexer.VOCABULARY.getDisplayName(expected), UriLexer.VOCABULARY.getDisplayName(curToken.getType()));
return this;
}
public TokenValidator isExType(final Class<?> exClass) {
assertEquals(exClass, curException.getClass());
return this;
}
public void globalMode(final int mode) {
startMode = mode;
}
// --- Helper ---
private List<? extends Token> parseInput(final String input) {
ANTLRInputStream inputStream = new ANTLRInputStream(input);
UriLexer lexer = new UriLexer(inputStream);
lexer.mode(startMode);
return lexer.getAllTokens();
}
}